fix(agc): route registered NGG workgroups
Defer registered NGG workgroup preparation until routing is known. Share the routing decision with static ABI resolution and draws. Gate the wave64 fixture on supported mesh subgroups; retain device-independent routing coverage.
This commit is contained in:
parent
a11c8f7f70
commit
1fe527bbec
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ std::shared_ptr<const ShaderSnapshot> 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<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr);
|
||||
|
||||
|
|
|
|||
|
|
@ -109,16 +109,7 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
|
|||
|
||||
std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Submission& submission) {
|
||||
auto product = std::make_shared<DrawDecode>();
|
||||
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<std::uint64_t>(low->second) << 8u) | (static_cast<std::uint64_t>(high->second & 0xffu) << 40u);
|
||||
const auto front = ProgramSnapshot(*submission.shaders, address, {2});
|
||||
vertexWorkgroup = front->header.empty() && ProgramUsesWorkgroup(*front, static_cast<std::size_t>((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);
|
||||
|
|
|
|||
|
|
@ -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<std::uint64_t>(low->second) << 8u) | (static_cast<std::uint64_t>(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<PreparedShaders> destination;
|
||||
|
|
@ -606,6 +622,7 @@ std::unique_ptr<RegisteredPreparation> 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<const Shader* const> 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) {
|
||||
|
|
|
|||
|
|
@ -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<const ShaderSnapshot>(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<const ShaderSnapshot>(std::move(shifted)));
|
||||
check(expectFailure([&] { static_cast<void>(VertexWorkgroupRequired(queue, unaligned)); }).find("not dword aligned") != std::string::npos, "an unaligned registered entry was accepted");
|
||||
queue.shader[0xc9] = 0x100;
|
||||
check(expectFailure([&] { static_cast<void>(VertexWorkgroupRequired(queue, registry)); }).find("reserved graphics program address") != std::string::npos, "reserved NGG program address bits were accepted");
|
||||
}
|
||||
|
||||
void testWorkerFailure() {
|
||||
std::array<std::uint32_t, 5> words{0xc0031500, 1, 1, 1, 0x41};
|
||||
Packet packet{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
|
||||
|
|
@ -722,6 +746,7 @@ int main() {
|
|||
#endif
|
||||
testProgramSnapshots();
|
||||
testProgramWorkgroupUse();
|
||||
testRegisteredVertexWorkgroupRouting();
|
||||
testEvents();
|
||||
testValidation();
|
||||
testClearState();
|
||||
|
|
|
|||
|
|
@ -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<std::uint32_t, 8> code{
|
||||
0xd8340000u, 0u, 0xd7600000u, 0x00017f00u, 0x7e020200u, 0xf80008cfu, 0x04030201u, 0xbf810000u
|
||||
};
|
||||
struct Header {
|
||||
Shader shader{};
|
||||
std::array<ShaderRegister, 4> registers{};
|
||||
std::array<ShaderRegister, 3> 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<std::uintptr_t>(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<std::uint32_t>(address >> 8u)}, {0xc9, static_cast<std::uint32_t>(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<ShaderRegister, 1> 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<std::uint32_t, 2> 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");
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue