From 49a0ca6d5d9929391e0633163ebbfec564d27cc1 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Wed, 5 Aug 2026 16:58:43 +0200 Subject: [PATCH] [vulkan] Initial implementation on Bindless Buffer/Descriptors (#4251) Adds a VK_EXT_descriptor_buffer, VK_KHR_device_buffer_address path alongside push descriptors and descriptor sets. The ring is chunked and sized from the device limits, so drivers with a small sampler range (QCOM reports 128 KiB) get more, chunks per frame instead of losing the feature. A pipeline uses one path or the other, never both, since some drivers do not support bufferless push descriptors. The chunk binding is only re-emitted when it changes, and a draw that repeats the previous payload reuses its allocation instead of rewriting it. This whole change will help to reduce the constant use of descriptors on the slower path, improves the "slow implementation" on QCOM drivers (A7xx and older) for push descriptors, improves latency when compiling pipelines. The implementation on indexing descriptors was also refined. _Special Thanks_ 1.- Meowly The Bindless Smol (@Gidoly) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4251 --- src/video_core/CMakeLists.txt | 2 + .../renderer_vulkan/pipeline_helper.h | 151 +++++++++++++++++- .../renderer_vulkan/vk_buffer_cache.cpp | 16 ++ .../renderer_vulkan/vk_buffer_cache.h | 27 +++- .../renderer_vulkan/vk_compute_pipeline.cpp | 77 +++++++-- .../renderer_vulkan/vk_compute_pipeline.h | 11 +- .../renderer_vulkan/vk_descriptor_buffer.cpp | 141 ++++++++++++++++ .../renderer_vulkan/vk_descriptor_buffer.h | 71 ++++++++ .../renderer_vulkan/vk_graphics_pipeline.cpp | 117 +++++++++++--- .../renderer_vulkan/vk_graphics_pipeline.h | 17 +- .../renderer_vulkan/vk_pipeline_cache.cpp | 9 +- .../renderer_vulkan/vk_pipeline_cache.h | 2 + .../renderer_vulkan/vk_rasterizer.cpp | 14 +- .../renderer_vulkan/vk_rasterizer.h | 2 + .../renderer_vulkan/vk_resource_pool.cpp | 22 ++- .../renderer_vulkan/vk_scheduler.cpp | 10 ++ src/video_core/renderer_vulkan/vk_scheduler.h | 5 + .../vk_staging_buffer_pool.cpp | 15 ++ .../renderer_vulkan/vk_staging_buffer_pool.h | 7 + .../renderer_vulkan/vk_update_descriptor.cpp | 21 ++- .../renderer_vulkan/vk_update_descriptor.h | 56 ++++++- .../vulkan_common/vulkan_device.cpp | 50 ++++++ src/video_core/vulkan_common/vulkan_device.h | 33 +++- .../vulkan_common/vulkan_wrapper.cpp | 6 + src/video_core/vulkan_common/vulkan_wrapper.h | 51 ++++++ 25 files changed, 852 insertions(+), 81 deletions(-) create mode 100644 src/video_core/renderer_vulkan/vk_descriptor_buffer.cpp create mode 100644 src/video_core/renderer_vulkan/vk_descriptor_buffer.h diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 5c02b5ed90..aba602c3b3 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -158,6 +158,8 @@ add_library(video_core STATIC renderer_vulkan/vk_compute_pass.h renderer_vulkan/vk_compute_pipeline.cpp renderer_vulkan/vk_compute_pipeline.h + renderer_vulkan/vk_descriptor_buffer.cpp + renderer_vulkan/vk_descriptor_buffer.h renderer_vulkan/vk_descriptor_pool.cpp renderer_vulkan/vk_descriptor_pool.h renderer_vulkan/vk_fence_manager.cpp diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h index 882372aee2..051305ec42 100644 --- a/src/video_core/renderer_vulkan/pipeline_helper.h +++ b/src/video_core/renderer_vulkan/pipeline_helper.h @@ -47,6 +47,93 @@ using Shader::Backend::SPIRV::NUM_TEXTURE_AND_IMAGE_SCALING_WORDS; return std::nullopt; } +[[nodiscard]] inline VkDeviceSize DescriptorSizeForType(const Device& device, + VkDescriptorType type) { + const auto& props = device.DescriptorBufferProperties(); + const bool robust = device.IsRobustBufferAccessEnabled(); + switch (type) { + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + return robust ? props.robustUniformBufferDescriptorSize : props.uniformBufferDescriptorSize; + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: + return robust ? props.robustStorageBufferDescriptorSize : props.storageBufferDescriptorSize; + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + return robust ? props.robustUniformTexelBufferDescriptorSize + : props.uniformTexelBufferDescriptorSize; + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + return robust ? props.robustStorageTexelBufferDescriptorSize + : props.storageTexelBufferDescriptorSize; + case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + return props.combinedImageSamplerDescriptorSize; + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: + return props.storageImageDescriptorSize; + default: + return 0; + } +} + +struct DescriptorBufferBinding { + VkDescriptorType type; + u32 count; + VkDeviceSize offset; + VkDeviceSize stride; +}; + +struct DescriptorBufferLayout { + VkDeviceSize size{}; + boost::container::small_vector bindings; + + [[nodiscard]] bool Empty() const noexcept { + return bindings.empty(); + } +}; + +inline void WriteDescriptorBuffer(const Device& device, const DescriptorBufferLayout& layout, + const DescriptorUpdateEntry* payload, u8* host) { + const vk::Device& dev = device.GetLogical(); + for (const DescriptorBufferBinding& binding : layout.bindings) { + for (u32 index = 0; index < binding.count; ++index) { + const DescriptorUpdateEntry& entry = *(payload++); + const VkDescriptorAddressInfoEXT address_info{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_ADDRESS_INFO_EXT, + .pNext = nullptr, + .address = entry.address.address, + .range = entry.address.range, + .format = entry.address.format, + }; + VkDescriptorGetInfoEXT get_info{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_GET_INFO_EXT, + .pNext = nullptr, + .type = binding.type, + .data{}, + }; + switch (binding.type) { + case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + get_info.data.pUniformBuffer = &address_info; + break; + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: + get_info.data.pStorageBuffer = &address_info; + break; + case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: + get_info.data.pUniformTexelBuffer = &address_info; + break; + case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: + get_info.data.pStorageTexelBuffer = &address_info; + break; + case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: + get_info.data.pCombinedImageSampler = &entry.image; + break; + case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: + get_info.data.pStorageImage = &entry.image; + break; + default: + continue; + } + dev.GetDescriptorEXT(get_info, binding.stride, + host + binding.offset + index * binding.stride); + } + } +} + [[nodiscard]] inline u32 NumDescriptorEntries(const Shader::Info& info) { return Shader::NumDescriptors(info.constant_buffer_descriptors) + Shader::NumDescriptors(info.storage_buffers_descriptors) + @@ -61,20 +148,70 @@ public: DescriptorLayoutBuilder(const Device& device_) : device{&device_} {} bool CanUsePushDescriptor() const noexcept { - return device->IsKhrPushDescriptorSupported() && - num_descriptors <= device->MaxPushDescriptors(); + if (!device->IsKhrPushDescriptorSupported() || + num_descriptors > device->MaxPushDescriptors()) { + return false; + } + return !device->IsExtDescriptorBufferSupported() || + device->DescriptorBufferProperties().bufferlessPushDescriptors; + } + + bool CanUseDescriptorBuffer() const noexcept { + const auto& props = device->DescriptorBufferProperties(); + if (!device->IsExtDescriptorBufferSupported() || bindings.empty() || + !props.combinedImageSamplerDescriptorSingleArray) { + return false; + } + return !props.bufferlessPushDescriptors || !CanUsePushDescriptor(); } - // TODO(crueter): utilize layout binding flags - vk::DescriptorSetLayout CreateDescriptorSetLayout(bool use_push_descriptor) const { + DescriptorBufferLayout MakeDescriptorBufferLayout(VkDescriptorSetLayout layout) const { + DescriptorBufferLayout result; + if (!layout) { + return result; + } + const vk::Device& dev = device->GetLogical(); + result.size = dev.GetDescriptorSetLayoutSizeEXT(layout); + result.bindings.reserve(bindings.size()); + for (const VkDescriptorSetLayoutBinding& entry : bindings) { + result.bindings.push_back(DescriptorBufferBinding{ + .type = entry.descriptorType, + .count = entry.descriptorCount, + .offset = dev.GetDescriptorSetLayoutBindingOffsetEXT(layout, entry.binding), + .stride = DescriptorSizeForType(*device, entry.descriptorType), + }); + } + return result; + } + + vk::DescriptorSetLayout CreateDescriptorSetLayout(bool use_push_descriptor, + bool use_descriptor_buffer = false) const { if (bindings.empty()) { return nullptr; } - const VkDescriptorSetLayoutCreateFlags flags = - use_push_descriptor ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR : 0; + VkDescriptorSetLayoutCreateFlags flags = 0; + if (use_push_descriptor) { + flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR; + } + if (use_descriptor_buffer) { + flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT; + } + boost::container::small_vector binding_flags; + VkDescriptorSetLayoutBindingFlagsCreateInfo binding_flags_ci{}; + const void* pnext = nullptr; + if (!use_push_descriptor && device->IsDescriptorBindingPartiallyBoundSupported()) { + binding_flags.assign(bindings.size(), VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT); + binding_flags_ci = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO, + .pNext = nullptr, + .bindingCount = static_cast(binding_flags.size()), + .pBindingFlags = binding_flags.data(), + }; + pnext = &binding_flags_ci; + } return device->GetLogical().CreateDescriptorSetLayout({ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, - .pNext = nullptr, + .pNext = pnext, .flags = flags, .bindingCount = static_cast(bindings.size()), .pBindings = bindings.data(), diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index eb37f922a1..a734bd049c 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -69,6 +69,9 @@ vk::Buffer CreateBuffer(const Device& device, const MemoryAllocator& memory_allo if (device.IsExtConditionalRendering()) { flags |= VK_BUFFER_USAGE_CONDITIONAL_RENDERING_BIT_EXT; } + if (device.IsBufferDeviceAddressSupported()) { + flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } const VkBufferCreateInfo buffer_ci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, @@ -91,6 +94,9 @@ Buffer::Buffer(BufferCacheRuntime& runtime, VideoCommon::NullBufferParams null_p device = &runtime.device; buffer = runtime.CreateNullBuffer(); is_null = true; + if (device->IsBufferDeviceAddressSupported()) { + device_address = device->GetLogical().GetBufferDeviceAddress(*buffer); + } } Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) @@ -100,6 +106,9 @@ Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_) if (runtime.device.HasDebuggingToolAttached()) { buffer.SetObjectNameEXT(fmt::format("Buffer {:#x}", CpuAddr()).c_str()); } + if (device->IsBufferDeviceAddressSupported()) { + device_address = device->GetLogical().GetBufferDeviceAddress(*buffer); + } } void Buffer::MarkUsage(u64 offset, u64 size) noexcept { @@ -364,6 +373,10 @@ StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool def return staging_pool.Request(size, MemoryUsage::Download, deferred); } +VkFormat BufferCacheRuntime::TexelBufferFormat(VideoCore::Surface::PixelFormat format) const { + return MaxwellToVK::SurfaceFormat(device, FormatType::Buffer, false, format).format; +} + void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) { staging_pool.FreeDeferred(ref); } @@ -690,6 +703,9 @@ vk::Buffer BufferCacheRuntime::CreateNullBuffer() { if (device.IsExtTransformFeedbackSupported()) { create_info.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; } + if (device.IsBufferDeviceAddressSupported()) { + create_info.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } vk::Buffer ret = memory_allocator.CreateBuffer(create_info, MemoryUsage::DeviceLocal); if (device.HasDebuggingToolAttached()) { ret.SetObjectNameEXT("Null buffer"); diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index d4ad156073..9b3dd50eaa 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -39,6 +39,10 @@ public: return *buffer; } + [[nodiscard]] VkDeviceAddress DeviceAddress() const noexcept { + return device_address; + } + [[nodiscard]] bool IsRegionUsed(u64 offset, u64 size) const noexcept { return tracker.IsUsed(offset, size); } @@ -70,6 +74,7 @@ private: vk::Buffer buffer; std::vector views; VideoCommon::UsageTracker tracker; + VkDeviceAddress device_address{}; u64 last_usage_tick{}; bool is_null{}; }; @@ -145,22 +150,25 @@ public: [[maybe_unused]] u32 binding_index, u32 size) { const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload); - BindBuffer(ref.buffer, static_cast(ref.offset), size); + guest_descriptor_queue.AddBuffer(ref.buffer, ref.device_address, + static_cast(ref.offset), size); return ref.mapped_span; } - void BindUniformBuffer(VkBuffer buffer, u32 offset, u32 size) { + void BindUniformBuffer(const Buffer& buffer, u32 offset, u32 size) { BindBuffer(buffer, offset, size); } - void BindStorageBuffer(VkBuffer buffer, u32 offset, u32 size, + void BindStorageBuffer(const Buffer& buffer, u32 offset, u32 size, [[maybe_unused]] bool is_written) { BindBuffer(buffer, offset, size); } void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size, VideoCore::Surface::PixelFormat format) { - guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format)); + guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format), + buffer.DeviceAddress(), offset, size, + TexelBufferFormat(format)); } bool ShouldLimitDynamicStorageBuffers() const { @@ -172,14 +180,17 @@ public: } private: - void BindBuffer(VkBuffer buffer, u32 offset, u32 size) { - if (buffer == VK_NULL_HANDLE) { - guest_descriptor_queue.AddBuffer(buffer, 0, VK_WHOLE_SIZE); + void BindBuffer(const Buffer& buffer, u32 offset, u32 size) { + const VkBuffer handle = buffer.Handle(); + if (handle == VK_NULL_HANDLE) { + guest_descriptor_queue.AddBuffer(handle, 0, 0, VK_WHOLE_SIZE); } else { - guest_descriptor_queue.AddBuffer(buffer, offset, size); + guest_descriptor_queue.AddBuffer(handle, buffer.DeviceAddress(), offset, size); } } + VkFormat TexelBufferFormat(VideoCore::Surface::PixelFormat format) const; + void ReserveNullBuffer(); vk::Buffer CreateNullBuffer(); diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 006bfc0a09..29356cfcec 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -34,12 +34,14 @@ using Tegra::Texture::TexturePair; ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk::PipelineCache& pipeline_cache_, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue_, + DescriptorBufferRing& descriptor_buffer_ring_, Common::ThreadWorker* thread_worker, PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_, vk::ShaderModule spv_module_, u64 shader_hash_) : device{device_}, - pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_}, info{info_}, + pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_}, + descriptor_buffer_ring{descriptor_buffer_ring_}, info{info_}, shader_hash{shader_hash_}, spv_module(std::move(spv_module_)) { if (shader_notify) { shader_notify->MarkShaderBuilding(); @@ -48,18 +50,36 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk uniform_buffer_sizes.begin()); num_descriptor_entries = NumDescriptorEntries(info); - auto func{[this, &scheduler, &descriptor_pool, shader_notify, pipeline_statistics] { - DescriptorLayoutBuilder builder{device}; - builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); + DescriptorLayoutBuilder builder{device}; + builder.Add(info, VK_SHADER_STAGE_COMPUTE_BIT); - uses_push_descriptor = builder.CanUsePushDescriptor(); - descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); - pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout); + uses_push_descriptor = builder.CanUsePushDescriptor(); + uses_descriptor_buffer = builder.CanUseDescriptorBuffer() && descriptor_buffer_ring.IsValid(); + descriptor_set_layout = + builder.CreateDescriptorSetLayout(uses_push_descriptor, uses_descriptor_buffer); + if (uses_descriptor_buffer) { + descriptor_buffer_layout = builder.MakeDescriptorBufferLayout(*descriptor_set_layout); + if (!descriptor_buffer_ring.CanAllocate(descriptor_buffer_layout.size)) { + LOG_DEBUG(Render_Vulkan, + "Compute shader {:016X} needs {} descriptor bytes per dispatch, falling " + "back to sets", + shader_hash, descriptor_buffer_layout.size); + uses_descriptor_buffer = false; + descriptor_buffer_layout = {}; + descriptor_set_layout = builder.CreateDescriptorSetLayout(false); + } + } + pipeline_layout = builder.CreatePipelineLayout(*descriptor_set_layout); + if (!uses_descriptor_buffer) { descriptor_update_template = builder.CreateTemplate(*descriptor_set_layout, *pipeline_layout, uses_push_descriptor); if (!uses_push_descriptor) { - descriptor_allocator = descriptor_pool.Allocator(device, scheduler, *descriptor_set_layout, info); + descriptor_allocator = + descriptor_pool.Allocator(device, scheduler, *descriptor_set_layout, info); } + } + + auto func{[this, shader_notify, pipeline_statistics] { const VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT subgroup_size_ci{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT, .pNext = nullptr, @@ -69,6 +89,9 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } + if (uses_descriptor_buffer) { + flags |= VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT; + } const VkComputePipelineCreateInfo compute_ci{ .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .pNext = nullptr, @@ -125,10 +148,10 @@ ComputePipeline::ComputePipeline(const Device& device_, Scheduler& scheduler, vk } } -void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, +bool ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, Tegra::MemoryManager& gpu_memory, Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache) { - guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries); + guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries, uses_descriptor_buffer); buffer_cache.SetComputeUniformBufferState(info.constant_buffer_mask, &uniform_buffer_sizes); buffer_cache.UnbindComputeStorageBuffers(); @@ -249,10 +272,33 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, GPU::Logging::GPULogger::GetInstance().LogPipelineBind(true, "compute pipeline"); } - const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; + const DescriptorUpdateEntry* const descriptor_data{guest_descriptor_queue.UpdateData()}; + VkDeviceSize descriptor_buffer_offset{}; + u32 descriptor_buffer_chunk{}; + if (uses_descriptor_buffer) { + const DescriptorBufferRing::Allocation alloc{ + descriptor_buffer_ring.Allocate(scheduler, descriptor_buffer_layout.size)}; + if (!alloc.host) { + LOG_DEBUG(Render_Vulkan, "Failed to reserve descriptor memory, skipping dispatch"); + return false; + } + WriteDescriptorBuffer(device, descriptor_buffer_layout, descriptor_data, alloc.host); + descriptor_buffer_offset = alloc.offset; + descriptor_buffer_chunk = alloc.chunk; + } + + const bool bind_descriptor_buffer{ + uses_descriptor_buffer && scheduler.UpdateDescriptorBufferChunk(descriptor_buffer_chunk)}; + const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty(); - scheduler.Record([this, descriptor_data, is_rescaling, + scheduler.Record([this, descriptor_data, is_rescaling, descriptor_buffer_offset, + descriptor_buffer_chunk, bind_descriptor_buffer, rescaling_data = rescaling.Data()](vk::CommandBuffer cmdbuf) { + if (bind_descriptor_buffer) { + const VkDescriptorBufferBindingInfoEXT binding_info{ + descriptor_buffer_ring.BindingInfo(descriptor_buffer_chunk)}; + cmdbuf.BindDescriptorBuffersEXT(binding_info); + } if (!pipeline) { return; } @@ -265,7 +311,11 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, RESCALING_LAYOUT_WORDS_OFFSET, sizeof(rescaling_data), rescaling_data.data()); } - if (uses_push_descriptor) { + if (uses_descriptor_buffer) { + const u32 buffer_index{}; + cmdbuf.SetDescriptorBufferOffsetsEXT(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline_layout, + 0, buffer_index, descriptor_buffer_offset); + } else if (uses_push_descriptor) { cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout, 0, descriptor_data); } else { @@ -276,6 +326,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, descriptor_set, nullptr); } }); + return true; } } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h index 073e4b5079..fba0519b58 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h @@ -13,7 +13,9 @@ #include "common/common_types.h" #include "common/thread_worker.h" #include "shader_recompiler/shader_info.h" +#include "video_core/renderer_vulkan/pipeline_helper.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_descriptor_buffer.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" #include "video_core/renderer_vulkan/vk_update_descriptor.h" @@ -34,6 +36,7 @@ public: explicit ComputePipeline(const Device& device, Scheduler& scheduler, vk::PipelineCache& pipeline_cache, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue, + DescriptorBufferRing& descriptor_buffer_ring, Common::ThreadWorker* thread_worker, PipelineStatistics* pipeline_statistics, VideoCore::ShaderNotify* shader_notify, const Shader::Info& info, @@ -45,8 +48,9 @@ public: ComputePipeline& operator=(const ComputePipeline&) = delete; ComputePipeline(const ComputePipeline&) = delete; - void Configure(Tegra::Engines::KeplerCompute& kepler_compute, Tegra::MemoryManager& gpu_memory, - Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache); + [[nodiscard]] bool Configure(Tegra::Engines::KeplerCompute& kepler_compute, + Tegra::MemoryManager& gpu_memory, Scheduler& scheduler, + BufferCache& buffer_cache, TextureCache& texture_cache); bool IsBound() const noexcept { return static_cast(pipeline); @@ -56,6 +60,7 @@ private: const Device& device; vk::PipelineCache& pipeline_cache; GuestDescriptorQueue& guest_descriptor_queue; + DescriptorBufferRing& descriptor_buffer_ring; Shader::Info info; u64 shader_hash{}; u32 num_descriptor_entries{}; @@ -65,6 +70,8 @@ private: vk::ShaderModule spv_module; vk::DescriptorSetLayout descriptor_set_layout; bool uses_push_descriptor{false}; + bool uses_descriptor_buffer{false}; + DescriptorBufferLayout descriptor_buffer_layout; DescriptorAllocator descriptor_allocator; vk::PipelineLayout pipeline_layout; vk::DescriptorUpdateTemplate descriptor_update_template; diff --git a/src/video_core/renderer_vulkan/vk_descriptor_buffer.cpp b/src/video_core/renderer_vulkan/vk_descriptor_buffer.cpp new file mode 100644 index 0000000000..279847fa99 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_descriptor_buffer.cpp @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging.h" +#include "video_core/renderer_vulkan/vk_descriptor_buffer.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/vulkan_common/vulkan_device.h" + +namespace Vulkan { + +DescriptorBufferRing::DescriptorBufferRing(const Device& device_, + MemoryAllocator& memory_allocator) + : device{device_} { + if (!device.IsExtDescriptorBufferSupported() || !device.IsBufferDeviceAddressSupported()) { + return; + } + const VkPhysicalDeviceDescriptorBufferPropertiesEXT& props{device.DescriptorBufferProperties()}; + alignment = std::max(props.descriptorBufferOffsetAlignment, 1); + + const VkDeviceSize max_bound{(std::min)({props.maxSamplerDescriptorBufferRange, + props.maxResourceDescriptorBufferRange, + props.samplerDescriptorBufferAddressSpaceSize, + props.resourceDescriptorBufferAddressSpaceSize, + props.descriptorBufferAddressSpaceSize})}; + const VkDeviceSize frame_size{device.IsTiler() ? TILER_FRAME_SIZE : DESKTOP_FRAME_SIZE}; + const VkDeviceSize chunk_size{ + Common::AlignDown((std::min)(frame_size, max_bound), alignment)}; + if (chunk_size <= alignment) { + LOG_DEBUG(Render_Vulkan, "Descriptor buffer binding limit of {} is unusable, disabling", + max_bound); + return; + } + chunk_capacity = chunk_size - alignment; + chunks_per_frame = static_cast(frame_size / chunk_size); + + const VkBufferCreateInfo buffer_ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = chunk_size, + .usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + const size_t total_chunks{chunks_per_frame * FRAMES_IN_FLIGHT}; + chunks.reserve(total_chunks); + chunk_addresses.reserve(total_chunks); + chunk_hosts.reserve(total_chunks); + for (size_t index = 0; index < total_chunks; ++index) { + vk::Buffer buffer{memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::Upload)}; + if (!buffer.IsHostVisible()) { + LOG_DEBUG(Render_Vulkan, "Descriptor buffer is not host visible, disabling"); + chunks.clear(); + return; + } + if (!buffer.IsHostCoherent()) { + LOG_DEBUG(Render_Vulkan, "Descriptor buffer is not host coherent, disabling"); + chunks.clear(); + return; + } + if (device.HasDebuggingToolAttached()) { + buffer.SetObjectNameEXT("Descriptor buffer"); + } + const VkDeviceAddress raw_address{device.GetLogical().GetBufferDeviceAddress(*buffer)}; + const VkDeviceAddress address{Common::AlignUp(raw_address, alignment)}; + chunk_addresses.push_back(address); + chunk_hosts.push_back(buffer.Mapped().data() + (address - raw_address)); + chunks.push_back(std::move(buffer)); + } +} + +DescriptorBufferRing::~DescriptorBufferRing() = default; + +void DescriptorBufferRing::TickFrame() { + if (++frame_index >= FRAMES_IN_FLIGHT) { + frame_index = 0; + } + chunk_cursor = 0; + cursor = 0; + ++generation; + frame_reused = true; +} + +void DescriptorBufferRing::TouchFrame(Scheduler& scheduler) { + frame_ticks[frame_index] = scheduler.CurrentTick(); +} + +DescriptorBufferRing::Allocation DescriptorBufferRing::Allocate(Scheduler& scheduler, + VkDeviceSize size) { + ASSERT(!chunks.empty()); + if (!CanAllocate(size)) { + LOG_DEBUG(Render_Vulkan, "Descriptor set of {} bytes exceeds chunk capacity {}", size, + chunk_capacity); + return Allocation{}; + } + const VkDeviceSize needed{Common::AlignUp(size, alignment)}; + if (frame_reused) { + frame_reused = false; + scheduler.Wait(frame_ticks[frame_index]); + } + if (cursor + needed > chunk_capacity) { + if (chunk_cursor + 1 < chunks_per_frame) { + ++chunk_cursor; + } else { + LOG_DEBUG(Render_Vulkan, "Descriptor buffer frame exhausted, stalling on the GPU"); + scheduler.Finish(); + chunk_cursor = 0; + ++generation; + } + cursor = 0; + } + const size_t chunk{frame_index * chunks_per_frame + chunk_cursor}; + const VkDeviceSize offset{cursor}; + cursor += needed; + frame_ticks[frame_index] = scheduler.CurrentTick(); + return Allocation{ + .host = chunk_hosts[chunk] + offset, + .offset = offset, + .chunk = static_cast(chunk), + .generation = generation, + }; +} + +VkDescriptorBufferBindingInfoEXT DescriptorBufferRing::BindingInfo(u32 chunk) const noexcept { + return VkDescriptorBufferBindingInfoEXT{ + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT, + .pNext = nullptr, + .address = chunk_addresses[chunk], + .usage = VK_BUFFER_USAGE_RESOURCE_DESCRIPTOR_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_SAMPLER_DESCRIPTOR_BUFFER_BIT_EXT, + }; +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_descriptor_buffer.h b/src/video_core/renderer_vulkan/vk_descriptor_buffer.h new file mode 100644 index 0000000000..6f96bcc326 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_descriptor_buffer.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "common/alignment.h" +#include "common/common_types.h" +#include "video_core/vulkan_common/vulkan_memory_allocator.h" +#include "video_core/vulkan_common/vulkan_wrapper.h" + +namespace Vulkan { + +class Device; +class Scheduler; + +class DescriptorBufferRing final { + static constexpr size_t FRAMES_IN_FLIGHT = 8; + static constexpr VkDeviceSize TILER_FRAME_SIZE = 2 * 1024 * 1024; + static constexpr VkDeviceSize DESKTOP_FRAME_SIZE = 4 * 1024 * 1024; + +public: + explicit DescriptorBufferRing(const Device& device_, MemoryAllocator& memory_allocator); + ~DescriptorBufferRing(); + + struct Allocation { + u8* host{}; + VkDeviceSize offset{}; + u32 chunk{}; + u64 generation{}; + }; + + [[nodiscard]] u64 CurrentGeneration() const noexcept { + return generation; + } + + void TouchFrame(Scheduler& scheduler); + + [[nodiscard]] bool CanAllocate(VkDeviceSize size) const noexcept { + return Common::AlignUp(size, alignment) <= chunk_capacity; + } + + void TickFrame(); + + [[nodiscard]] Allocation Allocate(Scheduler& scheduler, VkDeviceSize size); + + [[nodiscard]] VkDescriptorBufferBindingInfoEXT BindingInfo(u32 chunk) const noexcept; + + [[nodiscard]] bool IsValid() const noexcept { + return !chunks.empty(); + } + +private: + const Device& device; + std::vector chunks; + std::vector chunk_addresses; + std::vector chunk_hosts; + VkDeviceSize alignment{1}; + VkDeviceSize chunk_capacity{}; + size_t chunks_per_frame{}; + size_t frame_index{}; + size_t chunk_cursor{}; + VkDeviceSize cursor{}; + u64 generation{1}; + std::array frame_ticks{}; + bool frame_reused{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index bc59600f43..8b5b0bc9c5 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include @@ -250,13 +251,15 @@ GraphicsPipeline::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, + GuestDescriptorQueue& guest_descriptor_queue_, DescriptorBufferRing& descriptor_buffer_ring_, + 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)} { + guest_descriptor_queue{guest_descriptor_queue_}, + descriptor_buffer_ring{descriptor_buffer_ring_}, spv_modules{std::move(stages)} { if (shader_notify) { shader_notify->MarkShaderBuilding(); } @@ -276,20 +279,37 @@ GraphicsPipeline::GraphicsPipeline( num_descriptor_entries += NumDescriptorEntries(*info); } fragment_has_color0_output = stage_infos[NUM_STAGES - 1].stores_frag_color[0]; - auto func{[this, shader_notify, &render_pass_cache, &descriptor_pool, pipeline_statistics] { - DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)}; - uses_push_descriptor = builder.CanUsePushDescriptor(); - descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); - if (!uses_push_descriptor) { - descriptor_allocator = descriptor_pool.Allocator(device, scheduler, *descriptor_set_layout, stage_infos); + DescriptorLayoutBuilder builder{MakeBuilder(device, stage_infos)}; + uses_push_descriptor = builder.CanUsePushDescriptor(); + uses_descriptor_buffer = builder.CanUseDescriptorBuffer() && descriptor_buffer_ring.IsValid(); + descriptor_set_layout = + builder.CreateDescriptorSetLayout(uses_push_descriptor, uses_descriptor_buffer); + if (uses_descriptor_buffer) { + descriptor_buffer_layout = builder.MakeDescriptorBufferLayout(*descriptor_set_layout); + if (!descriptor_buffer_ring.CanAllocate(descriptor_buffer_layout.size)) { + LOG_WARNING(Render_Vulkan, + "Graphics pipeline {:016X} needs {} descriptor bytes per draw, falling back " + "to sets", + key.Hash(), descriptor_buffer_layout.size); + uses_descriptor_buffer = false; + descriptor_buffer_layout = {}; + descriptor_set_layout = builder.CreateDescriptorSetLayout(uses_push_descriptor); } + } - const VkDescriptorSetLayout set_layout{*descriptor_set_layout}; - pipeline_layout = builder.CreatePipelineLayout(set_layout); + const VkDescriptorSetLayout set_layout{*descriptor_set_layout}; + pipeline_layout = builder.CreatePipelineLayout(set_layout); + if (!uses_descriptor_buffer) { descriptor_update_template = builder.CreateTemplate(set_layout, *pipeline_layout, uses_push_descriptor); + if (!uses_push_descriptor) { + descriptor_allocator = + descriptor_pool.Allocator(device, scheduler, set_layout, stage_infos); + } + } + auto func{[this, shader_notify, &render_pass_cache, pipeline_statistics] { const VkRenderPass render_pass{render_pass_cache.Get(MakeRenderPassKey(key.state, device))}; Validate(); try { @@ -496,7 +516,7 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { buffer_cache.UpdateGraphicsBuffers(is_indexed); buffer_cache.BindHostGeometryBuffers(is_indexed); - guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries); + guest_descriptor_queue.Acquire(scheduler, num_descriptor_entries, uses_descriptor_buffer); RescalingPushConstant rescaling; RenderAreaPushConstant render_area; @@ -538,13 +558,43 @@ bool GraphicsPipeline::ConfigureImpl(bool is_indexed) { if (IsBuilt() && !pipeline) { return false; } - ConfigureDraw(rescaling, render_area); - - return true; + return ConfigureDraw(rescaling, render_area); } -void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, +bool GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, const RenderAreaPushConstant& render_area) { + const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; + + VkDeviceSize descriptor_buffer_offset{}; + u32 descriptor_buffer_chunk{}; + if (descriptor_set_layout && uses_descriptor_buffer) { + const auto* const entries = static_cast(descriptor_data); + const bool reuse_allocation = + last_descriptor_buffer_generation == descriptor_buffer_ring.CurrentGeneration() && + last_descriptor_payload.size() == num_descriptor_entries && + std::memcmp(last_descriptor_payload.data(), entries, + num_descriptor_entries * sizeof(DescriptorUpdateEntry)) == 0; + if (reuse_allocation) { + descriptor_buffer_offset = last_descriptor_buffer_offset; + descriptor_buffer_chunk = last_descriptor_buffer_chunk; + descriptor_buffer_ring.TouchFrame(scheduler); + } else { + const DescriptorBufferRing::Allocation alloc{ + descriptor_buffer_ring.Allocate(scheduler, descriptor_buffer_layout.size)}; + if (!alloc.host) { + LOG_DEBUG(Render_Vulkan, "Failed to reserve descriptor memory, skipping draw"); + return false; + } + WriteDescriptorBuffer(device, descriptor_buffer_layout, entries, alloc.host); + descriptor_buffer_offset = alloc.offset; + descriptor_buffer_chunk = alloc.chunk; + last_descriptor_buffer_offset = alloc.offset; + last_descriptor_buffer_chunk = alloc.chunk; + last_descriptor_buffer_generation = alloc.generation; + last_descriptor_payload.assign(entries, entries + num_descriptor_entries); + } + } + scheduler.RequestRenderpass(texture_cache.GetFramebuffer()); if (!is_built.load(std::memory_order::relaxed)) { // Wait for the pipeline to be built @@ -556,6 +606,9 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, const bool is_rescaling{texture_cache.IsRescaling()}; const bool update_rescaling{scheduler.UpdateRescaling(is_rescaling)}; const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)}; + const bool bind_descriptor_buffer{ + descriptor_set_layout && uses_descriptor_buffer && + scheduler.UpdateDescriptorBufferChunk(descriptor_buffer_chunk)}; // Log graphics pipeline binding if (bind_pipeline && GPU::Logging::IsActive() && @@ -564,11 +617,27 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, GPU::Logging::GPULogger::GetInstance().LogPipelineBind(false, pipeline_info); } - const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; - scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(), - is_rescaling, update_rescaling, + bool update_descriptors = true; + if (descriptor_set_layout && !uses_push_descriptor && !uses_descriptor_buffer) { + const auto* const entries = static_cast(descriptor_data); + update_descriptors = + bind_pipeline || last_descriptor_payload.size() != num_descriptor_entries || + std::memcmp(last_descriptor_payload.data(), entries, + num_descriptor_entries * sizeof(DescriptorUpdateEntry)) != 0; + if (update_descriptors) { + last_descriptor_payload.assign(entries, entries + num_descriptor_entries); + } + } + scheduler.Record([this, descriptor_data, bind_pipeline, update_descriptors, + descriptor_buffer_offset, descriptor_buffer_chunk, bind_descriptor_buffer, + rescaling_data = rescaling.Data(), is_rescaling, update_rescaling, uses_render_area = render_area.uses_render_area, render_area_data = render_area.words](vk::CommandBuffer cmdbuf) { + if (bind_descriptor_buffer) { + const VkDescriptorBufferBindingInfoEXT binding_info{ + descriptor_buffer_ring.BindingInfo(descriptor_buffer_chunk)}; + cmdbuf.BindDescriptorBuffersEXT(binding_info); + } if (bind_pipeline) { if (!pipeline) { return; @@ -593,10 +662,14 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, if (!descriptor_set_layout) { return; } - if (uses_push_descriptor) { + if (uses_descriptor_buffer) { + const u32 buffer_index{}; + cmdbuf.SetDescriptorBufferOffsetsEXT(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, + 0, buffer_index, descriptor_buffer_offset); + } else if (uses_push_descriptor) { cmdbuf.PushDescriptorSetWithTemplateKHR(*descriptor_update_template, *pipeline_layout, 0, descriptor_data); - } else { + } else if (update_descriptors) { const VkDescriptorSet descriptor_set{descriptor_allocator.Commit()}; const vk::Device& dev{device.GetLogical()}; dev.UpdateDescriptorSet(descriptor_set, *descriptor_update_template, descriptor_data); @@ -604,6 +677,7 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, descriptor_set, nullptr); } }); + return true; } void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { @@ -995,6 +1069,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { if (device.IsKhrPipelineExecutablePropertiesEnabled() && Settings::values.renderer_debug.GetValue()) { flags |= VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; } + if (uses_descriptor_buffer) { + flags |= VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT; + } 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 d1caeaee8e..b1ac5a3fcc 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -12,14 +12,18 @@ #include #include #include +#include #include "common/thread_worker.h" #include "shader_recompiler/shader_info.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/renderer_vulkan/fixed_pipeline_state.h" +#include "video_core/renderer_vulkan/pipeline_helper.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_descriptor_buffer.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/renderer_vulkan/vk_update_descriptor.h" #include "video_core/vulkan_common/vulkan_wrapper.h" namespace VideoCore { @@ -76,7 +80,8 @@ public: 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, + GuestDescriptorQueue& guest_descriptor_queue, + DescriptorBufferRing& descriptor_buffer_ring, Common::ThreadWorker* worker_thread, PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache, const GraphicsPipelineCacheKey& key, std::array stages, const std::array& infos); @@ -132,7 +137,7 @@ private: template bool ConfigureImpl(bool is_indexed); - void ConfigureDraw(const RescalingPushConstant& rescaling, + bool ConfigureDraw(const RescalingPushConstant& rescaling, const RenderAreaPushConstant& render_are); void MakePipeline(VkRenderPass render_pass); @@ -148,6 +153,7 @@ private: vk::PipelineCache& pipeline_cache; Scheduler& scheduler; GuestDescriptorQueue& guest_descriptor_queue; + DescriptorBufferRing& descriptor_buffer_ring; bool (*configure_func)(GraphicsPipeline*, bool){}; @@ -170,10 +176,17 @@ private: vk::DescriptorUpdateTemplate descriptor_update_template; vk::Pipeline pipeline; + DescriptorBufferLayout descriptor_buffer_layout; + std::vector last_descriptor_payload; + VkDeviceSize last_descriptor_buffer_offset{}; + u32 last_descriptor_buffer_chunk{}; + u64 last_descriptor_buffer_generation{}; + std::condition_variable build_condvar; std::mutex build_mutex; std::atomic_bool is_built{false}; bool uses_push_descriptor{false}; + bool uses_descriptor_buffer{false}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index b1b424ca08..2772a68d7a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -340,10 +340,12 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_, GuestDescriptorQueue& guest_descriptor_queue_, + DescriptorBufferRing& descriptor_buffer_ring_, RenderPassCache& render_pass_cache_, BufferCache& buffer_cache_, TextureCache& texture_cache_, VideoCore::ShaderNotify& shader_notify_) : VideoCommon::ShaderCache{device_memory_}, device{device_}, scheduler{scheduler_}, descriptor_pool{descriptor_pool_}, guest_descriptor_queue{guest_descriptor_queue_}, + descriptor_buffer_ring{descriptor_buffer_ring_}, render_pass_cache{render_pass_cache_}, buffer_cache{buffer_cache_}, texture_cache{texture_cache_}, shader_notify{shader_notify_}, use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, @@ -836,8 +838,8 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( 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); + descriptor_pool, guest_descriptor_queue, descriptor_buffer_ring, thread_worker, statistics, + render_pass_cache, key, std::move(modules), infos); } catch (const Shader::Exception& exception) { auto hash = key.Hash(); @@ -957,7 +959,8 @@ std::unique_ptr PipelineCache::CreateComputePipeline( } Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr}; return std::make_unique(device, scheduler, vulkan_pipeline_cache, descriptor_pool, - guest_descriptor_queue, thread_worker, statistics, + guest_descriptor_queue, descriptor_buffer_ring, + thread_worker, statistics, &shader_notify, program.info, std::move(spv_module), key.unique_hash); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h index 490f9da81a..ce89e981d2 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h @@ -105,6 +105,7 @@ public: explicit PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, const Device& device, Scheduler& scheduler, DescriptorPool& descriptor_pool, GuestDescriptorQueue& guest_descriptor_queue, + DescriptorBufferRing& descriptor_buffer_ring, RenderPassCache& render_pass_cache, BufferCache& buffer_cache, TextureCache& texture_cache, VideoCore::ShaderNotify& shader_notify_); ~PipelineCache(); @@ -147,6 +148,7 @@ private: Scheduler& scheduler; DescriptorPool& descriptor_pool; GuestDescriptorQueue& guest_descriptor_queue; + DescriptorBufferRing& descriptor_buffer_ring; RenderPassCache& render_pass_cache; BufferCache& buffer_cache; TextureCache& texture_cache; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 720f3b868c..a10d2e01e6 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -203,7 +203,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra : gpu{gpu_}, device_memory{device_memory_}, device{device_}, memory_allocator{memory_allocator_}, state_tracker{state_tracker_}, scheduler{scheduler_}, staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler), - guest_descriptor_queue(device), compute_pass_descriptor_queue(device), + guest_descriptor_queue(device, UpdateDescriptorQueue::GUEST_FRAME_PAYLOAD_SIZE, + device.IsExtDescriptorBufferSupported()), + compute_pass_descriptor_queue(device, UpdateDescriptorQueue::COMPUTE_FRAME_PAYLOAD_SIZE), + descriptor_buffer_ring(device, memory_allocator), blit_image(device, scheduler, state_tracker, descriptor_pool), render_pass_cache(device), texture_cache_runtime{ device, scheduler, memory_allocator, staging_pool, @@ -216,7 +219,8 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra staging_pool, compute_pass_descriptor_queue, descriptor_pool, texture_cache), query_cache(gpu, *this, device_memory, query_cache_runtime), pipeline_cache(device_memory, device, scheduler, descriptor_pool, guest_descriptor_queue, - render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()), + descriptor_buffer_ring, render_pass_cache, buffer_cache, texture_cache, + gpu.ShaderNotify()), accelerate_dma(buffer_cache, texture_cache, scheduler), fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler), wfi_event(device.GetLogical().CreateEvent()) { @@ -583,7 +587,10 @@ void RasterizerVulkan::DispatchCompute() { return; } std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex}; - pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); + if (!pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, + texture_cache)) { + return; + } const auto& qmd{kepler_compute->launch_description}; auto indirect_address = kepler_compute->GetIndirectComputeAddress(); @@ -882,6 +889,7 @@ void RasterizerVulkan::TickFrame() { draw_counter = 0; guest_descriptor_queue.TickFrame(); compute_pass_descriptor_queue.TickFrame(); + descriptor_buffer_ring.TickFrame(); fence_manager.TickFrame(); staging_pool.TickFrame(); { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 206bf1043a..7470aa4f14 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -17,6 +17,7 @@ #include "video_core/rasterizer_interface.h" #include "video_core/renderer_vulkan/blit_image.h" #include "video_core/renderer_vulkan/vk_buffer_cache.h" +#include "video_core/renderer_vulkan/vk_descriptor_buffer.h" #include "video_core/renderer_vulkan/vk_descriptor_pool.h" #include "video_core/renderer_vulkan/vk_fence_manager.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" @@ -207,6 +208,7 @@ private: DescriptorPool descriptor_pool; GuestDescriptorQueue guest_descriptor_queue; ComputePassDescriptorQueue compute_pass_descriptor_queue; + DescriptorBufferRing descriptor_buffer_ring; BlitImageHelper blit_image; RenderPassCache render_pass_cache; diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp index 6572f82ba9..92e66b789c 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -12,10 +15,7 @@ ResourcePool::ResourcePool(MasterSemaphore& master_semaphore_, size_t grow_step_ : master_semaphore{&master_semaphore_}, grow_step{grow_step_} {} size_t ResourcePool::CommitResource() { - // Refresh semaphore to query updated results - master_semaphore->Refresh(); - const u64 gpu_tick = master_semaphore->KnownGpuTick(); - const auto search = [this, gpu_tick](size_t begin, size_t end) -> std::optional { + const auto search = [this](size_t begin, size_t end, u64 gpu_tick) -> std::optional { for (size_t iterator = begin; iterator < end; ++iterator) { if (gpu_tick >= ticks[iterator]) { ticks[iterator] = master_semaphore->CurrentTick(); @@ -24,11 +24,17 @@ size_t ResourcePool::CommitResource() { } return std::nullopt; }; - // Try to find a free resource from the hinted position to the end. - std::optional found = search(hint_iterator, ticks.size()); + const auto find_free = [&](u64 gpu_tick) -> std::optional { + std::optional result = search(hint_iterator, ticks.size(), gpu_tick); + if (!result) { + result = search(0, hint_iterator, gpu_tick); + } + return result; + }; + std::optional found = find_free(master_semaphore->KnownGpuTick()); if (!found) { - // Search from beginning to the hinted position. - found = search(0, hint_iterator); + master_semaphore->Refresh(); + found = find_free(master_semaphore->KnownGpuTick()); if (!found) { // Both searches failed, the pool is full; handle it. const size_t free_resource = ManageOverflow(); diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 6234b41978..7432c94cfc 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -247,6 +247,15 @@ bool Scheduler::UpdateRescaling(bool is_rescaling) { return true; } +bool Scheduler::UpdateDescriptorBufferChunk(u32 descriptor_chunk) { + if (state.descriptor_buffer_bound && descriptor_chunk == state.descriptor_buffer_chunk) { + return false; + } + state.descriptor_buffer_bound = true; + state.descriptor_buffer_chunk = descriptor_chunk; + return true; +} + void Scheduler::WorkerThread(std::stop_token stop_token) { Common::SetCurrentThreadName("VulkanWorker"); @@ -369,6 +378,7 @@ void Scheduler::AllocateNewContext() { void Scheduler::InvalidateState() { state.graphics_pipeline = nullptr; state.rescaling_defined = false; + state.descriptor_buffer_bound = false; state_tracker.InvalidateCommandBufferState(); } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 5c4af8c146..b810862f73 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -80,6 +80,9 @@ public: /// Update the rescaling state. Returns true if the state has to be updated. bool UpdateRescaling(bool is_rescaling); + /// Returns true when the descriptor buffer chunk has to be bound into the command buffer. + bool UpdateDescriptorBufferChunk(u32 descriptor_chunk); + /// Invalidates current command buffer state except for render passes void InvalidateState(); @@ -255,6 +258,8 @@ private: bool is_rescaling = false; bool rescaling_defined = false; bool needs_state_enable_refresh = false; + u32 descriptor_buffer_chunk = 0; + bool descriptor_buffer_bound = false; }; struct DeferredClear { diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index b03449f9f7..0e52dceaad 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -84,10 +84,16 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem if (device.IsExtTransformFeedbackSupported()) { stream_ci.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; } + if (device.IsBufferDeviceAddressSupported()) { + stream_ci.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } stream_buffer = memory_allocator.CreateBuffer(stream_ci, MemoryUsage::Stream); if (device.HasDebuggingToolAttached()) { stream_buffer.SetObjectNameEXT("Stream Buffer"); } + if (device.IsBufferDeviceAddressSupported()) { + stream_buffer_address = device.GetLogical().GetBufferDeviceAddress(*stream_buffer); + } stream_pointer = stream_buffer.Mapped(); ASSERT_MSG(!stream_pointer.empty(), "Stream buffer must be host visible!"); } @@ -149,6 +155,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT); return StagingBufferRef{ .buffer = *stream_buffer, + .device_address = stream_buffer_address, .offset = static_cast(offset), .mapped_span = stream_pointer.subspan(offset, size), .usage{}, @@ -212,14 +219,22 @@ StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, MemoryUsage if (device.IsExtTransformFeedbackSupported()) { buffer_ci.usage |= VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT; } + if (device.IsBufferDeviceAddressSupported()) { + buffer_ci.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } vk::Buffer buffer = memory_allocator.CreateBuffer(buffer_ci, usage); if (device.HasDebuggingToolAttached()) { ++buffer_index; buffer.SetObjectNameEXT(fmt::format("Staging Buffer {}", buffer_index).c_str()); } const std::span mapped_span = buffer.Mapped(); + const VkDeviceAddress buffer_address = + device.IsBufferDeviceAddressSupported() + ? device.GetLogical().GetBufferDeviceAddress(*buffer) + : VkDeviceAddress{}; StagingBuffer& entry = GetCache(usage)[log2_size].entries.emplace_back(StagingBuffer{ .buffer = std::move(buffer), + .device_address = buffer_address, .mapped_span = mapped_span, .usage = usage, .log2_level = log2_size, diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index f63a203272..6d967abb41 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later @@ -18,6 +21,7 @@ class Scheduler; struct StagingBufferRef { VkBuffer buffer; + VkDeviceAddress device_address; VkDeviceSize offset; std::span mapped_span; MemoryUsage usage; @@ -50,6 +54,7 @@ private: struct StagingBuffer { vk::Buffer buffer; + VkDeviceAddress device_address; std::span mapped_span; MemoryUsage usage; u32 log2_level; @@ -60,6 +65,7 @@ private: StagingBufferRef Ref() const noexcept { return { .buffer = *buffer, + .device_address = device_address, .offset = 0, .mapped_span = mapped_span, .usage = usage, @@ -103,6 +109,7 @@ private: Scheduler& scheduler; vk::Buffer stream_buffer; + VkDeviceAddress stream_buffer_address{}; std::span stream_pointer; VkDeviceSize stream_buffer_size; VkDeviceSize region_size; diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp index b0227ac908..742a8d9969 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp @@ -16,8 +16,11 @@ namespace Vulkan { -UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_) - : device{device_} +UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, size_t frame_payload_size_, + bool supports_descriptor_buffer_) + : device{device_}, frame_payload_size{frame_payload_size_}, + supports_descriptor_buffer{supports_descriptor_buffer_}, + payload(frame_payload_size_ * FRAMES_IN_FLIGHT) { payload_start = payload.data(); payload_cursor = payload.data(); @@ -29,19 +32,21 @@ void UpdateDescriptorQueue::TickFrame() { if (++frame_index >= FRAMES_IN_FLIGHT) { frame_index = 0; } - payload_start = payload.data() + frame_index * FRAME_PAYLOAD_SIZE; + payload_start = payload.data() + frame_index * frame_payload_size; payload_cursor = payload_start; } -void UpdateDescriptorQueue::Acquire(Scheduler& scheduler, size_t required_entries) { +void UpdateDescriptorQueue::Acquire(Scheduler& scheduler, size_t required_entries, + bool use_descriptor_buffer_) { + use_descriptor_buffer = supports_descriptor_buffer && use_descriptor_buffer_; static constexpr size_t DEFAULT_REQUIRED_ENTRIES = 0x400; const size_t reserve = required_entries > 0 ? required_entries : DEFAULT_REQUIRED_ENTRIES; - ASSERT_MSG(reserve < FRAME_PAYLOAD_SIZE, "Descriptor reservation {} >= frame capacity {}", - reserve, FRAME_PAYLOAD_SIZE); + ASSERT_MSG(reserve < frame_payload_size, "Descriptor reservation {} >= frame capacity {}", + reserve, frame_payload_size); const size_t used = static_cast(std::distance(payload_start, payload_cursor)); - if (used + reserve >= FRAME_PAYLOAD_SIZE) { + if (used + reserve >= frame_payload_size) { LOG_WARNING(Render_Vulkan, "Payload overflow (used={}, reserve={}, capacity={})", - used, reserve, FRAME_PAYLOAD_SIZE); + used, reserve, frame_payload_size); scheduler.WaitWorker(); payload_cursor = payload_start; } diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h index 39b2b5a688..9f30e65dbd 100644 --- a/src/video_core/renderer_vulkan/vk_update_descriptor.h +++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h @@ -6,8 +6,8 @@ #pragma once -#include #include +#include #include "video_core/vulkan_common/vulkan_wrapper.h" namespace Vulkan { @@ -15,30 +15,45 @@ namespace Vulkan { class Device; class Scheduler; +struct DescriptorAddress { + VkDeviceAddress address; + VkDeviceSize range; + VkFormat format; +}; + union DescriptorUpdateEntry { DescriptorUpdateEntry() = default; DescriptorUpdateEntry(VkDescriptorImageInfo image_) : image{image_} {} DescriptorUpdateEntry(VkDescriptorBufferInfo buffer_) : buffer{buffer_} {} DescriptorUpdateEntry(VkBufferView texel_buffer_) : texel_buffer{texel_buffer_} {} + DescriptorUpdateEntry(DescriptorAddress address_) : address{address_} {} std::monostate empty{}; VkDescriptorImageInfo image; VkDescriptorBufferInfo buffer; VkBufferView texel_buffer; + DescriptorAddress address; }; class UpdateDescriptorQueue final { // This should be plenty for the vast majority of cases. Most desktop platforms only // provide up to 3 swapchain images. static constexpr size_t FRAMES_IN_FLIGHT = 8; - static constexpr size_t FRAME_PAYLOAD_SIZE = 0x20000; - static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT; public: - explicit UpdateDescriptorQueue(const Device& device_); + static constexpr size_t GUEST_FRAME_PAYLOAD_SIZE = 0x80000; + static constexpr size_t COMPUTE_FRAME_PAYLOAD_SIZE = 0x20000; + + explicit UpdateDescriptorQueue(const Device& device_, size_t frame_payload_size_, + bool supports_descriptor_buffer_ = false); ~UpdateDescriptorQueue(); + [[nodiscard]] bool UsesDescriptorBuffer() const noexcept { + return use_descriptor_buffer; + } + void TickFrame(); - void Acquire(Scheduler& scheduler, size_t required_entries = 0); + void Acquire(Scheduler& scheduler, size_t required_entries = 0, + bool use_descriptor_buffer_ = false); const DescriptorUpdateEntry* UpdateData() const noexcept { return upload_start; @@ -68,17 +83,46 @@ public: }; } + void AddBuffer(VkBuffer buffer, VkDeviceAddress base_address, VkDeviceSize offset, + VkDeviceSize size) { + if (!use_descriptor_buffer) { + AddBuffer(buffer, offset, size); + return; + } + *(payload_cursor++) = DescriptorAddress{ + .address = base_address == 0 ? 0 : base_address + offset, + .range = base_address == 0 ? VK_WHOLE_SIZE : size, + .format = VK_FORMAT_UNDEFINED, + }; + } + void AddTexelBuffer(VkBufferView texel_buffer) { *(payload_cursor++) = texel_buffer; } + void AddTexelBuffer(VkBufferView texel_buffer, VkDeviceAddress base_address, + VkDeviceSize offset, VkDeviceSize size, VkFormat format) { + if (!use_descriptor_buffer) { + AddTexelBuffer(texel_buffer); + return; + } + *(payload_cursor++) = DescriptorAddress{ + .address = base_address == 0 ? 0 : base_address + offset, + .range = base_address == 0 ? VK_WHOLE_SIZE : size, + .format = format, + }; + } + private: const Device& device; + const size_t frame_payload_size; + const bool supports_descriptor_buffer; + bool use_descriptor_buffer{false}; size_t frame_index{0}; DescriptorUpdateEntry* payload_cursor = nullptr; DescriptorUpdateEntry* payload_start = nullptr; const DescriptorUpdateEntry* upload_start = nullptr; - std::array payload; + std::vector payload; }; // TODO: should these be separate classes instead? diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 7e9df7fed0..8c41a7eb67 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -506,6 +506,8 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (is_qualcomm) { must_emulate_scaled_formats = true; LOG_WARNING(Render_Vulkan, "Qualcomm drivers require scaled vertex format emulation."); + has_broken_descriptor_aliasing = true; + LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken descriptor aliasing."); LOG_WARNING(Render_Vulkan, "Qualcomm drivers have broken custom border color."); RemoveExtensionFeature(extensions.custom_border_color, features.custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); @@ -713,6 +715,37 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR RemoveExtensionFeature(extensions.vertex_input_dynamic_state, features.vertex_input_dynamic_state, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); } + // Descriptors feature list + { + auto& descriptor_indexing = features.descriptor_indexing; + descriptor_indexing.shaderInputAttachmentArrayDynamicIndexing = false; + descriptor_indexing.shaderUniformTexelBufferArrayDynamicIndexing = false; + descriptor_indexing.shaderStorageTexelBufferArrayDynamicIndexing = false; + descriptor_indexing.shaderUniformBufferArrayNonUniformIndexing = false; + descriptor_indexing.shaderStorageBufferArrayNonUniformIndexing = false; + descriptor_indexing.shaderInputAttachmentArrayNonUniformIndexing = false; + descriptor_indexing.descriptorBindingUniformBufferUpdateAfterBind = false; + descriptor_indexing.descriptorBindingSampledImageUpdateAfterBind = false; + descriptor_indexing.descriptorBindingStorageImageUpdateAfterBind = false; + descriptor_indexing.descriptorBindingStorageBufferUpdateAfterBind = false; + descriptor_indexing.descriptorBindingUniformTexelBufferUpdateAfterBind = false; + descriptor_indexing.descriptorBindingStorageTexelBufferUpdateAfterBind = false; + descriptor_indexing.descriptorBindingUpdateUnusedWhilePending = false; + descriptor_indexing.descriptorBindingVariableDescriptorCount = false; + descriptor_indexing.runtimeDescriptorArray = false; + } + + // VK_EXT_descriptor_buffer requires VK_KHR_buffer_device_address + if (extensions.descriptor_buffer && !features.buffer_device_address.bufferDeviceAddress) { + LOG_WARNING(Render_Vulkan, "Descriptor buffer needs buffer device address, disabling."); + RemoveExtensionFeature(extensions.descriptor_buffer, features.descriptor_buffer, + VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME); + } + if (!extensions.descriptor_buffer) { + RemoveExtensionFeature(extensions.buffer_device_address, features.buffer_device_address, + VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + } + logical = vk::Device::Create(physical, queue_cis, ExtensionListForVulkan(loaded_extensions), first_next, dld); graphics_queue = logical.GetQueue(graphics_family); @@ -726,6 +759,9 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR if (extensions.memory_budget) { flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT; } + if (extensions.buffer_device_address) { + flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; + } const VmaAllocatorCreateInfo allocator_info{ .flags = flags, .physicalDevice = physical, @@ -972,6 +1008,10 @@ bool Device::GetSuitability(bool requires_swapchain) { CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME); } + if (instance_version < VK_API_VERSION_1_2) { + CHECK_EXTENSION(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); + } + #undef LOG_EXTENSION #undef CHECK_EXTENSION @@ -1082,6 +1122,11 @@ bool Device::GetSuitability(bool requires_swapchain) { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR; SetNext(next, properties.push_descriptor); } + if (extensions.descriptor_buffer) { + properties.descriptor_buffer.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_PROPERTIES_EXT; + SetNext(next, properties.descriptor_buffer); + } if (extensions.subgroup_size_control || features.subgroup_size_control.subgroupSizeControl) { properties.subgroup_size_control.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_PROPERTIES; @@ -1213,6 +1258,11 @@ void Device::RemoveUnsuitableExtensions() { RemoveExtensionFeatureIfUnsuitable(extensions.depth_clip_control, features.depth_clip_control, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME); + // VK_EXT_descriptor_buffer + extensions.descriptor_buffer = features.descriptor_buffer.descriptorBuffer; + RemoveExtensionFeatureIfUnsuitable(extensions.descriptor_buffer, features.descriptor_buffer, + VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME); + // VK_EXT_extended_dynamic_state extensions.extended_dynamic_state = features.extended_dynamic_state.extendedDynamicState; RemoveExtensionFeatureIfUnsuitable(extensions.extended_dynamic_state, diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index 0ba852f183..2708281297 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -36,6 +36,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, DescriptorIndexing, DESCRIPTOR_INDEXING, descriptor_indexing) \ FEATURE(EXT, HostQueryReset, HOST_QUERY_RESET, host_query_reset) \ FEATURE(KHR, 8BitStorage, 8BIT_STORAGE, bit8_storage) \ + FEATURE(KHR, BufferDeviceAddress, BUFFER_DEVICE_ADDRESS, buffer_device_address) \ FEATURE(KHR, TimelineSemaphore, TIMELINE_SEMAPHORE, timeline_semaphore) #define FOR_EACH_VK_FEATURE_1_3(FEATURE) \ @@ -55,6 +56,7 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE(EXT, CustomBorderColor, CUSTOM_BORDER_COLOR, custom_border_color) \ FEATURE(EXT, DepthBiasControl, DEPTH_BIAS_CONTROL, depth_bias_control) \ FEATURE(EXT, DepthClipControl, DEPTH_CLIP_CONTROL, depth_clip_control) \ + FEATURE(EXT, DescriptorBuffer, DESCRIPTOR_BUFFER, descriptor_buffer) \ FEATURE(EXT, ExtendedDynamicState, EXTENDED_DYNAMIC_STATE, extended_dynamic_state) \ FEATURE(EXT, ExtendedDynamicState2, EXTENDED_DYNAMIC_STATE_2, extended_dynamic_state2) \ FEATURE(EXT, ExtendedDynamicState3, EXTENDED_DYNAMIC_STATE_3, extended_dynamic_state3) \ @@ -175,6 +177,8 @@ VK_DEFINE_HANDLE(VmaAllocator) FEATURE_NAME(depth_bias_control, depthBiasControl) \ FEATURE_NAME(depth_bias_control, leastRepresentableValueForceUnormRepresentation) \ FEATURE_NAME(depth_bias_control, depthBiasExact) \ + FEATURE_NAME(descriptor_indexing, descriptorBindingPartiallyBound) \ + FEATURE_NAME(descriptor_indexing, shaderSampledImageArrayNonUniformIndexing) \ FEATURE_NAME(extended_dynamic_state, extendedDynamicState) \ FEATURE_NAME(format_a4b4g4r4, formatA4B4G4R4) \ FEATURE_NAME(robust_image_access, robustImageAccess) \ @@ -383,7 +387,7 @@ FN_MAX_LIMIT_LIST /// Returns true if descriptor aliasing is natively supported. bool IsDescriptorAliasingSupported() const { - return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY; + return !has_broken_descriptor_aliasing; } bool IsSampledImageArrayNonUniformIndexingSupported() const { @@ -466,6 +470,26 @@ FN_MAX_LIMIT_LIST return properties.push_descriptor.maxPushDescriptors; } + /// Returns true if robust buffer access is enabled on the device. + bool IsRobustBufferAccessEnabled() const { + return features.features.robustBufferAccess == VK_TRUE; + } + + /// Returns true if the device supports descriptor buffers. + bool IsExtDescriptorBufferSupported() const { + return extensions.descriptor_buffer; + } + + /// Returns the descriptor buffer properties of the device. + const VkPhysicalDeviceDescriptorBufferPropertiesEXT& DescriptorBufferProperties() const { + return properties.descriptor_buffer; + } + + /// Returns true if the device supports buffer device address. + bool IsBufferDeviceAddressSupported() const { + return extensions.buffer_device_address; + } + /// Returns true if formatless image load is supported. bool IsFormatlessImageLoadSupported() const { return features.features.shaderStorageImageReadWithoutFormat; @@ -818,6 +842,11 @@ FN_MAX_LIMIT_LIST return extensions.astc_decode_mode; } + /// Returns true if descriptor bindings is partially bound. + bool IsDescriptorBindingPartiallyBoundSupported() const { + return features.descriptor_indexing.descriptorBindingPartiallyBound; + } + bool HasTimelineSemaphore() const; /// Returns true if the device supports VK_KHR_synchronization2. @@ -1109,6 +1138,7 @@ private: VkPhysicalDeviceSubgroupProperties subgroup_properties{}; VkPhysicalDeviceFloatControlsProperties float_controls{}; VkPhysicalDevicePushDescriptorPropertiesKHR push_descriptor{}; + VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{}; VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{}; VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{}; VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{}; @@ -1133,6 +1163,7 @@ private: bool is_non_gpu{}; ///< Is SoftwareRasterizer, FPGA, non-GPU device. bool has_broken_compute{}; ///< Compute shaders can cause crashes bool has_broken_cube_compatibility{}; ///< Has broken cube compatibility bit + bool has_broken_descriptor_aliasing{}; ///< Miscompiles descriptors aliased on one binding bool has_broken_parallel_compiling{}; ///< Has broken parallel shader compiling. bool has_renderdoc{}; ///< Has RenderDoc attached bool has_nsight_graphics{}; ///< Has Nsight Graphics attached diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp index 4943705859..24f9da0a9e 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp @@ -237,6 +237,12 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkUnmapMemory); X(vkUpdateDescriptorSetWithTemplate); X(vkUpdateDescriptorSets); + X(vkGetBufferDeviceAddress); + X(vkGetDescriptorSetLayoutSizeEXT); + X(vkGetDescriptorSetLayoutBindingOffsetEXT); + X(vkGetDescriptorEXT); + X(vkCmdBindDescriptorBuffersEXT); + X(vkCmdSetDescriptorBufferOffsetsEXT); X(vkWaitForFences); X(vkWaitSemaphores); diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h index ef975e2bda..c2d867838d 100644 --- a/src/video_core/vulkan_common/vulkan_wrapper.h +++ b/src/video_core/vulkan_common/vulkan_wrapper.h @@ -352,6 +352,12 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkSetDebugUtilsObjectTagEXT vkSetDebugUtilsObjectTagEXT{}; PFN_vkUnmapMemory vkUnmapMemory{}; PFN_vkUpdateDescriptorSetWithTemplate vkUpdateDescriptorSetWithTemplate{}; + PFN_vkGetBufferDeviceAddress vkGetBufferDeviceAddress{}; + PFN_vkGetDescriptorSetLayoutSizeEXT vkGetDescriptorSetLayoutSizeEXT{}; + PFN_vkGetDescriptorSetLayoutBindingOffsetEXT vkGetDescriptorSetLayoutBindingOffsetEXT{}; + PFN_vkGetDescriptorEXT vkGetDescriptorEXT{}; + PFN_vkCmdBindDescriptorBuffersEXT vkCmdBindDescriptorBuffersEXT{}; + PFN_vkCmdSetDescriptorBufferOffsetsEXT vkCmdSetDescriptorBufferOffsetsEXT{}; PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets{}; PFN_vkWaitForFences vkWaitForFences{}; PFN_vkWaitSemaphores vkWaitSemaphores{}; @@ -793,6 +799,11 @@ public: return !mapped.empty(); } + /// Returns true if host writes are visible to the device. + bool IsHostCoherent() const noexcept { + return is_coherent; + } + void Flush() const; void Invalidate() const; @@ -1087,6 +1098,34 @@ public: dld->vkUpdateDescriptorSetWithTemplate(handle, set, update_template, data); } + [[nodiscard]] VkDeviceAddress GetBufferDeviceAddress(VkBuffer buffer) const noexcept { + const VkBufferDeviceAddressInfo info{ + .sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, + .pNext = nullptr, + .buffer = buffer, + }; + return dld->vkGetBufferDeviceAddress(handle, &info); + } + + [[nodiscard]] VkDeviceSize GetDescriptorSetLayoutSizeEXT( + VkDescriptorSetLayout layout) const noexcept { + VkDeviceSize size{}; + dld->vkGetDescriptorSetLayoutSizeEXT(handle, layout, &size); + return size; + } + + [[nodiscard]] VkDeviceSize GetDescriptorSetLayoutBindingOffsetEXT( + VkDescriptorSetLayout layout, u32 binding) const noexcept { + VkDeviceSize offset{}; + dld->vkGetDescriptorSetLayoutBindingOffsetEXT(handle, layout, binding, &offset); + return offset; + } + + void GetDescriptorEXT(const VkDescriptorGetInfoEXT& info, size_t size, + void* descriptor) const noexcept { + dld->vkGetDescriptorEXT(handle, &info, size, descriptor); + } + VkResult AcquireNextImageKHR(VkSwapchainKHR swapchain, u64 timeout, VkSemaphore semaphore, VkFence fence, u32* image_index) const noexcept { return dld->vkAcquireNextImageKHR(handle, swapchain, timeout, semaphore, fence, @@ -1400,6 +1439,18 @@ public: PipelineBarrier(src_stage_mask, dst_stage_mask, dependency_flags, {}, {}, image_barrier); } + void BindDescriptorBuffersEXT(Span bindings) const noexcept { + dld->vkCmdBindDescriptorBuffersEXT(handle, bindings.size(), bindings.data()); + } + + void SetDescriptorBufferOffsetsEXT(VkPipelineBindPoint bind_point, VkPipelineLayout layout, + u32 first_set, Span buffer_indices, + Span offsets) const noexcept { + dld->vkCmdSetDescriptorBufferOffsetsEXT(handle, bind_point, layout, first_set, + buffer_indices.size(), buffer_indices.data(), + offsets.data()); + } + void CopyBufferToImage(VkBuffer src_buffer, VkImage dst_image, VkImageLayout dst_image_layout, Span regions) const noexcept { dld->vkCmdCopyBufferToImage(handle, src_buffer, dst_image, dst_image_layout, regions.size(),