From 5af01bef9fe3b3b8711e277d230f09ff0872c493 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 3 Jul 2026 00:58:40 -0400 Subject: [PATCH] [vulkan] Initial implementation Synchronization2 --- .../renderer_vulkan/vk_master_semaphore.cpp | 124 ++++++++++++++++++ .../vulkan_common/vulkan_device.cpp | 5 + src/video_core/vulkan_common/vulkan_device.h | 9 +- .../vulkan_common/vulkan_wrapper.cpp | 10 ++ src/video_core/vulkan_common/vulkan_wrapper.h | 78 +++++++++++ 5 files changed, 225 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index b535097b36..be5a1144ce 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -130,6 +130,70 @@ VkResult MasterSemaphore::SubmitQueueTimeline(vk::CommandBuffer& cmdbuf, VkSemaphore wait_semaphore, u64 host_tick) { const VkSemaphore timeline_semaphore = *semaphore; + if (device.HasSynchronization2()) { + const std::array cmdbuffer_infos{{ + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *upload_cmdbuf, + .deviceMask = 0, + }, + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *cmdbuf, + .deviceMask = 0, + }, + }}; + + std::array signal_infos{{ + { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = timeline_semaphore, + .value = host_tick, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }, + {}, + }}; + u32 num_signal_semaphores = 1; + if (signal_semaphore) { + signal_infos[1] = VkSemaphoreSubmitInfo{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = signal_semaphore, + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }; + num_signal_semaphores = 2; + } + + const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = wait_semaphore, + .value = 0, + .stageMask = static_cast(wait_stage_mask), + .deviceIndex = 0, + }; + + const VkSubmitInfo2 submit_info2{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .pNext = nullptr, + .flags = 0, + .waitSemaphoreInfoCount = num_wait_semaphores, + .pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr, + .commandBufferInfoCount = static_cast(cmdbuffer_infos.size()), + .pCommandBufferInfos = cmdbuffer_infos.data(), + .signalSemaphoreInfoCount = num_signal_semaphores, + .pSignalSemaphoreInfos = signal_infos.data(), + }; + return device.GetGraphicsQueue().Submit2(submit_info2); + } + const u32 num_signal_semaphores = signal_semaphore ? 2 : 1; const std::array signal_values{host_tick, u64(0)}; const std::array signal_semaphores{timeline_semaphore, signal_semaphore}; @@ -172,6 +236,66 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, vk::CommandBuffer& upload_cmdbuf, VkSemaphore signal_semaphore, VkSemaphore wait_semaphore, u64 host_tick) { + if (device.HasSynchronization2()) { + const std::array cmdbuffer_infos{{ + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *upload_cmdbuf, + .deviceMask = 0, + }, + { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, + .pNext = nullptr, + .commandBuffer = *cmdbuf, + .deviceMask = 0, + }, + }}; + + const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo signal_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = signal_semaphore, + .value = 0, + .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, + .deviceIndex = 0, + }; + + const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; + const VkSemaphoreSubmitInfo wait_info{ + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, + .pNext = nullptr, + .semaphore = wait_semaphore, + .value = 0, + .stageMask = static_cast(wait_stage_mask), + .deviceIndex = 0, + }; + + const VkSubmitInfo2 submit_info2{ + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, + .pNext = nullptr, + .flags = 0, + .waitSemaphoreInfoCount = num_wait_semaphores, + .pWaitSemaphoreInfos = num_wait_semaphores ? &wait_info : nullptr, + .commandBufferInfoCount = static_cast(cmdbuffer_infos.size()), + .pCommandBufferInfos = cmdbuffer_infos.data(), + .signalSemaphoreInfoCount = num_signal_semaphores, + .pSignalSemaphoreInfos = num_signal_semaphores ? &signal_info : nullptr, + }; + + auto fence = GetFreeFence(); + auto result = device.GetGraphicsQueue().Submit2(submit_info2, *fence); + + if (result == VK_SUCCESS) { + std::scoped_lock lock{wait_mutex}; + wait_queue.emplace(host_tick, std::move(fence)); + wait_cv.notify_one(); + } + + return result; + } + const u32 num_signal_semaphores = signal_semaphore ? 1 : 0; const u32 num_wait_semaphores = wait_semaphore ? 1 : 0; diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 7bf73dcead..ce46234fff 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -1375,6 +1375,11 @@ void Device::RemoveUnsuitableExtensions() { // VK_KHR_maintenance8 extensions.maintenance8 = loaded_extensions.contains(VK_KHR_MAINTENANCE_8_EXTENSION_NAME); RemoveExtensionIfUnsuitable(extensions.maintenance8, VK_KHR_MAINTENANCE_8_EXTENSION_NAME); + + // VK_KHR_synchronization2 + extensions.synchronization2 = features.synchronization2.synchronization2; + RemoveExtensionFeatureIfUnsuitable(extensions.synchronization2, features.synchronization2, + VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); } void Device::SetupFamilies(VkSurfaceKHR surface) { diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 110d0c1199..d224ad62b0 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -43,7 +43,8 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, ShaderDemoteToHelperInvocation, SHADER_DEMOTE_TO_HELPER_INVOCATION, \ shader_demote_to_helper_invocation) \ FEATURE(EXT, SubgroupSizeControl, SUBGROUP_SIZE_CONTROL, subgroup_size_control) \ - FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) + FEATURE(KHR, Maintenance4, MAINTENANCE_4, maintenance4) \ + FEATURE(KHR, Synchronization2, SYNCHRONIZATION_2, synchronization2) #define FOR_EACH_VK_FEATURE_1_4(FEATURE) @@ -180,6 +181,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE_NAME(robustness2, nullDescriptor) \ FEATURE_NAME(shader_float16_int8, shaderFloat16) \ FEATURE_NAME(shader_float16_int8, shaderInt8) \ + FEATURE_NAME(synchronization2, synchronization2) \ FEATURE_NAME(timeline_semaphore, timelineSemaphore) \ FEATURE_NAME(transform_feedback, transformFeedback) \ FEATURE_NAME(uniform_buffer_standard_layout, uniformBufferStandardLayout) \ @@ -722,6 +724,11 @@ FN_MAX_LIMIT_LIST bool HasTimelineSemaphore() const; + /// Returns true if the device supports VK_KHR_synchronization2. + bool HasSynchronization2() const { + return extensions.synchronization2; + } + /// Returns the minimum supported version of SPIR-V. u32 SupportedSpirvVersion() const { if (instance_version >= VK_API_VERSION_1_3) { diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 3d082c8360..95793d252f 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -123,6 +123,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCmdEndDebugUtilsLabelEXT); X(vkCmdFillBuffer); X(vkCmdPipelineBarrier); + X(vkCmdPipelineBarrier2); X(vkCmdPushConstants); X(vkCmdPushDescriptorSetWithTemplateKHR); X(vkCmdSetBlendConstants); @@ -226,6 +227,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkGetSemaphoreCounterValue); X(vkMapMemory); X(vkQueueSubmit); + X(vkQueueSubmit2); X(vkResetFences); X(vkResetQueryPool); X(vkSetDebugUtilsObjectNameEXT); @@ -252,6 +254,14 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { Proc(dld.vkCmdDrawIndirectCount, dld, "vkCmdDrawIndirectCountKHR", device); Proc(dld.vkCmdDrawIndexedIndirectCount, dld, "vkCmdDrawIndexedIndirectCountKHR", device); } + + // Synchronization2 is core in Vulkan 1.3, otherwise requires VK_KHR_synchronization2 + if (!dld.vkCmdPipelineBarrier2) { + Proc(dld.vkCmdPipelineBarrier2, dld, "vkCmdPipelineBarrier2KHR", device); + } + if (!dld.vkQueueSubmit2) { + Proc(dld.vkQueueSubmit2, dld, "vkQueueSubmit2KHR", device); + } #undef X } diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index 87870893c5..6b458080d3 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -237,6 +238,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCmdEndTransformFeedbackEXT vkCmdEndTransformFeedbackEXT{}; PFN_vkCmdFillBuffer vkCmdFillBuffer{}; PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier{}; + PFN_vkCmdPipelineBarrier2 vkCmdPipelineBarrier2{}; PFN_vkCmdPushConstants vkCmdPushConstants{}; PFN_vkCmdPushDescriptorSetWithTemplateKHR vkCmdPushDescriptorSetWithTemplateKHR{}; PFN_vkCmdResolveImage vkCmdResolveImage{}; @@ -340,6 +342,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{}; PFN_vkMapMemory vkMapMemory{}; PFN_vkQueueSubmit vkQueueSubmit{}; + PFN_vkQueueSubmit2 vkQueueSubmit2{}; PFN_vkResetFences vkResetFences{}; PFN_vkResetQueryPool vkResetQueryPool{}; PFN_vkSetDebugUtilsObjectNameEXT vkSetDebugUtilsObjectNameEXT{}; @@ -819,6 +822,13 @@ public: return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence); } + /// Submits using VK_KHR_synchronization2 / Vulkan 1.3 vkQueueSubmit2. + /// Only valid to call when the device dispatch table has vkQueueSubmit2 loaded. + VkResult Submit2(Span submit_infos, + VkFence fence = VK_NULL_HANDLE) const noexcept { + return dld->vkQueueSubmit2(queue, submit_infos.size(), submit_infos.data(), fence); + } + VkResult Present(const VkPresentInfoKHR& present_info) const noexcept { return dld->vkQueuePresentKHR(queue, &present_info); } @@ -1287,6 +1297,74 @@ public: VkDependencyFlags dependency_flags, Span memory_barriers, Span buffer_barriers, Span image_barriers) const noexcept { + // Legacy VkPipelineStageFlagBits/VkAccessFlagBits are bit-compatible with their + // Synchronization2 *2 counterparts, so barriers can be widened without a lookup table. + static constexpr u32 MaxBarriers = 16; + if (dld->vkCmdPipelineBarrier2 && memory_barriers.size() <= MaxBarriers && + buffer_barriers.size() <= MaxBarriers && image_barriers.size() <= MaxBarriers) { + const auto src_stage_mask2 = static_cast(src_stage_mask); + const auto dst_stage_mask2 = static_cast(dst_stage_mask); + + std::array memory_barriers2; + for (u32 i = 0; i < memory_barriers.size(); ++i) { + memory_barriers2[i] = VkMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(memory_barriers[i].srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(memory_barriers[i].dstAccessMask), + }; + } + std::array buffer_barriers2; + for (u32 i = 0; i < buffer_barriers.size(); ++i) { + const auto& barrier = buffer_barriers[i]; + buffer_barriers2[i] = VkBufferMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(barrier.srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(barrier.dstAccessMask), + .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex, + .buffer = barrier.buffer, + .offset = barrier.offset, + .size = barrier.size, + }; + } + std::array image_barriers2; + for (u32 i = 0; i < image_barriers.size(); ++i) { + const auto& barrier = image_barriers[i]; + image_barriers2[i] = VkImageMemoryBarrier2{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = src_stage_mask2, + .srcAccessMask = static_cast(barrier.srcAccessMask), + .dstStageMask = dst_stage_mask2, + .dstAccessMask = static_cast(barrier.dstAccessMask), + .oldLayout = barrier.oldLayout, + .newLayout = barrier.newLayout, + .srcQueueFamilyIndex = barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = barrier.dstQueueFamilyIndex, + .image = barrier.image, + .subresourceRange = barrier.subresourceRange, + }; + } + const VkDependencyInfo dependency_info{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .dependencyFlags = dependency_flags, + .memoryBarrierCount = memory_barriers.size(), + .pMemoryBarriers = memory_barriers2.data(), + .bufferMemoryBarrierCount = buffer_barriers.size(), + .pBufferMemoryBarriers = buffer_barriers2.data(), + .imageMemoryBarrierCount = image_barriers.size(), + .pImageMemoryBarriers = image_barriers2.data(), + }; + dld->vkCmdPipelineBarrier2(handle, &dependency_info); + return; + } dld->vkCmdPipelineBarrier(handle, src_stage_mask, dst_stage_mask, dependency_flags, memory_barriers.size(), memory_barriers.data(), buffer_barriers.size(), buffer_barriers.data(),