From 8c802e5eb1e44299357055eb818bb1b09866a87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 10 Oct 2026 11:50:25 +0200 Subject: [PATCH] perf(agc): give each graphics stage a fixed push constant slot with pipeline libraries Co-Authored-By: Claude --- .../Execution/include/VulkanDevice.hpp | 1 + .../Execution/src/Driver/Draw/Draw.cpp | 4 ++- .../Execution/src/Driver/Draw/DrawCapture.cpp | 2 +- .../src/Driver/Shaders/ShaderRegistry.cpp | 11 +++++--- .../Execution/src/VulkanDevice.cpp | 13 +++++++--- .../Graphics/include/Shaders.hpp | 25 ++++++++++++++++++- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 2 +- .../libSceAgcDriver/Graphics/src/Pipeline.cpp | 25 ++++++++++++++----- .../Graphics/src/ShaderValidation.cpp | 4 +-- .../prx/libSceAgcDriver/tests/Graphics.cpp | 7 +++++- .../prx/libSceAgcDriver/tests/Recorder.cpp | 15 ++++++++--- .../execution/ConservativeRasterization.cpp | 4 +-- .../tests/execution/DepthBiasPerFace.cpp | 4 +-- .../tests/execution/DrawOffsetIndices.cpp | 4 +-- .../tests/execution/DualSourceBlend.cpp | 4 +-- .../tests/execution/ExecHostLanes.cpp | 4 +-- .../tests/execution/InterpolationF16.cpp | 4 +-- .../tests/execution/NggVertexWaveInfo.cpp | 4 +-- .../execution/PackedUnormColorTarget.cpp | 4 +-- .../execution/PixelBarycentricEmulation.cpp | 4 +-- .../execution/PixelFixedPointPosition.cpp | 4 +-- .../tests/execution/PixelHelperLanes.cpp | 4 +-- .../tests/execution/PixelInterlock.cpp | 4 +-- .../tests/execution/PixelLds.cpp | 4 +-- .../tests/execution/PixelLdsLaneSlots.cpp | 4 +-- .../tests/execution/PixelUint16Export.cpp | 4 +-- .../tests/execution/PixelValidMask.cpp | 4 +-- .../tests/execution/PixelWaveReduction.cpp | 4 +-- .../tests/execution/ScalarSwappcFetch.cpp | 4 +-- .../tests/execution/Srgb8ColorTarget.cpp | 4 +-- .../tests/execution/Uint8x4ColorTarget.cpp | 4 +-- .../tests/execution/VulkanTestDevice.hpp | 9 +++++++ .../tests/execution/ZeroStrideVertexFetch.cpp | 4 +-- .../IrMetadata/DescriptorBinding.hpp | 3 +++ .../IrMetadata/ResourcePlan.hpp | 3 ++- .../Optimization/src/BindingAllocator.cpp | 3 +++ core/shader/recompiler/Recompiler.hpp | 1 + .../src/SpirvMemory/SpirvBufferAccess.cpp | 2 +- .../src/SpirvMemory/SpirvEmitterHelpers.cpp | 2 +- docs/dev/ARCHITECTURE.md | 2 +- 40 files changed, 149 insertions(+), 69 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index 4144b30fa..d35c1e2ef 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -165,6 +165,7 @@ public: bool ConservativeRasterization() const; VkShaderStageFlags SubgroupStages() const; bool ProvokingVertexLast() const; + bool GraphicsPipelineLibraries() const; // A presentation is a few steps so the presenter holds GuestMemory::GpuMutex only while it // touches the queue. Presentations are slots (FlipInFlight() + 1, each with its own command // buffer, fence, kept resident image and dump buffer): RetirePresents(keep) (no mutex) retires diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp index 82465097b..6ab1a5cb5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -187,6 +187,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe for (std::size_t i = 0; i < programs.size(); ++i) { if (roles[i] == Role::GeometryBack) continue; const auto& program = programs[i]; + pushCursorBytes = Graphics::StagePushOffset(pushCursorBytes, program.binary.stage, localDevice->GraphicsPipelineLibraries()); pushOffsets[i] = pushCursorBytes; if (drawHit) { @@ -204,7 +205,8 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe } else if (i == 0) { fold(result, drawParameters); } - require(result.pushConstants.size() <= Graphics::PipelinePushConstantBytes - pushCursorBytes, "stage push constants exceed the pipeline push constant block"); + const auto slotEnd = (pushCursorBytes / Graphics::PipelinePushSlotBytes + 1u) * Graphics::PipelinePushSlotBytes; + require(result.pushConstants.size() <= slotEnd - pushCursorBytes, "stage push constants exceed the pipeline push constant block"); stages.push_back({program.binary.stage, &result, result.pushConstants.empty() ? 0u : pushCursorBytes}); pushCursorBytes += static_cast(result.pushConstants.size()); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp index ad07dcdc6..d6d026478 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp @@ -20,7 +20,7 @@ ShaderRecompiler::RecompileResult Driver::materializeDrawStage(std::size_t i, st program.binary, {waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, vertexInfos[i], memory, RegisteredFloatMode(*program.snapshot)}, localDevice->Target(), - {0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushConstantBytes) - pushOffset}, + {0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushSlotBytes) - pushOffset % Graphics::PipelinePushSlotBytes}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}} }; const auto waitedBefore = traceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp index 5660f2933..52a46d03b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -490,6 +490,7 @@ std::unique_ptr PlanRegistered(const ShaderSnapshot& snap plan->memory = {{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}, {snapshot.headerAddress, snapshot.header}}}; auto& request = plan->request.emplace(ShaderRecompiler::RecompileRequest{{stage, address, code, snapshot.headerAddress, snapshot.header}, {wave, firstUser, plan->userData, compute, pixel, vertex, plan->memory, RegisteredFloatMode(snapshot)}, stage == Stage::Compute ? device.ComputeTarget(wave) : device.Target(), {0, 0, 0, 128}, graphics}); if (graphics && graphics->mesh) request.layout.pushConstantSizeBytes = ShaderRecompiler::MeshDrawPushOffsetBytes; + if (pixel && device.GraphicsPipelineLibraries()) request.layout.pushConstantOffsetBytes = Graphics::FixedPushOffset(Stage::Fragment); return plan; } @@ -629,7 +630,8 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod } std::vector prepared; std::uint32_t pushOffset = 0; - const auto capacity = decoded.state.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushConstantBytes; + const bool fixedSlots = target.fixedPushSlots; + const auto capacity = decoded.state.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushSlotBytes; for (std::size_t index = 0; index < decoded.programs.size(); ++index) { if (decoded.roles[index] == ShaderRecompiler::ProgramRole::GeometryBack) continue; const auto& program = decoded.programs[index]; @@ -637,7 +639,10 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod const auto wave = fragment ? decoded.state.stages.fragmentWaveSize : decoded.state.stages.vertexWaveSize; std::optional vertex; if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true); - ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory, RegisteredFloatMode(*program.snapshot)}, target, {0, 0, pushOffset, capacity - pushOffset}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; + pushOffset = Graphics::StagePushOffset(pushOffset, program.binary.stage, fixedSlots); + const auto offset = pushOffset; + const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes; + ShaderRecompiler::RecompileRequest request{program.binary, {wave, program.firstUserSgpr, program.userData, {}, fragment ? std::optional(decoded.pixel) : std::nullopt, vertex, memory, RegisteredFloatMode(*program.snapshot)}, target, {0, 0, offset, room}, ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, decoded.state.stages.mesh, decoded.state.stages.tessellation, {}}}; std::vector key; ShaderRecompiler::BuildPreparedShaderKey(request, key); std::shared_ptr handle; @@ -652,7 +657,7 @@ std::vector PrepareGraphicsStages(const DrawDecode& decod handle = PrepareShaderWithDiagnostics(request); } const auto bytes = handle->artifact->bindings.pushConstantSizeBytes; - require(bytes <= capacity - pushOffset, "prepared graphics stages exceed the push constant block"); + require(bytes <= room, "prepared graphics stages exceed the push constant block"); prepared.push_back({program.snapshot, {program.codeOffset, handle}}); pushOffset += bytes; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 7cfd5f2b5..590942e1c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -1044,7 +1044,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window) : state(std::make_u VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT libraryProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_PROPERTIES_EXT}; VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &libraryProperties}; state->InstanceFunction("vkGetPhysicalDeviceProperties2")(selected, &properties); - state->graphicsPipelineLibrary = libraryFeatures.graphicsPipelineLibrary == VK_TRUE && dynamicStateFeatures.extendedDynamicState == VK_TRUE && renderingFeatures.dynamicRendering == VK_TRUE && libraryProperties.graphicsPipelineLibraryFastLinking == VK_TRUE; + state->graphicsPipelineLibrary = libraryFeatures.graphicsPipelineLibrary == VK_TRUE && dynamicStateFeatures.extendedDynamicState == VK_TRUE && renderingFeatures.dynamicRendering == VK_TRUE && libraryProperties.graphicsPipelineLibraryFastLinking == VK_TRUE && properties.properties.limits.maxPushConstantsSize >= Graphics::PipelinePushConstantBytes; if (state->graphicsPipelineLibrary) deviceExtensions.insert(deviceExtensions.end(), libraryExtensions.begin(), libraryExtensions.end()); } renderingFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR, nullptr, VK_TRUE}; @@ -2623,6 +2623,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const { target.fragmentShaderBarycentricEnabled = state->fragmentShaderBarycentric; target.nonConstantImageOffsets = state->maintenance8; target.narrowSubgroupClock = state->narrowSubgroupClock; + target.fixedPushSlots = state->graphicsPipelineLibrary; target.srgbDecodeFormats = state->srgbDecodeFormats; if (state->meshShader) { const auto& mesh = state->meshLimits; @@ -2662,6 +2663,10 @@ bool VulkanDevice::ProvokingVertexLast() const { return state->provokingVertexLast; } +bool VulkanDevice::GraphicsPipelineLibraries() const { + return state->graphicsPipelineLibrary; +} + Graphics::Context VulkanDevice::graphicsContext() const { static const bool noCache = std::getenv("APS5_NO_CONTEXT_CACHE") != nullptr; if (state->contextReady && !noCache) return state->context; @@ -3526,7 +3531,7 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { recorder.Keep(std::move(memory)); } if (record.pushStages != 0) { - context.Resolved(&Graphics::DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, record.objects->layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, Graphics::PipelinePushConstantBytes, record.pushBytes->data()); + context.Resolved(&Graphics::DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, record.objects->layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, Graphics::ComputePushConstantBytes, record.pushBytes->data()); } const auto gpuTiming = recorder.BeginGpuTiming(record.programAddress != 0 ? record.programAddress : record.shader->program->variantId); if (argumentImport != nullptr) context.Resolved(&Graphics::DeviceFunctions::cmdDispatchIndirect, "vkCmdDispatchIndirect")(commands, argumentImport->buffer, arguments - argumentImport->base); @@ -3572,7 +3577,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec } const std::array shaders{{{ShaderRecompiler::ShaderStage::Compute, &shader, 0}}}; const auto pushStages = Graphics::PushConstantStages(shaders); - if (pushStages != 0 && state->properties.limits.maxPushConstantsSize < Graphics::PipelinePushConstantBytes) { + if (pushStages != 0 && state->properties.limits.maxPushConstantsSize < Graphics::ComputePushConstantBytes) { throw std::runtime_error("Vulkan dispatch: compute push constant range exceeds device limit"); } auto pushBytes = Graphics::AssemblePushConstants(shaders); @@ -3739,7 +3744,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec check(state->DeviceFunction("vkCreateShaderModule")(state->device, &moduleInfo, nullptr, &objects->module), "vkCreateShaderModule"); timing.Mark("validate_shader_module"); const std::array setLayouts{resources->Layout(), shader.workgroupMemoryDwords != 0 ? state->workgroupMemoryLayout : VK_NULL_HANDLE}; - const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, Graphics::PipelinePushConstantBytes}; + const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, Graphics::ComputePushConstantBytes}; VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; layoutInfo.setLayoutCount = shader.workgroupMemoryDwords != 0 ? ShaderRecompiler::WorkgroupMemoryDescriptorSet + 1 : 1; layoutInfo.pSetLayouts = setLayouts.data(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp index 976f905fa..a7e8bd9ec 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp @@ -9,7 +9,29 @@ namespace AgcDriver::Graphics { -inline constexpr std::uint32_t PipelinePushConstantBytes = 128; +inline constexpr std::uint32_t PipelinePushConstantBytes = 256; +inline constexpr std::uint32_t PipelinePushSlotBytes = 128; +inline constexpr std::uint32_t ComputePushConstantBytes = 128; + +inline std::uint32_t PushBlockBytes(bool libraries) { + return libraries ? PipelinePushConstantBytes : PipelinePushSlotBytes; +} + +inline VkShaderStageFlags PreRasterizationPushStages(bool meshShader, bool tessellation, bool geometry) { + VkShaderStageFlags stages = VK_SHADER_STAGE_VERTEX_BIT; + if (tessellation) stages |= VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + if (geometry) stages |= VK_SHADER_STAGE_GEOMETRY_BIT; + if (meshShader) stages |= VK_SHADER_STAGE_MESH_BIT_EXT; + return stages; +} + +inline std::uint32_t FixedPushOffset(ShaderRecompiler::ShaderStage stage) { + return stage == ShaderRecompiler::ShaderStage::Fragment ? PipelinePushSlotBytes : 0u; +} + +inline std::uint32_t StagePushOffset(std::uint32_t cursor, ShaderRecompiler::ShaderStage stage, bool fixedSlots) { + return fixedSlots && stage == ShaderRecompiler::ShaderStage::Fragment ? PipelinePushSlotBytes : cursor; +} struct CompiledShader { ShaderRecompiler::ShaderStage stage; @@ -65,6 +87,7 @@ inline std::array AssemblePushConstants(st if (bytes.empty()) continue; Require(bytes.size() % 4 == 0 && shader.pushConstantOffset % 4 == 0, "shader push constant range is not DWORD aligned"); Require(shader.pushConstantOffset < PipelinePushConstantBytes && bytes.size() <= PipelinePushConstantBytes - shader.pushConstantOffset, "shader push constant range lies outside the pipeline push constant block"); + Require(bytes.size() <= PipelinePushSlotBytes - shader.pushConstantOffset % PipelinePushSlotBytes, "shader push constant range crosses a push constant slot"); for (std::size_t i = 0; i < bytes.size(); ++i) { const auto position = shader.pushConstantOffset + i; Require(!occupied[position], "shader push constant ranges of different stages overlap"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index fc24d9b14..c1a08b12f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -1494,7 +1494,7 @@ void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const const auto firstVertex = draw.indirect ? draw.indirect->vertexConstant : draw.firstVertex; const auto firstInstance = draw.indirect ? draw.indirect->instanceConstant : draw.firstInstance; const std::array words{draw.indexCount, firstVertex, firstInstance, draw.indexed ? draw.indexSize : 0u, static_cast(meshArguments), static_cast(meshArguments >> 32u)}; - static_assert(ShaderRecompiler::MeshDrawPushOffsetBytes + ShaderRecompiler::MeshDrawPushBytes == PipelinePushConstantBytes); + static_assert(ShaderRecompiler::MeshDrawPushOffsetBytes + ShaderRecompiler::MeshDrawPushBytes == PipelinePushSlotBytes); std::memcpy(block.data() + ShaderRecompiler::MeshDrawPushOffsetBytes, words.data(), sizeof(words)); pipeline.PushConstants(commands, stages | VK_SHADER_STAGE_MESH_BIT_EXT, block); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index e59fb490c..941bbc9e4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -117,6 +117,10 @@ void ValidateViewport(const Context& context, const VkViewport& viewport) { namespace { +VkShaderStageFlags pushStagesOf(const Context& context) { + return PreRasterizationPushStages(context.meshShader, context.tessellationShader, context.geometryShader); +} + template void appendKey(std::vector& key, const TValue& value) { static_assert(std::is_trivially_copyable_v); @@ -165,7 +169,7 @@ 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, PushConstantStages(shaders)); + appendKey(keys.layout, pushStagesOf(context)); for (const auto word : resources.LayoutKey()) appendKey(keys.layout, word); return true; } @@ -200,7 +204,10 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput Require(mesh.maxVertices <= context.meshLimits.maxMeshOutputVertices && mesh.maxPrimitives <= context.meshLimits.maxMeshOutputPrimitives && static_cast(mesh.ldsSizeDwords) * 4 <= context.meshLimits.maxMeshSharedMemorySize, "mesh output or LDS exceeds device limits"); } const auto pushStages = PushConstantStages(shaders); - Require(pushStages == 0 || context.limits.maxPushConstantsSize >= PipelinePushConstantBytes, "graphics push constant range exceeds device limit"); + Require(pushStages == 0 || context.limits.maxPushConstantsSize >= PushBlockBytes(dynamicRendering), "graphics push constant range exceeds device limit"); + if (dynamicRendering) { + for (const auto& shader : shaders) Require(shader.program->pushConstants.empty() || (shader.pushConstantOffset >= PipelinePushSlotBytes) == (shader.stage == ShaderRecompiler::ShaderStage::Fragment), "a stage's push constants lie outside its push constant slot"); + } try { std::vector stages(shaders.size()); std::vector specializations; @@ -222,12 +229,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 VkPushConstantRange push{pushStages, 0, PipelinePushConstantBytes}; + 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.pushConstantRangeCount = pushStages != 0 ? 1 : 0; - layoutInfo.pPushConstantRanges = pushStages != 0 ? &push : nullptr; + 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"); std::vector colors; std::vector references(state.blends.size(), VkAttachmentReference{VK_ATTACHMENT_UNUSED, attachmentLayout}); @@ -536,7 +544,12 @@ void Pipeline::ContinueBackFaces(VkCommandBuffer commands, const State& state) c void Pipeline::PushConstants(VkCommandBuffer commands, VkShaderStageFlags stages, std::span bytes) const { if (stages == 0) return; - context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, layout, stages, 0, PipelinePushConstantBytes, bytes.data()); + if (dynamicRendering) { + context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, layout, pushStagesOf(context), 0, PipelinePushSlotBytes, bytes.data()); + context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, layout, VK_SHADER_STAGE_FRAGMENT_BIT, PipelinePushSlotBytes, PipelinePushSlotBytes, bytes.data() + PipelinePushSlotBytes); + return; + } + context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, layout, stages, 0, PipelinePushSlotBytes, bytes.data()); } namespace { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index f8a460c4f..e880bddaf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -478,11 +478,11 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi push = true; Require(type.size() == 3 && module.decorations[typeId].block, "push constant variable must be a Block struct with exactly one member"); const auto offset = module.offsets.find({typeId, 0}); - Require(offset != module.offsets.end() && offset->second == 0, "push constant member must have offset zero"); + Require(offset != module.offsets.end() && offset->second % PipelinePushSlotBytes == 0 && offset->second < PipelinePushConstantBytes, "push constant member must start at a stage slot"); const auto& array = module.Type(type[2]); Require(array.size() == 4 && (array[0] & 0xffffu) == spv::OpTypeArray && module.Signature(array[2]) == "u32", "push constant member must be an array of u32"); const auto length = module.constants.find(array[3]); - Require(length != module.constants.end() && length->second == PipelinePushConstantBytes / 4, "push constant array must contain 32 elements"); + Require(length != module.constants.end() && length->second == PipelinePushSlotBytes / 4, "push constant array must contain 32 elements"); const auto stride = module.decorations[type[2]].stride; Require(stride.has_value() && *stride == 4, "push constant array must have an ArrayStride of 4"); } else if (variable.storage == spv::StorageClassWorkgroup) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 3c38d8e39..cfe05b1fc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -1918,11 +1918,16 @@ void pushConstantTests() { std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, 8}}}; Require(AgcDriver::Graphics::PushConstantStages(shaders) == (VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT), "push constant stage union changed"); const auto bytes = AgcDriver::Graphics::AssemblePushConstants(shaders); - Require(bytes.size() == 128 && bytes[0] == std::byte{1} && bytes[7] == std::byte{1} && bytes[8] == std::byte{2} && bytes[19] == std::byte{2} && bytes[20] == std::byte{0} && bytes[127] == std::byte{0}, "assembled push constants are misplaced"); + Require(bytes.size() == AgcDriver::Graphics::PipelinePushConstantBytes && bytes[0] == std::byte{1} && bytes[7] == std::byte{1} && bytes[8] == std::byte{2} && bytes[19] == std::byte{2} && bytes[20] == std::byte{0} && bytes[127] == std::byte{0} && bytes.back() == std::byte{0}, "assembled push constants are misplaced"); shaders[1].pushConstantOffset = 4; expectFailure([&] { AgcDriver::Graphics::AssemblePushConstants(shaders); }, "overlap"); shaders[1].pushConstantOffset = 120; + expectFailure([&] { AgcDriver::Graphics::AssemblePushConstants(shaders); }, "crosses a push constant slot"); + shaders[1].pushConstantOffset = AgcDriver::Graphics::PipelinePushConstantBytes - 4; expectFailure([&] { AgcDriver::Graphics::AssemblePushConstants(shaders); }, "outside the pipeline push constant block"); + shaders[1].pushConstantOffset = AgcDriver::Graphics::PipelinePushSlotBytes; + const auto slotted = AgcDriver::Graphics::AssemblePushConstants(shaders); + Require(slotted[128] == std::byte{2} && slotted[139] == std::byte{2} && slotted[8] == std::byte{0}, "a pixel shader in its push slot was misplaced"); shaders[1].pushConstantOffset = 2; expectFailure([&] { AgcDriver::Graphics::AssemblePushConstants(shaders); }, "DWORD aligned"); shaders[1].pushConstantOffset = 8; diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 34dc7009f..4fd62e369 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -205,7 +205,7 @@ public: VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT libraryProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_PROPERTIES_EXT}; VkPhysicalDeviceProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &libraryProperties}; function("vkGetPhysicalDeviceProperties2")(context.physical, &properties); - context.graphicsPipelineLibrary = library.graphicsPipelineLibrary == VK_TRUE && dynamicState.extendedDynamicState == VK_TRUE && rendering.dynamicRendering == VK_TRUE && libraryProperties.graphicsPipelineLibraryFastLinking == VK_TRUE; + context.graphicsPipelineLibrary = library.graphicsPipelineLibrary == VK_TRUE && dynamicState.extendedDynamicState == VK_TRUE && rendering.dynamicRendering == VK_TRUE && libraryProperties.graphicsPipelineLibraryFastLinking == VK_TRUE && properties.properties.limits.maxPushConstantsSize >= PipelinePushConstantBytes; } rendering = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR, address.pNext, VK_TRUE}; library = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GRAPHICS_PIPELINE_LIBRARY_FEATURES_EXT, &dynamicState, VK_TRUE}; @@ -3815,12 +3815,21 @@ void pipelineLibraryTests(const Device& device) { input.attributes.push_back({0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0}); static_cast(lookup()); Require(counters().built == std::array{2, 1, 1, 2} && counters().linked == 4, "a vertex input change rebuilt more than the vertex input library"); + Require(StagePushOffset(88, ShaderRecompiler::ShaderStage::Fragment, true) == PipelinePushSlotBytes && StagePushOffset(88, ShaderRecompiler::ShaderStage::Fragment, false) == 88 && StagePushOffset(40, ShaderRecompiler::ShaderStage::TessellationEvaluation, true) == 40, "a stage got the wrong push offset"); + const auto beforeSlots = counters().built; + ShaderRecompiler::RecompileResult otherVertex = vertex; + otherVertex.variantId = 14; + std::array slotted{CompiledShader{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, CompiledShader{ShaderRecompiler::ShaderStage::Fragment, &fragment, PipelinePushSlotBytes}}; + static_cast(CachedPipeline(context, state, input, resources, slotted, VK_IMAGE_LAYOUT_GENERAL)); + slotted.front().program = &otherVertex; + static_cast(CachedPipeline(context, state, input, resources, slotted, VK_IMAGE_LAYOUT_GENERAL)); + Require(counters().built[2] == beforeSlots[2] + 1u && counters().built[1] == beforeSlots[1] + 1u, "a pixel shader in its fixed slot built its library again for another vertex shader"); fragment.variantId = 13; static_cast(lookup()); - Require(counters().built == std::array{2, 1, 2, 2}, "a new pixel shader rebuilt more than the fragment shader library"); + Require(counters().built == std::array{2, 2, 3, 2}, "a new pixel shader rebuilt more than the fragment shader library"); state.colors.front().format = VK_FORMAT_R16G16B16A16_SFLOAT; static_cast(lookup()); - Require(counters().built == std::array{2, 1, 2, 3}, "another target format rebuilt more than the fragment output library"); + Require(counters().built == std::array{2, 2, 3, 3}, "another target format rebuilt more than the fragment output library"); ClearCachedPipelines(context.device); Require(counters().linked == 0, "clearing the pipelines kept the device's libraries"); std::cout << "Pipeline library reuse tests passed\n"; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ConservativeRasterization.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ConservativeRasterization.cpp index 4bc982a2b..3a641d905 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ConservativeRasterization.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ConservativeRasterization.cpp @@ -86,13 +86,13 @@ void Draw(AgcDriver::VulkanDevice& device, VkConservativeRasterizationModeEXT mo {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {WaveSize, 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/DepthBiasPerFace.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthBiasPerFace.cpp index 583d0212e..d54ab0399 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthBiasPerFace.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthBiasPerFace.cpp @@ -78,13 +78,13 @@ void Draw(AgcDriver::VulkanDevice& device, float frontBias, float backBias) { {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {WaveSize, 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/DrawOffsetIndices.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp index 1596fa7f9..6754f0c84 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp @@ -285,10 +285,10 @@ public: 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, vertexPush, 128u - vertexPush}}; + ShaderRecompiler::RecompileRequest pixelRequest{{ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {32u, 0u, {}, std::nullopt, pixelInfo, std::nullopt, {}}, device->Target(), PixelPushLayout(vertexPush, device->Target())}; pixelRequest.useCache = false; const auto pixel = ShaderRecompiler::Recompile(pixelRequest); - const std::array shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, vertexPush}}}; + const std::array shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, PixelPushOffset(vertexPush, device->Target())}}}; Output.fill(Unwritten); device->Draw(State(), draw, shaders); device->WaitIdle(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DualSourceBlend.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DualSourceBlend.cpp index 4f15acde2..043d19548 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DualSourceBlend.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DualSourceBlend.cpp @@ -236,7 +236,7 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge {ShaderStage::Fragment, codeAddress, pixelCode, 0, {}}, {waveSize, 0, {}, std::nullopt, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), std::nullopt, pixelMemory}, target, - {0, 0, vertexPush, 128 - vertexPush} + PixelPushLayout(vertexPush, target) }; const auto handle = ShaderRecompiler::PrepareShader(fragment); AgcDriver::DriverDetail::ShaderSnapshot snapshot{codeAddress, 0, 1, {pixelCode.begin(), pixelCode.end()}, {}}; @@ -257,7 +257,7 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge Require(plain->PipelineVariantId() != pixelResult->PipelineVariantId() && materialize(fragment)->PipelineVariantId() == pixelResult->PipelineVariantId(), "dual-source blending did not key its own specialization"); const std::array shaders{{ {ShaderStage::Vertex, &vertexResult, 0}, - {ShaderStage::Fragment, pixelResult.get(), vertexPush} + {ShaderStage::Fragment, pixelResult.get(), PixelPushOffset(vertexPush, target)} }}; const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; device.Draw(state, draw, shaders); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ExecHostLanes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ExecHostLanes.cpp index 1978891b9..2eb9752be 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ExecHostLanes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ExecHostLanes.cpp @@ -99,13 +99,13 @@ void Draw(AgcDriver::VulkanDevice& device) { {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {64, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, device.Target(), - {0, 0, vertexPush, 128 - vertexPush} + PixelPushLayout(vertexPush, device.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, device.Target())} }}; AgcDriver::Graphics::State state{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationF16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationF16.cpp index 6cfadd21b..8b519385a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationF16.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationF16.cpp @@ -133,7 +133,7 @@ ShaderRecompiler::RecompileResult RecompilePixel(std::span {ShaderStage::Fragment, reinterpret_cast(code.data()), code, 0, {}}, {waveSize, 0, pixelUserData, std::nullopt, PixelStage(waveSize, input0), std::nullopt, pixelMemory}, target, - {0, 0, pushOffset, 128 - pushOffset} + PixelPushLayout(pushOffset, target) }; fragment.useCache = false; fragment.context.floatMode = ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}; @@ -162,7 +162,7 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { const auto pixelResult = RecompilePixel(PixelCode, waveSize, Fp16Input, target, vertexPush); 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/NggVertexWaveInfo.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/NggVertexWaveInfo.cpp index ba1c0124f..ad361921f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/NggVertexWaveInfo.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/NggVertexWaveInfo.cpp @@ -68,13 +68,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {waveSize, 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/PackedUnormColorTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnormColorTarget.cpp index d3ac094b3..31bf88c82 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnormColorTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PackedUnormColorTarget.cpp @@ -217,7 +217,7 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge {ShaderStage::Fragment, codeAddress, pixelCode, 0, {}}, {waveSize, 0, {}, std::nullopt, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), std::nullopt, pixelMemory}, target, - {0, 0, vertexPush, 128 - vertexPush} + PixelPushLayout(vertexPush, target) }; const auto handle = ShaderRecompiler::PrepareShader(fragment); AgcDriver::DriverDetail::ShaderSnapshot snapshot{codeAddress, 0, 1, {pixelCode.begin(), pixelCode.end()}, {}}; @@ -238,7 +238,7 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge Require(plain->PipelineVariantId() != pixelResult->PipelineVariantId() && materialize(fragment)->PipelineVariantId() == pixelResult->PipelineVariantId(), "the 10_11_11 unorm packing did not key its own specialization"); const std::array shaders{{ {ShaderStage::Vertex, &vertexResult, 0}, - {ShaderStage::Fragment, pixelResult.get(), vertexPush} + {ShaderStage::Fragment, pixelResult.get(), PixelPushOffset(vertexPush, target)} }}; const AgcDriver::Pm4::DrawParameters draw{0, static_cast(Triangle.size()), 0, 1, 0, false}; device.Draw(state, draw, shaders); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp index 220f6b84d..3a9d3178e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelBarycentricEmulation.cpp @@ -92,7 +92,7 @@ ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, std::spa {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {64u, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, target, - {0, 0, vertexPush, 128 - vertexPush} + PixelPushLayout(vertexPush, target) }; fragment.useCache = false; auto pixelResult = ShaderRecompiler::Recompile(fragment); @@ -102,7 +102,7 @@ ShaderRecompiler::RecompileResult Draw(AgcDriver::VulkanDevice& device, std::spa geometry = ShaderRecompiler::BuildBarycentricGeometryShader(vertexResult, pixelResult, target, device.GeometryLimits()); shaders.push_back({ShaderStage::Geometry, &geometry, 0}); } - shaders.push_back({ShaderStage::Fragment, &pixelResult, vertexPush}); + shaders.push_back({ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, target)}); AgcDriver::Graphics::State state{}; state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, 64u, 64u, std::nullopt, std::nullopt}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelFixedPointPosition.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelFixedPointPosition.cpp index a32c241b2..60aa9b05e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelFixedPointPosition.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelFixedPointPosition.cpp @@ -176,13 +176,13 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {waveSize, 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)} }}; VkPipelineColorBlendAttachmentState blend{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp index 0660b8036..606fcdb41 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelHelperLanes.cpp @@ -87,13 +87,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const ShaderR {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {waveSize, 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/PixelInterlock.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp index 68ae18013..116235fa1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp @@ -137,13 +137,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, std::span(pixelCode.data()), pixelCode, 0, {}}, {waveSize, 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/PixelLds.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp index ba7b210d8..174ef5e52 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLds.cpp @@ -102,13 +102,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, bool bounded) {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {waveSize, 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/PixelLdsLaneSlots.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp index a1aa594a1..8311db542 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelLdsLaneSlots.cpp @@ -95,13 +95,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {waveSize, 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/PixelUint16Export.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelUint16Export.cpp index 968bb3af6..f354ffc86 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelUint16Export.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelUint16Export.cpp @@ -187,13 +187,13 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {waveSize, 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/PixelValidMask.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelValidMask.cpp index 914ba8e97..2b8f7b61f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelValidMask.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelValidMask.cpp @@ -71,13 +71,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, std::span(pixelCode.data()), pixelCode, 0, {}}, {waveSize, 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/PixelWaveReduction.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelWaveReduction.cpp index efdfff7d9..83f21e2b2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelWaveReduction.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelWaveReduction.cpp @@ -105,13 +105,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::span pixelC {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {64u, 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/ScalarSwappcFetch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSwappcFetch.cpp index 7b1b8a95b..a5ad23d65 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSwappcFetch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScalarSwappcFetch.cpp @@ -99,13 +99,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t select, bool onlyRestar {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {64u, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, device.Target(), - {0, 0, vertexPush, 128 - vertexPush} + PixelPushLayout(vertexPush, device.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, device.Target())} }}; AgcDriver::Graphics::State state{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Srgb8ColorTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Srgb8ColorTarget.cpp index f4eec88b2..58de25068 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Srgb8ColorTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Srgb8ColorTarget.cpp @@ -227,13 +227,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::vector(pixelCode.data()), pixelCode, 0, {}}, {waveSize, 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/Uint8x4ColorTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Uint8x4ColorTarget.cpp index 6f6151f2a..f03fe5dbb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Uint8x4ColorTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Uint8x4ColorTarget.cpp @@ -179,13 +179,13 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {waveSize, 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)} }}; VkPipelineColorBlendAttachmentState blend{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.hpp b/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.hpp index bab6cab86..0b7c23d83 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VulkanTestDevice.hpp @@ -27,4 +27,13 @@ inline std::unique_ptr OpenVulkanTestDevice() { } } +inline std::uint32_t PixelPushOffset(std::uint32_t vertexPush, const ShaderRecompiler::SpirvTarget& target) { + return AgcDriver::Graphics::StagePushOffset(vertexPush, ShaderRecompiler::ShaderStage::Fragment, target.fixedPushSlots); +} + +inline ShaderRecompiler::BindingLayout PixelPushLayout(std::uint32_t vertexPush, const ShaderRecompiler::SpirvTarget& target) { + const auto offset = PixelPushOffset(vertexPush, target); + return {0, 0, offset, AgcDriver::Graphics::PipelinePushSlotBytes - offset % AgcDriver::Graphics::PipelinePushSlotBytes}; +} + #endif diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp index 9cbf04887..50e546f07 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp @@ -88,13 +88,13 @@ void Draw(AgcDriver::VulkanDevice& device, const Case& value) { {ShaderStage::Fragment, reinterpret_cast(PixelCode.data()), PixelCode, 0, {}}, {64u, 0, pixelUserData, std::nullopt, pixel, std::nullopt, pixelMemory}, device.Target(), - {0, 0, vertexPush, 128 - vertexPush} + PixelPushLayout(vertexPush, device.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, device.Target())} }}; AgcDriver::Graphics::State state{}; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp index 91952766e..c1a9500b7 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/DescriptorBinding.hpp @@ -58,6 +58,9 @@ struct IrBindingLayout { [[nodiscard]] bool UsesPushData() const { return pushDataStartDword != PushData::NoStart; } + [[nodiscard]] std::uint32_t PushSlotDword() const { + return UsesPushData() ? pushDataStartDword / PushData::DwordCount * PushData::DwordCount : 0u; + } void AdvancePushData(std::uint32_t& cursor) const { if (UsesPushData()) { cursor = pushDataStartDword + ShaderDataDwords(); diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp index 953dacc16..3f1b27f6d 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp @@ -49,7 +49,8 @@ struct UniformFillPlan { std::array values{}; }; -inline constexpr std::uint32_t NativePushConstantSize = sizeof(PushData); +inline constexpr std::uint32_t NativePushSlotSize = sizeof(PushData); +inline constexpr std::uint32_t NativePushConstantSize = 2u * NativePushSlotSize; struct IrResourcePlan { IrShaderStage stage = IrShaderStage::Unknown; diff --git a/core/shader/recompiler/Optimization/src/BindingAllocator.cpp b/core/shader/recompiler/Optimization/src/BindingAllocator.cpp index 185c7ea89..f99616a96 100644 --- a/core/shader/recompiler/Optimization/src/BindingAllocator.cpp +++ b/core/shader/recompiler/Optimization/src/BindingAllocator.cpp @@ -89,6 +89,9 @@ BindingAllocationResult BindingAllocator::Allocate(IrProgram& program, const Bin if (layout.pushConstantOffsetBytes + layout.pushConstantSizeBytes > NativePushConstantSize) { fail("shader binding layout failed: push constant range exceeds the native push constant size"); } + if (layout.pushConstantSizeBytes != 0u && layout.pushConstantOffsetBytes / NativePushSlotSize != (layout.pushConstantOffsetBytes + layout.pushConstantSizeBytes - 1u) / NativePushSlotSize) { + fail("shader binding layout failed: push constant range crosses a stage slot"); + } const ShaderInfo& info = program.Resources().info; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index a8a342f56..e22a50162 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -225,6 +225,7 @@ struct SpirvTarget { bool nonConstantImageOffsets = false; std::uint32_t srgbDecodeFormats = 0; bool narrowSubgroupClock = false; + bool fixedPushSlots = false; }; struct BindingLayout { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp index 68fe7a3d5..b0c4e7b32 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp @@ -38,7 +38,7 @@ std::uint32_t EmitShaderDataDwordLoad(SpirvEmitterState& state, std::uint32_t dw const auto value = state.module.AllocateId(); if (layout.UsesPushData()) { const auto base = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::PushDataOffset, layout.pushDataStartDword); - const auto index = Binary(state, spv::OpIAdd, TypeU32(state), base, ConstantU32(state, dwordIndex)); + const auto index = Binary(state, spv::OpIAdd, TypeU32(state), Binary(state, spv::OpISub, TypeU32(state), base, ConstantU32(state, layout.PushSlotDword())), ConstantU32(state, dwordIndex)); state.module.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer, state.pushConstantVariable, ConstantU32(state, 0), index); } else if (state.shaderDataStorageVariable != 0) { state.module.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer, state.shaderDataStorageVariable, ConstantU32(state, 0), ConstantU32(state, dwordIndex)); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp index 8ac4bc722..79c762646 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp @@ -120,7 +120,7 @@ std::uint32_t PushConstantArrayType(SpirvEmitterState& state) { std::uint32_t PushConstantBlockType(SpirvEmitterState& state) { return state.module.DecoratedType(spv::OpTypeStruct, - {{spv::OpMemberDecorate, {0u, spv::DecorationOffset, 0u}}, + {{spv::OpMemberDecorate, {0u, spv::DecorationOffset, state.program.Metadata().bindings.PushSlotDword() * 4u}}, {spv::OpDecorate, {spv::DecorationBlock}}}, PushConstantArrayType(state)); } diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index d57337d62..81646c4b0 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -70,7 +70,7 @@ flowchart LR - A compute shader whose LDS plus its lock dword exceeds the host's `maxComputeSharedMemorySize` is not given a `Workgroup` array. `Recompile` sets `ShaderInfo::sharedMemoryBytes` for it and the SPIR-V backend addresses the LDS in a coherent storage buffer at descriptor set 1 (`WorkgroupMemoryDescriptorSet`), with a slice of `RecompileResult::workgroupMemoryDwords` per workgroup, indexed by `WorkgroupID` and `NumWorkGroups`. `VulkanDevice` keeps one such buffer, sized from the dispatch dimensions, and binds it after the resource set; programs that fit keep using shared memory. - `Driver::RegisterShader` splits shader preparation in two. `PlanRegistered` runs on the registering guest thread: it validates the registers and entry point, decodes the code and builds the recompile request, so invalid registrations still fail there. The RDNA to IR translation and resource plan (`PrepareShader`) run on a background pool of `hardware_concurrency() / 2` detached threads (at least 2, 64 MiB stacks). - The snapshot is published as pending. `SourceHandleFor`, `InvocationFor` and `ShaderPreparationTransaction::Edit` / `Read` wait for the pending job before reading `PreparedShaders`, and rethrow its failure. `APS5_SYNC_SHADER_PREPARE=1` prepares on the registering thread instead. -- A draw's `Graphics::Pipeline` comes from `CachedPipeline`. When the device has `VK_EXT_graphics_pipeline_library` with fast linking, `VK_EXT_extended_dynamic_state` and `VK_KHR_dynamic_rendering`, draws render with `vkCmdBeginRenderingKHR` instead of render passes and the pipeline is linked from four libraries (vertex input, pre-rasterization shaders, fragment shader, fragment output) kept per device in [`PipelineLibrary.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp). A library built for one pipeline is reused by the next: another blend, vertex layout or target format builds only that part (the shader libraries name no attachment format), and cull mode, front face, depth and stencil state are dynamic, so changing them only links. Pipelines with per-face depth bias, rect lists, geometry shaders or a stage without a variant id are created whole, still with dynamic rendering; devices without the extensions (or with `APS5_NO_GPL=1`) keep render passes and whole pipelines. +- A draw's `Graphics::Pipeline` comes from `CachedPipeline`. When the device has `VK_EXT_graphics_pipeline_library` with fast linking, `VK_EXT_extended_dynamic_state` and `VK_KHR_dynamic_rendering`, draws render with `vkCmdBeginRenderingKHR` instead of render passes and the pipeline is linked from four libraries (vertex input, pre-rasterization shaders, fragment shader, fragment output) kept per device in [`PipelineLibrary.cpp`](../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineLibrary.cpp). A library built for one pipeline is reused by the next: another blend, vertex layout or target format builds only that part (the shader libraries name no attachment format), and cull mode, front face, depth and stencil state are dynamic, so changing them only links. Push constants then use a 256-byte block with a 128-byte slot per side: the pre-rasterization stages stack from byte 0, the pixel shader always starts at byte 128 (`StagePushOffset`), so its library does not depend on how much push data its vertex shader uses. Pipelines with per-face depth bias, rect lists, geometry shaders or a stage without a variant id are created whole, still with dynamic rendering; devices without the extensions (or with `APS5_NO_GPL=1`) keep render passes and whole pipelines. ### Shader MODE register access