From d505b6b53ae5875ba3009f913f969e686f6a45f6 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:20:40 +0900 Subject: [PATCH] [Vulkan] Create per-swapchain-image present semaphores Fixes vulkan validation issues. --- src/xenia/ui/vulkan/vulkan_presenter.cc | 49 +++++++++++++++++++------ src/xenia/ui/vulkan/vulkan_presenter.h | 3 +- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/xenia/ui/vulkan/vulkan_presenter.cc b/src/xenia/ui/vulkan/vulkan_presenter.cc index 648e894cb..a19722545 100644 --- a/src/xenia/ui/vulkan/vulkan_presenter.cc +++ b/src/xenia/ui/vulkan/vulkan_presenter.cc @@ -75,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); } @@ -98,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; @@ -816,6 +806,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; } @@ -1242,6 +1255,13 @@ VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface( VkSwapchainKHR VulkanPresenter::PaintContext::PrepareForSwapchainRetirement() { if (swapchain != VK_NULL_HANDLE) { completion_timeline.AwaitAllSubmissions(); + // 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(); @@ -1250,6 +1270,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; @@ -1386,6 +1410,8 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl( // safe to return early from this function in case of an error. VkSemaphore acquire_semaphore = paint_submission.acquire_semaphore(); + + uint32_t swapchain_image_index; VkResult acquire_result = dfn.vkAcquireNextImageKHR( device, paint_context_.swapchain, UINT64_MAX, acquire_semaphore, @@ -1997,7 +2023,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 2268a5a2a..ee342d9c7 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,