diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index bef499a10..e1de66ffb 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1105,7 +1105,7 @@ if(BUILD_TESTING) endif() endforeach() endforeach() - foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable) + foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable registered_workgroup registered_workgroup_sync) set(preparedTest "agc_driver_prepared_shaders_${preparedMode}") if(preparedMode STREQUAL "indirect") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --indirect) @@ -1113,6 +1113,8 @@ if(BUILD_TESTING) add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --deferred) elseif(preparedMode STREQUAL "deferred_undecodable") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --deferred-undecodable) + elseif(preparedMode STREQUAL "registered_workgroup" OR preparedMode STREQUAL "registered_workgroup_sync") + add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --registered-workgroup) elseif(preparedMode STREQUAL "debug_failure") add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --fail-before-registration) set_tests_properties(${preparedTest} PROPERTIES WILL_FAIL TRUE TIMEOUT 30) @@ -1122,6 +1124,8 @@ if(BUILD_TESTING) set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(preparedMode STREQUAL "uncached") set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_NO_DISPATCH_CACHE=1") + elseif(preparedMode STREQUAL "registered_workgroup_sync") + set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_SYNC_SHADER_PREPARE=1") elseif(preparedMode STREQUAL "debug" OR preparedMode STREQUAL "debug_failure") set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_PROFILE_DRAW=1" "APS5_NO_UNLOCKED_DEVICE=1") endif() 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 7ccb5d690..f0ee74af4 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -92,6 +92,7 @@ std::shared_ptr ProgramSnapshot(const ShaderRegistry& shad void NoteRawGraphicsDraw(); bool RawGraphicsDrawn(); bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset); +bool VertexWorkgroupRequired(const QueueState& queue, const ShaderRegistry& registry); std::shared_ptr PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp index ba5510217..2af5a114d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp @@ -109,16 +109,7 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const std::shared_ptr Driver::decodeDraw(const QueueState& queue, const Submission& submission) { auto product = std::make_shared(); - bool vertexWorkgroup = false; - const auto stages = queue.context.find(0x2d5); - const auto low = queue.shader.find(0xc8); - const auto high = queue.shader.find(0xc9); - if (stages != queue.context.end() && (stages->second & 0x02002024u) == 0x2000u && low != queue.shader.end() && high != queue.shader.end()) { - const auto address = (static_cast(low->second) << 8u) | (static_cast(high->second & 0xffu) << 40u); - const auto front = ProgramSnapshot(*submission.shaders, address, {2}); - vertexWorkgroup = front->header.empty() && ProgramUsesWorkgroup(*front, static_cast((address - front->codeAddress) / sizeof(std::uint32_t))); - } - product->state = Graphics::DecodeState(queue, vertexWorkgroup); + product->state = Graphics::DecodeState(queue, VertexWorkgroupRequired(queue, *submission.shaders)); DecodeGraphicsPrograms(*product, queue, *submission.shaders, false, true); product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product->state), Graphics::PixelProgramSkipped(queue)); product->pixel.targetExportPacking = Graphics::ExportPackings(product->state); 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 9b46369ce..e81280ca7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -147,6 +147,22 @@ bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset } +bool VertexWorkgroupRequired(const QueueState& queue, const ShaderRegistry& registry) { + Graphics::NoteRegisterRead(Graphics::RegisterBank::Context, 0x2d5); + const auto routing = queue.context.find(0x2d5); + if (routing == queue.context.end() || (routing->second & 0x02002024u) != 0x2000u) return false; + Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, 0xc8); + Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, 0xc9); + const auto low = queue.shader.find(0xc8); + const auto high = queue.shader.find(0xc9); + if (low == queue.shader.end() || high == queue.shader.end()) return false; + require((high->second & ~0xffu) == 0, "reserved graphics program address bits are set"); + const auto address = (static_cast(low->second) << 8u) | (static_cast(high->second) << 40u); + const auto snapshot = ProgramSnapshot(registry, address, {2}); + require((address - snapshot->codeAddress) % sizeof(std::uint32_t) == 0, "graphics entry point is not dword aligned"); + return (snapshot->header.empty() || snapshot->type == 2) && ProgramUsesWorkgroup(*snapshot, (address - snapshot->codeAddress) / sizeof(std::uint32_t)); +} + struct ShaderPreparationTransaction::State { struct Change { std::shared_ptr destination; @@ -606,6 +622,7 @@ std::unique_ptr PlanRegistered(const ShaderSnapshot& snap } else { const auto routing = RegisterValue(state.context, 0x2d5); wave = (routing & 0x00400000u) != 0 ? 32u : 64u; + if (registration && stage == Stage::Vertex && (routing & 0x02002024u) == 0x2000u && ProgramUsesWorkgroup(snapshot, codeOffset)) return nullptr; if ((routing & 0x20u) != 0 || stage == Stage::Mesh || (routing & 4u) != 0 || ((routing >> 3u) & 3u) == 1u) { if (registration) return {}; auto stageState = state; @@ -883,7 +900,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span stages, std return; } DrawDecode decoded{}; - decoded.state.stages = Graphics::DecodeShaderStages(state); + decoded.state.stages = Graphics::DecodeShaderStages(state, VertexWorkgroupRequired(state, *registry)); DecodeGraphicsPrograms(decoded, state, *registry, true, false); auto prepared = PrepareGraphicsStages(decoded, localDevice->Target()); for (auto& stage : prepared) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 10fd4fc9c..da1c9ba0b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -629,6 +629,30 @@ void testProgramWorkgroupUse() { check(!AgcDriver::DriverDetail::ProgramUsesWorkgroup(entry, 2) && AgcDriver::DriverDetail::ProgramUsesWorkgroup(entry, 0), "the workgroup check did not start at the program's entry"); } +void testRegisteredVertexWorkgroupRouting() { + using namespace AgcDriver::DriverDetail; + ShaderSnapshot snapshot{0x20000, 0x30000, 2, {0xd8340000u, 0x00000100u, 0xbf810000u, 0xbf810000u}, {}}; + snapshot.header.resize(sizeof(Shader)); + const ShaderRegistry registry{{snapshot.codeAddress, {std::make_shared(std::move(snapshot))}}}; + AgcDriver::QueueState queue{}; + queue.shader = {{0xc8, 0x200}, {0xc9, 0}}; + queue.context[0x2d5] = 0x2000; + check(VertexWorkgroupRequired(queue, registry), "a registered NGG vertex program using LDS did not need a workgroup"); + for (const auto routing : {0u, 0x2020u, 0x2004u, 0x02002000u}) { + queue.context[0x2d5] = routing; + check(!VertexWorkgroupRequired(queue, registry), "legacy, geometry, tessellation or passthrough routing changed to an NGG vertex workgroup"); + } + queue.context[0x2d5] = 0x2000; + auto unaligned = registry; + const auto aligned = unaligned.begin()->second.front(); + ShaderSnapshot shifted{0x1ffff, 0x30000, 2, aligned->code, aligned->header}; + unaligned.clear(); + unaligned[shifted.codeAddress].push_back(std::make_shared(std::move(shifted))); + check(expectFailure([&] { static_cast(VertexWorkgroupRequired(queue, unaligned)); }).find("not dword aligned") != std::string::npos, "an unaligned registered entry was accepted"); + queue.shader[0xc9] = 0x100; + check(expectFailure([&] { static_cast(VertexWorkgroupRequired(queue, registry)); }).find("reserved graphics program address") != std::string::npos, "reserved NGG program address bits were accepted"); +} + void testWorkerFailure() { std::array words{0xc0031500, 1, 1, 1, 0x41}; Packet packet{words.data(), static_cast(words.size()), 0, {}}; @@ -722,6 +746,7 @@ int main() { #endif testProgramSnapshots(); testProgramWorkgroupUse(); + testRegisteredVertexWorkgroupRouting(); testEvents(); testValidation(); testClearState(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index 93dffc420..4aa54af7d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -525,6 +525,54 @@ void Registration(bool indirect) { Require(destination == 0, "failed dispatch executed a subsequent memory write"); } +void RegisteredVertexWorkgroup(const ShaderRecompiler::SpirvTarget& target, bool registrationOnly = false) { + if (!registrationOnly && ((target.subgroupSize != 32u && target.subgroupSize != 64u) || std::find(target.supportedCapabilities.begin(), target.supportedCapabilities.end(), spv::CapabilityMeshShadingEXT) == target.supportedCapabilities.end())) { + std::cout << "registered NGG wave64 preparation not run: requires mesh shading and a 32- or 64-lane host subgroup; subgroup=" << target.subgroupSize << '\n'; + return; + } + alignas(256) static constexpr std::array code{ + 0xd8340000u, 0u, 0xd7600000u, 0x00017f00u, 0x7e020200u, 0xf80008cfu, 0x04030201u, 0xbf810000u + }; + struct Header { + Shader shader{}; + std::array registers{}; + std::array context{{{0x1ff, 64}, {0x2ab, 4}, {0x2ce, 0}}}; + ShaderSpecialRegs specials{{0x25b, 0x8040}, {0x2d5, 0x2000}, 0, {}, {}, {0x29b, 2}, {0x260, 0}}; + ShaderUserData userData{}; + } header; + const auto address = reinterpret_cast(code.data()); + header.shader.file_header = 0x34333231u; + header.shader.version = 0x18; + header.shader.header_size = sizeof(header); + header.shader.shader_size = sizeof(code); + header.shader.code = code.data(); + header.shader.type = 2; + header.shader.user_data = &header.userData; + header.shader.sh_registers = header.registers.data(); + header.shader.num_sh_registers = header.registers.size(); + header.shader.cx_registers = header.context.data(); + header.shader.num_cx_registers = header.context.size(); + header.shader.specials = &header.specials; + header.registers = {{{0xc8, static_cast(address >> 8u)}, {0xc9, static_cast(address >> 40u)}, {0x8a, 0x622c0187}, {0x8b, 5u << 19u}}}; + AgcDriverRegisterShader_nid_postfix(&header.shader); + if (registrationOnly) { + header.shader.user_data = nullptr; + AgcDriverRegisterShader_nid_postfix(&header.shader); + header.specials.vgt_shader_stages_en.value = 0; + ExpectFailure([&] { AgcDriverRegisterShader_nid_postfix(&header.shader); }, "missing AGC user-data header"); + header.shader.user_data = &header.userData; + if (target.subgroupSize < 64u && std::getenv("APS5_SYNC_SHADER_PREPARE") != nullptr) { + ExpectFailure([&] { AgcDriverRegisterShader_nid_postfix(&header.shader); }, "v_readlane_b32 of lane 63 is outside"); + } + std::cout << "registered NGG preparation deferral passed; subgroup=" << target.subgroupSize << '\n'; + return; + } + const std::array primitive{{{0x242, 4}}}; + AgcDriverResolveShaderAbi_nid_postfix(&header.shader, {}, primitive); + AgcDriverResolveShaderAbi_nid_postfix(&header.shader, {}, primitive); + std::cout << "registered NGG wave64 preparation passed; subgroup=" << target.subgroupSize << '\n'; +} + void DeferredRegistration() { alignas(256) std::array code{0xbe842104u, 0xbf810000u}; struct Header { @@ -585,7 +633,14 @@ void DeferredUndecodableRegistration() { int main(int argc, char** argv) { try { - Require(argc == 1 || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable")), "invalid test arguments"); + Require(argc == 1 || (argc == 2 && (std::string_view(argv[1]) == "--indirect" || std::string_view(argv[1]) == "--fail-before-registration" || std::string_view(argv[1]) == "--deferred" || std::string_view(argv[1]) == "--deferred-undecodable" || std::string_view(argv[1]) == "--registered-workgroup")), "invalid test arguments"); + if (argc == 2 && std::string_view(argv[1]) == "--registered-workgroup") { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + RegisteredVertexWorkgroup(device->Target(), true); + AgcDriverShutdown_nid_postfix(); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--deferred") { if (!OpenVulkanTestDevice()) return VulkanTestSkipped; DeferredRegistration(); @@ -601,6 +656,7 @@ int main(int argc, char** argv) { auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; Run(*device); + RegisteredVertexWorkgroup(device->Target()); FailureCapture(*device); PendingRegistrationPreparation(*device); Require(argc != 2 || std::string_view(argv[1]) != "--fail-before-registration", "injected failure before registration"); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..5708ea62f 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -324,6 +324,7 @@ Throughout the project, every function at every stage either **does exactly what - [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 +- [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. - [Depth fast clear through HTILE](../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a surface cleared by an HTILE fill is cleared to DB_DEPTH_CLEAR, also when it is next sampled. For a depth-only HTILE the clear word also carries a value (radv writes `minz = maxz = round(depth * 0x3fff)`, `HTILE_Z_CLEAR_REG` in Mesa's `src/amd/common/ac_descriptors.h`, and allows TC-compatible fast clears only to 0.0 or 1.0); which of the two the texture unit returns for a cleared tile was not measured.