From 767b8f83a2aed4dc1f74f4febc8eba48a2874874 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 8 Oct 2026 10:25:45 -0500 Subject: [PATCH] feat(agc): accept graphics programs without an AGC shader header Co-Authored-By: Claude Opus 5.5 --- .../include/Driver/Shaders/ShaderRegistry.hpp | 3 +- .../src/Driver/Dispatch/Dispatch.cpp | 2 +- .../Execution/src/Driver/Draw/Draw.cpp | 4 ++- .../Execution/src/Driver/Draw/DrawDecode.cpp | 13 ++++---- .../src/Driver/Shaders/ShaderRegistry.cpp | 9 ++++-- .../Graphics/src/ShaderInputState.cpp | 1 + .../libs/prx/libSceAgcDriver/tests/Submit.cpp | 32 +++++++++---------- 7 files changed, 36 insertions(+), 28 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp index 25fd58e2d..3ddcc2398 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -84,7 +84,8 @@ struct ShaderSnapshot { }; -std::shared_ptr ReadRawComputeShader(std::uint64_t address); +std::shared_ptr ReadRawShader(std::uint64_t address); +std::shared_ptr ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list types); std::shared_ptr PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp index dcfcc848f..19d60397f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp @@ -17,7 +17,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, PerformanceTimer timing("Driver.Dispatch"); const auto address = (static_cast(readRegister(queue.shader, 0x20c)) << 8u) | (static_cast(readRegister(queue.shader, 0x20d) & 0xffu) << 40u); auto registeredShader = RegisteredProgram(*submission.shaders, address, {0}); - if (!registeredShader) registeredShader = ReadRawComputeShader(address); + if (!registeredShader) registeredShader = ReadRawShader(address); const auto& snapshot = *registeredShader; require(snapshot.type == 0, "compute program refers to a non-compute shader"); const auto userCount = (readRegister(queue.shader, 0x213) >> 1u) & 0x1fu; 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 6ab1a5cb5..26bc065d8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -123,7 +123,9 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe std::vector linked; for (std::size_t i = 0; i < programs.size(); ++i) { const auto& program = programs[i]; - memory.insert(memory.end(), program.memory.begin(), program.memory.end()); + for (const auto& region : program.memory) { + if (!region.bytes.empty()) memory.push_back(region); + } linked.push_back({roles[i], program.binary, program.userDataBase, program.firstUserSgpr, program.userData}); } timing.Mark("prepare"); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index f4444a808..60d97fb2f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -28,10 +28,10 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const const auto prepare = [&](std::uint64_t address, std::uint8_t type, Stage stage, std::uint32_t rsrc2, std::uint32_t userDataBase) { const bool nullPixel = stage == Stage::Fragment && (address == 0 || pixelSkipped); if (nullPixel) address = NullPixelProgramAddress(); - const auto registered = RegisteredProgram(registry, address, {type}); - require(registered != nullptr, "graphics program does not belong to a compatible registered shader"); - const auto& snapshot = *registered; + const auto program = ProgramSnapshot(registry, address, {type}); + const auto& snapshot = *program; require((address - snapshot.codeAddress) % sizeof(std::uint32_t) == 0, "graphics entry point is not dword aligned"); + require(snapshot.header.empty() || snapshot.type == type, "graphics program refers to an incompatible shader binary type"); if (!nullPixel) Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, rsrc2); const auto resources = nullPixel ? 0u : ReadGraphicsRegister(queue.shader, rsrc2); const auto userCount = ((resources >> 1u) & 0x1fu) | (((resources >> 27u) & 1u) << 5u); @@ -43,7 +43,7 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const 8, {}, {{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}, {snapshot.headerAddress, snapshot.header}}}, - registered, + program, codeOffset }; for (std::uint32_t i = 0; i < userCount; ++i) { @@ -86,9 +86,8 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const append(0x0c8, 2, Stage::TessellationEvaluation, 0x08b, 0x08c, Role::Domain); } else if (graphics.stages.path == Graphics::ShaderPath::Geometry) { const auto frontAddress = programAddress(0xc8); - const auto snapshot = RegisteredProgram(registry, frontAddress, {2, 4}); - require(snapshot != nullptr, "geometry front program is not registered"); - const auto type = snapshot->type; + const auto front = ProgramSnapshot(registry, frontAddress, {2, 4}); + const auto type = front->header.empty() ? std::uint8_t{2} : front->type; require(type == 2 || type == 4, "invalid geometry front binary type"); append(0xc8, type, Stage::Mesh, 0x8b, 0x8c, Role::Main); initializeMerged(programs.back(), 0x82, type == 4); 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 d01847729..47554a4e2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -37,7 +37,7 @@ std::shared_ptr PrepareShaderWithDiagnosti } } -std::shared_ptr ReadRawComputeShader(std::uint64_t address) { +std::shared_ptr ReadRawShader(std::uint64_t address) { GuestMemory::CheckRange(reinterpret_cast(address), sizeof(std::uint32_t), 256); static std::mutex cacheMutex; static std::list> cache; @@ -100,7 +100,12 @@ std::shared_ptr ReadRawComputeShader(std::uint64_t address if (snapshot.code.size() == available) break; } } - throw std::runtime_error("AGC driver: raw compute program has no reachable end within mapped code or the size limit"); + throw std::runtime_error("AGC driver: raw shader program has no reachable end within mapped code or the size limit"); +} + +std::shared_ptr ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list types) { + if (auto registered = RegisteredProgram(shaders, address, types)) return registered; + return ReadRawShader(address); } namespace { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp index 1939acc1e..3927f11a6 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderInputState.cpp @@ -163,6 +163,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con } ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span header, std::uint64_t headerAddress, std::span userData, std::vector* reads, bool staticAbi) { + if (header.empty()) return {}; if (header.size() < sizeof(Shader)) throw std::runtime_error("AGC graphics: shader header is smaller than the fixed AGC header"); Shader shader; std::memcpy(&shader, header.data(), sizeof(Shader)); diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index cd6f89778..329b4e42b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -593,20 +593,20 @@ int main() { rawCode[1] = 0xbf810000; rawCode[2] = 0xbf810000; const auto rawAddress = reinterpret_cast(rawCode.data()); - const auto literal = AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress); + const auto literal = AgcDriver::DriverDetail::ReadRawShader(rawAddress); check(literal->code.size() == 3 && literal->header.empty(), "raw compute stopped at an instruction literal"); - check(AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress) == literal, "unchanged raw compute code lost its snapshot identity"); + check(AgcDriver::DriverDetail::ReadRawShader(rawAddress) == literal, "unchanged raw compute code lost its snapshot identity"); rawCode[0] = 0xbf820002; rawCode[1] = 0xbf810000; rawCode[2] = 0xbf800000; rawCode[3] = 0xbf810000; - const auto branched = AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress); + const auto branched = AgcDriver::DriverDetail::ReadRawShader(rawAddress); check(branched->code.size() == 4 && literal->code[0] == 0xbe8003ff, "raw compute lost branch targets or modified an earlier snapshot"); check(branched != literal, "changed raw compute code reused a stale snapshot"); std::array, 8> concurrent; std::vector readers; for (auto& snapshot : concurrent) readers.emplace_back([&snapshot, rawAddress] { - snapshot = AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress); + snapshot = AgcDriver::DriverDetail::ReadRawShader(rawAddress); }); for (auto& reader : readers) reader.join(); for (const auto& snapshot : concurrent) check(snapshot == branched, "concurrent raw compute readers lost snapshot reuse"); @@ -615,23 +615,23 @@ int main() { const auto firstAddress = reinterpret_cast(programs.front().data()); readers.clear(); for (auto& snapshot : concurrent) readers.emplace_back([&snapshot, firstAddress] { - snapshot = AgcDriver::DriverDetail::ReadRawComputeShader(firstAddress); + snapshot = AgcDriver::DriverDetail::ReadRawShader(firstAddress); }); for (auto& reader : readers) reader.join(); for (const auto& snapshot : concurrent) check(snapshot == concurrent.front(), "concurrent raw compute misses duplicated snapshots"); - const auto evicted = AgcDriver::DriverDetail::ReadRawComputeShader(firstAddress); + const auto evicted = AgcDriver::DriverDetail::ReadRawShader(firstAddress); for (std::size_t i = 1; i < programs.size(); ++i) - AgcDriver::DriverDetail::ReadRawComputeShader(reinterpret_cast(programs[i].data())); - check(AgcDriver::DriverDetail::ReadRawComputeShader(firstAddress) != evicted && evicted->code[0] == 0xbf810000, + AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast(programs[i].data())); + check(AgcDriver::DriverDetail::ReadRawShader(firstAddress) != evicted && evicted->code[0] == 0xbf810000, "raw compute cache eviction lost snapshot lifetime or exceeded its entry limit"); - check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(rawAddress + 4); }).empty(), "raw compute accepted a misaligned entry"); - check(!expectFailure([] { AgcDriver::DriverDetail::ReadRawComputeShader(0); }).empty(), "raw compute accepted an unmapped entry"); + check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawShader(rawAddress + 4); }).empty(), "raw compute accepted a misaligned entry"); + check(!expectFailure([] { AgcDriver::DriverDetail::ReadRawShader(0); }).empty(), "raw compute accepted an unmapped entry"); alignas(256) std::array straddling{}; straddling.fill(0xbf800000); straddling[63] = 0xf4000000; straddling[64] = 0xfa000000; straddling[65] = 0xbf810000; - const auto straddled = AgcDriver::DriverDetail::ReadRawComputeShader(reinterpret_cast(straddling.data())); + const auto straddled = AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast(straddling.data())); check(straddled->code.size() == 66 && straddled->code[64] == 0xfa000000, "raw compute stopped at a memory instruction across its first read window"); #ifdef _WIN32 auto* mapping = static_cast(VirtualAlloc(nullptr, 8192, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); @@ -641,18 +641,18 @@ int main() { auto* boundedCode = mapping + 1024 - 64; std::fill_n(boundedCode, 64, 0xbf800000u); const auto boundedAddress = reinterpret_cast(boundedCode); - const auto unterminated = expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress); }); + const auto unterminated = expectFailure([&] { AgcDriver::DriverDetail::ReadRawShader(boundedAddress); }); boundedCode[63] = 0xbf810000; - const auto bounded = AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress); + const auto bounded = AgcDriver::DriverDetail::ReadRawShader(boundedAddress); check(VirtualProtect(mapping + 1024, 4096, PAGE_READWRITE, &protection) != 0, "cannot extend raw compute mapping"); boundedCode[63] = 0xbf800000; boundedCode[64] = 0xbf810000; - const auto extended = AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress); + const auto extended = AgcDriver::DriverDetail::ReadRawShader(boundedAddress); check(extended != bounded && extended->code.size() == 65, "raw compute reused code before its end changed"); check(VirtualProtect(mapping + 1024, 4096, PAGE_NOACCESS, &protection) != 0, "cannot revoke cached raw compute tail"); - check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress); }).empty(), "raw compute reused an inaccessible cached tail"); + check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawShader(boundedAddress); }).empty(), "raw compute reused an inaccessible cached tail"); check(VirtualProtect(mapping, 4096, PAGE_NOACCESS, &protection) != 0, "cannot revoke cached raw compute code"); - check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawComputeShader(boundedAddress); }).empty(), "raw compute reused inaccessible cached code"); + check(!expectFailure([&] { AgcDriver::DriverDetail::ReadRawShader(boundedAddress); }).empty(), "raw compute reused inaccessible cached code"); check(VirtualFree(mapping, 0, MEM_RELEASE) != 0, "cannot release raw compute boundary test"); check(!unterminated.empty() && bounded->code.size() == 64, "raw compute crossed inaccessible memory or missed its last instruction"); #endif