From 4d0b5c78984fbaa620acd39985e623ca8459cb53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 11 Oct 2026 14:50:51 +0200 Subject: [PATCH] perf(agc): give the fragment stage its own descriptor set when modules are built at registration Co-Authored-By: Claude --- .../Graphics/include/PipelineLibrary.hpp | 2 + .../Graphics/include/ShaderResources.hpp | 16 +++ .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 4 +- .../libSceAgcDriver/Graphics/src/Pipeline.cpp | 33 +++++- .../Graphics/src/PipelineLibrary.cpp | 27 +++-- .../Graphics/src/ShaderResources.cpp | 111 ++++++++++++++---- .../Graphics/src/ShaderValidation.cpp | 2 +- .../prx/libSceAgcDriver/tests/Graphics.cpp | 2 +- .../Optimization/src/BindingAllocator.cpp | 2 +- .../src/DescriptorBindingBuilder.cpp | 8 +- core/shader/recompiler/RuntimeAbi.hpp | 6 +- .../SpirvBackend/src/SpirvImageEmitter.cpp | 2 +- .../src/SpirvMemory/SpirvEmitterHelpers.cpp | 2 +- 13 files changed, 164 insertions(+), 53 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp index f480042a3..4ffb5e3bc 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineLibrary.hpp @@ -19,6 +19,8 @@ struct PipelineLibraryKeys { std::vector fragmentOutput; std::vector renderPass; std::vector layout; + std::vector preRasterizationLayout; + std::vector fragmentLayout; }; std::span PipelineLibraryDynamicStates(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 014a06035..4e4cf13b4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -117,6 +117,8 @@ public: std::vector LayoutBindings() const; bool LayoutUpdateAfterBind() const; std::span LayoutBindingFlags() const { return layoutBindingFlags; } + VkDescriptorSetLayout FragmentLayout() const { return fragment.layout; } + bool SplitSets() const { return splitSets; } void Bind(VkCommandBuffer commands, VkPipelineBindPoint bindPoint, VkPipelineLayout layout) const; struct DrawBindings { DrawBindings() = default; @@ -127,6 +129,7 @@ public: std::shared_ptr buffer; }; DescriptorCache* cache = nullptr; + DescriptorCache::SetAllocation fragmentAllocation; DescriptorCache::SetAllocation allocation; std::vector snapshots; ~DrawBindings(); @@ -165,6 +168,7 @@ public: bool ReadsImage(const StorageTexture* image) const; bool HasCachedStorageImages() const; const std::vector& LayoutKey() const { return layoutKey; } + const std::vector& FragmentLayoutKey() const { return fragmentLayoutKey; } // Debug aid: each bound guest resource with the fraction of sampled bytes that are nonzero. std::string Describe() const; @@ -428,6 +432,17 @@ private: VkDescriptorPool pool = VK_NULL_HANDLE; // The cache pool the set was allocated from, freed back to it on release. DescriptorCache::SetAllocation cacheAllocation; + struct SetObjects { + VkDescriptorSetLayout layout = VK_NULL_HANDLE; + bool ownsLayout = false; + VkDescriptorSet set = VK_NULL_HANDLE; + VkDescriptorPool pool = VK_NULL_HANDLE; + DescriptorCache::SetAllocation cacheAllocation; + }; + bool splitSets = false; + std::vector fragmentLayoutKey; + SetObjects fragment; + std::uint32_t setOf(const Binding& binding) const; std::vector allocations; std::vector descriptorBuffers; std::vector descriptorImages; @@ -479,6 +494,7 @@ private: // the descriptor counts the set was sized for, and the compute stage of a deferred build. std::vector bindings; std::vector layoutBindingFlags; + std::vector fragmentBindingFlags; std::vector refreshResourceKey; struct DeferredImages { const ShaderRecompiler::DescriptorBinding* binding; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 2f3bf711d..914e7b269 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1764,7 +1764,9 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam APS5_LOG_CHARS_OUT_DEBUG("Pipeline Begin OK"); if (drawBindings != nullptr) { const auto set = drawBindings->allocation.set; - context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout(), 0, 1, &set, 0, nullptr); + const auto fragmentSet = drawBindings->fragmentAllocation.set; + if (set != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout(), 0, 1, &set, 0, nullptr); + if (fragmentSet != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout(), ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet, 1, &fragmentSet, 0, nullptr); } else { resources.Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout()); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 139cff586..2bcd134d1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -217,8 +217,28 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State& } appendKey(keys.renderPass, state.depth.has_value()); if (state.depth) appendKey(keys.renderPass, state.depth->format); - appendKey(keys.layout, pushStagesOf(context)); - for (const auto word : resources.LayoutKey()) appendKey(keys.layout, word); + if (!resources.SplitSets()) { + appendKey(keys.layout, pushStagesOf(context)); + for (const auto word : resources.LayoutKey()) appendKey(keys.layout, word); + keys.preRasterizationLayout = keys.fragmentLayout = keys.layout; + return true; + } + const auto splitKey = [&](std::vector& key, std::uint32_t sets) { + appendKey(key, ~0u); + appendKey(key, sets); + appendKey(key, pushStagesOf(context)); + if ((sets & 1u) != 0u) { + appendKey(key, resources.LayoutKey().size()); + for (const auto word : resources.LayoutKey()) appendKey(key, word); + } + if ((sets & 2u) != 0u) { + appendKey(key, resources.FragmentLayoutKey().size()); + for (const auto word : resources.FragmentLayoutKey()) appendKey(key, word); + } + }; + splitKey(keys.layout, 3u); + splitKey(keys.preRasterizationLayout, 1u); + splitKey(keys.fragmentLayout, 2u); return true; } @@ -280,12 +300,13 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput } // A descriptor set layout with the same bindings as this one is compatible with the pipeline // layout, so later draws bind their own ShaderResources' set under it. - const auto setLayout = resources.Layout(); + const std::array setLayouts{resources.Layout(), resources.FragmentLayout()}; const VkPushConstantRange push{pushStages, 0, PipelinePushSlotBytes}; const std::array slots{{{pushStagesOf(context), 0, PipelinePushSlotBytes}, {VK_SHADER_STAGE_FRAGMENT_BIT, PipelinePushSlotBytes, PipelinePushSlotBytes}}}; VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; - layoutInfo.setLayoutCount = 1; - layoutInfo.pSetLayouts = &setLayout; + layoutInfo.flags = resources.SplitSets() ? VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT : 0; + layoutInfo.setLayoutCount = resources.SplitSets() ? 2u : 1u; + layoutInfo.pSetLayouts = setLayouts.data(); layoutInfo.pushConstantRangeCount = dynamicRendering ? static_cast(slots.size()) : pushStages != 0 ? 1 : 0; layoutInfo.pPushConstantRanges = dynamicRendering ? slots.data() : pushStages != 0 ? &push : nullptr; Check(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &layout), "vkCreatePipelineLayout graphics"); @@ -709,6 +730,8 @@ void pipelineKey(std::vector& key, const Context& context, const Stat } append(key, resources.LayoutKey().size()); for (const auto word : resources.LayoutKey()) append(key, word); + append(key, resources.FragmentLayoutKey().size()); + for (const auto word : resources.FragmentLayoutKey()) append(key, word); append(key, state.hasColorTarget); append(key, state.rectList); append(key, state.topology); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp index edb90c69e..df499bb21 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace AgcDriver::Graphics { @@ -111,12 +112,22 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip device.destroyLayout = context.Function("vkDestroyPipelineLayout"); device.destroyModule = context.Function("vkDestroyShaderModule"); } - auto pipelineLayout = device.layouts.find(keys.layout); - if (pipelineLayout == device.layouts.end()) { + const auto layoutFor = [&](const Key& key, std::optional set) { + auto found = device.layouts.find(key); + if (found != device.layouts.end()) return found->second; + auto info = layout; + std::array sets{VK_NULL_HANDLE, VK_NULL_HANDLE}; + if (set) { + sets.at(*set) = layout.pSetLayouts[*set]; + info.pSetLayouts = sets.data(); + } VkPipelineLayout handle = VK_NULL_HANDLE; - Check(context.Function("vkCreatePipelineLayout")(context.device, &layout, nullptr, &handle), "vkCreatePipelineLayout library"); - pipelineLayout = device.layouts.emplace(keys.layout, handle).first; - } + Check(context.Function("vkCreatePipelineLayout")(context.device, &info, nullptr, &handle), "vkCreatePipelineLayout library"); + return device.layouts.emplace(key, handle).first->second; + }; + const bool split = (layout.flags & VK_PIPELINE_LAYOUT_CREATE_INDEPENDENT_SETS_BIT_EXT) != 0; + const auto fullLayout = layoutFor(keys.layout, std::nullopt); + const std::array partLayouts{VK_NULL_HANDLE, split ? layoutFor(keys.preRasterizationLayout, 0u) : fullLayout, split ? layoutFor(keys.fragmentLayout, 1u) : fullLayout, VK_NULL_HANDLE}; std::vector preRasterization; const VkPipelineShaderStageCreateInfo* fragment = nullptr; bool mesh = false; @@ -135,7 +146,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip }; const std::array partKeys{&keys.vertexInput, &keys.preRasterization, &keys.fragmentShader, &keys.fragmentOutput}; const Key none; - const std::array contextKeys{&none, &keys.layout, &keys.layout, &keys.renderPass}; + const std::array contextKeys{&none, &keys.preRasterizationLayout, &keys.fragmentLayout, &keys.renderPass}; const VkPipelineRenderingCreateInfoKHR shaderRendering{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR}; VkPipelineDepthStencilStateCreateInfo dynamicDepth{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO}; static const bool backgroundLinking = std::getenv("APS5_NO_PIPELINE_LTO") == nullptr; @@ -155,7 +166,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip VkGraphicsPipelineCreateInfo create{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &library}; create.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT; create.pDynamicState = info.pDynamicState; - create.layout = pipelineLayout->second; + create.layout = partLayouts[part]; switch (flags[part]) { case VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT: create.pVertexInputState = info.pVertexInputState; @@ -212,7 +223,7 @@ VkPipeline LinkPipelineFromLibraries(const Context& context, const VkGraphicsPip *optimized = slot; const auto createPipelines = context.Function("vkCreateGraphicsPipelines"); const auto cache = context.pipelineCache; - const auto libraryLayout = pipelineLayout->second; + const auto libraryLayout = fullLayout; enqueue(store, context.device, [slot, createPipelines, cache, libraryLayout, libraries, &store, devicePointer = &device] { if (slot->released.load()) return; VkPipelineLibraryCreateInfoKHR linked{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 6c1d10286..e223a6d45 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1166,6 +1166,14 @@ VkDescriptorSetLayout CreateSetLayout(const Context& context, std::span PoolSizes(std::span bindings) { + std::map counts; + for (const auto& binding : bindings) counts[binding.descriptorType] += binding.descriptorCount; + std::vector sizes; + for (const auto& [type, count] : counts) sizes.push_back({type, count}); + return sizes; +} + VkDescriptorPool CreateSetPool(const Context& context, std::span sizes, bool updateAfterBind) { VkDescriptorPoolCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; info.flags = updateAfterBind ? VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT : 0; @@ -1339,7 +1347,7 @@ void ShaderResources::buildPrepare(std::span shaders, cons updateAfterBind = true; }; for (const auto& binding : shader.program->bindings) { - Require(binding.descriptorSet == 0, "unexpected descriptor set: every shader resource must use descriptor set zero"); + Require(binding.descriptorSet == ShaderRecompiler::RuntimeAbi::DescriptorSet || (shader.stage == ShaderRecompiler::ShaderStage::Fragment && binding.descriptorSet == ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet), "unexpected descriptor set: a shader resource must use its stage's descriptor set"); Require(occupied.insert(binding.binding).second, "duplicate shader binding"); const bool addressRole = binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer; const bool bufferRole = addressRole || binding.role == ShaderRecompiler::DescriptorRole::GuestBuffers || binding.role == ShaderRecompiler::DescriptorRole::ShaderData || binding.role == ShaderRecompiler::DescriptorRole::FlattenedSrt || binding.role == ShaderRecompiler::DescriptorRole::Gds; @@ -1416,23 +1424,62 @@ void ShaderResources::buildPrepare(std::span shaders, cons if (drawBuild && !usesBda && std::all_of(allocations.begin(), allocations.end(), [&](const Allocation& allocation) { return !allocation.guest || allocation.pushByte >= 0 || (allocation.dataAllocation >= 0 && allocation.dataByte < allocations[static_cast(allocation.dataAllocation)].size); })) guestMemory.AllowAdjustedRegions(); guestMemory.UploadPrepare(usesBda); timing.uploadMs = phase(BuildPhase::Upload); - std::vector description; + splitSets = std::any_of(shaders.begin(), shaders.end(), [](const CompiledShader& shader) { return shader.program != nullptr && std::any_of(shader.program->bindings.begin(), shader.program->bindings.end(), [](const auto& binding) { return binding.descriptorSet == ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet; }); }); + std::array, 2> descriptions; for (const auto& binding : bindings) { - description.push_back(binding.layout); - layoutBindingFlags.push_back(updateAfterBind && binding.layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0); - layoutKey.insert(layoutKey.end(), {binding.layout.binding, static_cast(binding.layout.descriptorType), binding.layout.descriptorCount, binding.layout.stageFlags}); + const auto set = setOf(binding); + descriptions[set].push_back(binding.layout); + (set == 0 ? layoutBindingFlags : fragmentBindingFlags).push_back(updateAfterBind && binding.layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0); + auto& key = set == 0 ? layoutKey : fragmentLayoutKey; + key.insert(key.end(), {binding.layout.binding, static_cast(binding.layout.descriptorType), binding.layout.descriptorCount, binding.layout.stageFlags}); + } + if (updateAfterBind) { + layoutKey.push_back(VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT); + if (splitSets) fragmentLayoutKey.push_back(VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT); } - if (updateAfterBind) layoutKey.push_back(VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT); static const bool noLayoutCache = std::getenv("APS5_NO_LAYOUT_CACHE") != nullptr; static const bool noPoolCache = std::getenv("APS5_NO_POOL_CACHE") != nullptr; - if (context.descriptorCache != nullptr && !noLayoutCache) { - _layout = context.descriptorCache->Layout(layoutKey, description, updateAfterBind, layoutBindingFlags); - } else { + const auto createLayout = [&](const std::vector& key, const std::vector& description, const std::vector& flags, bool& owns) { + if (context.descriptorCache != nullptr && !noLayoutCache) return context.descriptorCache->Layout(key, description, updateAfterBind, flags); ValidateDescriptorLayout(context, description, updateAfterBind); - _layout = CreateSetLayout(context, description, updateAfterBind, layoutBindingFlags); - ownsLayout = true; + owns = true; + return CreateSetLayout(context, description, updateAfterBind, flags); + }; + _layout = createLayout(layoutKey, descriptions[0], layoutBindingFlags, ownsLayout); + if (splitSets) { + fragment.layout = createLayout(fragmentLayoutKey, descriptions[1], fragmentBindingFlags, fragment.ownsLayout); + if (!descriptions[1].empty()) { + const auto sizes = PoolSizes(descriptions[1]); + if (context.descriptorCache != nullptr && !noPoolCache) { + fragment.cacheAllocation = context.descriptorCache->Allocate(fragment.layout, sizes, updateAfterBind); + fragment.set = fragment.cacheAllocation.set; + } + if (fragment.set == VK_NULL_HANDLE) { + fragment.pool = CreateSetPool(context, sizes, updateAfterBind); + VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + allocation.descriptorPool = fragment.pool; + allocation.descriptorSetCount = 1; + allocation.pSetLayouts = &fragment.layout; + Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocation, &fragment.set), "vkAllocateDescriptorSets"); + } + } } - if (!bindings.empty()) { + if (splitSets && !descriptions[0].empty()) { + const auto sizes = PoolSizes(descriptions[0]); + if (context.descriptorCache != nullptr && !noPoolCache) { + cacheAllocation = context.descriptorCache->Allocate(_layout, sizes, updateAfterBind); + _set = cacheAllocation.set; + } + if (_set == VK_NULL_HANDLE) { + pool = CreateSetPool(context, sizes, updateAfterBind); + VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + allocation.descriptorPool = pool; + allocation.descriptorSetCount = 1; + allocation.pSetLayouts = &_layout; + Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocation, &_set), "vkAllocateDescriptorSets"); + } + } + if (!splitSets && !bindings.empty()) { // The set is sized from the plan: every image element becomes exactly one descriptor // when stage B looks it up. std::vector sizes; @@ -1492,7 +1539,7 @@ void ShaderResources::buildComplete() { if (usesBda) bda = std::make_unique(context, guestMemory); else if (usesFaultBuffer) bda = std::make_unique(context); phase(BuildPhase::Bda); - if (_set != VK_NULL_HANDLE) { + if (_set != VK_NULL_HANDLE || fragment.set != VK_NULL_HANDLE) { // One update call for the whole set: the info arrays are sized up front so every write's // pointer into them stays valid until the call. std::size_t bufferCount = 0; @@ -1514,7 +1561,7 @@ void ShaderResources::buildComplete() { }; for (const auto& binding : bindings) { VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; - write.dstSet = _set; + write.dstSet = setOf(binding) == 0 ? _set : fragment.set; write.dstBinding = binding.layout.binding; write.descriptorCount = binding.layout.descriptorCount; write.descriptorType = binding.layout.descriptorType; @@ -3334,10 +3381,19 @@ void ShaderResources::release() noexcept { if (cacheAllocation.pool && context.descriptorCache != nullptr) context.descriptorCache->Free(cacheAllocation); if (pool) context.Function("vkDestroyDescriptorPool")(context.device, pool, nullptr); if (_layout && ownsLayout) context.Function("vkDestroyDescriptorSetLayout")(context.device, _layout, nullptr); + if (fragment.cacheAllocation.pool && context.descriptorCache != nullptr) context.descriptorCache->Free(fragment.cacheAllocation); + if (fragment.pool) context.Function("vkDestroyDescriptorPool")(context.device, fragment.pool, nullptr); + if (fragment.layout && fragment.ownsLayout) context.Function("vkDestroyDescriptorSetLayout")(context.device, fragment.layout, nullptr); cacheAllocation = {}; pool = VK_NULL_HANDLE; _set = VK_NULL_HANDLE; _layout = VK_NULL_HANDLE; + fragment = {}; +} + +std::uint32_t ShaderResources::setOf(const Binding& binding) const { + constexpr auto count = static_cast(ShaderRecompiler::RuntimeAbi::Binding::Count); + return splitSets && binding.layout.binding / count == static_cast(ShaderRecompiler::RuntimeAbi::Stage::Fragment) ? 1u : 0u; } VkDescriptorSetLayout ShaderResources::Layout() const { @@ -3356,11 +3412,12 @@ bool ShaderResources::LayoutUpdateAfterBind() const { ShaderResources::DrawBindings::~DrawBindings() { if (cache != nullptr && allocation.set != VK_NULL_HANDLE) cache->Free(allocation); + if (cache != nullptr && fragmentAllocation.set != VK_NULL_HANDLE) cache->Free(fragmentAllocation); } std::optional> ShaderResources::MovedReadOnlyBuffers(std::span shaders, Recorder& recorder) const { std::vector moved; - if (_set == VK_NULL_HANDLE || usesBda) return moved; + if ((_set == VK_NULL_HANDLE && fragment.set == VK_NULL_HANDLE) || usesBda) return moved; for (const auto& shader : shaders) { if (shader.program == nullptr) return std::nullopt; for (const auto& binding : shader.program->bindings) { @@ -3413,7 +3470,7 @@ std::size_t ShaderResources::MaxDrawSnapshotBytes() { } std::shared_ptr ShaderResources::PrepareDrawBindings(Recorder& recorder, std::span moved) const { - if (_set == VK_NULL_HANDLE || usesBda) return {}; + if ((_set == VK_NULL_HANDLE && fragment.set == VK_NULL_HANDLE) || usesBda) return {}; const auto reads = guestMemory.InPlaceReads(); const auto maxSnapshotBytes = MaxDrawSnapshotBytes(); std::size_t totalSnapshotBytes = 0; @@ -3460,13 +3517,17 @@ std::shared_ptr ShaderResources::PrepareDrawBindi } if (selected.empty()) return {}; Require(context.descriptorCache != nullptr, "draw snapshots require a descriptor cache"); - std::map counts; - for (const auto& binding : bindings) counts[binding.layout.descriptorType] += binding.layout.descriptorCount; - std::vector sizes; - for (const auto& [type, count] : counts) sizes.push_back({type, count}); + std::array, 2> descriptions; + for (const auto& binding : bindings) descriptions[setOf(binding)].push_back(binding.layout); result->cache = context.descriptorCache; - result->allocation = result->cache->Allocate(_layout, sizes); - Require(result->allocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed"); + if (!descriptions[0].empty()) { + result->allocation = result->cache->Allocate(_layout, PoolSizes(descriptions[0])); + Require(result->allocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed"); + } + if (!descriptions[1].empty()) { + result->fragmentAllocation = result->cache->Allocate(fragment.layout, PoolSizes(descriptions[1])); + Require(result->fragmentAllocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed"); + } auto buffers = descriptorBuffers; auto writes = descriptorWrites; std::vector snapshotIndices(allocations.size(), selected.size()); @@ -3474,7 +3535,7 @@ std::shared_ptr ShaderResources::PrepareDrawBindi for (std::size_t index = 0; index < bindings.size(); ++index) { const auto& binding = bindings[index]; auto& write = writes[index]; - write.dstSet = result->allocation.set; + write.dstSet = setOf(binding) == 0 ? result->allocation.set : result->fragmentAllocation.set; if (binding.allocations.empty()) continue; const auto offset = static_cast(write.pBufferInfo - descriptorBuffers.data()); write.pBufferInfo = buffers.data() + offset; @@ -3492,8 +3553,8 @@ std::shared_ptr ShaderResources::PrepareDrawBindi } void ShaderResources::Bind(VkCommandBuffer commands, VkPipelineBindPoint bindPoint, VkPipelineLayout layout) const { - if (_set == VK_NULL_HANDLE) return; - context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, 0, 1, &_set, 0, nullptr); + if (_set != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, 0, 1, &_set, 0, nullptr); + if (fragment.set != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet, 1, &fragment.set, 0, nullptr); } namespace { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index d30076237..d6e0fbf69 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -574,7 +574,7 @@ std::set ValidateShaders(std::span shaders, const auto expected = state.rectList ? (i == 0 ? Stage::Vertex : tessStages[i]) : tessellation ? tessStages[i] : geometry && i == 1 ? Stage::Geometry : i == shaders.size() - 1 ? Stage::Fragment : state.stages.path == ShaderPath::Geometry ? Stage::Mesh : Stage::Vertex; Require(shaders[i].program != nullptr, "missing compiled shader"); Require(shaders[i].stage == expected, "graphics stage order disagrees"); - for (const auto& binding : shaders[i].program->bindings) Require(binding.descriptorSet == 0, "graphics resource uses a descriptor set other than zero"); + for (const auto& binding : shaders[i].program->bindings) Require(binding.descriptorSet == ShaderRecompiler::RuntimeAbi::DescriptorSet || (shaders[i].stage == ShaderRecompiler::ShaderStage::Fragment && binding.descriptorSet == ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet), "graphics resource uses a descriptor set other than its stage's"); const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric, descriptorIndexing, imageInt64Atomics, geometryShader, sampleRateShading, bufferInt64Atomics, shaderClipDistance); if (i != 0) { for (const auto& [location, signature] : current.inputs) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 39c8dd8b2..29e5122ee 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -4388,7 +4388,7 @@ void validationTests() { { auto vertex = pushed({.push = true, .bufferArray = 2}); vertex.bindings.front().descriptorSet = 1; - expectFailure([&] { validate(vertex, 8); }, "descriptor set other than zero"); + expectFailure([&] { validate(vertex, 8); }, "descriptor set other than its stage's"); } { auto vertex = pushed({.push = true, .bufferArray = 2}); diff --git a/core/shader/recompiler/Optimization/src/BindingAllocator.cpp b/core/shader/recompiler/Optimization/src/BindingAllocator.cpp index 268265461..3f58c1398 100644 --- a/core/shader/recompiler/Optimization/src/BindingAllocator.cpp +++ b/core/shader/recompiler/Optimization/src/BindingAllocator.cpp @@ -103,7 +103,7 @@ BindingAllocationResult BindingAllocator::Allocate(IrProgram& program, const Bin if (info.buffers.size() > RuntimeAbi::BufferCapacity || info.images.size() > RuntimeAbi::ImageCapacity || info.samplers.size() > RuntimeAbi::SamplerHeapCapacity) fail("shader binding layout exceeds runtime metadata capacity"); if (std::ranges::any_of(next.userDataRegisters, [](auto reg) { return reg >= RuntimeAbi::UserDataCapacity; })) fail("shader user data exceeds runtime ABI capacity"); if (registrationModules || std::ranges::any_of(info.images, [](const auto& image) { return image.indirectRoot != ImageResource::NoIndirectImage; })) next.runtimeImageCount = static_cast(info.images.size()); - if (registrationModules && (program.Resources().stage == IrShaderStage::Pixel || std::ranges::any_of(info.samplers, [](const auto& sampler) { return FoldsTexelOffsets(sampler); }))) next.runtimeStateDwords = RuntimeAbi::StateDwords; + if (registrationModules && program.Resources().stage == IrShaderStage::Pixel) next.runtimeStateDwords = RuntimeAbi::StateDwords; const auto pushSize = layout.pushConstantSizeBytes / 4u; next.pushDataStartDword = next.runtimeImageCount == 0u && next.ShaderDataDwords() != 0u && next.ShaderDataDwords() <= pushSize ? layout.pushConstantOffsetBytes / 4u : PushData::NoStart; diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 851d92ed5..b76a88dce 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -257,11 +257,8 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l if (info.images.size() > ShaderInfo::MaxImages) fail("runtime image count exceeds the static capacity"); DescriptorBindingPlan plan; const auto unnormalized = ProveUnnormalized(info, snapshot); - std::uint32_t unnormalizedSamplers = 0u; for (std::uint32_t index = 0; index < info.samplers.size(); ++index) { - if (!FoldsTexelOffsets(info.samplers[index])) continue; - if (!registrationModules) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u}); - else if (unnormalized.samplers[index]) unnormalizedSamplers |= 1u << index; + if (FoldsTexelOffsets(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u}); } std::vector compareStates(info.images.size()); for (std::uint32_t index = 0; index < info.images.size(); ++index) { @@ -382,7 +379,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l if ((imageHeap || samplerHeap) && compact.resources.empty()) continue; PreparedDescriptorBinding entry{}; auto& physical = entry.descriptor; - physical.descriptorSet = RuntimeAbi::DescriptorSet; + physical.descriptorSet = registrationModules && stage == IrShaderStage::Pixel ? RuntimeAbi::FragmentDescriptorSet : RuntimeAbi::DescriptorSet; physical.binding = NativeBinding(stage, compact.kind); physical.count = compact.resources.empty() ? 1u : static_cast(compact.resources.size()); physical.kind = PhysicalKindFor(compact.kind); @@ -459,7 +456,6 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l } if (layout.runtimeStateDwords != 0u) { plan.runtimeState.assign(layout.runtimeStateDwords, 0u); - plan.runtimeState.at(RuntimeAbi::StateUnnormalizedSamplers) = unnormalizedSamplers; for (std::uint32_t target = 0; target < exportMappings.size(); ++target) plan.runtimeState.at(RuntimeAbi::StateExportMappings + target / 4u) |= static_cast(exportMappings[target]) << (target % 4u * 8u); } std::vector samplerMasks(info.samplers.size()); diff --git a/core/shader/recompiler/RuntimeAbi.hpp b/core/shader/recompiler/RuntimeAbi.hpp index 8d5019b93..2a303c504 100644 --- a/core/shader/recompiler/RuntimeAbi.hpp +++ b/core/shader/recompiler/RuntimeAbi.hpp @@ -11,6 +11,7 @@ namespace ShaderRecompiler::RuntimeAbi { inline constexpr std::uint32_t Version = 11u; inline constexpr std::uint32_t DescriptorSet = 0u; +inline constexpr std::uint32_t FragmentDescriptorSet = 1u; inline constexpr std::uint32_t StageCount = 4u; inline constexpr std::uint32_t PushConstantDwords = 32u; inline constexpr std::uint32_t FirstImageBinding = 1u; @@ -39,9 +40,8 @@ inline constexpr std::uint32_t BindlessTableSlots = 16u; inline constexpr std::uint32_t StorageMipSlots = 4u; inline constexpr std::uint32_t StorageHeapCapacity = 16u; inline constexpr std::uint32_t SamplerHeapCapacity = 32u; -inline constexpr std::uint32_t StateUnnormalizedSamplers = 0u; -inline constexpr std::uint32_t StateExportMappings = 1u; -inline constexpr std::uint32_t StateDwords = 3u; +inline constexpr std::uint32_t StateExportMappings = 0u; +inline constexpr std::uint32_t StateDwords = 2u; inline constexpr std::uint32_t ImageModeMask = 0xffu; inline constexpr std::uint32_t CompareStateShift = 9u; inline constexpr std::uint32_t CompareStateMask = 0x1fffu; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 7297e528d..d380b33fc 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -1494,7 +1494,7 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con } else if (!offsetOperand) { sample = emitSample(FoldedOffsetCoord(ctx, access, setup), foldedMask, foldedOperands); } else { - const auto selector = state.registrationModules ? EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), EmitShaderDataDwordLoad(state, state.program.Metadata().bindings.RuntimeStateDword() + RuntimeAbi::StateUnnormalizedSamplers), ConstantU32(state, mem.sampler)), 1u) : state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u); + const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u); const auto folded = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u)); const auto foldLabel = state.module.AllocateId(); const auto operandLabel = state.module.AllocateId(); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp index cc7ab59a8..4227e8d83 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp @@ -391,7 +391,7 @@ void DefineDescriptors(SpirvEmitterState& state) { const auto Define = [&](std::uint32_t type, const char* name, std::uint32_t storage = spv::StorageClassStorageBuffer) { const auto variable = state.module.DefineGlobalVariable(TypePointer(state, storage, type), storage); state.module.AddName(variable, name); - state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationDescriptorSet, 0u); + state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationDescriptorSet, state.registrationModules && stage == IrShaderStage::Pixel ? RuntimeAbi::FragmentDescriptorSet : RuntimeAbi::DescriptorSet); state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBinding, NativeBinding(stage, binding.kind)); return variable; };