From 2f260a5dc7e2aa9d5d9c1f494e8e7ba4d1e16133 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sat, 13 Dec 2025 12:12:52 +0900 Subject: [PATCH] [Vulkan] Create per-swapchain-image present semaphores Fixes vulkan validation issues. --- src/xenia/ui/vulkan/vulkan_presenter.cc | 65 ++++++++++++++----------- src/xenia/ui/vulkan/vulkan_presenter.h | 3 +- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/xenia/ui/vulkan/vulkan_presenter.cc b/src/xenia/ui/vulkan/vulkan_presenter.cc index d66a5b8b0..ed788c053 100644 --- a/src/xenia/ui/vulkan/vulkan_presenter.cc +++ b/src/xenia/ui/vulkan/vulkan_presenter.cc @@ -48,11 +48,6 @@ DEFINE_bool( "may present with tearing if frames don't meet the host display refresh " "rate.", "Vulkan"); -DEFINE_bool( - vulkan_semaphore_reuse_workaround, false, - "Wait for presentation queue idle before each frame to prevent semaphore " - "reuse. May fix rendering issues but causes significant performance loss.", - "Vulkan"); namespace xe { namespace ui { @@ -80,9 +75,6 @@ VulkanPresenter::PaintContext::Submission::~Submission() { dfn.vkDestroyCommandPool(device, draw_command_pool_, nullptr); } - if (present_semaphore_ != VK_NULL_HANDLE) { - dfn.vkDestroySemaphore(device, present_semaphore_, nullptr); - } if (acquire_semaphore_ != VK_NULL_HANDLE) { dfn.vkDestroySemaphore(device, acquire_semaphore_, nullptr); } @@ -103,13 +95,6 @@ bool VulkanPresenter::PaintContext::Submission::Initialize() { "semaphore"); return false; } - if (dfn.vkCreateSemaphore(device, &semaphore_create_info, nullptr, - &present_semaphore_) != VK_SUCCESS) { - XELOGE( - "VulkanPresenter: Failed to create a swapchain image presentation " - "semaphore"); - return false; - } VkCommandPoolCreateInfo command_pool_create_info; command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -865,6 +850,29 @@ VulkanPresenter::ConnectOrReconnectPaintingToSurfaceFromUIThread( paint_context_.swapchain_framebuffers.emplace_back(image_view, framebuffer); } + // Create per-swapchain-image present semaphores to avoid + // VUID-vkQueueSubmit-pSignalSemaphores-00067 (semaphore reuse before the + // previous present completes). + paint_context_.swapchain_image_present_semaphores.reserve( + paint_context_.swapchain_images.size()); + VkSemaphoreCreateInfo present_semaphore_create_info; + present_semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + present_semaphore_create_info.pNext = nullptr; + present_semaphore_create_info.flags = 0; + for (size_t i = 0; i < paint_context_.swapchain_images.size(); ++i) { + VkSemaphore present_semaphore; + if (dfn.vkCreateSemaphore(device, &present_semaphore_create_info, nullptr, + &present_semaphore) != VK_SUCCESS) { + XELOGE( + "VulkanPresenter: Failed to create a per-swapchain-image present " + "semaphore"); + paint_context_.DestroySwapchainAndVulkanSurface(); + return SurfacePaintConnectResult::kFailure; + } + paint_context_.swapchain_image_present_semaphores.push_back( + present_semaphore); + } + is_vsync_implicit_out = paint_context_.swapchain_is_fifo; return SurfacePaintConnectResult::kSuccess; } @@ -1295,6 +1303,13 @@ VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface( VkSwapchainKHR VulkanPresenter::PaintContext::PrepareForSwapchainRetirement() { if (swapchain != VK_NULL_HANDLE) { submission_tracker.AwaitAllSubmissionsCompletion(); + // Also wait for the presentation queue since vkQueuePresentKHR doesn't + // signal a fence, and the present semaphores may still be in use. + if (present_queue_family != UINT32_MAX) { + const VulkanDevice::Queue::Acquisition queue_acquisition = + vulkan_device->AcquireQueue(present_queue_family, 0); + vulkan_device->functions().vkQueueWaitIdle(queue_acquisition.queue()); + } } const VulkanDevice::Functions& dfn = vulkan_device->functions(); const VkDevice device = vulkan_device->device(); @@ -1303,6 +1318,10 @@ VkSwapchainKHR VulkanPresenter::PaintContext::PrepareForSwapchainRetirement() { dfn.vkDestroyImageView(device, framebuffer.image_view, nullptr); } swapchain_framebuffers.clear(); + for (VkSemaphore present_semaphore : swapchain_image_present_semaphores) { + dfn.vkDestroySemaphore(device, present_semaphore, nullptr); + } + swapchain_image_present_semaphores.clear(); swapchain_images.clear(); swapchain_extent.width = 0; swapchain_extent.height = 0; @@ -1442,19 +1461,6 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl( VkSemaphore acquire_semaphore = paint_submission.acquire_semaphore(); - // WORKAROUND: Wait for presentation queue to be idle to ensure semaphore - // from previous present is not in use. This prevents - // VUID-vkQueueSubmit-pSignalSemaphores-00067. - // The semaphore is unsignaled by vkQueuePresentKHR, not by submission fences, - // so we must wait for the present queue specifically. - // TODO(has207): Proper fix requires per-swapchain-image semaphores. - // See https://docs.vulkan.org/guide/latest/swapchain_semaphore_reuse.html - if (cvars::vulkan_semaphore_reuse_workaround) { - const VulkanDevice::Queue::Acquisition queue_acquisition = - vulkan_device_->AcquireQueue(paint_context_.present_queue_family, 0); - dfn.vkQueueWaitIdle(queue_acquisition.queue()); - } - uint32_t swapchain_image_index; VkResult acquire_result = dfn.vkAcquireNextImageKHR( device, paint_context_.swapchain, UINT64_MAX, acquire_semaphore, @@ -2065,7 +2071,8 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl( paint_context_.ui_setup_command_buffer_current_index = SIZE_MAX; } command_buffers[command_buffer_count++] = draw_command_buffer; - VkSemaphore present_semaphore = paint_submission.present_semaphore(); + VkSemaphore present_semaphore = + paint_context_.swapchain_image_present_semaphores[swapchain_image_index]; VkSubmitInfo submit_info; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.pNext = nullptr; diff --git a/src/xenia/ui/vulkan/vulkan_presenter.h b/src/xenia/ui/vulkan/vulkan_presenter.h index 5339b4551..6e5b0f73f 100644 --- a/src/xenia/ui/vulkan/vulkan_presenter.h +++ b/src/xenia/ui/vulkan/vulkan_presenter.h @@ -297,7 +297,6 @@ class VulkanPresenter final : public Presenter { ~Submission(); VkSemaphore acquire_semaphore() const { return acquire_semaphore_; } - VkSemaphore present_semaphore() const { return present_semaphore_; } VkCommandPool draw_command_pool() const { return draw_command_pool_; } VkCommandBuffer draw_command_buffer() const { return draw_command_buffer_; @@ -310,7 +309,6 @@ class VulkanPresenter final : public Presenter { const VulkanDevice* vulkan_device_; VkSemaphore acquire_semaphore_ = VK_NULL_HANDLE; - VkSemaphore present_semaphore_ = VK_NULL_HANDLE; VkCommandPool draw_command_pool_ = VK_NULL_HANDLE; VkCommandBuffer draw_command_buffer_ = VK_NULL_HANDLE; }; @@ -442,6 +440,7 @@ class VulkanPresenter final : public Presenter { bool swapchain_is_fifo = false; std::vector swapchain_images; std::vector swapchain_framebuffers; + std::vector swapchain_image_present_semaphores; }; explicit VulkanPresenter(HostGpuLossCallback host_gpu_loss_callback,