From 767b8f83a2aed4dc1f74f4febc8eba48a2874874 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 8 Oct 2026 10:25:45 -0500 Subject: [PATCH 1/5] 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 From dddd9f29c22d64a1bc27757f8e874a773eaab001 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 8 Oct 2026 10:49:55 -0500 Subject: [PATCH 2/5] test(agc): cover raw program snapshots and headerless vertex decode Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/tests/Graphics.cpp | 9 +++++++ .../libs/prx/libSceAgcDriver/tests/Submit.cpp | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index cf7a795c0..7deea9485 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -613,6 +613,14 @@ void ColorPipeBankXorTests() { } } +void HeaderlessVertexStageTests() { + const std::array userData{1, 2, 3, 4}; + const auto info = AgcDriver::Graphics::DecodeVertexStageInfo({}, 0, userData); + Require(!info.fetchEmbedded && info.resourcesNum == 0, "a vertex program without an AGC header received an embedded fetch table"); + const std::array truncated{}; + expectFailure([&] { static_cast(AgcDriver::Graphics::DecodeVertexStageInfo(truncated, 0x1000, userData)); }, "smaller than the fixed AGC header"); +} + void PixelInputLayoutTests() { using ShaderRecompiler::PixelInput; using ShaderRecompiler::PixelInputVgpr; @@ -4038,6 +4046,7 @@ int main() { TuningFieldTests(); PixelInputLayoutTests(); ColorPipeBankXorTests(); + HeaderlessVertexStageTests(); ComputeScratchTests(); shaderUserDataTailPaddingTests(); InitialContextTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 329b4e42b..4671c0087 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -565,6 +565,29 @@ void testRegisteredFloatMode() { } } +void testProgramSnapshots() { + alignas(256) static std::array registered{}; + registered.fill(0xbf800000); + registered[0] = 0xbf810000; + alignas(256) static std::array raw{}; + raw.fill(0xbf800000); + raw[1] = 0xbf810000; + const auto registeredAddress = reinterpret_cast(registered.data()); + const auto rawAddress = reinterpret_cast(raw.data()); + AgcDriver::DriverDetail::ShaderRegistry shaders; + AgcDriver::DriverDetail::ShaderSnapshot snapshot{registeredAddress, 0x1000, 2, {}, {}}; + snapshot.code.assign(registered.begin(), registered.end()); + snapshot.header.resize(sizeof(Shader)); + const auto entry = std::make_shared(std::move(snapshot)); + shaders.emplace(registeredAddress, entry); + check(AgcDriver::DriverDetail::ProgramSnapshot(shaders, registeredAddress) == entry, "a registered program was not resolved to its registration"); + check(AgcDriver::DriverDetail::ProgramSnapshot(shaders, registeredAddress + 4 * 8) == entry, "an entry inside registered code was not resolved to its registration"); + const auto unregistered = AgcDriver::DriverDetail::ProgramSnapshot(shaders, rawAddress); + check(unregistered != entry && unregistered->codeAddress == rawAddress && unregistered->header.empty() && unregistered->code.size() == 2, "an unregistered program was not read as raw code"); + check(AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress) == unregistered, "an empty registry did not fall back to raw code"); + check(!expectFailure([] { static_cast(AgcDriver::DriverDetail::ProgramSnapshot({}, 0)); }).empty(), "an unmapped unregistered program was accepted"); +} + void testWorkerFailure() { std::array words{0xc0031500, 1, 1, 1, 0x41}; Packet packet{words.data(), static_cast(words.size()), 0, {}}; @@ -656,6 +679,7 @@ int main() { 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 + testProgramSnapshots(); testEvents(); testValidation(); testClearState(); From 7373ee1311aa9c73906081ffe999a5fa4224dad8 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 8 Oct 2026 14:53:43 -0500 Subject: [PATCH 3/5] feat(agc): prepare unregistered graphics programs at first use Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libSceAgcDriver/CMakeLists.txt | 2 + .../src/Driver/Shaders/ShaderRegistry.cpp | 8 +- .../PreparedUnregisteredGraphics.cpp | 95 +++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 866503983..bea3eaaba 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -934,6 +934,8 @@ target_include_directories(agc_driver_prepared_publication_tests PRIVATE $) target_link_libraries(agc_driver_interpolation_modes_tests PRIVATE shader_recompiler_agc_driver) +target_include_directories(agc_driver_prepared_unregistered_graphics_tests PRIVATE $) + target_include_directories(agc_driver_prepared_shaders_tests PRIVATE $ ) 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 47554a4e2..8e6e210c8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -278,8 +278,12 @@ void ReportPreparedAtUse(const ShaderSnapshot& snapshot, const ShaderRecompiler: if (!snapshot.prepared->deferred) APS5_LOG_ERR("Shader 0x%llx stage %u has no artifact prepared at registration for the state of this draw or dispatch; preparing it at use", static_cast(request.shader.codeAddress), static_cast(request.shader.stage)); return; } - if (snapshot.type != 0 || request.shader.stage != ShaderRecompiler::ShaderStage::Compute) throw std::runtime_error("AGC driver: unregistered program is not a compute shader"); - APS5_LOG_ERR("Compute shader 0x%llx was not registered; preparing its artifact at dispatch", static_cast(snapshot.codeAddress)); + if (request.shader.stage == ShaderRecompiler::ShaderStage::Compute) { + if (snapshot.type != 0) throw std::runtime_error("AGC driver: unregistered program is not a compute shader"); + APS5_LOG_ERR("Compute shader 0x%llx was not registered; preparing its artifact at dispatch", static_cast(snapshot.codeAddress)); + return; + } + APS5_LOG_ERR("Graphics shader 0x%llx was not registered; preparing its artifact at first use", static_cast(snapshot.codeAddress)); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp new file mode 100644 index 000000000..c51f19029 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp @@ -0,0 +1,95 @@ +#include "VulkanTestDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp" +#include "SceShaders.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" +#include "Optimization/ResourceProgram.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +alignas(256) constexpr std::array VertexCode{0xbf810000u}; +alignas(256) constexpr std::array PixelCode{0xc8020002u, 0xc8060102u, 0xc80a0202u, 0xc80e0302u, 0xf800180fu, 0x03020100u, 0xbf810000u}; + +void Bind(AgcDriver::QueueState& queue, const void* code, std::uint32_t program, std::uint32_t resources) { + const auto address = reinterpret_cast(code); + queue.shader[program] = static_cast(address >> 8u); + queue.shader[program + 1u] = static_cast(address >> 40u); + queue.shader[resources] = 16u << 1u; +} + +void Check(AgcDriver::VulkanDevice& device) { + using namespace AgcDriver::DriverDetail; + using namespace ShaderRecompiler; + AgcDriver::QueueState queue{}; + queue.context[0x8e] = 0xfu; + queue.context[0x8f] = 0xfu; + Bind(queue, VertexCode.data(), 0xc8u, 0x8bu); + Bind(queue, PixelCode.data(), 0x8u, 0xbu); + DrawDecode draw{}; + draw.state.stages.path = AgcDriver::Graphics::ShaderPath::Vertex; + draw.state.stages.vertexWaveSize = 32u; + draw.state.stages.fragmentWaveSize = 32u; + draw.pixel.wave32 = true; + draw.pixel.interpolatorCount = 2; + draw.pixel.interpolatorSettings[0] = 0x403u; + draw.pixel.interpolatorSettings[1] = 0x220u; + draw.pixel.targetOutputMode[0] = 9; + draw.pixel.targetExportMapping.fill(0xe4u); + const ShaderRegistry registry; + DecodeGraphicsPrograms(draw, queue, registry, false, true); + Require(draw.programs.size() == 2 && draw.roles.back() == ProgramRole::Fragment, "unregistered graphics programs did not decode as a vertex and a fragment stage"); + for (const auto& program : draw.programs) Require(program.snapshot != nullptr && program.snapshot->header.empty() && program.snapshot->prepared->entries.empty(), "an unregistered graphics program was not read as raw code"); + const auto target = device.Target(); + std::vector linked; + std::vector memory; + for (std::size_t index = 0; index < draw.programs.size(); ++index) { + const auto& program = draw.programs[index]; + linked.push_back({draw.roles[index], program.binary, program.userDataBase, program.firstUserSgpr, program.userData}); + memory.insert(memory.end(), program.memory.begin(), program.memory.end()); + } + std::array variants{}; + for (std::size_t index = 0; index < draw.programs.size(); ++index) { + const auto& program = draw.programs[index]; + const bool pixel = program.binary.stage == ShaderStage::Fragment; + std::optional vertex; + if (!pixel) vertex = AgcDriver::Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true); + RecompileRequest request{program.binary, {32u, program.firstUserSgpr, program.userData, {}, pixel ? std::optional(draw.pixel) : std::nullopt, vertex, memory}, target, {0, 0, 0, 128u}, GraphicsCompileContext{program.firstUserSgpr, linked, std::nullopt, std::nullopt, {0, 3, 4, 1}}}; + static_cast(InvocationFor(*program.snapshot, program.codeOffset, request)); + Require(program.snapshot->prepared->entries.size() == 1, "an unregistered graphics program was not prepared at first use"); + variants[index] = GetPreparedArtifact(*program.snapshot->prepared->entries.front().handle).variantId; + static_cast(InvocationFor(*program.snapshot, program.codeOffset, request)); + Require(program.snapshot->prepared->entries.size() == 1, "a prepared unregistered graphics program was prepared again"); + request.layout.pushConstantOffsetBytes = 4; + request.layout.pushConstantSizeBytes -= 4; + static_cast(InvocationFor(*program.snapshot, program.codeOffset, request)); + Require(program.snapshot->prepared->entries.size() == 1, "a relocated push-constant block prepared an unregistered graphics program again"); + } + ResolvePreparedGraphics(*draw.programs[0].snapshot, draw.programs[1].snapshot, 17, target); + const auto rectangle = PreparedRectangle(*draw.programs[0].snapshot, variants[0], variants[1]); + Require(!rectangle.control.spirv.empty() && !rectangle.evaluation.spirv.empty(), "unregistered graphics programs did not prepare rectangle shaders"); +} + +} + +int main() { + try { + auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Check(*device); + std::cout << "prepared unregistered graphics tests passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} From d1d1681bae0a1b97e5c7f88e17824a60b9d0a73e Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Thu, 8 Oct 2026 16:04:29 -0500 Subject: [PATCH 4/5] fix(agc): drop a cached draw whose unregistered program was rewritten The register draw key identifies an unregistered program only by its address, and a key hit reuses the entry's decode without reading the program again. A different program uploaded to the same address with the same registers would run the old compiled shader. A hit now checks the decode's unregistered programs against guest memory and drops the entry when one changed. Co-Authored-By: Claude Opus 5.5 --- .../Execution/include/Driver/Draw/DrawCache.hpp | 2 ++ .../Execution/include/Driver/Driver.hpp | 1 + .../Execution/src/Driver/Draw/Draw.cpp | 7 +++++++ .../Execution/src/Driver/Draw/DrawCache.cpp | 17 +++++++++++++++++ core/libs/prx/libSceAgcDriver/tests/Submit.cpp | 13 +++++++++++++ 5 files changed, 40 insertions(+) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp index 16d7a286e..06d954d3b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -81,6 +81,8 @@ struct StageCapture { std::uint32_t pushOffset = 0; }; +bool DecodeProgramsCurrent(const DrawDecode& decode, const ShaderRegistry& registry); + } #endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index becdafd93..cc387b5e7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -114,6 +114,7 @@ private: static std::size_t drawCacheEntries(); void accountDrawVariant(const DispatchVariant& variant, bool added); void insertDrawEntry(std::uint64_t key, std::vector>& fresh, std::shared_ptr decode); + void eraseDrawEntry(std::uint64_t key, const std::shared_ptr& expected); std::shared_ptr findDrawRecipe(std::uint64_t key, const std::vector>& stages); void attachDrawRecipe(std::uint64_t key, const std::vector>& stages, std::shared_ptr recipe); void reportDrawCache(DrawEntryCounters& counters); 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 26bc065d8..fe033b378 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -81,6 +81,13 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe ++drawEntryCounters.absent; } } + if (decode != nullptr && !DecodeProgramsCurrent(*decode, *submission.shaders)) { + std::lock_guard cacheLock(drawCacheMutex); + eraseDrawEntry(drawKey, entry); + ++drawEntryCounters.absent; + entry = nullptr; + decode = nullptr; + } phaseTiming.Phase(DrawRowKeyLookupValidate); resolveDrawDecode(queue, submission, decode, registerKey, drawKey, profile); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp index bcd1a44fa..5287b7126 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp" +#include #include namespace AgcDriver::DriverDetail { @@ -107,6 +108,22 @@ void Driver::insertDrawEntry(std::uint64_t key, std::vector& expected) { + const auto found = drawCache.find(key); + if (found == drawCache.end() || found->second != expected) return; + for (const auto& variants : found->second->stages) { + for (const auto& variant : variants) accountDrawVariant(*variant, false); + } + drawOrder.erase(found->second->order); + drawCache.erase(found); +} + +bool DecodeProgramsCurrent(const DrawDecode& decode, const ShaderRegistry& registry) { + return std::all_of(decode.programs.begin(), decode.programs.end(), [&](const DrawProgram& program) { + return !program.snapshot->header.empty() || ProgramSnapshot(registry, program.snapshot->codeAddress + program.codeOffset * sizeof(std::uint32_t)) == program.snapshot; + }); +} + std::shared_ptr Driver::findDrawRecipe(std::uint64_t key, const std::vector>& stages) { std::shared_ptr> records; { diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 4671c0087..555de2555 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "execution/VulkanTestDevice.hpp" #include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libc/include/GuestHeap.hpp" @@ -586,6 +587,18 @@ void testProgramSnapshots() { check(unregistered != entry && unregistered->codeAddress == rawAddress && unregistered->header.empty() && unregistered->code.size() == 2, "an unregistered program was not read as raw code"); check(AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress) == unregistered, "an empty registry did not fall back to raw code"); check(!expectFailure([] { static_cast(AgcDriver::DriverDetail::ProgramSnapshot({}, 0)); }).empty(), "an unmapped unregistered program was accepted"); + AgcDriver::DriverDetail::DrawDecode decode{}; + decode.programs.resize(2); + decode.programs[0].snapshot = unregistered; + decode.programs[1].snapshot = entry; + decode.programs[1].codeOffset = 8; + check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "an unchanged decode was not current"); + registered[8] = 0xbf810000; + check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a rewritten registered program made a decode stale"); + raw[0] = 0xbf810000; + check(!AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a rewritten unregistered program left its decode current"); + decode.programs[0].snapshot = AgcDriver::DriverDetail::ProgramSnapshot(shaders, rawAddress); + check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a decode with the rewritten program was not current"); } void testWorkerFailure() { From 39fdc48b5f2ec587f11f706371ea3330ba1bbd37 Mon Sep 17 00:00:00 2001 From: Scott Schuster Date: Sat, 10 Oct 2026 19:12:14 -0500 Subject: [PATCH 5/5] fix(agc): read unregistered graphics programs only while no shader is registered An unregistered graphics program is read raw only while the shader registry is empty; with shaders registered it throws, as before. A draw that uses a raw program is noted, and RegisterShader throws once that has happened, since a title that registers shaders after drawing unregistered programs is not understood. The draw cache revalidates a raw program by reading it raw. AI-assisted (Claude Code). Co-Authored-By: Claude Opus 5.5 --- .../include/Driver/Shaders/ShaderRegistry.hpp | 2 ++ .../Execution/src/Driver/Draw/DrawCache.cpp | 4 ++-- .../Execution/src/Driver/Draw/DrawDecode.cpp | 1 + .../src/Driver/Shaders/ShaderRegistry.cpp | 15 +++++++++++++++ core/libs/prx/libSceAgcDriver/tests/Submit.cpp | 17 +++++++++-------- .../tests/execution/PreparedGraphics.cpp | 1 + .../execution/PreparedUnregisteredGraphics.cpp | 9 +++++++++ 7 files changed, 39 insertions(+), 10 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 3ddcc2398..297686f5e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -86,6 +86,8 @@ struct ShaderSnapshot { std::shared_ptr ReadRawShader(std::uint64_t address); std::shared_ptr ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list types); +void NoteRawGraphicsDraw(); +bool RawGraphicsDrawn(); std::shared_ptr PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp index 5287b7126..7844679fd 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp @@ -118,9 +118,9 @@ void Driver::eraseDrawEntry(std::uint64_t key, const std::shared_ptr& drawCache.erase(found); } -bool DecodeProgramsCurrent(const DrawDecode& decode, const ShaderRegistry& registry) { +bool DecodeProgramsCurrent(const DrawDecode& decode, const ShaderRegistry&) { return std::all_of(decode.programs.begin(), decode.programs.end(), [&](const DrawProgram& program) { - return !program.snapshot->header.empty() || ProgramSnapshot(registry, program.snapshot->codeAddress + program.codeOffset * sizeof(std::uint32_t)) == program.snapshot; + return !program.snapshot->header.empty() || ReadRawShader(program.snapshot->codeAddress + program.codeOffset * sizeof(std::uint32_t)) == program.snapshot; }); } 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 60d97fb2f..be0f1794d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -32,6 +32,7 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const 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 && snapshot.header.empty()) NoteRawGraphicsDraw(); 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); 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 8e6e210c8..75fb76947 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -11,6 +11,7 @@ #include "CompiledVariant.hpp" #include "Optimization/ResourceProgram.hpp" #include +#include #include #include #include @@ -105,9 +106,22 @@ std::shared_ptr ReadRawShader(std::uint64_t address) { std::shared_ptr ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list types) { if (auto registered = RegisteredProgram(shaders, address, types)) return registered; + require(shaders.empty(), "graphics program does not belong to a compatible registered shader"); return ReadRawShader(address); } +namespace { +std::atomic rawGraphicsDrawn{false}; +} + +void NoteRawGraphicsDraw() { + rawGraphicsDrawn.store(true, std::memory_order_relaxed); +} + +bool RawGraphicsDrawn() { + return rawGraphicsDrawn.load(std::memory_order_relaxed); +} + namespace { void AwaitRegisteredPreparation(PreparedShaders& prepared, std::unique_lock& lock) { @@ -965,6 +979,7 @@ void Driver::RegisterShader(const Shader* shader) { PerformanceTimer timing("Shader.Register"); ShaderPreparationTransaction transaction; CheckFailure(); + require(!RawGraphicsDrawn(), "a shader was registered after graphics programs were drawn without registration"); GuestMemory::CheckRange(shader, sizeof(Shader), 1); Shader fields; std::memcpy(&fields, static_cast(shader), sizeof(Shader)); diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 555de2555..4137e06b3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -580,13 +580,14 @@ void testProgramSnapshots() { snapshot.code.assign(registered.begin(), registered.end()); snapshot.header.resize(sizeof(Shader)); const auto entry = std::make_shared(std::move(snapshot)); - shaders.emplace(registeredAddress, entry); - check(AgcDriver::DriverDetail::ProgramSnapshot(shaders, registeredAddress) == entry, "a registered program was not resolved to its registration"); - check(AgcDriver::DriverDetail::ProgramSnapshot(shaders, registeredAddress + 4 * 8) == entry, "an entry inside registered code was not resolved to its registration"); - const auto unregistered = AgcDriver::DriverDetail::ProgramSnapshot(shaders, rawAddress); - check(unregistered != entry && unregistered->codeAddress == rawAddress && unregistered->header.empty() && unregistered->code.size() == 2, "an unregistered program was not read as raw code"); - check(AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress) == unregistered, "an empty registry did not fall back to raw code"); - check(!expectFailure([] { static_cast(AgcDriver::DriverDetail::ProgramSnapshot({}, 0)); }).empty(), "an unmapped unregistered program was accepted"); + shaders[registeredAddress] = {entry}; + check(AgcDriver::DriverDetail::ProgramSnapshot(shaders, registeredAddress, {2}) == entry, "a registered program was not resolved to its registration"); + check(AgcDriver::DriverDetail::ProgramSnapshot(shaders, registeredAddress + 4 * 8, {2}) == entry, "an entry inside registered code was not resolved to its registration"); + check(!expectFailure([&] { static_cast(AgcDriver::DriverDetail::ProgramSnapshot(shaders, rawAddress, {2})); }).empty(), "an unregistered program was read raw while shaders are registered"); + const auto unregistered = AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress, {2}); + check(unregistered != entry && unregistered->codeAddress == rawAddress && unregistered->header.empty() && unregistered->code.size() == 2, "an empty registry did not read the program as raw code"); + check(AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress, {2}) == unregistered, "unchanged raw code lost its snapshot identity"); + check(!expectFailure([] { static_cast(AgcDriver::DriverDetail::ProgramSnapshot({}, 0, {2})); }).empty(), "an unmapped unregistered program was accepted"); AgcDriver::DriverDetail::DrawDecode decode{}; decode.programs.resize(2); decode.programs[0].snapshot = unregistered; @@ -597,7 +598,7 @@ void testProgramSnapshots() { check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a rewritten registered program made a decode stale"); raw[0] = 0xbf810000; check(!AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a rewritten unregistered program left its decode current"); - decode.programs[0].snapshot = AgcDriver::DriverDetail::ProgramSnapshot(shaders, rawAddress); + decode.programs[0].snapshot = AgcDriver::DriverDetail::ProgramSnapshot({}, rawAddress, {2}); check(AgcDriver::DriverDetail::DecodeProgramsCurrent(decode, shaders), "a decode with the rewritten program was not current"); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp index 9f9a0ef90..8a6ac8b97 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp @@ -244,6 +244,7 @@ void Check(AgcDriver::VulkanDevice& device, AgcDriver::Graphics::ShaderPath path Require(program.snapshot->prepared->entries.size() == predicted + 1, "an artifact prepared at a draw was not reused"); } ShaderRegistry invalidRegistry; + invalidRegistry[1] = {std::make_shared(ShaderSnapshot{1, 0, 2, {}, {}})}; DrawDecode invalid{}; invalid.state = prepared.state; Reject([&] { DecodeGraphicsPrograms(invalid, queue, invalidRegistry, true, true); }, "registered"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp index c51f19029..191546739 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp @@ -2,6 +2,7 @@ #include "prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp" #include "SceShaders.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "Optimization/ResourceProgram.hpp" #include @@ -49,6 +50,14 @@ void Check(AgcDriver::VulkanDevice& device) { DecodeGraphicsPrograms(draw, queue, registry, false, true); Require(draw.programs.size() == 2 && draw.roles.back() == ProgramRole::Fragment, "unregistered graphics programs did not decode as a vertex and a fragment stage"); for (const auto& program : draw.programs) Require(program.snapshot != nullptr && program.snapshot->header.empty() && program.snapshot->prepared->entries.empty(), "an unregistered graphics program was not read as raw code"); + Require(RawGraphicsDrawn(), "a draw of unregistered graphics programs was not noted"); + bool refused = false; + try { + Driver::Get().RegisterShader(nullptr); + } catch (const std::exception& error) { + refused = std::string(error.what()).find("registered after graphics programs were drawn") != std::string::npos; + } + Require(refused, "a shader registration after unregistered graphics draws was accepted"); const auto target = device.Target(); std::vector linked; std::vector memory;