From 831ba5d2982ecb7b3eecbc03e4d866a92ad94189 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Wed, 7 Oct 2026 22:32:42 -0500 Subject: [PATCH] fix(agc): accept RB and primitive-group tuning fields Accept PA_SC_MODE_CNTL_0.ALTERNATE_RBS_PER_TILE, CB_COLOR_CONTROL.DISABLE_DUAL_QUAD and VGT_SHADER_STAGES_EN.MAX_PRIMGRP_IN_WAVE on the vertex path. They tune how the hardware distributes work between render backends and waves and do not change the rendered result. Mesa sets all three on GFX10. Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/Graphics/src/State.cpp | 5 ++-- .../prx/libSceAgcDriver/tests/Graphics.cpp | 30 ++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 53288a665..33d3431d4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -66,7 +66,7 @@ constexpr std::uint32_t DepthControlMask = ~0x007007f0u; // to run, which it always does here. constexpr std::uint32_t ShaderControlMask = ~(0x0000f870u | 0x00020600u); constexpr std::uint32_t AlphaToCoverageMask = ~0x0001ff00u; -constexpr std::uint32_t ScanModeMask = ~2u; +constexpr std::uint32_t ScanModeMask = ~0x22u; constexpr std::uint32_t ScanControlMask = ~0x06023fffu; constexpr std::uint32_t ScreenOffsetMask = ~0x01ff01ffu; // Bits 26/27 (ZCLIP_NEAR/FAR_DISABLE) become depth clamping; bit 19 selects the [0, 1] clip space. @@ -234,6 +234,7 @@ std::uint32_t effectiveDepthControl(std::uint32_t depthControl) { } bool colorControlSupported(std::uint32_t colorControl, bool hasColorTarget) { + colorControl &= ~1u; return colorControl == 0xcc0010u || (!hasColorTarget && (colorControl & ~0x70u) == 0xcc0000u); } @@ -396,7 +397,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue) { APS5_LOG_OUT_DEBUG("DecodeShaderStages value=0x%x primitive=%u path=%u vertexWave=%u", value, primitive, static_cast(result.path), result.vertexWaveSize); if (path == ShaderPath::Vertex) { validate((value & 0x2000u) != 0, "legacy vertex routing without PRIMGEN_EN is unsupported"); - validate((value & ~0x02402010u) == 0, "unsupported vertex routing, scheduling or wave-ID state"); + validate((value & ~0x0247a010u) == 0, "unsupported vertex routing, scheduling or wave-ID state"); } else if (path == ShaderPath::Tessellation) { validate((value & 0x00600020u) == 0, "wave32 tessellation or geometry amplification is unsupported"); validate((value & ~0x0007ed0du) == 0 && (value & 3u) == 1u && ((value >> 3u) & 3u) == 1u, "unsupported tessellation routing"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index e6e2faefe..9bc9c8760 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -319,7 +319,7 @@ void ShaderStageTests() { queue.context[0x2d5] = value; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "reserved"); } - for (const auto bit : {1u, 8u, 0x40u, 0x100u, 0x200u, 0x400u, 0x1000u, 0x4000u, 0x8000u, 0x80000u, 0x200000u, 0x800000u, 0x1000000u}) { + for (const auto bit : {1u, 8u, 0x40u, 0x100u, 0x200u, 0x400u, 0x1000u, 0x4000u, 0x80000u, 0x200000u, 0x800000u, 0x1000000u}) { queue.context[0x2d5] = 0x2000u | bit; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "unsupported vertex"); } @@ -477,6 +477,33 @@ void DisabledColorTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "missing register"); } +void TuningFieldTests() { + auto queue = makeState(); + queue.context[0x1b3] = 2; + queue.context[0x1b4] = 2; + const auto baseline = AgcDriver::Graphics::DecodeState(queue); + queue.context[0x292] = 0x22; + auto state = AgcDriver::Graphics::DecodeState(queue); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "ALTERNATE_RBS_PER_TILE was rejected"); + Require(state.scissor.offset.x == baseline.scissor.offset.x && state.scissor.extent.width == baseline.scissor.extent.width && state.scissor.extent.height == baseline.scissor.extent.height, "ALTERNATE_RBS_PER_TILE changed the scissor"); + queue.context[0x292] = 0x26; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "scan conversion mode"); + queue.context[0x292] = 2; + queue.context[0x202] = 0xcc0011; + state = AgcDriver::Graphics::DecodeState(queue); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "DISABLE_DUAL_QUAD was rejected"); + Require(state.hasColorTarget && state.blend.colorWriteMask == baseline.blend.colorWriteMask, "DISABLE_DUAL_QUAD changed color output"); + queue.context[0x202] = 0xcc0013; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "copy ROP"); + queue.context[0x202] = 0xcc0010; + for (const auto groups : {1u, 2u, 15u}) { + queue.context[0x2d5] = 0x2000u | (groups << 15u); + state = AgcDriver::Graphics::DecodeState(queue); + Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "MAX_PRIMGRP_IN_WAVE was rejected"); + Require(state.stages.path == AgcDriver::Graphics::ShaderPath::Vertex && state.stages.vertexWaveSize == 64u, "MAX_PRIMGRP_IN_WAVE changed vertex routing"); + } +} + void CompactedExportTests() { alignas(256) static std::array slotFourMemory{}; const auto slotFour = reinterpret_cast(slotFourMemory.data()); @@ -2148,6 +2175,7 @@ int main() { CompactedExportTests(); metadataPassTests(); ShaderStageTests(); + TuningFieldTests(); PixelInputLayoutTests(); InitialContextTests(); pushConstantTests();