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 4144b30fad..d35c1e2ef8 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 82465097b8..6ab1a5cb52 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 ad07dcdc66..d6d026478e 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 5660f29339..52a46d03b8 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 7cfd5f2b5d..590942e1cb 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 976f905fad..a7e8bd9eca 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 fc24d9b147..c1a08b12f0 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 e59fb490c7..941bbc9e40 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 f8a460c4fc..e880bddafc 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 3c38d8e395..cfe05b1fca 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 34dc7009f3..4fd62e369c 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 4bc982a2b4..3a641d9053 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 583d0212e9..d54ab03997 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 1596fa7f9c..6754f0c84d 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 4f15acde26..043d195484 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 1978891b9f..2eb9752be0 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 6cfadd21b5..8b519385a2 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 ba1c0124fc..ad361921fd 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 d3ac094b3f..31bf88c82d 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 220f6b84d8..3a9d3178ef 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 a32c241b2b..60aa9b05e5 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 0660b80360..606fcdb410 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 68ae180133..116235fa11 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 ba7b210d86..174ef5e521 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 a1aa594a10..8311db5429 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 968bb3af6c..f354ffc864 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 914ba8e972..2b8f7b61fe 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 efdfff7d9f..83f21e2b25 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 7b1b8a95b4..a5ad23d658 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 f4eec88b27..58de25068d 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 6f6151f2a5..f03fe5dbbe 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 bab6cab86c..0b7c23d836 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 9cbf048876..50e546f07a 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 91952766ec..c1a9500b77 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 953dacc163..3f1b27f6d7 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 185c7ea894..f99616a962 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 a8a342f56d..e22a501623 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 68fe7a3d5e..b0c4e7b325 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 8ac4bc722a..79c7626466 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 d57337d626..81646c4b03 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