fix(agc): select the registered back half for a fused hull header
sceAgcFuseShaderHalves returns a fused hull as a copy of its back half (type 7) that is not registered: type 3, no user data, and shader registers copied to scratch memory with the checksum, RSRC1/RSRC2 and the LS program address rewritten. sceAgcCreatePrimState accepts only that type-3 header, and graphics ABI resolution refused it as a replaced shader header. SameHeader now also takes a type-3 header as the registered type-7 header for the same code when every other field is equal and its shader registers differ only in the fused ones. Graphics ABI resolution then applies the bound header's shader registers over the registered state. PPSA26344 fuses its hull halves during its loading screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
02030f8a62
commit
687f73c951
|
|
@ -18,6 +18,8 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Shader;
|
||||
|
||||
namespace ShaderRecompiler {
|
||||
class ShaderPreparationContext;
|
||||
}
|
||||
|
|
@ -96,6 +98,7 @@ std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnosti
|
|||
std::uint64_t NullPixelProgramAddress();
|
||||
ShaderSnapshot PrepareNullPixelProgram(const VulkanDevice& device);
|
||||
std::optional<ShaderRecompiler::ShaderFloatMode> RegisteredFloatMode(const ShaderSnapshot& snapshot);
|
||||
std::shared_ptr<const ShaderSnapshot> RegisteredHeader(const ShaderRegistry& registry, const Shader* shader);
|
||||
std::shared_ptr<const ShaderSnapshot> RegisteredProgram(const ShaderRegistry& registry, std::uint64_t address, std::initializer_list<std::uint8_t> types);
|
||||
void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const std::shared_ptr<const ShaderSnapshot>& snapshot);
|
||||
void SubmitRegistrationPreparation(std::function<void()> task);
|
||||
|
|
|
|||
|
|
@ -230,6 +230,27 @@ void ShaderPreparationTransaction::Commit() {
|
|||
committed = true;
|
||||
}
|
||||
|
||||
bool FusedHullHeader(const ShaderSnapshot& snapshot, Shader used, const Shader& registered) {
|
||||
if (used.type != 3u || registered.type != 7u) return false;
|
||||
const auto* bound = used.sh_registers;
|
||||
used.type = registered.type;
|
||||
used.sh_registers = registered.sh_registers;
|
||||
if (std::memcmp(&used, ®istered, offsetof(Shader, num_sh_registers) + sizeof(Shader::num_sh_registers)) != 0) return false;
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(registered.sh_registers);
|
||||
const std::size_t bytes = registered.num_sh_registers * sizeof(ShaderRegister);
|
||||
if (bytes == 0 || bound == nullptr || address < snapshot.headerAddress || address - snapshot.headerAddress > snapshot.header.size() || snapshot.header.size() - (address - snapshot.headerAddress) < bytes) return false;
|
||||
GuestMemory::CheckRange(bound, bytes, alignof(ShaderRegister));
|
||||
for (std::size_t index = 0; index < registered.num_sh_registers; ++index) {
|
||||
ShaderRegister expected;
|
||||
std::memcpy(&expected, snapshot.header.data() + (address - snapshot.headerAddress) + index * sizeof(ShaderRegister), sizeof(ShaderRegister));
|
||||
const auto reg = bound[index];
|
||||
if (reg.offset != expected.offset) return false;
|
||||
const bool fused = reg.offset == 0x100u || reg.offset == 0x10au || reg.offset == 0x10bu || reg.offset == 0x148u || reg.offset == 0x149u;
|
||||
if (!fused && reg.value != expected.value) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SameHeader(const ShaderSnapshot& snapshot, const Shader* shader) {
|
||||
if (snapshot.headerAddress == reinterpret_cast<std::uintptr_t>(shader)) return true;
|
||||
if (snapshot.header.size() < sizeof(Shader)) return false;
|
||||
|
|
@ -239,7 +260,7 @@ bool SameHeader(const ShaderSnapshot& snapshot, const Shader* shader) {
|
|||
std::memcpy(&used, static_cast<const void*>(shader), sizeof(Shader));
|
||||
std::memcpy(®istered, snapshot.header.data(), sizeof(Shader));
|
||||
used.user_data = registered.user_data;
|
||||
return std::memcmp(&used, ®istered, offsetof(Shader, num_sh_registers) + sizeof(Shader::num_sh_registers)) == 0;
|
||||
return std::memcmp(&used, ®istered, offsetof(Shader, num_sh_registers) + sizeof(Shader::num_sh_registers)) == 0 || FusedHullHeader(snapshot, used, registered);
|
||||
}
|
||||
|
||||
std::shared_ptr<const ShaderSnapshot> RegisteredHeader(const ShaderRegistry& registry, const Shader* shader) {
|
||||
|
|
@ -836,6 +857,11 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
|
|||
registered.shader.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.shader.insert_or_assign(offset, value); });
|
||||
registered.context.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.context.insert_or_assign(offset, value); });
|
||||
registered.userConfig.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.userConfig.insert_or_assign(offset, value); });
|
||||
if (shader->type != snapshot.type) {
|
||||
const std::size_t count = shader->num_sh_registers;
|
||||
GuestMemory::CheckRange(shader->sh_registers, count * sizeof(ShaderRegister), alignof(ShaderRegister));
|
||||
for (std::size_t index = 0; index < count; ++index) state.shader.insert_or_assign(shader->sh_registers[index].offset, shader->sh_registers[index].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto reg : context) state.context.insert_or_assign(reg.offset, reg.value);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/ShaderPreparation.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "Optimization/ResourceProgram.hpp"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <barrier>
|
||||
#include <chrono>
|
||||
|
|
@ -147,6 +148,54 @@ void HeaderSelection() {
|
|||
Reject([&] { static_cast<void>(RegisteredProgram(*registry, 0x10000, {2, 4})); });
|
||||
}
|
||||
|
||||
void FusedHullHeaderSelection() {
|
||||
struct Hull {
|
||||
Shader shader{};
|
||||
std::array<ShaderRegister, 7> registers{{{0x100, 0x11}, {0x108, 0x100}, {0x109, 0}, {0x10a, 0x2}, {0x10b, 0x10}, {0x148, 0}, {0x149, 0}}};
|
||||
ShaderSpecialRegs specials{};
|
||||
} back;
|
||||
back.shader.file_header = 0x34333231u;
|
||||
back.shader.version = 0x18;
|
||||
back.shader.header_size = sizeof(Hull);
|
||||
back.shader.shader_size = 4;
|
||||
back.shader.code = reinterpret_cast<const void*>(std::uintptr_t{0x50000});
|
||||
back.shader.type = 7;
|
||||
back.shader.sh_registers = back.registers.data();
|
||||
back.shader.num_sh_registers = back.registers.size();
|
||||
back.shader.specials = &back.specials;
|
||||
auto snapshot = std::make_shared<ShaderSnapshot>();
|
||||
snapshot->codeAddress = 0x50000;
|
||||
snapshot->headerAddress = reinterpret_cast<std::uintptr_t>(&back);
|
||||
snapshot->type = 7;
|
||||
snapshot->code = {0xbf810000u};
|
||||
snapshot->header.resize(sizeof(Hull));
|
||||
std::memcpy(snapshot->header.data(), &back, sizeof(Hull));
|
||||
std::shared_ptr<ShaderRegistry> registry;
|
||||
PublishRegisteredShader(registry, snapshot);
|
||||
auto registers = back.registers;
|
||||
registers[0].value = 0x22;
|
||||
registers[3].value = 0x3;
|
||||
registers[4].value = 0x1e;
|
||||
registers[5].value = 0x600;
|
||||
registers[6].value = 0x1;
|
||||
Shader fused = back.shader;
|
||||
fused.type = 3;
|
||||
fused.sh_registers = registers.data();
|
||||
fused.user_data = nullptr;
|
||||
Require(RegisteredHeader(*registry, &fused) == snapshot, "a fused hull header did not select its registered back half");
|
||||
registers[1].value = 0x200;
|
||||
Require(RegisteredHeader(*registry, &fused) == nullptr, "a fused hull header with another hull program selected the back half");
|
||||
registers[1].value = 0x100;
|
||||
registers[2].offset = 0x10c;
|
||||
Require(RegisteredHeader(*registry, &fused) == nullptr, "a fused hull header with other registers selected the back half");
|
||||
registers[2].offset = 0x109;
|
||||
fused.target ^= 1u;
|
||||
Require(RegisteredHeader(*registry, &fused) == nullptr, "a fused hull header with another target selected the back half");
|
||||
fused.target ^= 1u;
|
||||
fused.type = 2;
|
||||
Require(RegisteredHeader(*registry, &fused) == nullptr, "a vertex header selected a registered hull back half");
|
||||
}
|
||||
|
||||
void Registration(AgcDriver::VulkanDevice& device) {
|
||||
const std::array<std::uint32_t, 1> code{0xbf810000u};
|
||||
RecompileRequest request{{ShaderStage::Compute, 0x10000, code, 0, {}}, {32, 0, {}, ShaderComputeStageInfo{{1, 1, 1}, 0, {false, false, false}, false, 0, {}}, {}, {}, {}}, device.ComputeTarget(32), {0, 0, 0, 128}};
|
||||
|
|
@ -234,6 +283,7 @@ int main() {
|
|||
try {
|
||||
Transactions();
|
||||
HeaderSelection();
|
||||
FusedHullHeaderSelection();
|
||||
auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Registration(*device);
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ flowchart LR
|
|||
- `ShaderRecompiler::Recompile` keeps compiled variants in memory, and `ShaderDiskCache` stores them on disk so later runs reuse them.
|
||||
- A compute shader whose LDS plus its lock dword exceeds the host's `maxComputeSharedMemorySize` is not given a `Workgroup` array. `Recompile` sets `ShaderInfo::sharedMemoryBytes` for it and the SPIR-V backend addresses the LDS in a coherent storage buffer at descriptor set 1 (`WorkgroupMemoryDescriptorSet`), with a slice of `RecompileResult::workgroupMemoryDwords` per workgroup, indexed by `WorkgroupID` and `NumWorkGroups`. `VulkanDevice` keeps one such buffer, sized from the dispatch dimensions, and binds it after the resource set; programs that fit keep using shared memory.
|
||||
- `Driver::RegisterShader` splits shader preparation in two. `PlanRegistered` runs on the registering guest thread: it validates the registers and entry point, decodes the code and builds the recompile request, so invalid registrations still fail there. The RDNA to IR translation and resource plan (`PrepareShader`) run on a background pool of `hardware_concurrency() / 2` detached threads (at least 2, 64 MiB stacks).
|
||||
- The shader registry maps a code address to every header registered for that code, in registration order. Static, graphics and rectangle ABI resolution pick the header at the bound address (or one equal to it, `SameHeader`); draws and dispatches pick the program by the stage's binary type (`RegisteredProgram`), and a front entry matching both type 2 and type 4 throws. Within a type the last registration wins; re-registering an identical earlier header makes its snapshot current again without preparing it again, and changed code drops the older headers.
|
||||
- The shader registry maps a code address to every header registered for that code, in registration order. Static, graphics and rectangle ABI resolution pick the header at the bound address (or one equal to it, `SameHeader`, which also takes a hull header fused by `sceAgcFuseShaderHalves` (type 3) as the registered back half (type 7) it copies, the fused registers aside; graphics ABI resolution then uses the fused header's shader registers); draws and dispatches pick the program by the stage's binary type (`RegisteredProgram`), and a front entry matching both type 2 and type 4 throws. Within a type the last registration wins; re-registering an identical earlier header makes its snapshot current again without preparing it again, and changed code drops the older headers.
|
||||
- The snapshot is published as pending. `SourceHandleFor`, `InvocationFor` and `ShaderPreparationTransaction::Edit` / `Read` wait for the pending job before reading `PreparedShaders`, and rethrow its failure. `APS5_SYNC_SHADER_PREPARE=1` prepares on the registering thread instead.
|
||||
- A draw or dispatch whose state matches no artifact prepared at registration (a title that builds its static ABI state itself, without `sceAgcDriverResolve*Abi`) gets one prepared at use by `SourceHandleFor` or `InvocationFor`, logged once per artifact and kept in the snapshot for the next use.
|
||||
- A compute shader whose `s_swappc_b64` calls a user-data pointer cannot be linked at registration, because the callee's address is only known at dispatch. `InvocationFor` in [`ShaderRegistry.cpp`](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) reads each callee from guest memory, appends one copy per call after the caller (`LinkUserDataCalls`) and prepares that linked code, so a different callee gets its own prepared entry.
|
||||
|
|
|
|||
Loading…
Reference in New Issue