From 000f243c821541d69744b59779df514d608b3780 Mon Sep 17 00:00:00 2001 From: collecting Date: Wed, 11 Feb 2026 18:08:18 -0500 Subject: [PATCH] fix(vulkan): Async Presentation & Shader Logic - Fix race condition in PipelineCache and ShaderPools using std::mutex. - Fix stutter in Asynchronous Shader Building by properly returning nullptr for unbuilt pipelines (pop-in behavior). - Modify AcquireNextImage to use MasterSemaphore for safer async presentation. --- .../renderer_vulkan/vk_compute_pipeline.cpp | 7 +-- .../renderer_vulkan/vk_compute_pipeline.h | 5 +- .../renderer_vulkan/vk_graphics_pipeline.cpp | 13 ++++-- .../renderer_vulkan/vk_graphics_pipeline.h | 12 +++-- .../renderer_vulkan/vk_pipeline_cache.cpp | 24 +++++----- .../renderer_vulkan/vk_pipeline_cache.h | 3 ++ .../renderer_vulkan/vk_swapchain.cpp | 46 +++++++++---------- 7 files changed, 61 insertions(+), 49 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 73e585c2b..e96a04b00 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -25,14 +25,14 @@ using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET; using Tegra::Texture::TexturePair; ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipeline_cache_, - DescriptorPool& descriptor_pool, + std::mutex& pipeline_cache_mutex_, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue_, Common::ThreadWorker* thread_worker, PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_, vk::ShaderModule spv_module_) - : device{device_}, - pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_}, info{info_}, + : device{device_}, pipeline_cache(pipeline_cache_), pipeline_cache_mutex(pipeline_cache_mutex_), + guest_descriptor_queue{guest_descriptor_queue_}, info{info_}, spv_module(std::move(spv_module_)) { if (shader_notify) { shader_notify->MarkShaderBuilding(); @@ -58,6 +58,7 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel if (device.IsKhrPipelineExecutablePropertiesEnabled()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } + std::scoped_lock cache_lock{pipeline_cache_mutex}; pipeline = device.GetLogical().CreateComputePipeline( { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index d1a1e2c46..28e894ded 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -3,10 +3,12 @@ #pragma once +#include #include #include #include + #include "common/common_types.h" #include "common/thread_worker.h" #include "shader_recompiler/shader_info.h" @@ -29,7 +31,7 @@ class Scheduler; class ComputePipeline { public: explicit ComputePipeline(const Device& device, vk::PipelineCache& pipeline_cache, - DescriptorPool& descriptor_pool, + std::mutex& pipeline_cache_mutex, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue, Common::ThreadWorker* thread_worker, PipelineStatistics* pipeline_statistics, @@ -48,6 +50,7 @@ public: private: const Device& device; vk::PipelineCache& pipeline_cache; + std::mutex& pipeline_cache_mutex; GuestDescriptorQueue& guest_descriptor_queue; Shader::Info info; diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index d92af0ece..6b705c298 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -88,7 +88,8 @@ bool SupportsPrimitiveRestart(VkPrimitiveTopology topology) { bool IsLine(VkPrimitiveTopology topology) { static constexpr std::array line_topologies{ - VK_PRIMITIVE_TOPOLOGY_LINE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, + VK_PRIMITIVE_TOPOLOGY_LINE_LIST, + VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, // VK_PRIMITIVE_TOPOLOGY_LINE_LOOP_EXT, }; return std::ranges::find(line_topologies, topology) == line_topologies.end(); @@ -237,15 +238,16 @@ ConfigureFuncPtr ConfigureFunc(const std::array& m GraphicsPipeline::GraphicsPipeline( Scheduler& scheduler_, BufferCache& buffer_cache_, TextureCache& texture_cache_, - vk::PipelineCache& pipeline_cache_, VideoCore::ShaderNotify* shader_notify, - const Device& device_, DescriptorPool& descriptor_pool, + vk::PipelineCache& pipeline_cache_, std::mutex& pipeline_cache_mutex_, + VideoCore::ShaderNotify* shader_notify, const Device& device_, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue_, Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key_, std::array stages, const std::array& infos) : key{key_}, device{device_}, texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, - pipeline_cache(pipeline_cache_), scheduler{scheduler_}, - guest_descriptor_queue{guest_descriptor_queue_}, spv_modules{std::move(stages)} { + pipeline_cache(pipeline_cache_), pipeline_cache_mutex(pipeline_cache_mutex_), + scheduler{scheduler_}, guest_descriptor_queue{guest_descriptor_queue_}, + spv_modules{std::move(stages)} { if (shader_notify) { shader_notify->MarkShaderBuilding(); } @@ -925,6 +927,7 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { if (device.IsKhrPipelineExecutablePropertiesEnabled()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } + std::scoped_lock lock{pipeline_cache_mutex}; pipeline = device.GetLogical().CreateGraphicsPipeline( { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index 99e56e9ad..e4d1b30fc 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -71,11 +71,12 @@ class GraphicsPipeline { public: explicit GraphicsPipeline( Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache, - vk::PipelineCache& pipeline_cache, VideoCore::ShaderNotify* shader_notify, - const Device& device, DescriptorPool& descriptor_pool, - GuestDescriptorQueue& guest_descriptor_queue, Common::ThreadWorker* worker_thread, - PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, - const GraphicsPipelineCacheKey& key, std::array stages, + vk::PipelineCache& pipeline_cache, std::mutex& pipeline_cache_mutex, + VideoCore::ShaderNotify* shader_notify, const Device& device, + DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue, + Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, + RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key, + std::array stages, const std::array& infos); GraphicsPipeline& operator=(GraphicsPipeline&&) noexcept = delete; @@ -131,6 +132,7 @@ private: TextureCache& texture_cache; BufferCache& buffer_cache; vk::PipelineCache& pipeline_cache; + std::mutex& pipeline_cache_mutex; Scheduler& scheduler; GuestDescriptorQueue& guest_descriptor_queue; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 772e7634c..250ce5373 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -677,11 +677,6 @@ GraphicsPipeline* PipelineCache::BuiltPipeline(GraphicsPipeline* pipeline) const if (pipeline->IsBuilt()) { return pipeline; } - if (!use_asynchronous_shaders) { - return pipeline; - } - // When asynchronous shaders are enabled, avoid blocking the main thread completely. - // Skip the draw until the pipeline is ready to prevent stutter. return nullptr; } @@ -769,10 +764,11 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( previous_stage = &program; } Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; - return std::make_unique( - scheduler, buffer_cache, texture_cache, vulkan_pipeline_cache, &shader_notify, device, - descriptor_pool, guest_descriptor_queue, thread_worker, statistics, render_pass_cache, key, - std::move(modules), infos); + auto pipeline{std::make_unique( + scheduler, buffer_cache, texture_cache, vulkan_pipeline_cache, pipeline_cache_mutex, + &shader_notify, device, descriptor_pool, guest_descriptor_queue, thread_worker, statistics, + render_pass_cache, key, std::move(modules), infos)}; + return pipeline; } catch (const vk::Exception& exception) { if (exception.GetResult() == VK_ERROR_OUT_OF_DEVICE_MEMORY) { @@ -804,6 +800,7 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline() { GraphicsEnvironments environments; GetGraphicsEnvironments(environments, graphics_key.unique_hashes); + std::scoped_lock lock{pools_mutex}; main_pools.ReleaseContents(); auto pipeline{ CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(), nullptr, true)}; @@ -830,6 +827,7 @@ std::unique_ptr PipelineCache::CreateComputePipeline( ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start}; env.SetCachedSize(shader->size_bytes); + std::scoped_lock lock{pools_mutex}; main_pools.ReleaseContents(); auto pipeline{CreateComputePipeline(main_pools, key, env, nullptr, true)}; if (!pipeline || pipeline_cache_filename.empty()) { @@ -874,9 +872,10 @@ std::unique_ptr PipelineCache::CreateComputePipeline( spv_module.SetObjectNameEXT(name.c_str()); } Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; - return std::make_unique(device, vulkan_pipeline_cache, descriptor_pool, - guest_descriptor_queue, thread_worker, statistics, - &shader_notify, program.info, std::move(spv_module)); + return std::make_unique(device, vulkan_pipeline_cache, pipeline_cache_mutex, + descriptor_pool, guest_descriptor_queue, thread_worker, + statistics, &shader_notify, program.info, + std::move(spv_module)); } catch (const vk::Exception& exception) { if (exception.GetResult() == VK_ERROR_OUT_OF_DEVICE_MEMORY) { @@ -904,6 +903,7 @@ void PipelineCache::SerializeVulkanPipelineCache(const std::filesystem::path& fi file.write(VULKAN_CACHE_MAGIC_NUMBER.data(), VULKAN_CACHE_MAGIC_NUMBER.size()) .write(reinterpret_cast(&cache_version), sizeof(cache_version)); + std::scoped_lock lock{pipeline_cache_mutex}; size_t cache_size = 0; std::vector cache_data; if (pipeline_cache) { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 8846a8337..e3cb3078a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -176,6 +177,8 @@ public: static constexpr u64 MEMORY_PRESSURE_COOLDOWN = 300; ShaderPools main_pools; + std::mutex pools_mutex; + std::mutex pipeline_cache_mutex; Shader::Profile profile; Shader::HostTranslateInfo host_info; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 456dcf17c..523ca42ce 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -64,35 +64,35 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox, return mode; } switch (mode) { - case Settings::VSyncMode::Fifo: - case Settings::VSyncMode::FifoRelaxed: - if (has_mailbox) { - return Settings::VSyncMode::Mailbox; - } else if (has_imm) { - return Settings::VSyncMode::Immediate; - } - [[fallthrough]]; - default: - return mode; + case Settings::VSyncMode::Fifo: + case Settings::VSyncMode::FifoRelaxed: + if (has_mailbox) { + return Settings::VSyncMode::Mailbox; + } else if (has_imm) { + return Settings::VSyncMode::Immediate; + } + [[fallthrough]]; + default: + return mode; } }(); if ((setting == Settings::VSyncMode::Mailbox && !has_mailbox) || (setting == Settings::VSyncMode::Immediate && !has_imm) || (setting == Settings::VSyncMode::FifoRelaxed && !has_fifo_relaxed)) { setting = Settings::VSyncMode::Fifo; - } + } - switch (setting) { - case Settings::VSyncMode::Immediate: - return VK_PRESENT_MODE_IMMEDIATE_KHR; - case Settings::VSyncMode::Mailbox: - return VK_PRESENT_MODE_MAILBOX_KHR; - case Settings::VSyncMode::Fifo: - return VK_PRESENT_MODE_FIFO_KHR; - case Settings::VSyncMode::FifoRelaxed: - return VK_PRESENT_MODE_FIFO_RELAXED_KHR; - default: - return VK_PRESENT_MODE_FIFO_KHR; + switch (setting) { + case Settings::VSyncMode::Immediate: + return VK_PRESENT_MODE_IMMEDIATE_KHR; + case Settings::VSyncMode::Mailbox: + return VK_PRESENT_MODE_MAILBOX_KHR; + case Settings::VSyncMode::Fifo: + return VK_PRESENT_MODE_FIFO_KHR; + case Settings::VSyncMode::FifoRelaxed: + return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + default: + return VK_PRESENT_MODE_FIFO_KHR; } } @@ -174,7 +174,7 @@ bool Swapchain::AcquireNextImage() { break; } - scheduler.Wait(resource_ticks[image_index]); + scheduler.GetMasterSemaphore().Wait(resource_ticks[image_index]); resource_ticks[image_index] = scheduler.CurrentTick(); return is_suboptimal || is_outdated;