aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h151
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp16
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h27
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp77
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.h11
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_buffer.cpp141
-rw-r--r--src/video_core/renderer_vulkan/vk_descriptor_buffer.h71
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp117
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h17
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_resource_pool.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp10
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h7
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.cpp21
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.h56
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp50
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h33
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp6
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h51
25 files changed, 852 insertions, 81 deletions
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<DescriptorBufferBinding, 32> 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<VkDescriptorBindingFlags, 32> 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<u32>(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<u32>(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<BufferView> 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<u32>(ref.offset), size);
+ guest_descriptor_queue.AddBuffer(ref.buffer, ref.device_address,
+ static_cast<u32>(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<bool>(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 <algorithm>
+
+#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<VkDeviceSize>(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<size_t>(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<u32>(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 <array>
+#include <vector>
+
+#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<vk::Buffer> chunks;
+ std::vector<VkDeviceAddress> chunk_addresses;
+ std::vector<u8*> 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<u64, FRAMES_IN_FLIGHT> 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 <algorithm>
+#include <cstring>
#include <iostream>
#include <span>
@@ -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<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& 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<const DescriptorUpdateEntry*>(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<const DescriptorUpdateEntry*>(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 <condition_variable>
#include <mutex>
#include <type_traits>
+#include <vector>
#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<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos);
@@ -132,7 +137,7 @@ private:
template <typename Spec>
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<DescriptorUpdateEntry> 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<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
return std::make_unique<GraphicsPipeline>(
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<ComputePipeline> PipelineCache::CreateComputePipeline(
}
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
return std::make_unique<ComputePipeline>(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<size_t> {
+ const auto search = [this](size_t begin, size_t end, u64 gpu_tick) -> std::optional<size_t> {
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<size_t> found = search(hint_iterator, ticks.size());
+ const auto find_free = [&](u64 gpu_tick) -> std::optional<size_t> {
+ std::optional<size_t> result = search(hint_iterator, ticks.size(), gpu_tick);
+ if (!result) {
+ result = search(0, hint_iterator, gpu_tick);
+ }
+ return result;
+ };
+ std::optional<size_t> 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<VkDeviceSize>(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<u8> 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<u8> mapped_span;
MemoryUsage usage;
@@ -50,6 +54,7 @@ private:
struct StagingBuffer {
vk::Buffer buffer;
+ VkDeviceAddress device_address;
std::span<u8> 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<u8> 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<size_t>(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 <array>
#include <variant>
+#include <vector>
#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<DescriptorUpdateEntry, PAYLOAD_SIZE> payload;
+ std::vector<DescriptorUpdateEntry> 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<VkDescriptorBufferBindingInfoEXT> bindings) const noexcept {
+ dld->vkCmdBindDescriptorBuffersEXT(handle, bindings.size(), bindings.data());
+ }
+
+ void SetDescriptorBufferOffsetsEXT(VkPipelineBindPoint bind_point, VkPipelineLayout layout,
+ u32 first_set, Span<u32> buffer_indices,
+ Span<VkDeviceSize> 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<VkBufferImageCopy> regions) const noexcept {
dld->vkCmdCopyBufferToImage(handle, src_buffer, dst_image, dst_image_layout, regions.size(),