diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ComputePipelineCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ComputePipelineCapture.cpp index 9d9fa84300..72880ca3b3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ComputePipelineCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ComputePipelineCapture.cpp @@ -155,7 +155,7 @@ void ComputePipelineCapture::Validate() const { RequireCapture(binding.stages == VK_SHADER_STAGE_COMPUTE_BIT, "unsupported descriptor stage flags"); constexpr VkDescriptorBindingFlags supported = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT; RequireCapture((binding.flags & ~supported) == 0, "unsupported descriptor binding flags"); - RequireCapture((binding.flags & VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT) == 0 || (binding.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER && (layout.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT) != 0), "unsupported update-after-bind descriptor"); + RequireCapture((binding.flags & VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT) == 0 || ((binding.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || binding.type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || binding.type == VK_DESCRIPTOR_TYPE_SAMPLER) && (layout.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT) != 0), "unsupported update-after-bind descriptor"); RequireCapture((binding.flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) == 0 || binding.binding == highest, "variable descriptor count must use the highest binding"); } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 3326b66c8d..a4fb4cfb54 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -318,6 +318,7 @@ struct VulkanDevice::State { // graphics stages, and compute workgroups wider than a wave). bool descriptorIndexing = false; bool storageBufferUpdateAfterBind = false; + bool sampledImageUpdateAfterBind = false; bool descriptorBindingPartiallyBound = false; bool descriptorBindingVariableDescriptorCount = false; bool runtimeDescriptorArray = false; @@ -906,13 +907,14 @@ ComputePipelineReplayResult VulkanDevice::ReplayComputePipeline(const ComputePip for (const auto& layout : capture.sets) { const bool updateAfterBind = (layout.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT) != 0; hasUpdateAfterBindLayout |= updateAfterBind; - require(!updateAfterBind || state->storageBufferUpdateAfterBind, "captured update-after-bind descriptors are unsupported"); + require(!updateAfterBind || state->storageBufferUpdateAfterBind || state->sampledImageUpdateAfterBind, "captured update-after-bind descriptors are unsupported"); std::vector bindings; std::vector flags; std::uint32_t variableCount = 0; for (const auto& binding : layout.bindings) { require((binding.flags & VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT) == 0 || state->descriptorBindingPartiallyBound, "captured partially-bound descriptors are unsupported"); require((binding.flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) == 0 || state->descriptorBindingVariableDescriptorCount, "captured variable descriptor count is unsupported"); + require((binding.flags & VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT) == 0 || (binding.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? state->storageBufferUpdateAfterBind : state->sampledImageUpdateAfterBind), "captured update-after-bind descriptors are unsupported"); if ((binding.flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) != 0) variableCount = binding.count; bindings.push_back({binding.binding, binding.type, binding.count, binding.stages, nullptr}); flags.push_back(binding.flags); @@ -1667,16 +1669,17 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : state->InstanceFunction("vkGetPhysicalDeviceFeatures2")(selected, &features); state->descriptorIndexing = descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing == VK_TRUE && descriptorIndexingFeatures.shaderStorageImageArrayNonUniformIndexing == VK_TRUE; state->storageBufferUpdateAfterBind = descriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind == VK_TRUE; + state->sampledImageUpdateAfterBind = descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind == VK_TRUE; state->descriptorBindingPartiallyBound = descriptorIndexingFeatures.descriptorBindingPartiallyBound == VK_TRUE; state->descriptorBindingVariableDescriptorCount = descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount == VK_TRUE; state->runtimeDescriptorArray = descriptorIndexingFeatures.runtimeDescriptorArray == VK_TRUE; - if (state->storageBufferUpdateAfterBind) { + if (state->storageBufferUpdateAfterBind || state->sampledImageUpdateAfterBind) { VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &state->descriptorIndexingProperties}; state->InstanceFunction("vkGetPhysicalDeviceProperties2")(selected, &properties); } } descriptorIndexingFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT}; - if (state->descriptorIndexing || state->storageBufferUpdateAfterBind || state->descriptorBindingPartiallyBound || state->descriptorBindingVariableDescriptorCount || state->runtimeDescriptorArray) deviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); + if (state->descriptorIndexing || state->storageBufferUpdateAfterBind || state->sampledImageUpdateAfterBind || state->descriptorBindingPartiallyBound || state->descriptorBindingVariableDescriptorCount || state->runtimeDescriptorArray) deviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); if (state->descriptorIndexing) { descriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing = VK_TRUE; descriptorIndexingFeatures.shaderStorageImageArrayNonUniformIndexing = VK_TRUE; @@ -1686,6 +1689,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : state->spirvExtensions.push_back("SPV_EXT_descriptor_indexing"); } descriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind = state->storageBufferUpdateAfterBind ? VK_TRUE : VK_FALSE; + descriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind = state->sampledImageUpdateAfterBind ? VK_TRUE : VK_FALSE; descriptorIndexingFeatures.descriptorBindingPartiallyBound = state->descriptorBindingPartiallyBound ? VK_TRUE : VK_FALSE; descriptorIndexingFeatures.descriptorBindingVariableDescriptorCount = state->descriptorBindingVariableDescriptorCount ? VK_TRUE : VK_FALSE; descriptorIndexingFeatures.runtimeDescriptorArray = state->runtimeDescriptorArray ? VK_TRUE : VK_FALSE; @@ -1781,7 +1785,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : imageRobustnessFeatures.pNext = byteFeatures.pNext; byteFeatures.pNext = &imageRobustnessFeatures; } - if (state->descriptorIndexing || state->storageBufferUpdateAfterBind || state->descriptorBindingPartiallyBound || state->descriptorBindingVariableDescriptorCount || state->runtimeDescriptorArray) { + if (state->descriptorIndexing || state->storageBufferUpdateAfterBind || state->sampledImageUpdateAfterBind || state->descriptorBindingPartiallyBound || state->descriptorBindingVariableDescriptorCount || state->runtimeDescriptorArray) { descriptorIndexingFeatures.pNext = byteFeatures.pNext; byteFeatures.pNext = &descriptorIndexingFeatures; } @@ -3691,8 +3695,11 @@ Graphics::Context VulkanDevice::buildContext() const { context.runtimeDescriptorArray = state->runtimeDescriptorArray; context.partiallyBound = state->descriptorBindingPartiallyBound; context.variableDescriptorCount = state->descriptorBindingVariableDescriptorCount; + context.sampledImageUpdateAfterBind = state->sampledImageUpdateAfterBind; context.imageTableCapacity = ImageTableCapacity(context); context.samplerTableCapacity = SamplerTableCapacity(context); + context.imageTableUpdateAfterBind = ImageTableUpdateAfterBind(context); + context.samplerTableUpdateAfterBind = SamplerTableUpdateAfterBind(context); context.geometryShader = state->geometryShader; context.sampleRateShading = state->sampleRateShading; context.storageImageMultisample = state->storageImageMultisample; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index eb28fe2922..b9e48fa16a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -211,9 +211,12 @@ struct Context { bool runtimeDescriptorArray = false; bool partiallyBound = false; bool variableDescriptorCount = false; + bool sampledImageUpdateAfterBind = false; std::uint32_t imageTableCapacity = 0; std::uint32_t samplerTableCapacity = 0; bool individuallyFreeDescriptorSets = false; + bool imageTableUpdateAfterBind = false; + bool samplerTableUpdateAfterBind = false; bool geometryShader = false; bool sampleRateShading = false; bool storageImageMultisample = false; @@ -336,6 +339,8 @@ inline void EndRenderPass(const Context& context, VkCommandBuffer commands) { std::uint32_t ImageTableCapacity(const Context& context); std::uint32_t StorageTableCapacity(const Context& context); std::uint32_t SamplerTableCapacity(const Context& context); +bool ImageTableUpdateAfterBind(const Context& context); +bool SamplerTableUpdateAfterBind(const Context& context); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 7d3a64775f..75ab4242bc 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1720,7 +1720,13 @@ std::string DescriptorLimitError(std::string_view description, std::uint64_t req } void ValidateDescriptorLayout(const Context& context, std::span bindings, bool updateAfterBind) { - const auto maxStageResources = updateAfterBind ? context.descriptorIndexingLimits.maxPerStageUpdateAfterBindResources : context.limits.maxPerStageResources; + const auto& indexed = context.descriptorIndexingLimits; + const bool sampledUpdateAfterBind = updateAfterBind && context.sampledImageUpdateAfterBind; + const auto maxStageResources = updateAfterBind ? indexed.maxPerStageUpdateAfterBindResources : context.limits.maxPerStageResources; + const auto maxStageSampledImages = sampledUpdateAfterBind ? indexed.maxPerStageDescriptorUpdateAfterBindSampledImages : context.limits.maxPerStageDescriptorSampledImages; + const auto maxStageSamplers = sampledUpdateAfterBind ? indexed.maxPerStageDescriptorUpdateAfterBindSamplers : context.limits.maxPerStageDescriptorSamplers; + const auto maxSetSampledImages = sampledUpdateAfterBind ? indexed.maxDescriptorSetUpdateAfterBindSampledImages : context.limits.maxDescriptorSetSampledImages; + const auto maxSetSamplers = sampledUpdateAfterBind ? indexed.maxDescriptorSetUpdateAfterBindSamplers : context.limits.maxDescriptorSetSamplers; std::map heapTotals; std::map, std::uint64_t> stageTotals; std::map stageResources; @@ -1732,13 +1738,13 @@ void ValidateDescriptorLayout(const Context& context, std::span context.limits.maxPerStageDescriptorSamplers) throw std::runtime_error(DescriptorLimitError("typed heaps exceed per-stage sampler capacity", count, context.limits.maxPerStageDescriptorSamplers)); + if (layout.descriptorType == VK_DESCRIPTOR_TYPE_SAMPLER && count > maxStageSamplers) throw std::runtime_error(DescriptorLimitError("typed heaps exceed per-stage sampler capacity", count, maxStageSamplers)); } } - Require(heapTotals[VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE] <= context.limits.maxDescriptorSetSampledImages && heapTotals[VK_DESCRIPTOR_TYPE_STORAGE_IMAGE] <= context.limits.maxDescriptorSetStorageImages, "typed heaps exceed descriptor set capacity"); - if (heapTotals[VK_DESCRIPTOR_TYPE_SAMPLER] > context.limits.maxDescriptorSetSamplers) throw std::runtime_error(DescriptorLimitError("typed heaps exceed descriptor set sampler capacity", heapTotals[VK_DESCRIPTOR_TYPE_SAMPLER], context.limits.maxDescriptorSetSamplers)); + Require(heapTotals[VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE] <= maxSetSampledImages && heapTotals[VK_DESCRIPTOR_TYPE_STORAGE_IMAGE] <= context.limits.maxDescriptorSetStorageImages, "typed heaps exceed descriptor set capacity"); + if (heapTotals[VK_DESCRIPTOR_TYPE_SAMPLER] > maxSetSamplers) throw std::runtime_error(DescriptorLimitError("typed heaps exceed descriptor set sampler capacity", heapTotals[VK_DESCRIPTOR_TYPE_SAMPLER], maxSetSamplers)); } void ValidateRuntimeResources(const CompiledShader& shader, std::span words) { @@ -1931,7 +1937,8 @@ void ShaderResources::buildPrepare(std::span shaders, cons for (std::uint32_t set = 0; set < tableFlags.size(); ++set) { if (tableFlags[set] == 0) continue; Require(context.imageTableCapacity != 0, "image tables need runtimeDescriptorArray, descriptorBindingPartiallyBound and descriptorBindingVariableDescriptorCount, which the device lacks"); - bindings.push_back({{ShaderRecompiler::RuntimeAbi::ImageTableBinding, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, context.imageTableCapacity, tableFlags[set], nullptr}, {}, {}, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT, static_cast(set)}); + const VkDescriptorBindingFlags imageTableFlags = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT | (context.imageTableUpdateAfterBind ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0); + bindings.push_back({{ShaderRecompiler::RuntimeAbi::ImageTableBinding, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, context.imageTableCapacity, tableFlags[set], nullptr}, {}, {}, imageTableFlags, static_cast(set)}); tableBindings[set] = bindings.size() - 1u; } if (storageBuffers > context.limits.maxDescriptorSetStorageBuffers) { @@ -1947,13 +1954,16 @@ void ShaderResources::buildPrepare(std::span shaders, cons timing.uploadMs = phase(BuildPhase::Upload); splitSets = SplitsSets(context, shaders); std::array, 2> descriptions; + bool tablesUpdateAfterBind = false; for (const auto& binding : bindings) { const auto set = setOf(binding); descriptions[set].push_back(binding.layout); (set == 0 ? layoutBindingFlags : fragmentBindingFlags).push_back(binding.flags | (updateAfterBind && binding.layout.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0)); + tablesUpdateAfterBind |= (binding.flags & 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}); } + updateAfterBind = updateAfterBind || tablesUpdateAfterBind; 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); @@ -2080,6 +2090,7 @@ DescribedSetLayouts DescribeSetLayouts(const Context& context, std::span tableFlags{}; std::array storageTableFlags{}; bool updateAfterBind = false; + bool tablesUpdateAfterBind = false; std::uint64_t storageBuffers = 0; for (const auto& shader : shaders) { Require(shader.program != nullptr, "missing compiled shader"); @@ -2098,7 +2109,8 @@ DescribedSetLayouts DescribeSetLayouts(const Context& context, std::span context.limits.maxDescriptorSetStorageBuffers) updateAfterBind = true; for (std::size_t set = 0; set < descriptions.size(); ++set) { @@ -2129,6 +2142,7 @@ DescribedSetLayouts DescribeSetLayouts(const Context& context, std::span(binding.descriptorType), binding.descriptorCount, binding.stageFlags}); @@ -3819,12 +3833,15 @@ VkDescriptorSetLayout DescriptorCache::Layout(std::span key } namespace { -std::uint32_t SetDescriptorLimit(const VkPhysicalDeviceLimits& limits, VkDescriptorType type) { +std::uint32_t SetDescriptorLimit(const Context& context, VkDescriptorType type, bool updateAfterBind) { + const auto& limits = context.limits; + const auto& indexed = context.descriptorIndexingLimits; + const bool sampledUpdateAfterBind = updateAfterBind && context.sampledImageUpdateAfterBind; switch (type) { - case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: return limits.maxDescriptorSetStorageBuffers; - case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: return limits.maxDescriptorSetSampledImages; + case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: return updateAfterBind ? indexed.maxDescriptorSetUpdateAfterBindStorageBuffers : limits.maxDescriptorSetStorageBuffers; + case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: return sampledUpdateAfterBind ? indexed.maxDescriptorSetUpdateAfterBindSampledImages : limits.maxDescriptorSetSampledImages; case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: return limits.maxDescriptorSetStorageImages; - case VK_DESCRIPTOR_TYPE_SAMPLER: return limits.maxDescriptorSetSamplers; + case VK_DESCRIPTOR_TYPE_SAMPLER: return sampledUpdateAfterBind ? indexed.maxDescriptorSetUpdateAfterBindSamplers : limits.maxDescriptorSetSamplers; default: throw std::runtime_error("AGC graphics: descriptor set uses an unsupported descriptor type " + std::to_string(type)); } } @@ -3838,7 +3855,7 @@ constexpr std::array ChainPoolCreateSizes{{{VK_DESCRIPT DescriptorCache::SetAllocation DescriptorCache::Allocate(VkDescriptorSetLayout layout, std::span sizes, bool updateAfterBind, std::optional variableCount) { bool fits = true; for (const auto& size : sizes) { - const auto limit = updateAfterBind && size.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? context.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindStorageBuffers : SetDescriptorLimit(context.limits, size.type); + const auto limit = SetDescriptorLimit(context, size.type, updateAfterBind); Require(size.descriptorCount <= limit, "descriptor set exceeds the device's per-set descriptor limit"); const auto capacity = std::find_if(ChainPoolSizes.begin(), ChainPoolSizes.end(), [&](const auto& item) { return item.type == size.type; }); if (capacity == ChainPoolSizes.end() || size.descriptorCount > capacity->descriptorCount) fits = false; @@ -4547,28 +4564,61 @@ std::uint32_t StorageTableCapacity(const Context& context) { return std::min({context.imageTableCapacity, 4096u, context.limits.maxPerStageDescriptorStorageImages / 4u}); } -std::uint32_t ImageTableCapacity(const Context& context) { +namespace { + +std::uint64_t Room(std::uint64_t limit, std::uint64_t reserve) { + return limit > reserve ? limit - reserve : 0u; +} + +std::uint32_t ImageTableCapacityFor(const Context& context, bool updateAfterBind) { if (!context.runtimeDescriptorArray || !context.partiallyBound || !context.variableDescriptorCount || !context.descriptorIndexing) return 0u; + if (updateAfterBind && !context.sampledImageUpdateAfterBind) return 0u; constexpr std::uint64_t sampledHeaps = static_cast(ShaderRecompiler::RuntimeAbi::FirstStorageImageBinding - ShaderRecompiler::RuntimeAbi::FirstImageBinding) * ShaderRecompiler::RuntimeAbi::SampledHeapCapacity; constexpr std::uint64_t storageHeaps = static_cast(static_cast(ShaderRecompiler::RuntimeAbi::Binding::Samplers) - ShaderRecompiler::RuntimeAbi::FirstStorageImageBinding) * ShaderRecompiler::RuntimeAbi::StorageHeapCapacity; constexpr std::uint64_t buffers = ShaderRecompiler::RuntimeAbi::BufferCapacity + 8u; - const auto room = [](std::uint64_t limit, std::uint64_t reserve) { return limit > reserve ? limit - reserve : 0u; }; + const auto& indexed = context.descriptorIndexingLimits; + const std::uint64_t stageImages = updateAfterBind ? indexed.maxPerStageDescriptorUpdateAfterBindSampledImages : context.limits.maxPerStageDescriptorSampledImages; + const std::uint64_t stageResources = updateAfterBind ? indexed.maxPerStageUpdateAfterBindResources : context.limits.maxPerStageResources; + const std::uint64_t setImages = updateAfterBind ? indexed.maxDescriptorSetUpdateAfterBindSampledImages : context.limits.maxDescriptorSetSampledImages; std::uint64_t capacity = static_cast(ShaderRecompiler::RuntimeAbi::StageCount) * ShaderRecompiler::ImageTableAbi::ImageElementBudget; - capacity = std::min(capacity, room(context.limits.maxPerStageDescriptorSampledImages, sampledHeaps)); - capacity = std::min(capacity, room(context.limits.maxPerStageResources, sampledHeaps + storageHeaps + buffers)); - capacity = std::min(capacity, room(context.limits.maxDescriptorSetSampledImages, sampledHeaps * ShaderRecompiler::RuntimeAbi::StageCount)); + capacity = std::min(capacity, Room(stageImages, sampledHeaps)); + capacity = std::min(capacity, Room(stageResources, sampledHeaps + storageHeaps + buffers)); + capacity = std::min(capacity, Room(setImages, sampledHeaps * ShaderRecompiler::RuntimeAbi::StageCount)); return static_cast(capacity); } -std::uint32_t SamplerTableCapacity(const Context& context) { +std::uint32_t SamplerTableCapacityFor(const Context& context, bool updateAfterBind) { if (!context.runtimeDescriptorArray || !context.partiallyBound) return 0u; - const auto room = [](std::uint64_t limit, std::uint64_t reserve) { return limit > reserve ? limit - reserve : 0u; }; + if (updateAfterBind && !context.sampledImageUpdateAfterBind) return 0u; + const auto& indexed = context.descriptorIndexingLimits; + const std::uint64_t stageSamplers = updateAfterBind ? indexed.maxPerStageDescriptorUpdateAfterBindSamplers : context.limits.maxPerStageDescriptorSamplers; + const std::uint64_t setSamplers = updateAfterBind ? indexed.maxDescriptorSetUpdateAfterBindSamplers : context.limits.maxDescriptorSetSamplers; std::uint64_t capacity = ShaderRecompiler::ImageTableAbi::SamplerElementBudget; - capacity = std::min(capacity, room(context.limits.maxPerStageDescriptorSamplers, ShaderRecompiler::RuntimeAbi::SamplerHeapCapacity)); - capacity = std::min(capacity, room(context.limits.maxDescriptorSetSamplers / ShaderRecompiler::RuntimeAbi::StageCount, ShaderRecompiler::RuntimeAbi::SamplerHeapCapacity)); + capacity = std::min(capacity, Room(stageSamplers, ShaderRecompiler::RuntimeAbi::SamplerHeapCapacity)); + capacity = std::min(capacity, Room(setSamplers / ShaderRecompiler::RuntimeAbi::StageCount, ShaderRecompiler::RuntimeAbi::SamplerHeapCapacity)); return static_cast(capacity - capacity % ShaderRecompiler::ImageTableAbi::SamplerEntryElements); } +} + +std::uint32_t ImageTableCapacity(const Context& context) { + const auto capacity = ImageTableCapacityFor(context, false); + return capacity != 0u ? capacity : ImageTableCapacityFor(context, true); +} + +std::uint32_t SamplerTableCapacity(const Context& context) { + const auto capacity = SamplerTableCapacityFor(context, false); + return capacity != 0u ? capacity : SamplerTableCapacityFor(context, true); +} + +bool ImageTableUpdateAfterBind(const Context& context) { + return ImageTableCapacityFor(context, false) == 0u && ImageTableCapacityFor(context, true) != 0u; +} + +bool SamplerTableUpdateAfterBind(const Context& context) { + return SamplerTableCapacityFor(context, false) == 0u && SamplerTableCapacityFor(context, true) != 0u; +} + ShaderResources::TableStage& ShaderResources::tableStage(const CompiledShader& shader) { for (auto& table : tableStages) { if (table.program == shader.program) return table; @@ -4597,7 +4647,8 @@ void ShaderResources::addSamplerTable(const ShaderRecompiler::DescriptorBinding& namespace Abi = ShaderRecompiler::ImageTableAbi; Require(binding.kind == ShaderRecompiler::DescriptorKind::Sampler && binding.guestDescriptor.size() == static_cast(binding.count) * 4u && binding.samplerDepthCompare.size() == binding.count && binding.count % Abi::SamplerEntryElements == 0u, "sampler table binding is malformed"); Require(context.samplerTableCapacity != 0, "sampler tables need runtimeDescriptorArray and descriptorBindingPartiallyBound, which the device lacks"); - Binding item{{binding.binding, VK_DESCRIPTOR_TYPE_SAMPLER, context.samplerTableCapacity, flags, nullptr}, {}, {}, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT}; + const VkDescriptorBindingFlags samplerTableFlags = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | (context.samplerTableUpdateAfterBind ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0); + Binding item{{binding.binding, VK_DESCRIPTOR_TYPE_SAMPLER, context.samplerTableCapacity, flags, nullptr}, {}, {}, samplerTableFlags}; static const bool noSamplerCache = std::getenv("APS5_NO_SAMPLER_CACHE") != nullptr; for (std::uint32_t entry = 0; entry < binding.count / Abi::SamplerEntryElements; ++entry) { const auto first = entry * Abi::SamplerEntryElements; @@ -4637,7 +4688,8 @@ void ShaderResources::addSamplerTable(const ShaderRecompiler::DescriptorBinding& item.imageAllocations.push_back(samplers.size() - 1u); } } - Require(samplers.size() <= context.limits.maxDescriptorSetSamplers, "pipeline sampler descriptors exceed device limits"); + const auto setSamplers = context.samplerTableUpdateAfterBind ? context.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindSamplers : context.limits.maxDescriptorSetSamplers; + Require(samplers.size() <= setSamplers, "pipeline sampler descriptors exceed device limits"); bindings.push_back(std::move(item)); } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 827fbfc41e..f6b15bbc10 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -6189,6 +6189,134 @@ void hostImportBudgetTests() { Require(HostImportBudget(memory, 16 * GiB) == 8 * GiB, "a host heap past memoryHeapCount limits the import budget"); } +void tableUpdateAfterBindTests() { + namespace Abi = ShaderRecompiler::ImageTableAbi; + using namespace AgcDriver::Graphics; + auto argumentLimits = mockContext(); + argumentLimits.runtimeDescriptorArray = true; + argumentLimits.partiallyBound = true; + argumentLimits.variableDescriptorCount = true; + argumentLimits.descriptorIndexing = true; + argumentLimits.limits.maxPerStageDescriptorSamplers = 16; + argumentLimits.limits.maxPerStageDescriptorSampledImages = 256; + argumentLimits.limits.maxPerStageResources = 287; + argumentLimits.limits.maxDescriptorSetSamplers = 80; + argumentLimits.limits.maxDescriptorSetSampledImages = 1280; + argumentLimits.descriptorIndexingLimits.maxPerStageDescriptorUpdateAfterBindSamplers = 500000; + argumentLimits.descriptorIndexingLimits.maxPerStageDescriptorUpdateAfterBindSampledImages = 1000000; + argumentLimits.descriptorIndexingLimits.maxPerStageUpdateAfterBindResources = 1000000; + argumentLimits.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindSamplers = 500000; + argumentLimits.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindSampledImages = 1000000; + Require(ImageTableCapacity(argumentLimits) == 0u && SamplerTableCapacity(argumentLimits) == 0u && !ImageTableUpdateAfterBind(argumentLimits) && !SamplerTableUpdateAfterBind(argumentLimits), "tables beyond the regular descriptor limits were enabled without update-after-bind sampled images"); + argumentLimits.sampledImageUpdateAfterBind = true; + Require(ImageTableCapacity(argumentLimits) == ShaderRecompiler::RuntimeAbi::StageCount * Abi::ImageElementBudget && ImageTableUpdateAfterBind(argumentLimits), "an image table beyond the regular descriptor limits did not take the update-after-bind limits"); + Require(SamplerTableCapacity(argumentLimits) == Abi::SamplerElementBudget && SamplerTableUpdateAfterBind(argumentLimits), "a sampler table beyond the regular descriptor limits did not take the update-after-bind limits"); + auto regularLimits = argumentLimits; + regularLimits.limits.maxPerStageDescriptorSamplers = 1048576; + regularLimits.limits.maxPerStageDescriptorSampledImages = 1048576; + regularLimits.limits.maxPerStageResources = 1048576; + regularLimits.limits.maxDescriptorSetSamplers = 1048576; + regularLimits.limits.maxDescriptorSetSampledImages = 1048576; + Require(ImageTableCapacity(regularLimits) == ShaderRecompiler::RuntimeAbi::StageCount * Abi::ImageElementBudget && !ImageTableUpdateAfterBind(regularLimits), "an image table within the regular descriptor limits took the update-after-bind path"); + Require(SamplerTableCapacity(regularLimits) == Abi::SamplerElementBudget && !SamplerTableUpdateAfterBind(regularLimits), "a sampler table within the regular descriptor limits took the update-after-bind path"); + + const auto tableContext = [] { + auto context = mockContext(); + context.sampledImageUpdateAfterBind = true; + context.limits.maxPerStageDescriptorSampledImages = 1; + context.limits.maxDescriptorSetSampledImages = 1; + context.limits.maxPerStageDescriptorSamplers = 1; + context.limits.maxDescriptorSetSamplers = 1; + context.descriptorIndexingLimits.maxPerStageDescriptorUpdateAfterBindSampledImages = 2; + context.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindSampledImages = 2; + context.descriptorIndexingLimits.maxPerStageDescriptorUpdateAfterBindSamplers = 2u * Abi::SamplerEntryElements; + context.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindSamplers = 2u * Abi::SamplerEntryElements; + context.descriptorIndexingLimits.maxPerStageUpdateAfterBindResources = 128; + context.descriptorIndexingLimits.maxDescriptorSetUpdateAfterBindStorageBuffers = 32; + context.descriptorIndexingLimits.maxPerStageDescriptorUpdateAfterBindStorageBuffers = 16; + return context; + }; + const auto requireTableLayout = [](std::uint32_t tableBinding, VkDescriptorBindingFlags tableFlags, std::string_view what) { + Require(mock.layoutFlags == VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT, std::string(what) + " did not get an update-after-bind layout"); + Require(mock.layoutBindingFlags.size() == mock.layoutBindings.size(), std::string(what) + " lost its binding flags"); + for (std::size_t index = 0; index < mock.layoutBindings.size(); ++index) { + const auto expected = mock.layoutBindings[index].binding == tableBinding ? tableFlags | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0u; + Require(mock.layoutBindingFlags[index] == expected, std::string(what) + " put update-after-bind on the wrong bindings"); + } + Require(mock.poolFlags == VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT, std::string(what) + " did not come from an update-after-bind pool"); + }; + for (const bool cached : {false, true}) { + mock = MockVulkan{}; + auto context = tableContext(); + context.imageTableCapacity = 2; + context.imageTableUpdateAfterBind = true; + { + DescriptorCache cache(context); + if (cached) context.descriptorCache = &cache; + ShaderRecompiler::RecompileResult program; + auto map = makeBinding(Role::ImageTableMap, static_cast(ShaderRecompiler::RuntimeAbi::Binding::ImageTableMap), 1, std::vector(Abi::HeaderWords + 2u)); + map.guestDescriptor[Abi::HeaderVersion] = Abi::Version; + map.guestDescriptor[Abi::HeaderElementCount] = 2u; + map.guestDescriptor[Abi::HeaderElementFaults] = Abi::HeaderWords; + map.guestDescriptor[Abi::HeaderSamplerFaults] = Abi::HeaderWords + 2u; + program.bindings.push_back(map); + auto images = makeBinding(Role::ImageTable, ShaderRecompiler::RuntimeAbi::ImageTableBinding, 2u, std::vector(16u)); + images.kind = Kind::SampledImage; + images.imageShapes.resize(2u); + images.imageDepthCompare.resize(2u); + images.imageSamplers.resize(2u); + program.bindings.push_back(std::move(images)); + const CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &program, 0}; + { + ShaderResources resources(context, shader, {}, true); + requireTableLayout(ShaderRecompiler::RuntimeAbi::ImageTableBinding, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT, "an image table beyond the regular limits"); + if (cached) Require(DescribeSetLayouts(context, std::span(&shader, 1)).layouts.main == resources.Layout(), "the described image table layout differs from the built one"); + } + } + Require(mock.live == 0, "the update-after-bind image table leaked Vulkan objects"); + } + for (const bool cached : {false, true}) { + mock = MockVulkan{}; + auto context = tableContext(); + constexpr auto count = 2u * Abi::SamplerEntryElements; + context.samplerTableCapacity = count; + context.samplerTableUpdateAfterBind = true; + context.samplerAnisotropy = true; + context.samplerFilterMinmax = true; + context.limits.maxSamplerAnisotropy = 2.0f; + context.limits.maxSamplerLodBias = 0.5f; + { + DescriptorCache cache(context); + if (cached) context.descriptorCache = &cache; + ShaderRecompiler::RecompileResult program; + auto map = makeBinding(Role::ImageTableMap, static_cast(ShaderRecompiler::RuntimeAbi::Binding::ImageTableMap), 1, std::vector(Abi::HeaderWords + 2u)); + map.guestDescriptor[Abi::HeaderVersion] = Abi::Version; + map.guestDescriptor[Abi::HeaderSamplerCount] = count; + map.guestDescriptor[Abi::HeaderElementFaults] = Abi::HeaderWords; + map.guestDescriptor[Abi::HeaderSamplerFaults] = Abi::HeaderWords; + program.bindings.push_back(map); + const auto samplerBinding = static_cast(ShaderRecompiler::RuntimeAbi::Binding::SamplerTable); + auto samplers = makeBinding(Role::SamplerTable, samplerBinding, count, {}); + samplers.kind = Kind::Sampler; + const std::array valid{0x92u, (4u * 256u) << 12u, 0u, 0u}; + for (std::uint32_t element = 0; element < count; ++element) { + samplers.guestDescriptor.insert(samplers.guestDescriptor.end(), valid.begin(), valid.end()); + samplers.samplerDepthCompare.push_back(false); + } + program.bindings.push_back(std::move(samplers)); + const CompiledShader shader{ShaderRecompiler::ShaderStage::Compute, &program, 0}; + { + ShaderResources resources(context, shader, {}, true); + resources.Complete(); + requireTableLayout(samplerBinding, VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT, "a sampler table beyond the regular limits"); + Require(findWrite(samplerBinding).count == count, "the update-after-bind sampler table did not write its samplers"); + if (cached) Require(DescribeSetLayouts(context, std::span(&shader, 1)).layouts.main == resources.Layout(), "the described sampler table layout differs from the built one"); + } + } + Require(mock.live == 0 && mock.samplerFlags.empty(), "the update-after-bind sampler table leaked Vulkan objects"); + } +} + void textureCacheBudgetTests() { using AgcDriver::Graphics::TextureCacheBudget; constexpr std::uint64_t GiB = 1ull << 30u; @@ -10116,6 +10244,7 @@ int main(int argc, char** argv) { imageTableSnapshotAllocationTests(); samplerTableNonSeamlessCubeTests(); samplerTableDeviceLimitTests(); + tableUpdateAfterBindTests(); misalignedShaderDataTests(); debugBranchTests(); hostImportBudgetTests();