#include "DisplayVk.h" #include #include #include #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; using gfxstream::vk::formatIsDepthOrStencil; using gfxstream::vk::formatIsSInt; using gfxstream::vk::formatIsUInt; using gfxstream::vk::formatRequiresSamplerYcbcrConversion; #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 compositorVkQueueLock, VkQueue swapChainVkqueue, std::shared_ptr 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(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) { 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(sourceImageInfo); struct ImageBorrower { ImageBorrower(const VulkanDispatch& vk, VkQueue queue, std::shared_ptr 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 acquireQueueTransferBarriers; std::vector acquireLayoutTransitionBarriers; std::vector releaseLayoutTransitionBarriers; std::vector 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(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(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(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(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 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 = 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()}; } 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(sourceImageInfoVk->imageCreateInfo.extent.width), static_cast(sourceImageInfoVk->imageCreateInfo.extent.height), 1}}, .dstSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1}, .dstOffsets = {{0, 0, 0}, {static_cast(swapchainImageExtent.width), static_cast(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> 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()}; } 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(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::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(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::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( 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