diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp index 99955da54..29891d598 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -184,13 +184,13 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) { {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {64, 0, {}, std::nullopt, pixel, std::nullopt, pixelMemory}, target, - {0, 0, meshPush, ShaderRecompiler::MeshDrawPushOffsetBytes - meshPush}, + target.fixedPushSlots ? PixelPushLayout(meshPush, target) : ShaderRecompiler::BindingLayout{0, 0, meshPush, ShaderRecompiler::MeshDrawPushOffsetBytes - meshPush}, std::nullopt }; const auto pixelResult = ShaderRecompiler::Recompile(fragment); const std::array shaders{{ {ShaderStage::Mesh, &meshResult, 0}, - {ShaderStage::Fragment, &pixelResult, meshPush} + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(meshPush, target)} }}; AgcDriver::Graphics::State state{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp index 1948f7e20..eafac530e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/BufferDescriptorRuntime.cpp @@ -73,6 +73,7 @@ void Run(AgcDriver::VulkanDevice& device, bool registrationModules) { {"absent", 0, 128, false}, }}; const void* first = nullptr; + std::uint64_t variant = 0; for (const auto& test : cases) { Output.fill(Fill); const auto input = test.present ? BufferDescriptor(Input.data() + test.baseOffset, test.records) : std::array{0u, 0u, 0u, 0x31016facu}; @@ -83,8 +84,11 @@ void Run(AgcDriver::VulkanDevice& device, bool registrationModules) { runtime.userData = userData; const auto capture = ShaderRecompiler::CaptureResources(request, runtime, *handle); const auto result = ShaderRecompiler::MaterializeShader(request, *capture, *handle); - if (first == nullptr) first = result->spirv.data(); - if (registrationModules) Require(result->spirv.data() == first, "buffer descriptor runtime, " + mode + ", " + test.name + ": the V# specialized the module"); + if (first == nullptr) { + first = result->spirv.data(); + variant = result->PipelineVariantId(); + } + if (registrationModules) Require(result->spirv.data() == first && result->PipelineVariantId() == variant, "buffer descriptor runtime, " + mode + ", " + test.name + ": the V# specialized the module"); device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(Code.data())); device.WaitIdle(); for (std::uint32_t lane = 0; lane < Lanes; ++lane) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp index 18d3083b4..1e4e5bcb3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp @@ -4,6 +4,7 @@ #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" #include #include #include @@ -96,7 +97,7 @@ inline Shaders Compile(AgcDriver::VulkanDevice& device) { {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {32, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, target, - {0, 0, shaders.vertexPush, 128 - shaders.vertexPush} + PixelPushLayout(shaders.vertexPush, target) }; fragment.useCache = false; shaders.pixel = ShaderRecompiler::Recompile(fragment); @@ -107,7 +108,7 @@ inline std::uint32_t Draw(AgcDriver::VulkanDevice& device, const Shaders& compil Pixels.fill(std::byte{0}); const std::array shaders{{ {ShaderStage::Vertex, &compiled.vertex, 0}, - {ShaderStage::Fragment, &compiled.pixel, compiled.vertexPush} + {ShaderStage::Fragment, &compiled.pixel, PixelPushOffset(compiled.vertexPush, device.Target())} }}; AgcDriver::Graphics::State state{}; state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedExports.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedExports.cpp index b0b85a7b7..bdbfd1362 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedExports.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedExports.cpp @@ -29,7 +29,7 @@ void Run(AgcDriver::VulkanDevice& device, bool integer) { const std::array values = integer ? std::array{11, 22, 33, 44} : std::array{0, 0x3e800000u, 0x3f000000u, 0x3f800000u}; const std::array code{0x7e0002ffu, values[0], 0x7e0202ffu, values[1], 0x7e0402ffu, values[2], 0x7e0602ffu, values[3], 0xf800100fu, 0x03020100u, 0xf800183fu, 0x03020100u, 0xbf810000u}; AgcDriver::Registers context{{0x1b6, 0x8000}, {0x1b3, 0}, {0x1b4, 0}, {0x203, 0}, {0x1c5, integer ? 0x7007u : 0x9009u}}; - RecompileRequest request{{ShaderStage::Fragment, 0x20000, code, 0, {}}, {32, 0, {}, {}, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), {}, {}}, device.Target(), {0, 0, 0, 128}}; + RecompileRequest request{{ShaderStage::Fragment, 0x20000, code, 0, {}}, {32, 0, {}, {}, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), {}, {}}, device.Target(), PixelPushLayout(0u, device.Target())}; const auto handle = PrepareShader(request); const auto& artifact = GetPreparedArtifact(*handle); std::uint32_t scalarType = 0; @@ -85,6 +85,10 @@ void Run(AgcDriver::VulkanDevice& device, bool integer) { request.useCache = cached; const auto materialized = MaterializeShader(request, *capture, *handle); Require(materialized->spirv.data() == pixel->spirv.data() && materialized->PipelineVariantId() == pixel->PipelineVariantId(), "export mapping did not reuse its specialized module"); + if (device.Target().registrationModules) { + Require(materialized->PipelineVariantId() == first->PipelineVariantId(), "registration modules: the export mapping specialized the fragment module"); + continue; + } for (std::size_t cursor = 5; cursor < materialized->spirv.size();) { const auto count = materialized->spirv[cursor] >> 16u; Require(count != 0u && count <= materialized->spirv.size() - cursor, "invalid specialized fragment instruction"); @@ -94,7 +98,7 @@ void Run(AgcDriver::VulkanDevice& device, bool integer) { } request.useCache = true; if (integer) continue; - const std::array shaders{{{ShaderStage::Vertex, &vertex, 0}, {ShaderStage::Fragment, pixel.get(), 0}}}; + const std::array shaders{{{ShaderStage::Vertex, &vertex, 0}, {ShaderStage::Fragment, pixel.get(), PixelPushOffset(0u, device.Target())}}}; pixels.fill(0xdeadbeef); otherPixels.fill(0xdeadbeef); device.Draw(state, draw, shaders); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ProvokingVertex.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ProvokingVertex.cpp index f1651a590..2654a113b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ProvokingVertex.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ProvokingVertex.cpp @@ -50,7 +50,7 @@ ShaderRecompiler::RecompileResult GuestPixel(const ShaderRecompiler::SpirvTarget request.context.waveSize = 64; request.context.pixel = pixel; request.target = target; - request.layout.pushConstantSizeBytes = 128; + request.layout = PixelPushLayout(0u, target); request.useCache = false; auto result = ShaderRecompiler::Recompile(request); result.variantId = 3; @@ -130,7 +130,7 @@ int main() { const auto guestPixel = GuestPixel(device->Target()); const std::array guestShaders{{ {ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, - {ShaderRecompiler::ShaderStage::Fragment, &guestPixel, 0} + {ShaderRecompiler::ShaderStage::Fragment, &guestPixel, PixelPushOffset(0u, device->Target())} }}; for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP}) Run(*device, topology, guestShaders, true, "guest P0"); std::puts("provoking vertex rendering tests passed: GLSL and guest P0, triangle list and strip, first/last/first"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/RecordedKeptBytes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/RecordedKeptBytes.cpp index a41fe4456..f6e0f04fd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/RecordedKeptBytes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/RecordedKeptBytes.cpp @@ -200,13 +200,13 @@ void Draw(AgcDriver::VulkanDevice& device, GuestBuffer& pixels, GuestBuffer& ver {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, target, - {0, 0, vertexPush, 128 - vertexPush} + PixelPushLayout(vertexPush, target) }; fragment.useCache = false; const auto pixelResult = ShaderRecompiler::Recompile(fragment); const std::array shaders{{ {ShaderStage::Vertex, &vertexResult, 0}, - {ShaderStage::Fragment, &pixelResult, vertexPush} + {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)} }}; AgcDriver::Graphics::State state{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VertexFetch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VertexFetch.cpp index 4baa2d05b..c42f5c35c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/VertexFetch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VertexFetch.cpp @@ -85,9 +85,9 @@ void Run(AgcDriver::VulkanDevice& device) { pixelInfo.wave32 = true; pixelInfo.targetOutputMode[0] = 9u; pixelInfo.targetExportMapping.fill(0xe4u); - ShaderRecompiler::RecompileRequest pixelRequest{{ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {32u, 0u, {}, std::nullopt, pixelInfo, std::nullopt, {}}, device.Target(), {0u, 0u, pushBytes, 128u - pushBytes}}; + ShaderRecompiler::RecompileRequest pixelRequest{{ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {32u, 0u, {}, std::nullopt, pixelInfo, std::nullopt, {}}, device.Target(), PixelPushLayout(pushBytes, device.Target())}; const auto pixel = ShaderRecompiler::Recompile(pixelRequest); - const std::array shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, pushBytes}}}; + const std::array shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, PixelPushOffset(pushBytes, device.Target())}}}; AgcDriver::Graphics::State state{}; state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 32u, 32u, std::nullopt, std::nullopt}; state.color = {reinterpret_cast(pixels.data()), {Extent, Extent}, VK_FORMAT_R8G8B8A8_UNORM, pixels.size(), 0xe4u}; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp index c1a9500b7..976a74f8e 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp @@ -43,6 +43,7 @@ struct IrBindingLayout { std::uint32_t memoryOffsetCount = 0; bool dispatchThreadLimit = false; std::uint32_t runtimeImageCount = 0; + std::uint32_t runtimeStateDwords = 0; std::vector userDataRegisters; std::vector descriptors; @@ -52,9 +53,12 @@ struct IrBindingLayout { [[nodiscard]] std::uint32_t ImageMetadataDword() const { return DispatchThreadLimitDword() + (dispatchThreadLimit ? 3u : 0u); } - [[nodiscard]] std::uint32_t ShaderDataDwords() const { + [[nodiscard]] std::uint32_t RuntimeStateDword() const { return ImageMetadataDword() + runtimeImageCount * (sizeof(RuntimeAbi::ResourceMetadata) / sizeof(std::uint32_t)); } + [[nodiscard]] std::uint32_t ShaderDataDwords() const { + return RuntimeStateDword() + runtimeStateDwords; + } [[nodiscard]] bool UsesPushData() const { return pushDataStartDword != PushData::NoStart; } diff --git a/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp b/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp index d600e6fbb..6c15439dc 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp @@ -29,6 +29,7 @@ struct DescriptorBindingPlan { std::vector bindings; std::vector imageMetadata; std::vector specialization; + std::vector runtimeState; bool storageData = false; }; diff --git a/core/shader/recompiler/Optimization/src/BindingAllocator.cpp b/core/shader/recompiler/Optimization/src/BindingAllocator.cpp index 822cc38b7..268265461 100644 --- a/core/shader/recompiler/Optimization/src/BindingAllocator.cpp +++ b/core/shader/recompiler/Optimization/src/BindingAllocator.cpp @@ -103,6 +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; 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 bb333af47..851d92ed5 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -208,7 +208,7 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh return proof; } -std::vector ShaderDataDwordsFor(const IrBindingLayout& layout, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array& partialThreads, const std::vector& imageMetadata) { +std::vector ShaderDataDwordsFor(const IrBindingLayout& layout, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array& partialThreads, const std::vector& imageMetadata, std::span runtimeState) { std::vector result(layout.ShaderDataDwords(), 0u); for (std::size_t i = 0; i < layout.userDataRegisters.size(); ++i) { const auto reg = layout.userDataRegisters[i]; @@ -226,6 +226,8 @@ std::vector ShaderDataDwordsFor(const IrBindingLayout& layout, st std::copy_n(descriptor.dwords.begin(), metadata.descriptor.size(), metadata.descriptor.begin()); std::memcpy(result.data() + entry.offset, &metadata, sizeof(metadata)); } + if (runtimeState.size() != layout.runtimeStateDwords) fail("runtime state does not match its shader data layout"); + std::copy(runtimeState.begin(), runtimeState.end(), result.begin() + layout.RuntimeStateDword()); return result; } @@ -255,8 +257,11 @@ 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])) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u}); + 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; } std::vector compareStates(info.images.size()); for (std::uint32_t index = 0; index < info.images.size(); ++index) { @@ -264,7 +269,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l const auto compare = compareStates[index] = ResourceMaterializer::EmulatedCompareState(info, snapshot, index); const auto first = PipelineSpecialization::CompareBase + index * PipelineSpecialization::CompareWords; const std::array values{EmulatedCompare::Function(compare), (compare & EmulatedCompare::Linear) != 0u, EmulatedCompare::AddressX(compare), EmulatedCompare::AddressY(compare), EmulatedCompare::Reference(compare), (compare & EmulatedCompare::BorderWhite) != 0u}; - for (std::uint32_t word = 0; word < values.size(); ++word) plan.specialization.push_back({first + word, values[word]}); + for (std::uint32_t word = 0; word < values.size() && !registrationModules; ++word) plan.specialization.push_back({first + word, values[word]}); } std::vector imageModes(info.images.size()); for (std::size_t index = 0; index < info.images.size(); ++index) imageModes[index] = ResourceMaterializer::RuntimeImageMode(info.images[index], snapshot.images.at(index), info.runtimeImageModes.at(index)); @@ -301,7 +306,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l plan.specialization.push_back({first, imageModes[index]}); for (std::uint32_t component = 0; component < 4u; ++component) plan.specialization.push_back({first + 1u + component, (snapshot.images.at(index).dwords[3] >> (component * 3u)) & 7u}); } - for (std::uint32_t target = 0; target < exportMappings.size(); ++target) { + for (std::uint32_t target = 0; target < exportMappings.size() && !registrationModules; ++target) { for (std::uint32_t component = 0; component < 4u; ++component) plan.specialization.push_back({PipelineSpecialization::ExportBase + target * 4u + component, (exportMappings[target] >> (component * 2u)) & 3u}); } std::vector samplerModes(info.samplers.size(), 0u); @@ -416,7 +421,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l metadata.binding = static_cast(compact.kind); metadata.firstElement = static_cast(element); metadata.elementCount = 1u; - metadata.flags = imageModes[resource] << 1u; + metadata.flags = imageModes[resource] << 1u | (registrationModules ? (compareStates[resource] & RuntimeAbi::CompareStateMask) << RuntimeAbi::CompareStateShift : 0u); if (descriptor.dwords[0] == 0u && (descriptor.dwords[1] & 0xffu) == 0u) metadata.flags |= 1u; const auto offset = layout.ImageMetadataDword() + resource * static_cast(sizeof(metadata) / sizeof(std::uint32_t)); plan.imageMetadata.push_back({resource, offset, metadata}); @@ -447,10 +452,16 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l RuntimeAbi::ResourceMetadata metadata{}; if (descriptor.dwordCount != metadata.descriptor.size()) fail("invalid runtime image descriptor width"); metadata.elementCount = 1u; - metadata.flags = imageModes[resource] << 1u; + if (imageModes[resource] > RuntimeAbi::ImageModeMask) fail("runtime image mode exceeds its metadata field"); + metadata.flags = imageModes[resource] << 1u | (registrationModules ? (compareStates[resource] & RuntimeAbi::CompareStateMask) << RuntimeAbi::CompareStateShift : 0u); if (descriptor.dwords[0] == 0u && (descriptor.dwords[1] & 0xffu) == 0u) metadata.flags |= 1u; plan.imageMetadata.push_back({resource, layout.ImageMetadataDword() + resource * static_cast(sizeof(metadata) / sizeof(std::uint32_t)), metadata}); } + 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()); for (const auto& binding : plan.bindings) { if (binding.descriptor.role != DescriptorRole::GuestSamplers) continue; @@ -488,13 +499,14 @@ DescriptorBindingPlan DescriptorBindingBuilder::Select(const DescriptorBindingPl } } selected.imageMetadata = plan.imageMetadata; + selected.runtimeState = plan.runtimeState; return selected; } void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, const CompiledBindingLayout& compiled, const DescriptorBindingPlan& plan, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array& partialThreads) const { const auto& layout = compiled.layout; const bool needsShaderData = layout.UsesPushData() || plan.storageData; - auto shaderData = needsShaderData ? ShaderDataDwordsFor(layout, userDataBase, snapshot, partialThreads, plan.imageMetadata) : std::vector{}; + auto shaderData = needsShaderData ? ShaderDataDwordsFor(layout, userDataBase, snapshot, partialThreads, plan.imageMetadata, plan.runtimeState) : std::vector{}; std::vector bindings; bindings.reserve(plan.bindings.size()); std::size_t writtenHere = 0; diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 8978f1851..f7d2ff5b2 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -12,6 +12,7 @@ #include "RdnaDecoder/RdnaDescriptorFormat.hpp" #include "ShaderDiskCache.hpp" #include +#include #include #include #include @@ -602,6 +603,38 @@ struct PreparedBindingPlan { std::map, std::shared_ptr> modules; }; +struct RegistrationModuleEntry { + std::once_flag ready; + std::exception_ptr failure; + std::shared_ptr module; +}; + +std::shared_ptr registrationModule(std::uint64_t variantId, std::span moduleKey, const std::function()>& specialize) { + std::vector key{variantId}; + key.insert(key.end(), moduleKey.begin(), moduleKey.end()); + static std::shared_mutex mutex; + static std::map, std::shared_ptr> modules; + std::shared_ptr entry; + { + std::shared_lock lock(mutex); + if (const auto found = modules.find(key); found != modules.end()) entry = found->second; + } + if (entry == nullptr) { + std::unique_lock lock(mutex); + const auto found = modules.find(key); + entry = found != modules.end() ? found->second : modules.emplace(key, std::make_shared()).first->second; + } + std::call_once(entry->ready, [&] { + try { + entry->module = specialize(); + } catch (...) { + entry->failure = std::current_exception(); + } + }); + if (entry->failure) std::rethrow_exception(entry->failure); + return entry->module; +} + std::shared_ptr preparedBindingPlan(const CompiledVariant& variant, const ResourceSnapshot& snapshot, std::span exports, bool registrationModules) { struct BindingPlanKeyStorage {}; auto& key = HostThreadLocal, BindingPlanKeyStorage>(); @@ -769,7 +802,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil constants.push_back({first + 5u, kind}); } } - const auto module = specializeModule(variant.artifact, vertexClasses, constants, request.target); + const auto module = request.target.registrationModules && plan->bindings.specialization.empty() ? registrationModule(artifact.variantId, moduleKey, [&] { return specializeModule(variant.artifact, vertexClasses, constants, request.target); }) : specializeModule(variant.artifact, vertexClasses, constants, request.target); if (variant.bindings.layout.UsesPushData() && !module->pushData) throw std::runtime_error("specialization removed the prepared push constant interface"); auto selected = DescriptorBindingBuilder{}.Select(plan->bindings, module->bindings); entry->bindings = std::move(selected); diff --git a/core/shader/recompiler/RuntimeAbi.hpp b/core/shader/recompiler/RuntimeAbi.hpp index 131372e68..8d5019b93 100644 --- a/core/shader/recompiler/RuntimeAbi.hpp +++ b/core/shader/recompiler/RuntimeAbi.hpp @@ -39,6 +39,12 @@ 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 ImageModeMask = 0xffu; +inline constexpr std::uint32_t CompareStateShift = 9u; +inline constexpr std::uint32_t CompareStateMask = 0x1fffu; struct ResourceMetadata { std::uint32_t binding; diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index 203770688..0949aef04 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -416,6 +416,7 @@ void encodeLayout(Writer& writer, const IrBindingLayout& layout) { writer.Value(layout.memoryOffsetCount); writer.Value(layout.dispatchThreadLimit); writer.Value(layout.runtimeImageCount); + writer.Value(layout.runtimeStateDwords); writer.Values(std::span(layout.userDataRegisters)); writer.List(layout.descriptors, [](Writer& out, const IrDescriptorBinding& descriptor) { out.Value(descriptor.kind); @@ -429,6 +430,7 @@ void decodeLayout(Reader& reader, IrBindingLayout& layout) { reader.Value(layout.memoryOffsetCount); reader.Value(layout.dispatchThreadLimit); reader.Value(layout.runtimeImageCount); + reader.Value(layout.runtimeStateDwords); reader.Values(layout.userDataRegisters); reader.List(layout.descriptors, 12, [](Reader& in, IrDescriptorBinding& descriptor) { in.Value(descriptor.kind); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 81806a48f..7297e528d 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -47,6 +47,11 @@ struct SampleSetup { std::uint32_t coord; }; +std::uint32_t RuntimeImageMode(SpirvEmitterState& state, std::uint32_t resource) { + const auto mode = Binary(state, spv::OpShiftRightLogical, TypeU32(state), RuntimeImageDword(state, resource, offsetof(RuntimeAbi::ResourceMetadata, flags) / sizeof(std::uint32_t)), ConstantU32(state, 1u)); + return state.registrationModules ? Binary(state, spv::OpBitwiseAnd, TypeU32(state), mode, ConstantU32(state, RuntimeAbi::ImageModeMask)) : mode; +} + std::uint32_t RuntimeImageSwizzle(SpirvEmitterState& state, std::uint32_t resource, std::uint32_t component) { if (state.program.Info().images.at(resource).indirectRoot == ImageResource::NoIndirectImage && !state.registrationModules) return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ImageBase + resource * PipelineSpecialization::ImageWords + 1u + component, component + 4u); const auto descriptor = RuntimeImageDword(state, resource, offsetof(RuntimeAbi::ResourceMetadata, descriptor) / sizeof(std::uint32_t) + 3u); @@ -1264,7 +1269,12 @@ void EmitEmulatedCompareSample(SpirvValueEmitContext& ctx, const ImageEmitAccess auto& state = ctx.state; const auto& mem = access.mem; const auto& image = access.image; - const auto parameter = [&](std::uint32_t word) { return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::CompareBase + mem.resource * PipelineSpecialization::CompareWords + word, 0u); }; + const auto compare = state.registrationModules ? EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), RuntimeImageDword(state, mem.resource, offsetof(RuntimeAbi::ResourceMetadata, flags) / sizeof(std::uint32_t)), ConstantU32(state, RuntimeAbi::CompareStateShift)), RuntimeAbi::CompareStateMask) : 0u; + const auto parameter = [&](std::uint32_t word) { + if (!state.registrationModules) return state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::CompareBase + mem.resource * PipelineSpecialization::CompareWords + word, 0u); + constexpr std::array, 6> fields{{{EmulatedCompare::FunctionShift, 7u}, {4u, 1u}, {EmulatedCompare::ClampXShift, 3u}, {EmulatedCompare::ClampYShift, 3u}, {EmulatedCompare::ReferenceShift, 3u}, {12u, 1u}}}; + return EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), compare, ConstantU32(state, fields.at(word).first)), fields.at(word).second); + }; const auto equal = [&](std::uint32_t value, std::uint32_t literal) { return Binary(state, spv::OpIEqual, TypeBool(state), value, ConstantU32(state, literal)); }; if (access.slot != 0) ctx.Fail(access.inst, "compares against a color texture through a bindless image table, which is not implemented"); if (HasFlag(mem, RdnaImageSampleFlagDerivative) || HasFlag(mem, RdnaImageSampleFlagLod)) ctx.Fail(access.inst, "compares against a color texture with gradients or an explicit LOD, which is not implemented"); @@ -1484,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.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::FoldOffsetBase + mem.sampler, 0u); + 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 folded = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u)); const auto foldLabel = state.module.AllocateId(); const auto operandLabel = state.module.AllocateId(); @@ -1644,7 +1654,7 @@ void EmitImage(SpirvValueEmitContext& ctx, const IrValue& inst) { state.runtimeImageMetadata = 0u; return; } - const auto selector = base.indirectRoot == ImageResource::NoIndirectImage && !state.registrationModules ? state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ImageBase + memory.resource * PipelineSpecialization::ImageWords, 0u) : Binary(state, spv::OpShiftRightLogical, TypeU32(state), RuntimeImageDword(state, memory.resource, offsetof(RuntimeAbi::ResourceMetadata, flags) / sizeof(std::uint32_t)), ConstantU32(state, 1u)); + const auto selector = base.indirectRoot == ImageResource::NoIndirectImage && !state.registrationModules ? state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ImageBase + memory.resource * PipelineSpecialization::ImageWords, 0u) : RuntimeImageMode(state, memory.resource); const bool returnsValue = inst.Opcode() != IrOpcode::ImageWrite; const auto resultId = returnsValue ? ctx.Result(inst) : 0u; ctx.definitions.erase(&inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index 1793ea2f8..ac2cc1366 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -1319,7 +1319,7 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) { if (exp.index >= 8u) throw std::runtime_error("fragment export target exceeds the runtime mapping table"); std::array components{}; for (std::uint32_t component = 0; component < components.size(); ++component) { - const auto index = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ExportBase + exp.index * 4u + component, component); + const auto index = state.registrationModules ? EmitAndConstant(state, Binary(state, spv::OpShiftRightLogical, TypeU32(state), EmitShaderDataDwordLoad(state, state.program.Metadata().bindings.RuntimeStateDword() + RuntimeAbi::StateExportMappings + exp.index / 4u), ConstantU32(state, exp.index % 4u * 8u + component * 2u)), 3u) : state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ExportBase + exp.index * 4u + component, component); components[component] = state.module.AllocateId(); state.module.AddFunction(spv::OpVectorExtractDynamic, uintOutput ? TypeU32(state) : TypeF32(state), components[component], value, index); }