767 lines
36 KiB
C++
767 lines
36 KiB
C++
#include "DisplayVk.h"
|
||
|
||
#include <algorithm>
|
||
#include <glm/glm.hpp>
|
||
#include <glm/gtx/matrix_transform_2d.hpp>
|
||
|
||
#include "host-common/GfxstreamFatalError.h"
|
||
#include "host-common/logging.h"
|
||
#include "vulkan/VkFormatUtils.h"
|
||
#include "vulkan/vk_enum_string_helper.h"
|
||
|
||
namespace gfxstream {
|
||
namespace vk {
|
||
|
||
using emugl::ABORT_REASON_OTHER;
|
||
using emugl::FatalError;
|
||
|
||
#define DISPLAY_VK_ERROR(fmt, ...) \
|
||
do { \
|
||
fprintf(stderr, "%s(%s:%d): " fmt "\n", __func__, __FILE__, __LINE__, ##__VA_ARGS__); \
|
||
fflush(stderr); \
|
||
} while (0)
|
||
|
||
#define DISPLAY_VK_ERROR_ONCE(fmt, ...) \
|
||
do { \
|
||
static bool displayVkInternalLogged = false; \
|
||
if (!displayVkInternalLogged) { \
|
||
DISPLAY_VK_ERROR(fmt, ##__VA_ARGS__); \
|
||
displayVkInternalLogged = true; \
|
||
} \
|
||
} while (0)
|
||
|
||
namespace {
|
||
|
||
bool shouldRecreateSwapchain(VkResult result) {
|
||
switch (result) {
|
||
case VK_SUBOPTIMAL_KHR:
|
||
case VK_ERROR_OUT_OF_DATE_KHR:
|
||
// b/217229121: drivers may return VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT in
|
||
// vkQueuePresentKHR even if VK_EXT_full_screen_exclusive is not enabled.
|
||
case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
|
||
return true;
|
||
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
} // namespace
|
||
|
||
DisplayVk::DisplayVk(const VulkanDispatch& vk, VkPhysicalDevice vkPhysicalDevice,
|
||
uint32_t swapChainQueueFamilyIndex, uint32_t compositorQueueFamilyIndex,
|
||
VkDevice vkDevice, VkQueue compositorVkQueue,
|
||
std::shared_ptr<android::base::Lock> compositorVkQueueLock,
|
||
VkQueue swapChainVkqueue,
|
||
std::shared_ptr<android::base::Lock> swapChainVkQueueLock)
|
||
: m_vk(vk),
|
||
m_vkPhysicalDevice(vkPhysicalDevice),
|
||
m_swapChainQueueFamilyIndex(swapChainQueueFamilyIndex),
|
||
m_compositorQueueFamilyIndex(compositorQueueFamilyIndex),
|
||
m_vkDevice(vkDevice),
|
||
m_compositorVkQueue(compositorVkQueue),
|
||
m_compositorVkQueueLock(compositorVkQueueLock),
|
||
m_swapChainVkQueue(swapChainVkqueue),
|
||
m_swapChainVkQueueLock(swapChainVkQueueLock),
|
||
m_vkCommandPool(VK_NULL_HANDLE),
|
||
m_swapChainStateVk(nullptr) {
|
||
// TODO(kaiyili): validate the capabilites of the passed in Vulkan
|
||
// components.
|
||
VkCommandPoolCreateInfo commandPoolCi = {
|
||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||
.queueFamilyIndex = m_compositorQueueFamilyIndex,
|
||
};
|
||
VK_CHECK(m_vk.vkCreateCommandPool(m_vkDevice, &commandPoolCi, nullptr, &m_vkCommandPool));
|
||
constexpr size_t imageBorrowResourcePoolSize = 10;
|
||
for (size_t i = 0; i < imageBorrowResourcePoolSize; i++) {
|
||
m_imageBorrowResources.emplace_back(
|
||
ImageBorrowResource::create(m_vk, m_vkDevice, m_vkCommandPool));
|
||
}
|
||
}
|
||
|
||
DisplayVk::~DisplayVk() {
|
||
destroySwapchain();
|
||
m_imageBorrowResources.clear();
|
||
m_vk.vkDestroyCommandPool(m_vkDevice, m_vkCommandPool, nullptr);
|
||
}
|
||
|
||
void DisplayVk::drainQueues() {
|
||
{
|
||
android::base::AutoLock lock(*m_swapChainVkQueueLock);
|
||
VK_CHECK(vk_util::waitForVkQueueIdleWithRetry(m_vk, m_swapChainVkQueue));
|
||
}
|
||
// We don't assume all VkCommandBuffer submitted to m_compositorVkQueueLock is always followed
|
||
// by another operation on the m_swapChainVkQueue. Therefore, only waiting for the
|
||
// m_swapChainVkQueue is not enough to guarantee all resources used are free to be destroyed.
|
||
{
|
||
android::base::AutoLock lock(*m_compositorVkQueueLock);
|
||
VK_CHECK(vk_util::waitForVkQueueIdleWithRetry(m_vk, m_compositorVkQueue));
|
||
}
|
||
}
|
||
|
||
void DisplayVk::bindToSurfaceImpl(gfxstream::DisplaySurface* surface) {
|
||
m_needToRecreateSwapChain = true;
|
||
}
|
||
|
||
void DisplayVk::surfaceUpdated(gfxstream::DisplaySurface* surface) {
|
||
m_needToRecreateSwapChain = true;
|
||
}
|
||
|
||
void DisplayVk::unbindFromSurfaceImpl() {
|
||
destroySwapchain();
|
||
}
|
||
|
||
void DisplayVk::destroySwapchain() {
|
||
drainQueues();
|
||
m_freePostResources.clear();
|
||
m_postResourceFutures.clear();
|
||
m_swapChainStateVk.reset();
|
||
m_needToRecreateSwapChain = true;
|
||
}
|
||
|
||
bool DisplayVk::recreateSwapchain() {
|
||
destroySwapchain();
|
||
|
||
const auto* surface = getBoundSurface();
|
||
if (!surface) {
|
||
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
|
||
<< "DisplayVk can't create VkSwapchainKHR without a VkSurfaceKHR";
|
||
}
|
||
const auto* surfaceVk = static_cast<const DisplaySurfaceVk*>(surface->getImpl());
|
||
|
||
if (!SwapChainStateVk::validateQueueFamilyProperties(m_vk, m_vkPhysicalDevice,
|
||
surfaceVk->getSurface(),
|
||
m_swapChainQueueFamilyIndex)) {
|
||
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
|
||
<< "DisplayVk can't create VkSwapchainKHR with given VkDevice and VkSurfaceKHR.";
|
||
}
|
||
INFO("Creating swapchain with size %" PRIu32 "x%" PRIu32 ".", surface->getWidth(),
|
||
surface->getHeight());
|
||
auto swapChainCi = SwapChainStateVk::createSwapChainCi(
|
||
m_vk, surfaceVk->getSurface(), m_vkPhysicalDevice, surface->getWidth(),
|
||
surface->getHeight(), {m_swapChainQueueFamilyIndex, m_compositorQueueFamilyIndex});
|
||
if (!swapChainCi) {
|
||
return false;
|
||
}
|
||
VkFormatProperties formatProps;
|
||
m_vk.vkGetPhysicalDeviceFormatProperties(m_vkPhysicalDevice,
|
||
swapChainCi->mCreateInfo.imageFormat, &formatProps);
|
||
if (!(formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) {
|
||
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
|
||
<< "DisplayVk: The image format chosen for present VkImage can't be used as the color "
|
||
"attachment, and therefore can't be used as the render target of CompositorVk.";
|
||
}
|
||
m_swapChainStateVk =
|
||
SwapChainStateVk::createSwapChainVk(m_vk, m_vkDevice, swapChainCi->mCreateInfo);
|
||
if (m_swapChainStateVk == nullptr) return false;
|
||
int numSwapChainImages = m_swapChainStateVk->getVkImages().size();
|
||
|
||
m_postResourceFutures.resize(numSwapChainImages, std::nullopt);
|
||
for (uint32_t i = 0; i < numSwapChainImages + 1; ++i) {
|
||
m_freePostResources.emplace_back(PostResource::create(m_vk, m_vkDevice, m_vkCommandPool));
|
||
}
|
||
|
||
m_inFlightFrameIndex = 0;
|
||
m_needToRecreateSwapChain = false;
|
||
return true;
|
||
}
|
||
|
||
DisplayVk::PostResult DisplayVk::post(const BorrowedImageInfo* sourceImageInfo) {
|
||
auto completedFuture = std::async(std::launch::deferred, [] {}).share();
|
||
completedFuture.wait();
|
||
|
||
const auto* surface = getBoundSurface();
|
||
if (!surface) {
|
||
ERR("Trying to present to non-existing surface!");
|
||
return PostResult{
|
||
.success = true,
|
||
.postCompletedWaitable = completedFuture,
|
||
};
|
||
}
|
||
|
||
if (m_needToRecreateSwapChain) {
|
||
INFO("Recreating swapchain...");
|
||
|
||
constexpr const int kMaxRecreateSwapchainRetries = 8;
|
||
int retriesRemaining = kMaxRecreateSwapchainRetries;
|
||
while (retriesRemaining >= 0 && !recreateSwapchain()) {
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||
--retriesRemaining;
|
||
INFO("Swapchain recreation failed, retrying...");
|
||
}
|
||
|
||
if (retriesRemaining < 0) {
|
||
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
|
||
<< "Failed to create Swapchain."
|
||
<< " w:" << surface->getWidth()
|
||
<< " h:" << surface->getHeight();
|
||
}
|
||
|
||
INFO("Recreating swapchain completed.");
|
||
}
|
||
|
||
auto result = postImpl(sourceImageInfo);
|
||
if (!result.success) {
|
||
m_needToRecreateSwapChain = true;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
DisplayVk::PostResult DisplayVk::postImpl(
|
||
const BorrowedImageInfo* sourceImageInfo) {
|
||
auto completedFuture = std::async(std::launch::deferred, [] {}).share();
|
||
completedFuture.wait();
|
||
|
||
// One for acquire, one for release.
|
||
const ImageBorrowResource* imageBorrowResources[2] = {nullptr};
|
||
for (size_t i = 0; i < std::size(imageBorrowResources); i++) {
|
||
auto freeImageBorrowResource =
|
||
std::find_if(m_imageBorrowResources.begin(), m_imageBorrowResources.end(),
|
||
[this](const std::unique_ptr<ImageBorrowResource>& imageBorrowResource) {
|
||
VkResult fenceStatus = m_vk.vkGetFenceStatus(
|
||
m_vkDevice, imageBorrowResource->m_completeFence);
|
||
if (fenceStatus == VK_SUCCESS) { return true; }
|
||
if (fenceStatus == VK_NOT_READY) { return false; }
|
||
VK_CHECK(fenceStatus);
|
||
return false;
|
||
});
|
||
if (freeImageBorrowResource == m_imageBorrowResources.end()) {
|
||
freeImageBorrowResource = m_imageBorrowResources.begin();
|
||
VK_CHECK(m_vk.vkWaitForFences(
|
||
m_vkDevice, 1, &(*freeImageBorrowResource)->m_completeFence, VK_TRUE, UINT64_MAX));
|
||
}
|
||
VK_CHECK(m_vk.vkResetFences(m_vkDevice, 1, &(*freeImageBorrowResource)->m_completeFence));
|
||
imageBorrowResources[i] = freeImageBorrowResource->get();
|
||
}
|
||
// We need to unconditionally acquire and release the image to satisfy the requiremment for the
|
||
// borrowed image.
|
||
const auto* sourceImageInfoVk = static_cast<const BorrowedImageInfoVk*>(sourceImageInfo);
|
||
struct ImageBorrower {
|
||
ImageBorrower(const VulkanDispatch& vk, VkQueue queue,
|
||
std::shared_ptr<android::base::Lock> queueLock, uint32_t usedQueueFamilyIndex,
|
||
const BorrowedImageInfoVk& image, const ImageBorrowResource& acquireResource,
|
||
const ImageBorrowResource& releaseResource)
|
||
: m_vk(vk),
|
||
m_vkQueue(queue),
|
||
m_queueLock(queueLock),
|
||
m_releaseResource(releaseResource) {
|
||
std::vector<VkImageMemoryBarrier> acquireQueueTransferBarriers;
|
||
std::vector<VkImageMemoryBarrier> acquireLayoutTransitionBarriers;
|
||
std::vector<VkImageMemoryBarrier> releaseLayoutTransitionBarriers;
|
||
std::vector<VkImageMemoryBarrier> releaseQueueTransferBarriers;
|
||
addNeededBarriersToUseBorrowedImage(
|
||
image, usedQueueFamilyIndex,
|
||
/*usedInitialImageLayout=*/VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||
/*usedFinalImageLayout=*/VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||
VK_ACCESS_TRANSFER_READ_BIT, &acquireQueueTransferBarriers,
|
||
&acquireLayoutTransitionBarriers, &releaseLayoutTransitionBarriers,
|
||
&releaseQueueTransferBarriers);
|
||
|
||
// Record the acquire commands.
|
||
const VkCommandBufferBeginInfo acquireBeginInfo = {
|
||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||
};
|
||
VK_CHECK(
|
||
m_vk.vkBeginCommandBuffer(acquireResource.m_vkCommandBuffer, &acquireBeginInfo));
|
||
if (!acquireQueueTransferBarriers.empty()) {
|
||
m_vk.vkCmdPipelineBarrier(
|
||
acquireResource.m_vkCommandBuffer,
|
||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
0, 0, nullptr, 0, nullptr,
|
||
static_cast<uint32_t>(acquireQueueTransferBarriers.size()),
|
||
acquireQueueTransferBarriers.data());
|
||
}
|
||
if (!acquireLayoutTransitionBarriers.empty()) {
|
||
m_vk.vkCmdPipelineBarrier(
|
||
acquireResource.m_vkCommandBuffer,
|
||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr,
|
||
static_cast<uint32_t>(acquireLayoutTransitionBarriers.size()),
|
||
acquireLayoutTransitionBarriers.data());
|
||
}
|
||
VK_CHECK(m_vk.vkEndCommandBuffer(acquireResource.m_vkCommandBuffer));
|
||
|
||
// Record the release commands.
|
||
const VkCommandBufferBeginInfo releaseBeginInfo = {
|
||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||
};
|
||
VK_CHECK(
|
||
m_vk.vkBeginCommandBuffer(releaseResource.m_vkCommandBuffer, &releaseBeginInfo));
|
||
if (!releaseLayoutTransitionBarriers.empty()) {
|
||
m_vk.vkCmdPipelineBarrier(
|
||
releaseResource.m_vkCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr,
|
||
static_cast<uint32_t>(releaseLayoutTransitionBarriers.size()),
|
||
releaseLayoutTransitionBarriers.data());
|
||
}
|
||
if (!releaseQueueTransferBarriers.empty()) {
|
||
m_vk.vkCmdPipelineBarrier(
|
||
releaseResource.m_vkCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr,
|
||
static_cast<uint32_t>(releaseQueueTransferBarriers.size()),
|
||
releaseQueueTransferBarriers.data());
|
||
}
|
||
VK_CHECK(m_vk.vkEndCommandBuffer(releaseResource.m_vkCommandBuffer));
|
||
|
||
VkSubmitInfo submitInfo = {
|
||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||
.waitSemaphoreCount = 0,
|
||
.pWaitSemaphores = nullptr,
|
||
.pWaitDstStageMask = nullptr,
|
||
.commandBufferCount = 1,
|
||
.pCommandBuffers = &acquireResource.m_vkCommandBuffer,
|
||
.signalSemaphoreCount = 0,
|
||
.pSignalSemaphores = nullptr,
|
||
};
|
||
// Submit the acquire commands.
|
||
{
|
||
android::base::AutoLock lock(*m_queueLock);
|
||
VK_CHECK(
|
||
m_vk.vkQueueSubmit(m_vkQueue, 1, &submitInfo, acquireResource.m_completeFence));
|
||
}
|
||
}
|
||
|
||
const VulkanDispatch& m_vk;
|
||
const VkQueue m_vkQueue;
|
||
std::shared_ptr<android::base::Lock> m_queueLock;
|
||
const ImageBorrowResource& m_releaseResource;
|
||
~ImageBorrower() {
|
||
VkSubmitInfo submitInfo = {
|
||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||
.waitSemaphoreCount = 0,
|
||
.pWaitSemaphores = nullptr,
|
||
.pWaitDstStageMask = nullptr,
|
||
.commandBufferCount = 1,
|
||
.pCommandBuffers = &m_releaseResource.m_vkCommandBuffer,
|
||
.signalSemaphoreCount = 0,
|
||
.pSignalSemaphores = nullptr,
|
||
};
|
||
// Submit the release commands.
|
||
{
|
||
android::base::AutoLock lock(*m_queueLock);
|
||
VK_CHECK(m_vk.vkQueueSubmit(m_vkQueue, 1, &submitInfo,
|
||
m_releaseResource.m_completeFence));
|
||
}
|
||
}
|
||
} imageBorrower(m_vk, m_compositorVkQueue, m_compositorVkQueueLock,
|
||
m_compositorQueueFamilyIndex, *sourceImageInfoVk, *imageBorrowResources[0],
|
||
*imageBorrowResources[1]);
|
||
|
||
const auto* surface = getBoundSurface();
|
||
if (!m_swapChainStateVk || !surface) {
|
||
DISPLAY_VK_ERROR("Haven't bound to a surface, can't post ColorBuffer.");
|
||
return PostResult{true, std::move(completedFuture)};
|
||
}
|
||
|
||
if (!canPost(sourceImageInfoVk->imageCreateInfo)) {
|
||
DISPLAY_VK_ERROR("Can't post ColorBuffer.");
|
||
return PostResult{true, std::move(completedFuture)};
|
||
}
|
||
|
||
for (auto& postResourceFutureOpt : m_postResourceFutures) {
|
||
if (!postResourceFutureOpt.has_value()) {
|
||
continue;
|
||
}
|
||
auto postResourceFuture = postResourceFutureOpt.value();
|
||
if (!postResourceFuture.valid()) {
|
||
GFXSTREAM_ABORT(FatalError(ABORT_REASON_OTHER))
|
||
<< "Invalid postResourceFuture in m_postResourceFutures.";
|
||
}
|
||
std::future_status status = postResourceFuture.wait_for(std::chrono::seconds(0));
|
||
if (status == std::future_status::ready) {
|
||
m_freePostResources.emplace_back(postResourceFuture.get());
|
||
postResourceFutureOpt = std::nullopt;
|
||
}
|
||
}
|
||
if (m_freePostResources.empty()) {
|
||
for (auto& postResourceFutureOpt : m_postResourceFutures) {
|
||
if (!postResourceFutureOpt.has_value()) {
|
||
continue;
|
||
}
|
||
m_freePostResources.emplace_back(postResourceFutureOpt.value().get());
|
||
postResourceFutureOpt = std::nullopt;
|
||
break;
|
||
}
|
||
}
|
||
std::shared_ptr<PostResource> postResource = m_freePostResources.front();
|
||
m_freePostResources.pop_front();
|
||
|
||
VkSemaphore imageReadySem = postResource->m_swapchainImageAcquireSemaphore;
|
||
|
||
uint32_t imageIndex;
|
||
VkResult acquireRes =
|
||
m_vk.vkAcquireNextImageKHR(m_vkDevice, m_swapChainStateVk->getSwapChain(), UINT64_MAX,
|
||
imageReadySem, VK_NULL_HANDLE, &imageIndex);
|
||
if (shouldRecreateSwapchain(acquireRes)) {
|
||
return PostResult{false, std::shared_future<void>()};
|
||
}
|
||
VK_CHECK(acquireRes);
|
||
|
||
if (m_postResourceFutures[imageIndex].has_value()) {
|
||
m_freePostResources.emplace_back(m_postResourceFutures[imageIndex].value().get());
|
||
m_postResourceFutures[imageIndex] = std::nullopt;
|
||
}
|
||
|
||
VkCommandBuffer cmdBuff = postResource->m_vkCommandBuffer;
|
||
VK_CHECK(m_vk.vkResetCommandBuffer(cmdBuff, 0));
|
||
|
||
const VkCommandBufferBeginInfo beginInfo = {
|
||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||
};
|
||
VK_CHECK(m_vk.vkBeginCommandBuffer(cmdBuff, &beginInfo));
|
||
|
||
VkImageMemoryBarrier acquireSwapchainImageBarrier = {
|
||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||
.pNext = nullptr,
|
||
.srcAccessMask = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
.dstAccessMask = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||
.image = m_swapChainStateVk->getVkImages()[imageIndex],
|
||
.subresourceRange =
|
||
{
|
||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||
.baseMipLevel = 0,
|
||
.levelCount = 1,
|
||
.baseArrayLayer = 0,
|
||
.layerCount = 1,
|
||
},
|
||
};
|
||
m_vk.vkCmdPipelineBarrier(cmdBuff, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1,
|
||
&acquireSwapchainImageBarrier);
|
||
|
||
// Note: The extent used during swapchain creation must be used here and not the
|
||
// current surface's extent as the swapchain may not have been updated after the
|
||
// surface resized. The blit must not try to write outside of the extent of the
|
||
// existing swapchain images.
|
||
const VkExtent2D swapchainImageExtent = m_swapChainStateVk->getImageExtent();
|
||
const VkImageBlit region = {
|
||
.srcSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||
.mipLevel = 0,
|
||
.baseArrayLayer = 0,
|
||
.layerCount = 1},
|
||
.srcOffsets = {{0, 0, 0},
|
||
{static_cast<int32_t>(sourceImageInfoVk->imageCreateInfo.extent.width),
|
||
static_cast<int32_t>(sourceImageInfoVk->imageCreateInfo.extent.height), 1}},
|
||
.dstSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||
.mipLevel = 0,
|
||
.baseArrayLayer = 0,
|
||
.layerCount = 1},
|
||
.dstOffsets = {{0, 0, 0},
|
||
{static_cast<int32_t>(swapchainImageExtent.width),
|
||
static_cast<int32_t>(swapchainImageExtent.height), 1}},
|
||
};
|
||
VkFormat displayBufferFormat = sourceImageInfoVk->imageCreateInfo.format;
|
||
VkImageTiling displayBufferTiling = sourceImageInfoVk->imageCreateInfo.tiling;
|
||
VkFilter filter = VK_FILTER_NEAREST;
|
||
VkFormatFeatureFlags displayBufferFormatFeatures =
|
||
getFormatFeatures(displayBufferFormat, displayBufferTiling);
|
||
if (formatIsDepthOrStencil(displayBufferFormat)) {
|
||
DISPLAY_VK_ERROR_ONCE(
|
||
"The format of the display buffer, %s, is a depth/stencil format, we can only use the "
|
||
"VK_FILTER_NEAREST filter according to VUID-vkCmdBlitImage-srcImage-00232.",
|
||
string_VkFormat(displayBufferFormat));
|
||
filter = VK_FILTER_NEAREST;
|
||
} else if (!(displayBufferFormatFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) {
|
||
DISPLAY_VK_ERROR_ONCE(
|
||
"The format of the display buffer, %s, with the tiling, %s, doesn't support "
|
||
"VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT, so we can only use the "
|
||
"VK_FILTER_NEAREST filter according VUID-vkCmdBlitImage-filter-02001. The supported "
|
||
"features are %s.",
|
||
string_VkFormat(displayBufferFormat), string_VkImageTiling(displayBufferTiling),
|
||
string_VkFormatFeatureFlags(displayBufferFormatFeatures).c_str());
|
||
filter = VK_FILTER_NEAREST;
|
||
} else {
|
||
filter = VK_FILTER_LINEAR;
|
||
}
|
||
m_vk.vkCmdBlitImage(cmdBuff, sourceImageInfoVk->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||
m_swapChainStateVk->getVkImages()[imageIndex],
|
||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, filter);
|
||
|
||
VkImageMemoryBarrier releaseSwapchainImageBarrier = {
|
||
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
||
.srcAccessMask = VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
|
||
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||
.image = m_swapChainStateVk->getVkImages()[imageIndex],
|
||
.subresourceRange =
|
||
{
|
||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||
.baseMipLevel = 0,
|
||
.levelCount = 1,
|
||
.baseArrayLayer = 0,
|
||
.layerCount = 1,
|
||
},
|
||
};
|
||
m_vk.vkCmdPipelineBarrier(cmdBuff, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
||
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1,
|
||
&releaseSwapchainImageBarrier);
|
||
|
||
VK_CHECK(m_vk.vkEndCommandBuffer(cmdBuff));
|
||
|
||
VkFence postCompleteFence = postResource->m_swapchainImageReleaseFence;
|
||
VK_CHECK(m_vk.vkResetFences(m_vkDevice, 1, &postCompleteFence));
|
||
VkSemaphore postCompleteSemaphore = postResource->m_swapchainImageReleaseSemaphore;
|
||
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_TRANSFER_BIT};
|
||
VkSubmitInfo submitInfo = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||
.waitSemaphoreCount = 1,
|
||
.pWaitSemaphores = &imageReadySem,
|
||
.pWaitDstStageMask = waitStages,
|
||
.commandBufferCount = 1,
|
||
.pCommandBuffers = &cmdBuff,
|
||
.signalSemaphoreCount = 1,
|
||
.pSignalSemaphores = &postCompleteSemaphore};
|
||
{
|
||
android::base::AutoLock lock(*m_compositorVkQueueLock);
|
||
VK_CHECK(m_vk.vkQueueSubmit(m_compositorVkQueue, 1, &submitInfo, postCompleteFence));
|
||
}
|
||
std::shared_future<std::shared_ptr<PostResource>> postResourceFuture =
|
||
std::async(std::launch::deferred, [postCompleteFence, postResource, this]() mutable {
|
||
VkResult res = m_vk.vkWaitForFences(m_vkDevice, 1, &postCompleteFence, VK_TRUE,
|
||
kVkWaitForFencesTimeoutNsecs);
|
||
if (res == VK_SUCCESS) {
|
||
return postResource;
|
||
}
|
||
if (res == VK_TIMEOUT) {
|
||
// Retry. If device lost, hopefully this returns immediately.
|
||
res = m_vk.vkWaitForFences(m_vkDevice, 1, &postCompleteFence, VK_TRUE,
|
||
kVkWaitForFencesTimeoutNsecs);
|
||
}
|
||
VK_CHECK(res);
|
||
return postResource;
|
||
}).share();
|
||
m_postResourceFutures[imageIndex] = postResourceFuture;
|
||
|
||
auto swapChain = m_swapChainStateVk->getSwapChain();
|
||
VkPresentInfoKHR presentInfo = {.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||
.waitSemaphoreCount = 1,
|
||
.pWaitSemaphores = &postCompleteSemaphore,
|
||
.swapchainCount = 1,
|
||
.pSwapchains = &swapChain,
|
||
.pImageIndices = &imageIndex};
|
||
VkResult presentRes;
|
||
{
|
||
android::base::AutoLock lock(*m_swapChainVkQueueLock);
|
||
presentRes = m_vk.vkQueuePresentKHR(m_swapChainVkQueue, &presentInfo);
|
||
}
|
||
if (shouldRecreateSwapchain(presentRes)) {
|
||
postResourceFuture.wait();
|
||
return PostResult{false, std::shared_future<void>()};
|
||
}
|
||
VK_CHECK(presentRes);
|
||
return PostResult{true, std::async(std::launch::deferred, [postResourceFuture] {
|
||
// We can't directly wait for the VkFence here, because we
|
||
// share the VkFences on different frames, but we don't share
|
||
// the future on different frames. If we directly wait for the
|
||
// VkFence here, we may wait for a different frame if a new
|
||
// frame starts to be drawn before this future is waited.
|
||
postResourceFuture.wait();
|
||
}).share()};
|
||
}
|
||
|
||
VkFormatFeatureFlags DisplayVk::getFormatFeatures(VkFormat format, VkImageTiling tiling) {
|
||
auto i = m_vkFormatProperties.find(format);
|
||
if (i == m_vkFormatProperties.end()) {
|
||
VkFormatProperties formatProperties;
|
||
m_vk.vkGetPhysicalDeviceFormatProperties(m_vkPhysicalDevice, format, &formatProperties);
|
||
i = m_vkFormatProperties.emplace(format, formatProperties).first;
|
||
}
|
||
const VkFormatProperties& formatProperties = i->second;
|
||
VkFormatFeatureFlags formatFeatures = 0;
|
||
if (tiling == VK_IMAGE_TILING_LINEAR) {
|
||
formatFeatures = formatProperties.linearTilingFeatures;
|
||
} else if (tiling == VK_IMAGE_TILING_OPTIMAL) {
|
||
formatFeatures = formatProperties.optimalTilingFeatures;
|
||
} else {
|
||
DISPLAY_VK_ERROR("Unknown tiling %#" PRIx64 ".", static_cast<uint64_t>(tiling));
|
||
}
|
||
return formatFeatures;
|
||
}
|
||
|
||
bool DisplayVk::canPost(const VkImageCreateInfo& postImageCi) {
|
||
// According to VUID-vkCmdBlitImage-srcImage-01999, the format features of srcImage must contain
|
||
// VK_FORMAT_FEATURE_BLIT_SRC_BIT.
|
||
VkFormatFeatureFlags formatFeatures = getFormatFeatures(postImageCi.format, postImageCi.tiling);
|
||
if (!(formatFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT)) {
|
||
DISPLAY_VK_ERROR(
|
||
"VK_FORMAT_FEATURE_BLIT_SRC_BLIT is not supported for VkImage with format %s, tilling "
|
||
"%s. Supported features are %s.",
|
||
string_VkFormat(postImageCi.format), string_VkImageTiling(postImageCi.tiling),
|
||
string_VkFormatFeatureFlags(formatFeatures).c_str());
|
||
return false;
|
||
}
|
||
|
||
// According to VUID-vkCmdBlitImage-srcImage-06421, srcImage must not use a format that requires
|
||
// a sampler Y’CBCR conversion.
|
||
if (formatRequiresSamplerYcbcrConversion(postImageCi.format)) {
|
||
DISPLAY_VK_ERROR("Format %s requires a sampler Y'CbCr conversion. Can't be used to post.",
|
||
string_VkFormat(postImageCi.format));
|
||
return false;
|
||
}
|
||
|
||
if (!(postImageCi.usage & VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) {
|
||
// According to VUID-vkCmdBlitImage-srcImage-00219, srcImage must have been created with
|
||
// VK_IMAGE_USAGE_TRANSFER_SRC_BIT usage flag.
|
||
DISPLAY_VK_ERROR(
|
||
"The VkImage is not created with the VK_IMAGE_USAGE_TRANSFER_SRC_BIT usage flag. The "
|
||
"usage flags are %s.",
|
||
string_VkImageUsageFlags(postImageCi.usage).c_str());
|
||
return false;
|
||
}
|
||
|
||
VkFormat swapChainFormat = m_swapChainStateVk->getFormat();
|
||
if (formatIsSInt(postImageCi.format) || formatIsSInt(swapChainFormat)) {
|
||
// According to VUID-vkCmdBlitImage-srcImage-00229, if either of srcImage or dstImage was
|
||
// created with a signed integer VkFormat, the other must also have been created with a
|
||
// signed integer VkFormat.
|
||
if (!(formatIsSInt(postImageCi.format) && formatIsSInt(m_swapChainStateVk->getFormat()))) {
|
||
DISPLAY_VK_ERROR(
|
||
"The format(%s) doesn't match with the format of the presentable image(%s): either "
|
||
"of the formats is a signed integer VkFormat, but the other is not.",
|
||
string_VkFormat(postImageCi.format), string_VkFormat(swapChainFormat));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (formatIsUInt(postImageCi.format) || formatIsUInt(swapChainFormat)) {
|
||
// According to VUID-vkCmdBlitImage-srcImage-00230, if either of srcImage or dstImage was
|
||
// created with an unsigned integer VkFormat, the other must also have been created with an
|
||
// unsigned integer VkFormat.
|
||
if (!(formatIsUInt(postImageCi.format) && formatIsUInt(swapChainFormat))) {
|
||
DISPLAY_VK_ERROR(
|
||
"The format(%s) doesn't match with the format of the presentable image(%s): either "
|
||
"of the formats is an unsigned integer VkFormat, but the other is not.",
|
||
string_VkFormat(postImageCi.format), string_VkFormat(swapChainFormat));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (formatIsDepthOrStencil(postImageCi.format) || formatIsDepthOrStencil(swapChainFormat)) {
|
||
// According to VUID-vkCmdBlitImage-srcImage-00231, if either of srcImage or dstImage was
|
||
// created with a depth/stencil format, the other must have exactly the same format.
|
||
if (postImageCi.format != swapChainFormat) {
|
||
DISPLAY_VK_ERROR(
|
||
"The format(%s) doesn't match with the format of the presentable image(%s): either "
|
||
"of the formats is a depth/stencil VkFormat, but the other is not the same format.",
|
||
string_VkFormat(postImageCi.format), string_VkFormat(swapChainFormat));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (postImageCi.samples != VK_SAMPLE_COUNT_1_BIT) {
|
||
// According to VUID-vkCmdBlitImage-srcImage-00233, srcImage must have been created with a
|
||
// samples value of VK_SAMPLE_COUNT_1_BIT.
|
||
DISPLAY_VK_ERROR(
|
||
"The VkImage is not created with the VK_SAMPLE_COUNT_1_BIT samples value. The samples "
|
||
"value is %s.",
|
||
string_VkSampleCountFlagBits(postImageCi.samples));
|
||
return false;
|
||
}
|
||
if (postImageCi.flags & VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT) {
|
||
// According to VUID-vkCmdBlitImage-dstImage-02545, dstImage and srcImage must not have been
|
||
// created with flags containing VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT.
|
||
DISPLAY_VK_ERROR(
|
||
"The VkImage can't be created with flags containing "
|
||
"VK_IMAGE_CREATE_SUBSAMPLED_BIT_EXT. The flags are %s.",
|
||
string_VkImageCreateFlags(postImageCi.flags).c_str());
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
std::shared_ptr<DisplayVk::PostResource> DisplayVk::PostResource::create(
|
||
const VulkanDispatch& vk, VkDevice vkDevice, VkCommandPool vkCommandPool) {
|
||
VkFenceCreateInfo fenceCi = {
|
||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||
};
|
||
VkFence fence;
|
||
VK_CHECK(vk.vkCreateFence(vkDevice, &fenceCi, nullptr, &fence));
|
||
VkSemaphore semaphores[2];
|
||
for (uint32_t i = 0; i < std::size(semaphores); i++) {
|
||
VkSemaphoreCreateInfo semaphoreCi = {
|
||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||
};
|
||
VK_CHECK(vk.vkCreateSemaphore(vkDevice, &semaphoreCi, nullptr, &semaphores[i]));
|
||
}
|
||
VkCommandBuffer commandBuffer;
|
||
VkCommandBufferAllocateInfo commandBufferAllocInfo = {
|
||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||
.commandPool = vkCommandPool,
|
||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||
.commandBufferCount = 1,
|
||
};
|
||
VK_CHECK(vk.vkAllocateCommandBuffers(vkDevice, &commandBufferAllocInfo, &commandBuffer));
|
||
return std::shared_ptr<PostResource>(new PostResource(
|
||
vk, vkDevice, vkCommandPool, fence, semaphores[0], semaphores[1], commandBuffer));
|
||
}
|
||
|
||
DisplayVk::PostResource::~PostResource() {
|
||
m_vk.vkFreeCommandBuffers(m_vkDevice, m_vkCommandPool, 1, &m_vkCommandBuffer);
|
||
m_vk.vkDestroyFence(m_vkDevice, m_swapchainImageReleaseFence, nullptr);
|
||
m_vk.vkDestroySemaphore(m_vkDevice, m_swapchainImageAcquireSemaphore, nullptr);
|
||
m_vk.vkDestroySemaphore(m_vkDevice, m_swapchainImageReleaseSemaphore, nullptr);
|
||
}
|
||
|
||
DisplayVk::PostResource::PostResource(const VulkanDispatch& vk, VkDevice vkDevice,
|
||
VkCommandPool vkCommandPool,
|
||
VkFence swapchainImageReleaseFence,
|
||
VkSemaphore swapchainImageAcquireSemaphore,
|
||
VkSemaphore swapchainImageReleaseSemaphore,
|
||
VkCommandBuffer vkCommandBuffer)
|
||
: m_swapchainImageReleaseFence(swapchainImageReleaseFence),
|
||
m_swapchainImageAcquireSemaphore(swapchainImageAcquireSemaphore),
|
||
m_swapchainImageReleaseSemaphore(swapchainImageReleaseSemaphore),
|
||
m_vkCommandBuffer(vkCommandBuffer),
|
||
m_vk(vk),
|
||
m_vkDevice(vkDevice),
|
||
m_vkCommandPool(vkCommandPool) {}
|
||
|
||
std::unique_ptr<DisplayVk::ImageBorrowResource> DisplayVk::ImageBorrowResource::create(
|
||
const VulkanDispatch& vk, VkDevice device, VkCommandPool commandPool) {
|
||
const VkCommandBufferAllocateInfo allocInfo = {
|
||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||
.pNext = nullptr,
|
||
.commandPool = commandPool,
|
||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||
.commandBufferCount = 1,
|
||
};
|
||
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
||
VK_CHECK(vk.vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer));
|
||
const VkFenceCreateInfo fenceCi = {
|
||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||
.pNext = nullptr,
|
||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||
};
|
||
VkFence fence = VK_NULL_HANDLE;
|
||
VK_CHECK(vk.vkCreateFence(device, &fenceCi, nullptr, &fence));
|
||
return std::unique_ptr<ImageBorrowResource>(
|
||
new ImageBorrowResource(vk, device, commandPool, fence, commandBuffer));
|
||
}
|
||
|
||
DisplayVk::ImageBorrowResource::~ImageBorrowResource() {
|
||
m_vk.vkFreeCommandBuffers(m_vkDevice, m_vkCommandPool, 1, &m_vkCommandBuffer);
|
||
}
|
||
|
||
DisplayVk::ImageBorrowResource::ImageBorrowResource(const VulkanDispatch& vk, VkDevice device,
|
||
VkCommandPool commandPool, VkFence fence,
|
||
VkCommandBuffer commandBuffer)
|
||
: m_completeFence(fence),
|
||
m_vkCommandBuffer(commandBuffer),
|
||
m_vk(vk),
|
||
m_vkDevice(device),
|
||
m_vkCommandPool(commandPool) {}
|
||
|
||
} // namespace vk
|
||
} // namespace gfxstream
|