From 408a656e7b2e507fbf2db7eda43033cdbc53ad9a Mon Sep 17 00:00:00 2001 From: Lindamea1 <322051987+Lindamea1@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:41:26 +0100 Subject: [PATCH 1/2] fix(agc): put a stencil replay shader's guest buffer offsets where its shader data is A pixel shader whose stencil export is replayed gets a push constant block that ends with the stencil reference word. When its shader data is not in push constants (registration modules give a shader with an image a shader data buffer, and data over the stage push range goes there too), the block holds only that word, and buildPrepare took any non-empty block to mean the guest buffer offsets are in push constants: the draw threw "guest buffer offset lies outside the shader's push constants". Only the stencilReferenceDword words before the reference now count as shader data in push constants; with none, the offsets go to the shader data buffer, where the shader reads them. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/src/ShaderResources.cpp | 5 ++-- .../tests/execution/StencilReplay.cpp | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index d6d84cd4aa5f..d62578d41018 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1986,9 +1986,10 @@ void ShaderResources::buildPrepare(std::span shaders, cons const bool classified = element < binding.bufferWritten.size() && element < binding.bufferRead.size() && element < binding.bufferAtomic.size(); allocations[index].writeOnly = classified && allocations[index].guest && allocations[index].written && binding.bufferWritten[element] && !read && !atomic; const auto& push = shader.program->pushConstants; - if (!push.empty()) { + const auto pushDataBytes = shader.program->stencilReferences.empty() ? push.size() : shader.program->stencilReferenceDword * sizeof(std::uint32_t); + if (pushDataBytes != 0u) { const auto position = shader.program->memoryOffsetDword * 4u + element; - Require(position < push.size(), "guest buffer offset lies outside the shader's push constants"); + Require(position < pushDataBytes, "guest buffer offset lies outside the shader's push constants"); allocations[index].pushByte = static_cast(shader.pushConstantOffset + position); } else { allocations[index].dataByte = shader.program->memoryOffsetDword * 4u + element; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp index 3612fae6efb7..2f150274b03d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp @@ -369,6 +369,29 @@ int main() { catch (const std::exception& error) { faultThrown = std::string(error.what()).find("BDA") != std::string::npos; } Require(faultThrown, "poisoned readonly SRT replay did not throw a host fault"); Require(std::ranges::all_of(Pixels, [](std::byte value) { return value == Kept; }), "poisoned replay published guest color results before its fault"); + alignas(256) std::array columnValues{}; + alignas(256) std::array columnTexels{}; + columnValues[0] = 0x2fu; + for (std::uint32_t x = 0; x < Width; ++x) { + columnValues[x + 1u] = 0x30u + x; + columnTexels[x] = 1.0f; + } + constexpr std::array bufferImageCode{ + 0x7e060f00u, 0x7e080280u, 0xf0001108u, 0x00010503u, 0xe0302000u, 0x80000603u, 0xe0302000u, 0x80030803u, + 0xbf8c3f70u, 0x7e0e02f2u, 0xf8000082u, 0x00000600u, 0xf800180fu, 0x07050805u, 0xbf810000u, + }; + const auto columnBuffer = BufferDescriptor(columnValues.data() + 1, 4u, Width); + const auto alignedBuffer = BufferDescriptor(columnValues.data(), 4u, Width + 1u); + const auto texelAddress = static_cast(reinterpret_cast(columnTexels.data())); + const std::array columnTexture{static_cast(texelAddress >> 8u), static_cast((texelAddress >> 40u) & 0xffu) | (22u << 20u) | (((Width - 1u) & 3u) << 30u), (Width - 1u) >> 2u, 0xfacu | (9u << 28u), 0u, 0u, 0u, 0u}; + std::vector bufferImageData(columnBuffer.begin(), columnBuffer.end()); + bufferImageData.insert(bufferImageData.end(), columnTexture.begin(), columnTexture.end()); + bufferImageData.insert(bufferImageData.end(), alignedBuffer.begin(), alignedBuffer.end()); + Draw(*device, PlainPixelCode, plain, replace(0u)); + Draw(*device, bufferImageCode, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, bufferImageData); + Check(all, "a replayed export beside an image draws the image's texels"); + Draw(*device, PlainPixelCode, plain, equal(0x35u)); + Check([](std::uint32_t x, std::uint32_t) { return x == 5u; }, "a replayed export beside an image reads its guest buffer at a 4-byte offset"); std::puts("stencil replay tests passed"); return 0; } catch (const std::exception& error) { From 15803c7f1a3a2323459399bcaab0fe405931abbb Mon Sep 17 00:00:00 2001 From: battleblob1 Date: Thu, 22 Oct 2026 00:40:11 +0300 Subject: [PATCH 2/2] test(agc): replay a stencil export whose shader data is in a buffer Draw takes an optional fragment push range size. The new case sets it to the stencil reference dword, so the shader data of a pixel shader that reads its color with s_buffer_load_dword through a buffer descriptor in user data is in a buffer with or without the pipeline library, and the reference is the only push constant. The replayed draw must color every pixel from the buffer. Without the buildPrepare fix before it, both stencil replay registrations refuse that draw with "guest buffer offset lies outside the shader's push constants". Co-Authored-By: Claude Opus 5.5 --- .../tests/execution/StencilReplay.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp index 2f150274b03d..14d571cf481c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StencilReplay.cpp @@ -88,7 +88,7 @@ ShaderRecompiler::ShaderPixelStageInfo PixelInfo(std::uint32_t shaderControl) { return AgcDriver::Graphics::DecodePixelStageInfo(context, {0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}); } -void Draw(AgcDriver::VulkanDevice& device, std::span pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil, std::uint32_t vertices = 3u, std::uint32_t instances = 1u, bool depthFail = false, const std::function& alter = {}, const std::function& alterDraw = {}, std::span> vertexData = Vertices, std::span vertexCode = VertexCode, std::span pixelData = {}, std::span pixelSrt = {}, const std::function& alterVertex = {}) { +void Draw(AgcDriver::VulkanDevice& device, std::span pixelCode, std::uint32_t shaderControl, const VkStencilOpState& stencil, std::uint32_t vertices = 3u, std::uint32_t instances = 1u, bool depthFail = false, const std::function& alter = {}, const std::function& alterDraw = {}, std::span> vertexData = Vertices, std::span vertexCode = VertexCode, std::span pixelData = {}, std::span pixelSrt = {}, const std::function& alterVertex = {}, std::uint32_t pixelPushBytes = 0u) { Pixels.fill(Kept); auto target = device.Target(); std::vector capabilities(target.supportedCapabilities.begin(), target.supportedCapabilities.end()); @@ -113,11 +113,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::span pixelC auto pixelUserData = pixelData.empty() ? std::vector(8, 0u) : std::vector(pixelData.begin(), pixelData.end()); std::vector pixelMemory{{reinterpret_cast(pixelCode.data()), std::as_bytes(pixelCode)}}; pixelMemory.insert(pixelMemory.end(), pixelSrt.begin(), pixelSrt.end()); + auto pixelLayout = PixelPushLayout(vertexPush, target); + if (pixelPushBytes != 0u) pixelLayout.pushConstantSizeBytes = pixelPushBytes; ShaderRecompiler::RecompileRequest fragment{ {ShaderStage::Fragment, reinterpret_cast(pixelCode.data()), pixelCode, 0, {}}, {WaveSize, 0, pixelUserData, std::nullopt, PixelInfo(shaderControl), std::nullopt, pixelMemory}, target, - PixelPushLayout(vertexPush, target) + pixelLayout }; fragment.useCache = false; const auto pixelResult = ShaderRecompiler::Recompile(fragment); @@ -350,6 +352,14 @@ int main() { const std::array colorData{0x3f800000u}; Draw(*device, userColor, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, colorData); Check(all, "dynamic reference preserves the adjacent shader-data push payload"); + auto bufferColor = std::vector(alternating.begin(), alternating.end()); + *std::find(bufferColor.begin(), bufferColor.end(), 0x7e0e02f2u) = 0x7e0e0208u; + bufferColor.insert(bufferColor.begin(), {0xf4200200u, 0xfa000000u, 0xbf8cc07fu}); + alignas(256) const std::array colorBuffer{0x3f800000u, 0u, 0u, 0u}; + const auto colorDescriptor = BufferDescriptor(colorBuffer.data(), 4u, static_cast(colorBuffer.size())); + const std::array colorMemory{{{reinterpret_cast(colorBuffer.data()), std::as_bytes(std::span(colorBuffer))}}}; + Draw(*device, bufferColor, exported, replace(), 3u, 1u, false, {}, {}, Vertices, VertexCode, colorDescriptor, colorMemory, {}, sizeof(std::uint32_t)); + Check(all, "dynamic reference keeps guest buffer offsets in shader data outside the push constants"); auto guarded = std::vector(alternating.begin(), alternating.end()); *std::find(guarded.begin(), guarded.end(), 0x7e0e02f2u) = 0x7e0e020cu; guarded.insert(guarded.begin(), {0xf4040280u, 0xfa000018u, 0xf4000305u, 0xfa000000u, 0xbf8cc07fu});