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 <noreply@anthropic.com>
This commit is contained in:
Scott Schuster 2026-10-10 19:12:14 -05:00
parent d1d1681bae
commit 39fdc48b5f
7 changed files with 39 additions and 10 deletions

View File

@ -86,6 +86,8 @@ struct ShaderSnapshot {
std::shared_ptr<const ShaderSnapshot> ReadRawShader(std::uint64_t address);
std::shared_ptr<const ShaderSnapshot> ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list<std::uint8_t> types);
void NoteRawGraphicsDraw();
bool RawGraphicsDrawn();
std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr);

View File

@ -118,9 +118,9 @@ void Driver::eraseDrawEntry(std::uint64_t key, const std::shared_ptr<DrawEntry>&
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;
});
}

View File

@ -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);

View File

@ -11,6 +11,7 @@
#include "CompiledVariant.hpp"
#include "Optimization/ResourceProgram.hpp"
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <cstdio>
#include <cstddef>
@ -105,9 +106,22 @@ std::shared_ptr<const ShaderSnapshot> ReadRawShader(std::uint64_t address) {
std::shared_ptr<const ShaderSnapshot> ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list<std::uint8_t> 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<bool> 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<std::mutex>& 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<const void*>(shader), sizeof(Shader));

View File

@ -580,13 +580,14 @@ void testProgramSnapshots() {
snapshot.code.assign(registered.begin(), registered.end());
snapshot.header.resize(sizeof(Shader));
const auto entry = std::make_shared<const AgcDriver::DriverDetail::ShaderSnapshot>(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<void>(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<void>(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<void>(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");
}

View File

@ -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<const ShaderSnapshot>(ShaderSnapshot{1, 0, 2, {}, {}})};
DrawDecode invalid{};
invalid.state = prepared.state;
Reject([&] { DecodeGraphicsPrograms(invalid, queue, invalidRegistry, true, true); }, "registered");

View File

@ -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 <array>
@ -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<LinkedProgram> linked;
std::vector<MemoryRegion> memory;