Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const std::uint32_t> packet);
bool IsTagMarker(std::span<const std::uint32_t> packet);
inline bool Predicated(std::uint32_t header) { return !FillerPacket(header) && (header & 1u) != 0; }
bool PredicationPasses(const QueueState& queue);
bool WaitSatisfied(std::span<const std::uint32_t> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -180,6 +186,7 @@ struct QueueState {
std::uint32_t indexType = 0;
std::uint32_t instanceCount = 1;
std::vector<std::string> markers;
Predication predication;

void ClearContext() {
context = InitialContextRegisters();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned long long>(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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
36 changes: 31 additions & 5 deletions core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};
Expand All @@ -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:
Expand Down Expand Up @@ -213,7 +212,7 @@ void Validate(std::span<const std::uint32_t> 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) {
Expand All @@ -239,11 +238,11 @@ void Validate(std::span<const std::uint32_t> 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:
Expand All @@ -252,6 +251,17 @@ void Validate(std::span<const std::uint32_t> 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:
Expand Down Expand Up @@ -476,6 +486,16 @@ std::size_t ConditionalWords(std::span<const std::uint32_t> 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<const std::uint32_t> packet) {
require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet");
std::uint32_t value = 0;
Expand Down Expand Up @@ -725,6 +745,12 @@ void Execute(std::span<const std::uint32_t> 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;
Expand Down
95 changes: 92 additions & 3 deletions core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::uint32_t>::max();
check(AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {1, 2}), state).firstVertex == std::numeric_limits<std::uint32_t>::max(), "last vertex rejected");
expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {2, 2}), state); }, "vertex range overflow");
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand All @@ -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<std::uint32_t*>(&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<std::uintptr_t>(&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<const std::uint8_t*>(&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<std::uint32_t, 2> source{0x10, 73};
std::array<std::uint32_t, 1> destination{};
Expand Down Expand Up @@ -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<std::uint32_t, 4> written{};
alignas(16) std::array<std::uint32_t, 1> 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<std::uint32_t>(nested.size())}, 1);
const auto submit = [](std::vector<std::uint32_t>& commands) {
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
check(sceAgcDriverSubmitDcb(&packet) == 0, "predicated submission failed");
AgcDriverWaitIdle_nid_postfix();
};
std::vector<std::uint32_t> 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<std::uint32_t>(nested.size())}, 1);
Packet chained{chain.data(), static_cast<std::uint32_t>(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<std::uint32_t>(commands.size()), 0, {}};
Expand Down Expand Up @@ -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");
Expand Down
4 changes: 2 additions & 2 deletions core/libs/prx/libSceAgcDriver/tests/Submit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading