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 ffec875b5..7ccb5d690 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -18,6 +18,8 @@ #include #include +struct Shader; + namespace ShaderRecompiler { class ShaderPreparationContext; } @@ -96,6 +98,7 @@ std::shared_ptr PrepareShaderWithDiagnosti std::uint64_t NullPixelProgramAddress(); ShaderSnapshot PrepareNullPixelProgram(const VulkanDevice& device); std::optional RegisteredFloatMode(const ShaderSnapshot& snapshot); +std::shared_ptr RegisteredHeader(const ShaderRegistry& registry, const Shader* shader); std::shared_ptr RegisteredProgram(const ShaderRegistry& registry, std::uint64_t address, std::initializer_list types); void PublishRegisteredShader(std::shared_ptr& registry, const std::shared_ptr& snapshot); void SubmitRegistrationPreparation(std::function task); 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 9a42f6534..9b46369ce 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -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(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(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(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 RegisteredHeader(const ShaderRegistry& registry, const Shader* shader) { @@ -836,6 +857,11 @@ void Driver::ResolveGraphicsStagesAbi(std::span 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); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp index 5c5260c34..b6abe53ff 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp @@ -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 #include #include #include @@ -147,6 +148,54 @@ void HeaderSelection() { Reject([&] { static_cast(RegisteredProgram(*registry, 0x10000, {2, 4})); }); } +void FusedHullHeaderSelection() { + struct Hull { + Shader shader{}; + std::array 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(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(); + snapshot->codeAddress = 0x50000; + snapshot->headerAddress = reinterpret_cast(&back); + snapshot->type = 7; + snapshot->code = {0xbf810000u}; + snapshot->header.resize(sizeof(Hull)); + std::memcpy(snapshot->header.data(), &back, sizeof(Hull)); + std::shared_ptr 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 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); diff --git a/docs/dev/ARCHITECTURE.md b/docs/dev/ARCHITECTURE.md index 37557e4a0..0f72550b6 100644 --- a/docs/dev/ARCHITECTURE.md +++ b/docs/dev/ARCHITECTURE.md @@ -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.