feat(agc): accept graphics programs without an AGC shader header

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Scott Schuster 2026-10-08 10:25:45 -05:00
parent e28fbc277d
commit 767b8f83a2
7 changed files with 36 additions and 28 deletions

View File

@ -84,7 +84,8 @@ struct ShaderSnapshot {
};
std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address);
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);
std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr);

View File

@ -17,7 +17,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
PerformanceTimer timing("Driver.Dispatch");
const auto address = (static_cast<std::uint64_t>(readRegister(queue.shader, 0x20c)) << 8u) | (static_cast<std::uint64_t>(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;

View File

@ -123,7 +123,9 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
std::vector<ShaderRecompiler::LinkedProgram> 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");

View File

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

View File

@ -37,7 +37,7 @@ std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnosti
}
}
std::shared_ptr<const ShaderSnapshot> ReadRawComputeShader(std::uint64_t address) {
std::shared_ptr<const ShaderSnapshot> ReadRawShader(std::uint64_t address) {
GuestMemory::CheckRange(reinterpret_cast<const void*>(address), sizeof(std::uint32_t), 256);
static std::mutex cacheMutex;
static std::list<std::shared_ptr<const ShaderSnapshot>> cache;
@ -100,7 +100,12 @@ std::shared_ptr<const ShaderSnapshot> 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<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;
return ReadRawShader(address);
}
namespace {

View File

@ -163,6 +163,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
}
ShaderRecompiler::ShaderVertexStageInfo DecodeVertexStageInfo(std::span<const std::byte> header, std::uint64_t headerAddress, std::span<const std::uint32_t> userData, std::vector<DecodeRead>* 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));

View File

@ -593,20 +593,20 @@ int main() {
rawCode[1] = 0xbf810000;
rawCode[2] = 0xbf810000;
const auto rawAddress = reinterpret_cast<std::uintptr_t>(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<std::shared_ptr<const AgcDriver::DriverDetail::ShaderSnapshot>, 8> concurrent;
std::vector<std::thread> 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<std::uintptr_t>(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<std::uintptr_t>(programs[i].data()));
check(AgcDriver::DriverDetail::ReadRawComputeShader(firstAddress) != evicted && evicted->code[0] == 0xbf810000,
AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast<std::uintptr_t>(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<std::uint32_t, 128> straddling{};
straddling.fill(0xbf800000);
straddling[63] = 0xf4000000;
straddling[64] = 0xfa000000;
straddling[65] = 0xbf810000;
const auto straddled = AgcDriver::DriverDetail::ReadRawComputeShader(reinterpret_cast<std::uintptr_t>(straddling.data()));
const auto straddled = AgcDriver::DriverDetail::ReadRawShader(reinterpret_cast<std::uintptr_t>(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<std::uint32_t*>(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<std::uintptr_t>(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