diff --git a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp index b3b0da8f4..608c859ad 100644 --- a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp @@ -53,10 +53,21 @@ std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderS : CreateInterpolantValue(psWord, gsSemantic); } -int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue) { +std::uint32_t UpdateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) { + const std::uint32_t f16 = (psWord >> 20u) & 0x3u; + if (f16 != 0 && f16 != InterpolantF16HiOnly && ((psWord >> 28u) & 0x3u) != ((psWord >> 30u) & 0x3u)) { + throw std::runtime_error(std::string(__func__) + ": DEFAULT_VAL of an f16 input with different low and high defaults is unknown"); + } + return CreateInterpolantValueSplitF16(psWord, gsSemantic); +} + +int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue, bool fillIdentityTail) { if (regs == nullptr) { throw std::runtime_error(std::string(fn) + ": regs is null"); } + if (ps == nullptr && !fillIdentityTail) { + throw std::runtime_error(std::string(fn) + ": ps is null"); + } if (ps != nullptr && ps->num_input_semantics > 32) { throw std::runtime_error(std::string(fn) + ": input semantic count exceeds 32"); } @@ -66,13 +77,14 @@ int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* auto* output = regs; regs = values.data(); if (ps == nullptr || ps->num_input_semantics == 0) { - FillIdentityInterpolants(regs, 0); + const std::uint32_t count = fillIdentityTail ? InterpolantRegisterCount : 0; + if (fillIdentityTail) FillIdentityInterpolants(regs, 0); if (ps != nullptr) { - AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, 32}, {}); + AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, count}, {}); if (gs != nullptr) AgcDriverResolveGraphicsAbi_nid_postfix(gs, ps, 0); } transaction.Commit(); - std::copy(values.begin(), values.end(), output); + std::copy_n(values.begin(), count, output); return 0; } @@ -98,11 +110,12 @@ int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* SetInterpolantRegister(regs, i, createValue(ShaderSemanticWord(psSemantic), gsSemantic)); } - FillIdentityInterpolants(regs, ps->num_input_semantics); - AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, 32}, {}); + const std::uint32_t count = fillIdentityTail ? InterpolantRegisterCount : ps->num_input_semantics; + if (fillIdentityTail) FillIdentityInterpolants(regs, ps->num_input_semantics); + AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, count}, {}); AgcDriverResolveGraphicsAbi_nid_postfix(gs, ps, 0); transaction.Commit(); - std::copy(values.begin(), values.end(), output); + std::copy_n(values.begin(), count, output); return 0; } @@ -111,12 +124,17 @@ int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* extern "C" { int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps) { - return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue); + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue, true); } APS5_EXPORT("dbOlWdppb4o", sceAgcUnknownCreateInterpolantMapping); int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) { - return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16); + return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16, true); +} + +APS5_EXPORT("vieBRwlh1Lw", sceAgcUnknown_vieBRwlh1Lw); +int APS5_VABI sceAgcUnknown_vieBRwlh1Lw(ShaderRegister* regs, const Shader* gs, const Shader* ps) { + return CreateInterpolantMapping(__func__, regs, gs, ps, UpdateInterpolantValue, false); } } diff --git a/core/libs/prx/libSceAgc/Unimplemented.cpp b/core/libs/prx/libSceAgc/Unimplemented.cpp index 860242c9d..0b85ef7ff 100644 --- a/core/libs/prx/libSceAgc/Unimplemented.cpp +++ b/core/libs/prx/libSceAgc/Unimplemented.cpp @@ -90,10 +90,4 @@ int APS5_VABI sceAgcGetShaderInstrumentation() { return 0; } -APS5_EXPORT("vieBRwlh1Lw", sceAgcUnknown_vieBRwlh1Lw); -int APS5_VABI sceAgcUnknown_vieBRwlh1Lw(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp index 1ea1d6ca2..b176809a4 100644 --- a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp @@ -13,12 +13,14 @@ extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps); extern "C" int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps); +extern "C" int APS5_VABI sceAgcUnknown_vieBRwlh1Lw(ShaderRegister* regs, const Shader* gs, const Shader* ps); namespace { using Registers = std::array; const Shader* mappedPixel = nullptr; Registers preparedMapping{}; +std::size_t preparedCount = 0; unsigned mappings = 0; unsigned links = 0; @@ -65,6 +67,11 @@ void testIdentity() { regs = filled(); check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps) == 0, "mapping without inputs failed"); checkIdentity(regs, 0, "mapping without inputs is not the identity"); + regs = filled(); + check(sceAgcUnknown_vieBRwlh1Lw(regs.data(), nullptr, &ps) == 0, "update without inputs failed"); + check(preparedCount == 0u, "update without inputs prepared registers"); + const auto untouched = filled(); + check(std::memcmp(regs.data(), untouched.data(), sizeof(regs)) == 0, "update without inputs wrote registers"); } void testMapping() { @@ -99,6 +106,15 @@ void testMapping() { check(split[4].value == 0x03180002u, "full f16 interpolant mapping changed"); checkIdentity(split, static_cast(inputs.size()), "unused interpolants are not the identity"); + auto update = filled(); + const auto untouched = filled(); + const std::size_t written = inputs.size() * sizeof(ShaderRegister); + check(sceAgcUnknown_vieBRwlh1Lw(update.data(), &gs, &ps) == 0, "update mapping failed"); + check(preparedCount == inputs.size(), "update prepared registers past its inputs"); + check(std::memcmp(update.data(), preparedMapping.data(), written) == 0, "prepared update differs from published registers"); + check(std::memcmp(update.data(), split.data(), written) == 0, "update mapping differs from the split f16 mapping"); + check(std::memcmp(update.data() + inputs.size(), untouched.data(), sizeof(update) - written) == 0, "update wrote past its inputs"); + auto regular = filled(); check(sceAgcCreateInterpolantMapping_0100(regular.data(), &gs, &ps) == 0, "mapping failed"); check(std::memcmp(regular.data(), preparedMapping.data(), sizeof(regular)) == 0, "prepared mapping differs from published registers"); @@ -106,6 +122,51 @@ void testMapping() { checkIdentity(regular, static_cast(inputs.size()), "unused interpolants are not the identity"); } +void testUpdateMapping() { + std::array inputs{semantic(1, 0, 0), semantic(2, 0, 1), semantic(3, 0, 2), semantic(4, 0, 3)}; + inputs[0].is_custom = 1; + inputs[0].default_value = 2; + inputs[1].default_value = 1; + inputs[1].default_value_hi = 1; + inputs[2].default_value_hi = 2; + inputs[3].is_custom = 1; + inputs[3].default_value = 3; + inputs[3].default_value_hi = 3; + std::array outputs{semantic(1, 5, 0), semantic(2, 7, 1), semantic(4, 9, 3)}; + Shader gs{}; + gs.output_semantics = outputs.data(); + gs.num_output_semantics = static_cast(outputs.size()); + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + + auto update = filled(); + const auto untouched = filled(); + check(sceAgcUnknown_vieBRwlh1Lw(update.data(), &gs, &ps) == 0, "update mapping failed"); + check(update[0].value == 0x00000625u, "custom interpolant update changed"); + check(update[1].value == 0x01380107u, "low f16 half update changed"); + check(update[2].value == 0x02480220u, "unmatched high f16 half update changed"); + check(update[3].value == 0x03680709u, "full f16 interpolant update changed"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(update[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "updated register offset changed"); + } + const std::size_t written = inputs.size() * sizeof(ShaderRegister); + check(std::memcmp(update.data() + inputs.size(), untouched.data(), sizeof(update) - written) == 0, "update wrote past its inputs"); + + auto create = filled(); + check(sceAgcUnknownCreateInterpolantMapping(create.data(), &gs, &ps) == 0, "split f16 mapping failed"); + check(std::memcmp(create.data(), update.data(), written) == 0, "update differs from the split f16 mapping"); + checkIdentity(create, static_cast(inputs.size()), "unused interpolants are not the identity"); + + for (const std::uint32_t index : {1u, 3u}) { + inputs[index].default_value = 0; + update = filled(); + expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(update.data(), &gs, &ps); }); + check(std::memcmp(update.data(), untouched.data(), sizeof(update)) == 0, "rejected update wrote registers"); + inputs[index].default_value = inputs[index].default_value_hi; + } +} + void testRejections() { std::array inputs{}; Shader gs{}; @@ -117,18 +178,23 @@ void testRejections() { const auto saved = regs; expectFailure([&] { sceAgcCreateInterpolantMapping_0100(regs.data(), &gs, &ps); }); expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), &gs, &ps); }); + expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(regs.data(), &gs, &ps); }); check(std::memcmp(regs.data(), saved.data(), sizeof(regs)) == 0, "rejected mapping wrote registers"); ps.num_input_semantics = 1; expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps); }); expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(nullptr, &gs, &ps); }); + expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(regs.data(), nullptr, &ps); }); + expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(nullptr, &gs, &ps); }); + expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(regs.data(), &gs, nullptr); }); } } extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span context, std::span primitive) { - check(shader != nullptr && context.size() == preparedMapping.size() && primitive.empty(), "invalid mapping preparation call"); + check(shader != nullptr && context.size() <= preparedMapping.size() && primitive.empty(), "invalid mapping preparation call"); mappedPixel = shader; std::copy(context.begin(), context.end(), preparedMapping.begin()); + preparedCount = context.size(); ++mappings; } @@ -141,8 +207,9 @@ int main() { try { testIdentity(); testMapping(); + testUpdateMapping(); testRejections(); - check(mappings == 3u && links == 2u, "mapping preparation call count changed"); + check(mappings == 7u && links == 5u, "mapping preparation call count changed"); LibcRunShutdown_nid_postfix(); std::puts("AGC interpolant mapping tests passed"); return 0; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 2cf96fa61..148ce0621 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -239,7 +239,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceVoiceQoSInit](../../core/libs/prx/libSceVoiceQoS/Export.cpp) (libSceVoiceQoS) - `0x804E0905` for a second initialization comes from [prosper](https://github.com/mattias800/prosper)'s reading of the module and was not measured: on a PS5 a valid call blocks inside a homebrew payload, so it never returns there. The app type and the block are not used, as no endpoint is implemented - AudioIn and NpSessionSignaling exports added without an implementation have assumed signatures - [sceNpSessionSignalingGetConnectionFromPeerAddress](../../core/libs/prx/libSceNpSessionSignaling/Export.cpp) (libSceNpSessionSignaling) - imported by PPSA01289 and added without an implementation; unknown signature -- [vieBRwlh1Lw](../../core/libs/prx/libSceAgc/Unimplemented.cpp) (libSceAgc) - unknown name, signature +- [vieBRwlh1Lw](../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - unknown name; the `(regs, gs, ps)` arguments of `sceAgcCreateInterpolantMapping_0100` come from OpenAGC only - [scePadIsRemoteController](../../core/libs/prx/libScePad/Export.cpp) (libScePad) - the name hashes to NID `fCWdlnmB1Ks`; the signature `(int handle, bool* remote)` comes from PPSA16064, which zeroes a byte, passes its address after `scePadGetControllerInformation` and ignores the result. The error codes follow the other pad calls. Controllers are local devices, so the answer is always false - [scePadSetAngularVelocityBiasCorrectionState](../../core/libs/prx/libScePad/Export.cpp) (libScePad) - inferred signature `(int handle, bool enabled)` follows `scePadSetTiltCorrectionState` and `scePadSetAngularVelocityDeadbandState`; an invalid handle returns `PAD_ERROR_INVALID_HANDLE`, disabling is a no-op, and enabling throws because bias correction is not modelled - [sceKernelGetOperationMode](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - the signature `(int* mode, int* submode)` comes from PPSA12544, which logs both values after the call; the values are unknown and 0 is reported for both, as in the [prosper](https://github.com/mattias800/prosper/pull/4153) reimplementation. Null outputs throw @@ -342,6 +342,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceSharePlayInitialize](../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - the library keeps no state, so the heap is not used. A null heap is accepted: PPSA11386 passes a null heap and 0x1800 bytes and stops on any non-zero result. The error for a zero size is unknown, so it throws - [sceAgcDcbSetZPassPredicationEnableGetSize, sceAgcDcbSetPredicationDisableGetSize, sceAgcDcbSetBoolPredicationEnableGetSize](../../core/libs/prx/libSceAgc/DcbState/src/Predication.cpp) (libSceAgc) - the builders are inline in the SDK and not exported; the size is assumed to be that of the SET_PREDICATION packet `sceAgcDcbSetPredication` writes (16 bytes) - [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 +- [vieBRwlh1Lw](../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) follows [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c) only, not confirmed on hardware: the `dbOlWdppb4o` transform without the identity tail, so it writes one register per pixel shader input and none for a pixel shader without inputs, and a null pixel shader throws. For an `is_f16` 1 or 3 input whose `default_value` and `default_value_hi` differ it throws: OpenAGC takes `DEFAULT_VAL` from `default_value_hi`, while KytyPS5, sharpemu and `dbOlWdppb4o` here take it from `default_value` - [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 - [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.