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 eaaf07633..61cd0ee94 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -299,6 +299,13 @@ bool FusedHullHeader(const ShaderSnapshot& snapshot, Shader used, const Shader& return true; } +static Shader GuestShaderFields(const Shader* shader) { + GuestMemory::CheckRange(shader, sizeof(Shader), 1); + Shader fields; + std::memcpy(&fields, static_cast(shader), sizeof(Shader)); + return fields; +} + bool SameHeader(const ShaderSnapshot& snapshot, const Shader* shader) { if (snapshot.headerAddress == reinterpret_cast(shader)) return true; if (snapshot.header.size() < sizeof(Shader)) return false; @@ -312,7 +319,7 @@ bool SameHeader(const ShaderSnapshot& snapshot, const Shader* shader) { } std::shared_ptr RegisteredHeader(const ShaderRegistry& registry, const Shader* shader) { - const auto found = registry.find(reinterpret_cast(const_cast(shader->code))); + const auto found = registry.find(reinterpret_cast(const_cast(GuestShaderFields(shader).code))); if (found == registry.end()) return {}; for (const auto& snapshot : found->second) { if (snapshot->headerAddress == reinterpret_cast(shader)) return snapshot; @@ -950,8 +957,8 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std std::lock_guard lock(mutex); registry = shaders; for (const auto* shader : stages) { - GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader)); - const auto address = reinterpret_cast(const_cast(shader->code)); + const auto fields = GuestShaderFields(shader); + const auto address = reinterpret_cast(const_cast(fields.code)); require(registry != nullptr && registry->contains(address), "graphics ABI refers to an unregistered shader"); const auto selected = RegisteredHeader(*registry, shader); require(selected != nullptr, "graphics ABI refers to a replaced shader header"); @@ -962,10 +969,10 @@ 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); + if (fields.type != snapshot.type) { + const std::size_t count = fields.num_sh_registers; + GuestMemory::CheckRange(fields.sh_registers, count * sizeof(ShaderRegister), alignof(ShaderRegister)); + for (std::size_t index = 0; index < count; ++index) state.shader.insert_or_assign(fields.sh_registers[index].offset, fields.sh_registers[index].value); } } } @@ -1018,8 +1025,8 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::spantype != 0 && shader->type != 1) { + const auto fields = GuestShaderFields(shader); + if (fields.type != 0 && fields.type != 1) { const std::array stages{shader}; ResolveGraphicsStagesAbi(stages, context, primitive); transaction.Commit(); @@ -1028,7 +1035,7 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span snapshot; { std::lock_guard lock(mutex); - const auto address = reinterpret_cast(const_cast(shader->code)); + const auto address = reinterpret_cast(const_cast(fields.code)); require(shaders != nullptr && shaders->contains(address), "static ABI refers to an unregistered shader"); snapshot = RegisteredHeader(*shaders, shader); require(snapshot != nullptr, "static ABI refers to a replaced shader header"); @@ -1095,10 +1102,8 @@ void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std:: std::shared_ptr fragment; { std::lock_guard lock(mutex); - GuestMemory::CheckRange(vertex, sizeof(Shader), alignof(Shader)); - GuestMemory::CheckRange(pixel, sizeof(Shader), alignof(Shader)); const auto lookup = [&](const Shader* shader) { - const auto address = reinterpret_cast(const_cast(shader->code)); + const auto address = reinterpret_cast(const_cast(GuestShaderFields(shader).code)); require(shaders != nullptr && shaders->contains(address), "rectangle ABI refers to an unregistered shader"); const auto snapshot = RegisteredHeader(*shaders, shader); require(snapshot != nullptr, "rectangle ABI refers to a replaced shader header"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index e34258828..d1b66658c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -477,10 +477,12 @@ void Registration(bool indirect) { Shader copy = header.shader; copy.user_data = reinterpret_cast(©); AgcDriverResolveShaderAbi_nid_postfix(©, {}, {}); - alignas(Shader) std::array padded{}; - std::memcpy(padded.data(), ©, sizeof(Shader)); + alignas(Shader) std::array padded{}; + auto* unaligned = padded.data() + 4; + Require(reinterpret_cast(unaligned) % alignof(Shader) != 0, "shader ABI test header is unexpectedly aligned"); + std::memcpy(unaligned, ©, sizeof(Shader)); padded.back() = std::byte{0x7d}; - AgcDriverResolveShaderAbi_nid_postfix(reinterpret_cast(padded.data()), {}, {}); + AgcDriverResolveShaderAbi_nid_postfix(reinterpret_cast(unaligned), {}, {}); copy.target ^= 1u; ExpectFailure([&] { AgcDriverResolveShaderAbi_nid_postfix(©, {}, {}); }, "replaced shader header"); header.registers[1].value |= 0x100u; diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index 5aefe3934..a8909b8d0 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -15,7 +15,7 @@ - [Zero-stride vertex fetch range](../../../core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp) (libSceAgcDriver) - for a stride-0 V# with OOB_SELECT 2 the whole fetch reads memory unless NUM_RECORDS is 0; with OOB_SELECT 3 it reads zero when NUM_RECORDS is 0 and throws when the fetch only partly fits. Rules taken from the GFX10 OOB_SELECT table in Mesa [ac_descriptors.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/ac_descriptors.c), not measured on hardware. Whether OOB_SELECT 3 checks each dword of a multi-dword fetch separately is unknown. OOB_SELECT 0 and 1 keep the structured record checks. - [Resource registration](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - registration always fails with `RESOURCE_REGISTRATION_UNAVAILABLE`, so the owner and resource queries, `sceAgcDriverSetResourceUserData`, `sceAgcDriverFindResourcesPublic` and `sceAgcDriverUnregisterAllResourcesForOwner` return the same error without reading their arguments. Their parameter lists follow the libSceGnmDriver counterparts and are unverified. `sceAgcDriverGetResourceRegistrationMaxNameLength` writes 0xfc and returns 0, as [prosper](https://github.com/mattias800/prosper) does from a title's registration configuration; the `uint32_t*` parameter matches the SDK wrapper `sce::Agc::ResourceRegistration::getMaxNameLength(unsigned int*)`. sharpemu writes 256 and KytyPS5 returns `RESOURCE_REGISTRATION_UNAVAILABLE`, so the value is unverified; a null output throws - [sceAgcDriverRegisterMultipleResources](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) uses the existing unavailable-registration state and leaves caller memory untouched. Its four arguments (uint32 handle output, uint32 owner, descriptor pointer, uint32 count) follow the original PPSA34502 / 01.010.000 codzm call. The caller builds 40-byte descriptors and explicitly accepts 0x8A6C9018 with an invalid output handle. Enabled registration and console argument-validation precedence are unverified. -- [Shader registration](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a shader header is accepted at any address: PPSA26344 passes headers 4 and 1 bytes past an 8-byte boundary; whether the console refuses an alignment is unknown +- [Shader headers](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - registration and ABI resolution read fixed header fields through aligned copies, so the header pointer itself may have any alignment. PPSA26344 passes headers 4 and 1 bytes past an 8-byte boundary, and PPSA01317 passes 4-byte-aligned headers to ABI resolution; console alignment requirements are unknown - [NGG vertex workgroups](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - type-2 PRIMGEN programs using DS instructions or `s_barrier` are prepared with the draw's routing and primitive registers on the existing mesh path. The workgroup requirement follows [Mesa's NGG lowering](https://github.com/mihawk-99/PS5_Mesa/blob/b3588f78dd7fd46fbadc7ecd487e5f14d4213e4b/src/amd/common/nir/ac_nir_lower_ngg.c#L1135); the mesh path supplies LDS and two guest lanes per host invocation for wave64 on a 32-lane host subgroup. Console routing has not been measured. Passthrough and tessellation keep their existing paths; unsupported mesh assembly and subgroup budgets throw. - [Shader header copies](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a header bound for a draw or dispatch at another address than the registered one is accepted when its fixed fields match the registered header except `user_data`; the registered header's metadata is used. PPSA21567 binds such a copy of a vertex shader header with `user_data` set to null. Whether the console reads the copy's `user_data` is unknown - [HtileFillCovers](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a fill is taken to cover a depth surface's whole HTILE when it spans 32 KiB per 1024x512 pixel block from DB_HTILE_DATA_BASE, the pipe-aligned SW_64KB_Z_X metadata extent AMD PAL's gfx10 addrlib gives for GB_ADDR_CONFIG 0x00100044 (1-8 samples, D16/D32); other metadata layouts are not decoded. A partial fill is ignored (the surface keeps its contents); not measured on the console.