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:
parent
d1d1681bae
commit
39fdc48b5f
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue