diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index c413e357e..31f780e61 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -88,6 +88,8 @@ bool AccessesMemory(std::uint32_t header); // synchronization): such a packet needs a pipeline barrier, not a device drain. bool UsesGpuCacheBarrier(std::span packet); bool IsTagMarker(std::span packet); +inline bool Predicated(std::uint32_t header) { return !FillerPacket(header) && (header & 1u) != 0; } +bool PredicationPasses(const QueueState& queue); bool WaitSatisfied(std::span packet); // WaitSatisfied for a polling loop: the caller has validated the address once, so the value is read // directly instead of through the checked guest memory path. diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp index 6d18f6228..19493fc5e 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp @@ -167,6 +167,12 @@ inline Registers InitialContextRegisters() { return result; } +struct Predication { + std::uint64_t address = 0; + std::uint32_t operation = 0; + bool executeWhenSet = false; +}; + struct QueueState { Registers shader; Registers context = InitialContextRegisters(); @@ -180,6 +186,7 @@ struct QueueState { std::uint32_t indexType = 0; std::uint32_t instanceCount = 1; std::vector markers; + Predication predication; void ClearContext() { context = InitialContextRegisters(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp index 06dcb02cb..3c5dcd148 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -117,6 +117,17 @@ void Driver::execute(const Submission& submission) { GuestMemory::SetCurrentPacket(header == FlipPacketHeader ? 0xffffu : opcode, submission.queue); CaptureTrace::Log("packet submission=%llu queue=%x offset=%zu header=%08x words=%zu", static_cast(submission.serial), submission.queue, cursor, header, packet.size()); + if (Pm4::Predicated(header) && queue.predication.operation != 0) { + recordQueuedLabelsBeforeRead(submission.queue); + if (!Pm4::PredicationPasses(queue)) { + cursor = opcode == 0x3f ? submission.conditionalEnds.at(cursor) : nextCursor; + continue; + } + } + if (opcode == 0x3f) { + cursor = nextCursor; + continue; + } const auto flushStart = profilePackets ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; flushBetweenPackets(submission.queue, header, opcode == 0x49 || opcode == 0x37); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp index edd9b5b70..cdc6d68b8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp @@ -50,6 +50,19 @@ bool Driver::copySegment(Submission& submission, const std::uint32_t* guest, std const bool chain = (guest[cursor + 3] & (1u << 20u)) != 0; require(!chain || guarded.empty(), "a chained INDIRECT_BUFFER inside a conditional execution range is not implemented"); GuestMemory::CheckRange(target, targetWords * sizeof(std::uint32_t), alignof(std::uint32_t)); + if (Pm4::Predicated(header)) { + require(!chain, "predicated command buffer chains are not implemented"); + const auto start = submission.commands.size(); + submission.commands.insert(submission.commands.end(), guest + cursor, guest + cursor + count); + require(!copySegment(submission, target, targetWords, budget), "a REWIND inside a predicated command buffer is not implemented"); + for (auto inner = start + count; inner < submission.commands.size(); inner += Pm4::PacketWords(submission.commands[inner])) { + const auto innerHeader = submission.commands[inner]; + require(innerHeader != FlipPacketHeader && innerHeader != RenderingWaitPacketHeader, "flips and rendering waits in predicated command buffers are not implemented"); + } + submission.conditionalEnds.emplace(start, submission.commands.size()); + cursor += count; + continue; + } if (copySegment(submission, target, targetWords, budget)) { require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented"); return true; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index a07acee1a..26f818d92 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -166,7 +166,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { } } switch (opcode) { - case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x22: case 0x26: case 0x27: + case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x20: case 0x22: case 0x26: case 0x27: case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x46: case 0x50: case 0x58: case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a: case 0x81: case 0x83: case 0x9f: return {}; @@ -175,7 +175,6 @@ std::string_view UnsupportedReason(std::uint32_t header) { return {}; case 0x3a: case 0x8d: return "graphics draw, shader stages and guest render-target materialization are not implemented"; - case 0x20: return "GPU query predication is not implemented"; case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented"; case 0x3c: case 0x93: return {}; case 0x39: @@ -213,7 +212,7 @@ void Validate(std::span packet, std::uint32_t queue) { const auto size = [&](std::size_t count) { require(packet.size() == count, "invalid packet size"); }; const auto graphics = [&] { require(queue == 0, "graphics packet in compute queue"); }; const auto reason = UnsupportedReason(header); - if (!reason.empty()) throw std::runtime_error(std::string(reason)); + if (!reason.empty() && !(opcode == 0x3f && Predicated(header))) throw std::runtime_error(std::string(reason)); if (opcode == 0x10) { require((header & 3u) == 0, "unsupported NOP header flags"); switch ((header >> 2u) & 0x3fu) { @@ -239,11 +238,11 @@ void Validate(std::span packet, std::uint32_t queue) { return; } // Header bit 1 selects the compute shader type and bit 2 (register writes) resets the filter CAM; - // neither changes what the packet writes. Predication (bit 0) is not implemented. + // neither changes what the packet writes. const auto flags = header & 0xffu; const bool registerWrite = opcode == 0x69 || opcode == 0x76 || opcode == 0x79 || opcode == 0x7a; auto allowedFlags = opcode == 0x11 ? 2u : registerWrite ? 6u : opcode == 0x3c || opcode == 0x93 ? 2u : 0u; - if (IsTagMarker(packet)) allowedFlags |= 1u; + if (opcode != 0x20 && opcode != 0x22) allowedFlags |= 1u; if ((flags & ~allowedFlags) != 0) throw std::runtime_error("PM4 header flags 0x" + ToHex(flags) + " are not implemented"); switch (opcode) { case 0x11: @@ -252,6 +251,17 @@ void Validate(std::span packet, std::uint32_t queue) { if ((header & 2u) == 0) graphics(); break; case 0x12: graphics(); size(2); require((packet[1] & ~0xfu) == 0, "unsupported CLEAR_STATE payload bits"); break; + case 0x20: { + graphics(); + size(4); + const auto operation = (packet[1] >> 16u) & 7u; + require(operation != 1 && operation != 2, "GPU query predication is not implemented"); + require(operation == 0 || operation == 3 || operation == 4, "invalid predication operation"); + require((packet[1] & ~0x71100u) == 0, "unsupported SET_PREDICATION bits"); + if (operation != 0) require(address(packet[2], packet[3]) != 0 && (packet[2] & 0xfu) == 0, "unaligned predication address"); + break; + } + case 0x3f: size(4); break; case 0x13: case 0x2f: graphics(); size(2); break; case 0x26: graphics(); size(3); break; case 0x27: @@ -476,6 +486,16 @@ std::size_t ConditionalWords(std::span packet) { return packet[4] & ConditionalWordsMask; } +bool PredicationPasses(const QueueState& queue) { + const auto& predication = queue.predication; + if (predication.operation == 0) return true; + const std::size_t bytes = predication.operation == 3 ? 8 : 4; + std::uint64_t value = 0; + const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait); + GuestMemory::Read(predication.address, std::as_writable_bytes(std::span(&value, 1)).first(bytes), bytes); + return (value != 0) == predication.executeWhenSet; +} + std::uint32_t ReadCondition(std::span packet) { require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet"); std::uint32_t value = 0; @@ -725,6 +745,12 @@ void Execute(std::span packet, QueueState& queue) { APS5_LOG_OUT_DEBUG("CLEAR_STATE targetMaskBefore=0x%x shaderMaskBefore=0x%x", queue.context.contains(0x8e) ? queue.context.at(0x8e) : 0u, queue.context.contains(0x8f) ? queue.context.at(0x8f) : 0u); queue.ClearContext(); return; case 0x13: queue.indexBufferSize = packet[1]; return; + case 0x20: { + const auto operation = (packet[1] >> 16u) & 7u; + queue.predication = {operation == 0 ? 0 : address(packet[2], packet[3]), operation, (packet[1] & 0x100u) != 0}; + return; + } + case 0x3f: throw std::runtime_error(std::string(UnsupportedReason(packet[0]))); case 0x26: queue.indexBase = address(packet[1], packet[2]); return; case 0x2a: queue.indexType = packet[1]; return; case 0x2f: queue.instanceCount = packet[1]; return; diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 2efbe1ab5..3af51bbd8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -180,7 +180,7 @@ void testAutoDraw() { } expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3}), 0); }, "packet size"); expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2, 0}), 0); }, "packet size"); - expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}, 1), 0); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}, 2), 0); }, "header flags"); state.userConfig[0x24a] = std::numeric_limits::max(); check(AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {1, 2}), state).firstVertex == std::numeric_limits::max(), "last vertex rejected"); expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {2, 2}), state); }, "vertex range overflow"); @@ -508,7 +508,7 @@ void testEventWrite() { for (const auto bit : {0x40u, 0x80u, 0x800u, 0x80000000u}) { expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x410u | bit}), 0); }, "reserved bits"); } - expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x410}, 1), 0); }, "header flags"); + expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x410}, 2), 0); }, "header flags"); expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x410, 0, 0}), 0); }, "packet size"); expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x13a, 0, 0}), 0); }, "event type 58"); expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x0d}), 0); }, "event type 13"); @@ -541,7 +541,7 @@ void testAcquireMem() { packet[index] = value; expectFailure([&] { AgcDriver::Pm4::Validate(packet, 0); }, reason); }; - invalidWord(0, captured[0] | 1u, "header flags"); + invalidWord(0, captured[0] | 2u, "header flags"); invalidWord(1, 4, "control flags"); invalidWord(1, 0x00800000, "control flags"); invalidWord(3, 1, "above 40 bits"); @@ -554,6 +554,50 @@ void testAcquireMem() { expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x58, {0, 0, 0, 0, 0, 0, 0, 0}), 0); }, "packet size"); } +void testPredication() { + alignas(16) std::uint64_t flag[4] = {0, 0, 0, 0}; + auto* flag32 = reinterpret_cast(&flag[2]); + const auto setPredication = [&](std::uint32_t operation, bool executeWhenSet, const void* address) { + return makePacket(0x20, {(operation << 16u) | (executeWhenSet ? 0x100u : 0u) | 0x1000u, low(address), high(address)}); + }; + AgcDriver::QueueState state; + check(AgcDriver::Pm4::PredicationPasses(state), "inactive predication must pass"); + execute(state, setPredication(3, true, &flag[0])); + check(state.predication.operation == 3 && state.predication.executeWhenSet && state.predication.address == reinterpret_cast(&flag[0]), "BOOL64 predication state mismatch"); + check(!AgcDriver::Pm4::PredicationPasses(state), "zero BOOL64 value must skip draw-visible packets"); + flag[0] = 1ull << 40u; + check(AgcDriver::Pm4::PredicationPasses(state), "upper BOOL64 bits must count"); + execute(state, setPredication(3, false, &flag[0])); + check(!AgcDriver::Pm4::PredicationPasses(state), "non-zero BOOL64 value must skip draw-not-visible packets"); + flag[0] = 0; + check(AgcDriver::Pm4::PredicationPasses(state), "zero BOOL64 value must run draw-not-visible packets"); + execute(state, setPredication(4, true, &flag[2])); + flag[2] = 0xffffffff00000000ull; + check(*flag32 == 0 && !AgcDriver::Pm4::PredicationPasses(state), "BOOL32 must read only 32 bits"); + *flag32 = 7; + check(AgcDriver::Pm4::PredicationPasses(state), "non-zero BOOL32 value must run draw-visible packets"); + execute(state, setPredication(0, false, nullptr)); + check(state.predication.operation == 0 && AgcDriver::Pm4::PredicationPasses(state), "clear must end predication"); + + expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(1, true, &flag[0]), 0); }, "query predication"); + expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(2, true, &flag[0]), 0); }, "query predication"); + expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(5, true, &flag[0]), 0); }, "invalid predication operation"); + expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(3, true, &flag[0]), 0x20); }, "compute queue"); + expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(3, true, nullptr), 0); }, "unaligned predication address"); + expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(3, true, reinterpret_cast(&flag[0]) + 8), 0); }, "unaligned predication address"); + auto continued = setPredication(3, true, &flag[0]); + continued[1] |= 1u << 31u; + expectFailure([&] { AgcDriver::Pm4::Validate(continued, 0); }, "SET_PREDICATION bits"); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x20, {3u << 16u, low(&flag[0]), high(&flag[0])}, 1), 0); }, "header flags"); + + AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}, 1), 0); + AgcDriver::Pm4::Validate(makePacket(0x46, {0x410}, 1), 0); + const auto call = makePacket(0x3f, {0x1000, 0, 0x0f200010}, 1); + AgcDriver::Pm4::Validate(call, 0); + expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x3f, {0x1000, 0, 0x0f200010}), 0); }, "nested command buffers"); + expectFailure([&] { AgcDriver::Pm4::Execute(call, state); }, "nested command buffers"); +} + void testDriverSubmission() { std::array source{0x10, 73}; std::array destination{}; @@ -693,6 +737,49 @@ void testConditionalSubmission() { check(results[13] == 0, "a rejected conditional submission executed a guarded packet"); } +void testPredicatedSubmission() { + alignas(16) std::uint64_t flag[2] = {0, 0}; + alignas(16) std::array written{}; + alignas(16) std::array nestedWritten{}; + const auto nested = makePacket(0x37, {0x100, low(nestedWritten.data()), high(nestedWritten.data()), 3}); + const auto call = makePacket(0x3f, {low(nested.data()), high(nested.data()), 0x0f200000u | static_cast(nested.size())}, 1); + const auto submit = [](std::vector& commands) { + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "predicated submission failed"); + AgcDriverWaitIdle_nid_postfix(); + }; + std::vector commands; + for (const auto& packet : { + makePacket(0x20, {0x31100, low(&flag[0]), high(&flag[0])}), + makePacket(0x37, {0x100, low(&written[0]), high(&written[0]), 7}, 1), + call, + makePacket(0x20, {0, 0, 0}), + makePacket(0x37, {0x100, low(&written[1]), high(&written[1]), 9}, 1) + }) commands.insert(commands.end(), packet.begin(), packet.end()); + submit(commands); + check(written[0] == 0, "packet with a false predicate was executed"); + check(nestedWritten[0] == 0, "command buffer with a false predicate was executed"); + check(written[1] == 9, "cleared predication skipped a packet"); + flag[0] = 1; + commands.clear(); + for (const auto& packet : { + makePacket(0x20, {0x31100, low(&flag[0]), high(&flag[0])}), + makePacket(0x37, {0x100, low(&written[2]), high(&written[2]), 5}, 1), + call, + makePacket(0x20, {0x31000, low(&flag[0]), high(&flag[0])}), + makePacket(0x37, {0x100, low(&written[3]), high(&written[3]), 6}, 1), + makePacket(0x20, {0, 0, 0}) + }) commands.insert(commands.end(), packet.begin(), packet.end()); + submit(commands); + check(written[2] == 5, "packet with a true predicate was skipped"); + check(nestedWritten[0] == 3, "command buffer with a true predicate was skipped"); + check(written[3] == 0, "draw-not-visible packet ran with a set value"); + auto chain = makePacket(0x3f, {low(nested.data()), high(nested.data()), 0x0f300000u | static_cast(nested.size())}, 1); + Packet chained{chain.data(), static_cast(chain.size()), 0, {}}; + expectFailure([&] { sceAgcDriverSubmitDcb(&chained); }, "predicated command buffer chains"); + AgcDriverWaitIdle_nid_postfix(); +} + void testAsyncMemoryFailure() { auto commands = makePacket(0x37, {0x100, 0x1000, 0, 1}); Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; @@ -728,7 +815,9 @@ int main(int argc, char** argv) { testConditionReadSynchronization(); testEventWrite(); testAcquireMem(); + testPredication(); testDriverSubmission(); + testPredicatedSubmission(); testConditionalSubmission(); LibcRunShutdown_nid_postfix(); std::puts("PM4 catalog, registers, state, memory, conditional execution and submission tests passed"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index e2b8d991f..28a8f081a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -76,7 +76,7 @@ void testValidation() { expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }); commands[0] = 0xc001105c; expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }); - commands[0] = 0xc0017601; + commands[0] = 0xc0017608; expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }); commands[0] = 0xc0017600; commands[1] = 0x10000; @@ -110,7 +110,7 @@ void testClearState() { words[0] = 0xc0011200; packet.dw_num = 3; expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }); - words[0] = 0xc0001201; + words[0] = 0xc0001202; packet.dw_num = 2; expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }); words[0] = 0xc0001200; diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 92cad4624..09bf9eb24 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -33,6 +33,7 @@ Throughout the project, every function at every stage either **does exactly what - [sceFontGetRenderScaledKerning](../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - arguments, the cleared output and the bound renderer check taken from `cellFontGetRenderScaledKerning` in [RPCS3](https://github.com/RPCS3/rpcs3/blob/master/rpcs3/Emu/Cell/Modules/cellFont.cpp) (PS3 predecessor of the library), not confirmed on a title. The kerning is the `sceFontGetKerning` one at the render scale - [sceFontGetPixelResolution](../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - signature taken from the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json), not confirmed on a title. The value is the one the library's driver reports (64 for FreeType); the argument checks and the cleared output follow `sceFontGetLibrary` - [sceAgcSetSubmitMode](../../core/libs/prx/libSceAgc/Misc/src/Suspend.cpp) (libSceAgc) - mode values unknown; only 0 is accepted +- [SET_PREDICATION](../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (AGC driver) - boolean predication follows the AMD PM4 encoding [Mesa](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/amd/common/sid.h) uses: operation 3 reads 64 bits and 4 reads 32 bits, and bit 8 runs predicated packets when the value is non-zero (clear: when it is zero); the bit 12 hint is ignored. The address must have its low 4 bits clear, as `sceAgcDcbSetPredication` writes it. Query predication (Z-pass, primitive count), a predicated COND_EXEC, predicated command buffer chains and flips in predicated command buffers throw - [sceAgcGetIsTrinityMode](../../core/libs/prx/libSceAgc/Misc/src/Platform.cpp) (libSceAgc) - signature from the only PPSA26344 call (a pointer to a one-byte flag, result ignored); a null pointer throws because its error code is unknown - [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature - [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name