diff --git a/core/libs/prx/libSceAgc/Shader/src/PrimState.cpp b/core/libs/prx/libSceAgc/Shader/src/PrimState.cpp index e03b491b2..587ae15a3 100644 --- a/core/libs/prx/libSceAgc/Shader/src/PrimState.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/PrimState.cpp @@ -11,6 +11,18 @@ #include "prx/libSceAgc/Shader/include/ShaderUtils.hpp" #include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" +namespace { + +void RequireSpecialOffset(const ShaderRegister& reg, std::uint32_t expected, const char* name) { + if (reg.offset != expected) { + char message[160]; + std::snprintf(message, sizeof(message), "sceAgcCreatePrimState: %s special register at offset 0x%x, expected 0x%x", name, static_cast(reg.offset), static_cast(expected)); + throw std::runtime_error(message); + } +} + +} + extern "C" { int APS5_VABI sceAgcCreatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_regs, const Shader* hs, const Shader* gs, std::uint32_t prim_type) { @@ -25,32 +37,22 @@ int APS5_VABI sceAgcCreatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_ throw std::runtime_error(std::string(__func__) + ": invalid shader type or missing special registers"); } (void)GraphicsPrimTypeToGsOut(prim_type); - const auto validStages = [](const Shader* shader) { return shader->specials->vgt_shader_stages_en.offset == ShaderRegs::VGT_SHADER_STAGES_EN; }; - if (!validStages(gs) || (hs != nullptr && !validStages(hs))) { - throw std::runtime_error(std::string(__func__) + ": invalid context register offsets"); - } - const auto normalizeUserVgprEnable = [](ShaderRegister reg) { - if (reg.offset == ShaderRegs::GE_USER_VGPR1 && reg.value == 0) reg.offset = ShaderRegs::GE_USER_VGPR_EN; - return reg; - }; - const auto gsUserVgprEnable = normalizeUserVgprEnable(gs->specials->ge_user_vgpr_en); - const auto hsUserVgprEnable = hs != nullptr ? normalizeUserVgprEnable(hs->specials->ge_user_vgpr_en) : gsUserVgprEnable; - if (gs->specials->ge_cntl.offset != ShaderRegs::GE_CNTL || gsUserVgprEnable.offset != ShaderRegs::GE_USER_VGPR_EN || (hs != nullptr && hsUserVgprEnable.offset != ShaderRegs::GE_USER_VGPR_EN)) { - throw std::runtime_error(std::string(__func__) + ": invalid user configuration register offsets"); - } + RequireSpecialOffset(gs->specials->vgt_shader_stages_en, ShaderRegs::VGT_SHADER_STAGES_EN, "VGT_SHADER_STAGES_EN"); + if (hs != nullptr) RequireSpecialOffset(hs->specials->vgt_shader_stages_en, ShaderRegs::VGT_SHADER_STAGES_EN, "VGT_SHADER_STAGES_EN"); + RequireSpecialOffset(gs->specials->ge_cntl, ShaderRegs::GE_CNTL, "GE_CNTL"); std::array contextValues{gs->specials->vgt_shader_stages_en, gs->specials->vgt_gs_out_prim_type}; if (hs != nullptr) { contextValues[0].value |= hs->specials->vgt_shader_stages_en.value; } if ((contextValues[0].value & ShaderRegs::VGT_SHADER_STAGES_GS_BIT) != 0) { - if (gs->specials->vgt_gs_out_prim_type.offset != ShaderRegs::VGT_GS_OUT_PRIM_TYPE) throw std::runtime_error(std::string(__func__) + ": invalid context register offsets"); + RequireSpecialOffset(gs->specials->vgt_gs_out_prim_type, ShaderRegs::VGT_GS_OUT_PRIM_TYPE, "VGT_GS_OUT_PRIM_TYPE"); } else if (hs != nullptr) { - if (hs->specials->vgt_gs_out_prim_type.offset != ShaderRegs::VGT_GS_OUT_PRIM_TYPE) throw std::runtime_error(std::string(__func__) + ": invalid context register offsets"); + RequireSpecialOffset(hs->specials->vgt_gs_out_prim_type, ShaderRegs::VGT_GS_OUT_PRIM_TYPE, "VGT_GS_OUT_PRIM_TYPE"); contextValues[1] = hs->specials->vgt_gs_out_prim_type; } else { contextValues[1] = {ShaderRegs::VGT_GS_OUT_PRIM_TYPE, GraphicsPrimTypeToGsOut(prim_type)}; } - std::array primitiveValues{gs->specials->ge_cntl, hsUserVgprEnable, ShaderRegister{ShaderRegs::VGT_PRIMITIVE_TYPE, prim_type}}; + std::array primitiveValues{gs->specials->ge_cntl, hs != nullptr ? hs->specials->ge_user_vgpr_en : gs->specials->ge_user_vgpr_en, ShaderRegister{ShaderRegs::VGT_PRIMITIVE_TYPE, prim_type}}; const std::array stages{hs, gs}; const bool patch = prim_type == static_cast(ShaderRegs::PrimitiveType::Patch); if (patch == ((contextValues[0].value & ShaderRegs::VGT_SHADER_STAGES_HS_BIT) != 0)) { diff --git a/core/libs/prx/libSceAgc/tests/ShaderHelpers.cpp b/core/libs/prx/libSceAgc/tests/ShaderHelpers.cpp index 2dce67750..ce8451e0e 100644 --- a/core/libs/prx/libSceAgc/tests/ShaderHelpers.cpp +++ b/core/libs/prx/libSceAgc/tests/ShaderHelpers.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister*, ShaderRegister*, const void*, const Shader*, const Shader*, std::uint32_t); @@ -48,6 +49,30 @@ void OptionalGeometryOutputRegister(ShaderSpecialRegs& special, const Shader& ve special.vgt_shader_stages_en.value = 0; special.vgt_gs_out_prim_type = {VGT_GS_OUT_PRIM_TYPE, 2}; } + +void HandBuiltUserVgprRegister() { + using namespace ShaderRegs; + ShaderSpecialRegs handBuilt{}; + handBuilt.ge_cntl = {GE_CNTL, 0x100u}; + handBuilt.vgt_shader_stages_en = {VGT_SHADER_STAGES_EN, 0x2412010u}; + handBuilt.vgt_gs_out_prim_type = {0x2ceu, 2}; + handBuilt.ge_user_vgpr_en = {0x25cu, 0}; + Shader preRaster{}; + preRaster.type = static_cast(ShaderBinaryType::Gs); + preRaster.specials = &handBuilt; + std::array context{}; + std::array primitive{}; + preparations = 0; + Require(sceAgcCreatePrimState(context.data(), primitive.data(), nullptr, &preRaster, 4) == 0); + Require(context[0].offset == VGT_SHADER_STAGES_EN && context[0].value == 0x2412010u && context[1].offset == VGT_GS_OUT_PRIM_TYPE && context[1].value == static_cast(GsOutputPrimitiveType::Triangles)); + Require(primitive[0].offset == GE_CNTL && primitive[0].value == 0x100u && primitive[1].offset == 0x25cu && primitive[1].value == 0 && primitive[2].offset == VGT_PRIMITIVE_TYPE && primitive[2].value == 4); + Require(preparations == 1 && stageCount == 1 && preparedPrimitive[1].offset == 0x25cu && preparedPrimitive[1].value == 0); + handBuilt.ge_cntl.offset = 0x25cu; + std::string message; + try { static_cast(sceAgcCreatePrimState(context.data(), primitive.data(), nullptr, &preRaster, 4)); } + catch (const std::runtime_error& error) { message = error.what(); } + Require(message.find("GE_CNTL") != std::string::npos && message.find("0x25c") != std::string::npos); +} } extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span stages, std::span context, std::span primitive) { @@ -181,6 +206,7 @@ int main() { Require(sceAgcCreateInterpolantMapping_0100(interpolants.data(), &vertex, nullptr) == 0); Require(preparations == 0 && mappings == 0 && links == 0); OptionalGeometryOutputRegister(special, vertex, hull); + HandBuiltUserVgprRegister(); std::cout << "shader helper preparation tests passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 04123a3c1..bad8319a2 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -598,3 +598,4 @@ Throughout the project, every function at every stage either **does exactly what - [_fstatfs](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) fills the FreeBSD 11 `struct statfs` of the PS5 payload SDK with the host volume that holds the file: block size, transfer size, total, free and available blocks, maximum name length and the read-only, `nosuid` and `noexec` flags (on Windows only read-only, and `f_files` and `f_ffree` are 0, as NTFS has no fixed number of files). The filesystem type number and name, mount paths, owner, filesystem id and I/O counters are left 0 or empty, since they would describe a host mount, not a console one. Sockets and pipes return `EINVAL`, as FreeBSD's `getvnode` does; a Windows descriptor that is neither a file nor a pipe throws. Stremio Plus (PPSA74126) reads the free space before a download and only logs the other fields - [_WStoul](../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../core/libs/prx/libc/src/RuneLocale.cpp) and [swscanf_s](../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal mismatch is a matching failure and a field that is only a prefix, such as `0x` or `1e`, does not convert, as C11 requires; FreeBSD's `vfwscanf` reports an input failure for the first and converts the second. Integers out of range are truncated from `strtoll`/`strtoull`. It throws for `%p`, `%S`, `%C`, unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert - [sceSharePlayGetCurrentConnectionInfoA](../../core/libs/prx/libSceSharePlay/Export.cpp) (libSceSharePlay) - reports no Share Play session, as the library keeps none: status 0 (DORMANT), mode 0, empty online IDs, account IDs 0 and both user IDs -1 (`SCE_USER_SERVICE_USER_ID_INVALID`). A null pointer returns `SCE_SHARE_PLAY_ERROR_INVALID_ARGS` (0x810E0001). The 72-byte `SceSharePlayConnectionInfoA` layout and the error code follow [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libNet.cpp) and [fpPS4](https://github.com/red-prig/fpPS4/blob/trunk/src/ps4_libsceshareplay.pas), which agree on both; KytyPS5 sets the user IDs to -1 and fpPS4 leaves them 0, and which one the console returns is not verified. KytyPS5 also returns `SCE_SHARE_PLAY_ERROR_NOT_INITIALIZED` before `sceSharePlayInitialize`; this library keeps no initialization state and does not. +- [sceAgcCreatePrimState](../../core/libs/prx/libSceAgc/Shader/src/PrimState.cpp) (libSceAgc) - the `ge_user_vgpr_en` special is passed to the user configuration registers at whatever offset the shader header stores, as the SPRX does according to [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/3c7e8072f0d7684e930454794fd5c02056946d73/src/game_compat.c#L1181-L1252), which copies all four specials verbatim (KytyPS5 stops on any other offset). PPSA99039, PPSA99505, PPSA99515 and PPSA99997 build their headers by hand with it at 0x25c (`GE_USER_VGPR1`) instead of 0x262 (`GE_USER_VGPR_EN`), value 0, and run on the console; it is not moved to 0x262, and the driver reads neither register. `ge_cntl`, `vgt_shader_stages_en` and a used `vgt_gs_out_prim_type` must still be at their own offsets, since the driver reads those registers there, so a header that stores one elsewhere throws naming the offset, although by OpenAGC the SPRX would copy it as well