From 7fea9dec9bd28d85ed36d5ea03e7e499a1de1207 Mon Sep 17 00:00:00 2001 From: SamG-Coder <13449963+SamG-Coder@users.noreply.github.com> Date: Tue, 29 Sep 2026 12:40:37 +1000 Subject: [PATCH 1/5] test(agc): build mesh smoke test on Windows --- core/libs/prx/libSceAgcDriver/tests/Mesh.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp index 4808e4e13..8810dc72c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -180,7 +180,11 @@ void CheckTriangles(const char* what) { int main() { try { +#ifdef _WIN32 + _putenv_s("APS5_NO_SHADER_DISK_CACHE", "1"); +#else setenv("APS5_NO_SHADER_DISK_CACHE", "1", 1); +#endif for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) { const auto vertices = TriangleVertices(triangle); for (std::uint32_t k = 0; k < 3; ++k) { From 785c24e6522f1c7fb784d0a0fc59c5a03f734e6d Mon Sep 17 00:00:00 2001 From: SamG-Coder <13449963+SamG-Coder@users.noreply.github.com> Date: Tue, 29 Sep 2026 12:51:33 +1000 Subject: [PATCH 2/5] fix(shader): derive mesh launch state from guest wave size --- .../Translation/src/InstructionTranslator.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 7f898e969..1d63b09a8 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -237,7 +237,8 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO const auto& mesh = options.inputInfo.vertex->mesh; const std::uint32_t size = mesh.InputPrimitiveSize(); const std::uint32_t stepCount = mesh.InputPrimitiveStep(); - if (options.waveSize != 64u || mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % 64u != 0u || totalThreads > 15u * 64u || mesh.esgsItemSize == 0u || mesh.esgsItemSize * mesh.verticesPerGroup > 0xffffu) { + const std::uint32_t waveSize = options.waveSize; + if (mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % waveSize != 0u || totalThreads > 15u * waveSize || mesh.esgsItemSize == 0u || mesh.esgsItemSize * mesh.verticesPerGroup > 0xffffu) { throw std::runtime_error("mesh shader translation configuration is not supported (wave " + std::to_string(options.waveSize) + ", primitives per group " + std::to_string(mesh.primitivesPerGroup) + ", vertices per group " + std::to_string(mesh.verticesPerGroup) + ", threads " + std::to_string(totalThreads) + ", ESGS item size " + std::to_string(mesh.esgsItemSize) + ")"); } constexpr std::uint32_t kTriStripPrimitiveType = 6u; @@ -260,11 +261,11 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO IrValue& vertices = minimum(subtractSaturate(draw(0u), firstVertex), u32(mesh.verticesPerGroup)); IrValue& primitives = entryIr.Select(entryIr.ULessThan(vertices, u32(size)), u32(0u), entryIr.IAdd(entryIr.Emit(IrOpcode::UDiv32, IrOpcodeType(IrOpcode::UDiv32), {&subtractSaturate(vertices, u32(size)), &step}), u32(1u))); entryIr.SetScalarReg(static_cast(2), entryIr.BitwiseOr(entryIr.ShiftLeftLogical(vertices, u32(12u)), entryIr.ShiftLeftLogical(primitives, u32(22u)))); - IrValue& wave = entryIr.ShiftRightLogical(local, u32(6u)); - IrValue& waveBase = entryIr.BitwiseAnd(local, u32(~63u)); - IrValue& vertexCount = minimum(subtractSaturate(vertices, waveBase), u32(64u)); - IrValue& primitiveCount = minimum(subtractSaturate(primitives, waveBase), u32(64u)); - IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / 64u) << 28u)); + IrValue& wave = entryIr.ShiftRightLogical(local, u32(waveSize == 32u ? 5u : 6u)); + IrValue& waveBase = entryIr.BitwiseAnd(local, u32(~(waveSize - 1u))); + IrValue& vertexCount = minimum(subtractSaturate(vertices, waveBase), u32(waveSize)); + IrValue& primitiveCount = minimum(subtractSaturate(primitives, waveBase), u32(waveSize)); + IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / waveSize) << 28u)); entryIr.SetScalarReg(static_cast(3), entryIr.BitwiseOr(waveInfo, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(primitiveCount, u32(8u)), vertexCount))); IrValue& parity = mesh.inputPrimitive == kTriStripPrimitiveType ? entryIr.BitwiseAnd(entryIr.IAdd(firstPrimitive, local), u32(1u)) : u32(0u); IrValue& vertex = entryIr.IMul(local, step); From 4cd8e35366eee59b26c45fd65e6ad792c2e5b7b6 Mon Sep 17 00:00:00 2001 From: SamG-Coder <13449963+SamG-Coder@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:03:58 +1000 Subject: [PATCH 3/5] fix(shader): preserve NGG passthrough launch and primitive inputs --- .../Execution/src/Driver/Draw/DrawLookup.cpp | 1 + .../Execution/src/Driver/Draw/DrawRegisterKey.cpp | 2 +- core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp | 2 ++ .../prx/libSceAgcDriver/Graphics/src/Pipeline.cpp | 2 ++ .../libs/prx/libSceAgcDriver/Graphics/src/State.cpp | 13 ++++++++----- core/libs/prx/libSceAgcDriver/tests/Graphics.cpp | 13 ++++++++++++- .../libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp | 7 +++++++ core/shader/recompiler/CacheKey.hpp | 1 + .../ControlFlow/src/RequestSerializer.cpp | 2 ++ .../include/Optimization/ShaderStageInputInfo.hpp | 1 + core/shader/recompiler/Recompiler.hpp | 1 + .../Translation/src/InstructionTranslator.cpp | 10 ++++++++++ .../Translation/src/ShaderInputInfoBuilder.cpp | 1 + 13 files changed, 49 insertions(+), 7 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp index 64ec8646f..9e8088ea0 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp @@ -22,6 +22,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr& x, const std::optional& y) { if (x.has_value() != y.has_value()) return false; if (!x) return true; - return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize; + return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize && x->passthrough == y->passthrough; }; const auto sameTess = [](const std::optional& x, const std::optional& y) { if (x.has_value() != y.has_value()) return false; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 9aca4f99a..1ee746445 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -467,6 +467,8 @@ bool ValidationKey(const Context& context, std::span shade add(mesh.threadsPerGroup); add(mesh.ldsSizeDwords); add(mesh.provokingVertex); + add(mesh.esgsItemSize); + add(mesh.passthrough); } add(state.stages.tessellation.has_value()); if (state.stages.tessellation) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 36772cf21..5186111b5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -362,6 +362,8 @@ std::vector pipelineKey(const Context& context, const State& state, c append(key, mesh.threadsPerGroup); append(key, mesh.ldsSizeDwords); append(key, mesh.provokingVertex); + append(key, mesh.esgsItemSize); + append(key, mesh.passthrough); } append(key, state.stages.tessellation.has_value()); if (state.stages.tessellation) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index cdeb09c36..1545a74d9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -341,7 +341,9 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { validate((value & 3u) != 3u && ((value >> 3u) & 3u) != 3u && ((value >> 6u) & 3u) != 3u, "reserved LS_EN, ES_EN or VS_EN encoding"); const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig); const bool tessellation = primitive == 9; - const bool geometry = (value & 0x20u) != 0; + const bool passthrough = (value & 0x02000000u) != 0; + const bool geometry = (value & 0x20u) != 0 || passthrough; + validate(!passthrough || (value & 0x2000u) != 0, "passthrough routing without PRIMGEN_EN is unsupported"); validate(tessellation == ((value & 4u) != 0), "Patch topology and HS_EN disagree"); validate(!tessellation || !geometry, "combined tessellation and geometry is unsupported by the reference path"); const auto path = tessellation ? ShaderPath::Tessellation : geometry ? ShaderPath::Geometry : ShaderPath::Vertex; @@ -360,17 +362,17 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { validate(tess.domain == 1 && tess.partitioning == 2 && tess.outputTopology == 2, "only triangular, fractional-odd, clockwise tessellation is supported by the reference path"); result.tessellation = tess; } else { - validate((value & ~0x0047ec30u) == 0, "unsupported geometry routing, fast launch or wave-ID state"); + validate((value & ~(passthrough ? 0x02402010u : 0x0047ec30u)) == 0, "unsupported geometry routing, fast launch or wave-ID state"); const auto group = read(queue.userConfig, 0x25b, RegisterBank::UserConfig); const auto vertices = (group >> 9u) & 0x1ffu; const auto primitives = group & 0x1ffu; const auto maxVertices = read(queue.context, 0x1ff); - const auto verticesPerPrimitive = read(queue.context, 0x2ce); - validate((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3, "unsupported geometry input or output assembly"); + const auto verticesPerPrimitive = passthrough ? 3u : read(queue.context, 0x2ce); + validate(passthrough ? (primitive == 4 || primitive == 6) : ((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3), "unsupported geometry input or output assembly"); const auto inputSize = primitive == 1 ? 1u : primitive == 2 ? 2u : 3u; validate(vertices >= inputSize && maxVertices != 0 && maxVertices <= 256 && verticesPerPrimitive <= 256, "invalid geometry subgroup output"); const auto inputStep = primitive == 6 ? 1u : inputSize; - const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, maxVertices / verticesPerPrimitive}); + const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, passthrough ? (maxVertices >= inputSize ? (maxVertices - inputSize) / inputStep + 1u : 0u) : maxVertices / verticesPerPrimitive}); validate(groupPrimitives != 0, "geometry subgroup contains no primitives"); const auto resources = read(queue.shader, 0x8b, RegisterBank::Shader); validate(((read(queue.shader, 0x8a, RegisterBank::Shader) >> 29u) & 3u) == 3 && ((resources >> 16u) & 3u) == 3, "unsupported geometry VGPR allocation"); @@ -378,6 +380,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { validate(esgsItemSize != 0 && esgsItemSize * vertices <= 0xffffu, "invalid VGT_ESGS_RING_ITEMSIZE"); const auto threads = std::max({(groupPrimitives - 1u) * inputStep + inputSize, primitives, maxVertices, primitives * (verticesPerPrimitive - 2u)}); result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, primitives * (verticesPerPrimitive - 2u), ((threads + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0, esgsItemSize}; + result.mesh->passthrough = passthrough; } return result; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 5984166b3..be55bb57a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -186,7 +186,7 @@ void hardwareScreenOffsetTests() { void ShaderStageTests() { auto queue = makeState(); - for (const auto routing : {0x2000u, 0x2010u, 0x02002000u, 0x02002010u}) { + for (const auto routing : {0x2000u, 0x2010u}) { for (const auto vertexWave32 : {false, true}) { for (const auto fragmentWave32 : {false, true}) { queue.context[0x2d5] = routing | (vertexWave32 ? 0x00400000u : 0u); @@ -210,6 +210,17 @@ void ShaderStageTests() { auto stages = AgcDriver::Graphics::DecodeState(queue).stages; Require(stages.path == AgcDriver::Graphics::ShaderPath::Geometry && stages.mesh && stages.mesh->primitivesPerGroup == 21 && stages.mesh->verticesPerGroup == 63, "geometry assembly changed"); Require(stages.mesh->maxVertices == 64 && stages.mesh->maxPrimitives == 21 && stages.mesh->threadsPerGroup == 64 && stages.mesh->esgsItemSize == 4, "geometry subgroup outputs changed"); + for (const auto routing : {0x02002000u, 0x02002010u, 0x02402000u, 0x02402010u}) { + queue.context[0x2d5] = routing; + queue.context[0x2ce] = 0; + queue.context[0x29b] = 0; + const auto pass = AgcDriver::Graphics::DecodeState(queue).stages; + Require(pass.path == AgcDriver::Graphics::ShaderPath::Geometry && pass.mesh && pass.mesh->passthrough && pass.mesh->verticesPerGroup == 63 && pass.mesh->primitivesPerGroup == 21, "passthrough subgroup assembly changed"); + Require(pass.vertexWaveSize == ((routing & 0x00400000u) ? 32u : 64u), "passthrough wave size changed"); + } + queue.context[0x2d5] = 0x2020; + queue.context[0x2ce] = 3; + queue.context[0x29b] = 2; queue.context[0x2ab] = 0; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid VGT_ESGS_RING_ITEMSIZE"); queue.context[0x2ab] = 4; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 4f9586ac2..24fec9d49 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -449,6 +449,13 @@ void verifyMeshConfiguration() { other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}}; RecompileCacheKey::Build(other, key); require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh configuration"); + otherMesh = mesh; + otherMesh.passthrough = true; + other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}}; + RecompileCacheKey::Build(other, key); + require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore passthrough input layout"); + const auto passReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other)); + require(passReplay.request.graphics->mesh->passthrough && !replay.request.graphics->mesh->passthrough, "passthrough input layout was lost in serialization"); } ShaderRecompiler::ShaderPixelStageInfo twoParameterPixel() { diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index aacc33f90..78264ca57 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -78,6 +78,7 @@ private: const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr; append(key, mesh != nullptr); if (mesh == nullptr) return; + append(key, mesh->passthrough); for (const auto value : {mesh->inputPrimitive, mesh->primitivesPerGroup, mesh->verticesPerGroup, mesh->maxVertices, mesh->maxPrimitives, mesh->threadsPerGroup, mesh->ldsSizeDwords, mesh->provokingVertex, mesh->esgsItemSize}) append(key, value); } diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index f483746f1..4f36e6f1d 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -442,6 +442,7 @@ void writeMeshConfiguration(Writer& writer, const MeshConfiguration& configurati writer.WriteU32(configuration.ldsSizeDwords); writer.WriteU32(configuration.provokingVertex); writer.WriteU32(configuration.esgsItemSize); + writer.WriteBool(configuration.passthrough); } MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) { @@ -455,6 +456,7 @@ MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) { configuration.ldsSizeDwords = reader.ReadU32(); configuration.provokingVertex = reader.ReadU32(); configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u; + configuration.passthrough = version >= 5u ? reader.ReadBool() : false; return configuration; } diff --git a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp index 39e645996..38bc69cf3 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp @@ -102,6 +102,7 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo { std::uint32_t maxPrimitives = 0; std::uint32_t provokingVertex = 0; std::uint32_t esgsItemSize = 0; + bool passthrough = false; [[nodiscard]] std::uint32_t InputPrimitiveSize() const { return inputPrimitive == 1u ? 1u : inputPrimitive == 2u ? 2u : 3u; diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 6fcb04b64..5d9de25dc 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -225,6 +225,7 @@ struct MeshConfiguration { std::uint32_t ldsSizeDwords; std::uint32_t provokingVertex; std::uint32_t esgsItemSize = 0; + bool passthrough = false; }; struct TessellationConfiguration { diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 1d63b09a8..7d5d58eb0 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -238,6 +238,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO const std::uint32_t size = mesh.InputPrimitiveSize(); const std::uint32_t stepCount = mesh.InputPrimitiveStep(); const std::uint32_t waveSize = options.waveSize; + if (mesh.passthrough && mesh.inputPrimitive != 4u && mesh.inputPrimitive != 6u) { + throw std::runtime_error("passthrough mesh translation requires triangle list or strip topology"); + } if (mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % waveSize != 0u || totalThreads > 15u * waveSize || mesh.esgsItemSize == 0u || mesh.esgsItemSize * mesh.verticesPerGroup > 0xffffu) { throw std::runtime_error("mesh shader translation configuration is not supported (wave " + std::to_string(options.waveSize) + ", primitives per group " + std::to_string(mesh.primitivesPerGroup) + ", vertices per group " + std::to_string(mesh.verticesPerGroup) + ", threads " + std::to_string(totalThreads) + ", ESGS item size " + std::to_string(mesh.esgsItemSize) + ")"); } @@ -275,6 +278,13 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO IrValue& third = size == 3u ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : u32(0u); entryIr.SetVectorReg(static_cast(0), entryIr.BitwiseOr(entryIr.BitwiseAnd(first, u32(0xffffu)), entryIr.ShiftLeftLogical(second, u32(16u)))); entryIr.SetVectorReg(static_cast(1), entryIr.BitwiseAnd(third, u32(0xffffu))); + if (mesh.passthrough) { + IrValue& firstIndex = entryIr.IAdd(vertex, parity); + IrValue& secondIndex = entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity); + IrValue& thirdIndex = entryIr.IAdd(vertex, u32(2u)); + entryIr.SetVectorReg(static_cast(0), entryIr.BitwiseOr(firstIndex, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(secondIndex, u32(10u)), entryIr.ShiftLeftLogical(thirdIndex, u32(20u))))); + entryIr.SetVectorReg(static_cast(1), u32(0u)); + } entryIr.SetVectorReg(static_cast(2), entryIr.IAdd(firstPrimitive, local)); entryIr.SetVectorReg(static_cast(3), u32(0u)); entryIr.SetVectorReg(static_cast(4), u32(0u)); diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index 32887b3bc..8bb8d945a 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -184,6 +184,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues target.maxPrimitives = mesh->maxPrimitives; target.provokingVertex = mesh->provokingVertex; target.esgsItemSize = mesh->esgsItemSize; + target.passthrough = mesh->passthrough; if (target.threadsNum[0] == 0u || target.maxVertices == 0u || target.maxPrimitives == 0u || target.provokingVertex > 2u) throw std::runtime_error("ShaderInputInfoBuilder: invalid mesh configuration"); } ShaderStageInputInfo result; From d9482ea3aee6aeaf17452b9365116362e99338c2 Mon Sep 17 00:00:00 2001 From: SamG-Coder <13449963+SamG-Coder@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:22:54 +1000 Subject: [PATCH 4/5] fix(agc): exclude hidden mesh words from vertex table lookup --- .../prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 cf4936d96..9cc14c358 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -135,7 +135,12 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe const auto& program = programs[i]; if (program.binary.stage == Stage::Fragment || roles[i] == Role::GeometryBack) return; decodeReads[i].clear(); - vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i]); + std::span vertexUserData = program.userData; + if (program.binary.stage == Stage::Mesh) { + require(vertexUserData.size() >= 8u, "mesh vertex metadata requires eight hidden user words"); + vertexUserData = vertexUserData.subspan(8u); + } + vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, vertexUserData, &decodeReads[i]); }; if (!registerKey) { for (std::size_t i = 0; i < programs.size(); ++i) decodeVertexInfo(i); From 5bcb6eae3eb4f381821d2e93b4025863bf09ac7d Mon Sep 17 00:00:00 2001 From: SamG-Coder <13449963+SamG-Coder@users.noreply.github.com> Date: Thu, 1 Oct 2026 20:10:56 +1000 Subject: [PATCH 5/5] fix(shader): version passthrough requests after upstream pixel layout --- .../shader/recompiler/ControlFlow/src/RequestSerializer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index 4f36e6f1d..f9b3285c7 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -456,7 +456,7 @@ MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) { configuration.ldsSizeDwords = reader.ReadU32(); configuration.provokingVertex = reader.ReadU32(); configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u; - configuration.passthrough = version >= 5u ? reader.ReadBool() : false; + configuration.passthrough = version >= 6u ? reader.ReadBool() : false; return configuration; } @@ -679,7 +679,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); - writer.WriteU32(5u); + writer.WriteU32(6u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); @@ -702,7 +702,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const Reader reader(decoded); if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature"); const auto version = reader.ReadU32(); - if (version < 1u || version > 5u) throw std::runtime_error("unsupported recompile request serialization version"); + if (version < 1u || version > 6u) throw std::runtime_error("unsupported recompile request serialization version"); DeserializedRequest result{}; result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader); result.request.context = readGuestContext(reader, result, version);