From 0a1b31f5c4e53015abd56696e4e01b4e0ea37d49 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 8 Oct 2026 15:31:07 -0500 Subject: [PATCH] test(agc): cover NGG vertex programs routed to the mesh path Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/Graphics.cpp | 31 +++++++++++++++++++ .../libs/prx/libSceAgcDriver/tests/Submit.cpp | 16 ++++++++++ 2 files changed, 47 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 7deea9485..516c3b165 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -468,6 +468,36 @@ void ShaderStageTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "missing register"); } +void NggVertexWorkgroupTests() { + auto queue = makeState(); + queue.context[0x2d5] = 0x12010; + queue.userConfig[0x242] = 4; + queue.userConfig[0x25b] = 0x10080; + queue.context[0x1ff] = 0x80; + queue.context[0x2ce] = 0; + queue.context[0x29b] = 2; + queue.context[0x2ab] = 1; + queue.shader[0x8a] = 0x222c0009; + queue.shader[0x8b] = 0x43001e; + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + Require(AgcDriver::Graphics::DecodeState(queue).stages.path == AgcDriver::Graphics::ShaderPath::Vertex, "an NGG vertex program without a workgroup left the vertex path"); + auto state = AgcDriver::Graphics::DecodeState(queue, true); + Require(state.stages.path == AgcDriver::Graphics::ShaderPath::Geometry && state.stages.mesh, "an NGG vertex program that needs a workgroup did not take the mesh path"); + auto mesh = *state.stages.mesh; + Require(mesh.inputPrimitive == 4 && mesh.primitivesPerGroup == 42 && mesh.verticesPerGroup == 126 && mesh.maxVertices == 128 && mesh.maxPrimitives == 128 && mesh.threadsPerGroup == 128 && mesh.ldsSizeDwords == 1024 && mesh.esgsItemSize == 1, "the NGG vertex subgroup of a triangle list changed"); + queue.userConfig[0x242] = 6; + mesh = *AgcDriver::Graphics::DecodeState(queue, true).stages.mesh; + Require(mesh.primitivesPerGroup == 126 && mesh.verticesPerGroup == 128 && mesh.maxPrimitives == 128, "the NGG vertex subgroup of a triangle strip is not bounded by its vertex budget"); + queue.context[0x1ff] = 2; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue, true); }, "invalid geometry subgroup output"); + queue.context[0x1ff] = 0x80; + for (const auto stages : {0x02012010u, 0x00012030u, 0x00010010u}) { + queue.context[0x2d5] = stages; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue, true); }, "requires PRIMGEN_EN without GS_EN, PRIMGEN_PASSTHRU_EN or tessellation"); + } +} + constexpr std::array IdentityExports{0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u, 0xe4u}; std::vector pixelBuiltinsRead(std::uint32_t ena, std::uint32_t addr, std::uint32_t source) { @@ -4043,6 +4073,7 @@ int main() { uint16ExportTests(); uint8x4TargetTests(); ShaderStageTests(); + NggVertexWorkgroupTests(); TuningFieldTests(); PixelInputLayoutTests(); ColorPipeBankXorTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 4137e06b3..e6a9d0384 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -602,6 +602,21 @@ void testProgramSnapshots() { check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a decode with the rewritten program was not current"); } +void testProgramWorkgroupUse() { + const auto snapshot = [](std::vector code) { + AgcDriver::DriverDetail::ShaderSnapshot result{0x1000, 0, 2, std::move(code), {}}; + return result; + }; + const auto plain = snapshot({0xbf800000, 0xbf810000}); + check(!AgcDriver::DriverDetail::ProgramUsesWorkgroup(plain, 0) && !AgcDriver::DriverDetail::ProgramUsesWorkgroup(plain, 0), "a program without LDS or barriers needs a workgroup"); + const auto barrier = snapshot({0xbf8a0000, 0xbf810000}); + check(AgcDriver::DriverDetail::ProgramUsesWorkgroup(barrier, 0), "an s_barrier did not need a workgroup"); + const auto lds = snapshot({0xd8340000, 0x00000100, 0xbf810000}); + check(AgcDriver::DriverDetail::ProgramUsesWorkgroup(lds, 0) && AgcDriver::DriverDetail::ProgramUsesWorkgroup(lds, 0), "an LDS write did not need a workgroup"); + const auto entry = snapshot({0xbf8a0000, 0xbf810000, 0xbf800000, 0xbf810000}); + check(!AgcDriver::DriverDetail::ProgramUsesWorkgroup(entry, 2) && AgcDriver::DriverDetail::ProgramUsesWorkgroup(entry, 0), "the workgroup check did not start at the program's entry"); +} + void testWorkerFailure() { std::array words{0xc0031500, 1, 1, 1, 0x41}; Packet packet{words.data(), static_cast(words.size()), 0, {}}; @@ -694,6 +709,7 @@ int main() { check(!unterminated.empty() && bounded->code.size() == 64, "raw compute crossed inaccessible memory or missed its last instruction"); #endif testProgramSnapshots(); + testProgramWorkgroupUse(); testEvents(); testValidation(); testClearState();