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
1 change: 1 addition & 0 deletions core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ std::vector<PacketStore> DecodeStores(std::span<const std::uint32_t> packet);
// packet made by reading them there (through the checked guest memory path, which waits for
// recorded GPU work that writes them), and both steps in one for the packet.
std::uint64_t DispatchArgumentAddress(std::span<const std::uint32_t> packet, const QueueState& queue);
void NoteDispatchRegisters(std::span<const std::uint32_t> packet, QueueState& queue);
std::array<std::uint32_t, 5> ReadDispatchArguments(std::uint64_t arguments, std::uint32_t initiator);
std::array<std::uint32_t, 3> PartialGroupThreads(const std::array<std::uint32_t, 3>& groups, const std::array<std::uint32_t, 3>& numThreads);
std::array<std::uint32_t, 5> ResolveDispatch(std::span<const std::uint32_t> packet, const QueueState& queue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l
try {
if (opcode == 0x15) dispatch(scratch, packet, submission);
else dispatchIndirect(scratch, packet, submission);
Pm4::NoteDispatchRegisters(packet, scratch);
} catch (const std::exception&) {
++stats.failures;
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ void Driver::execute(const Submission& submission) {
}
if (opcode == 0x15) timed(&WorkerProfile::dispatchMs, [&] { dispatch(queue, packet, submission); });
else timed(&WorkerProfile::dispatchMs, [&] { dispatchIndirect(queue, packet, submission); });
Pm4::NoteDispatchRegisters(packet, queue);
Graphics::Recorder::CountRecordedWork();
finishDispatchPacket(opcode == 0x16);
} else if (opcode == 0x3c || opcode == 0x93) {
Expand Down
41 changes: 37 additions & 4 deletions core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,11 @@ void copyMemory(std::uint64_t source, std::uint64_t destination, std::size_t byt
GuestMemory::Write(destination, copySource(source, bytes, immediate));
}

constexpr std::uint32_t CopyDataRegisterSource = 0;
constexpr std::uint32_t CopyDataCachePolicy = (3u << 13u) | (3u << 25u);
constexpr std::uint32_t ShaderRegisterBase = 0x2c00;
constexpr std::uint32_t ContextRegisterBase = 0xa000;
constexpr std::uint32_t UserConfigRegisterBase = 0xc000;

std::uint32_t copyDataCount(std::span<const std::uint32_t> packet) {
return (packet[1] & 0x10000u) != 0 ? 2u : 1u;
Expand All @@ -142,8 +145,27 @@ std::uint64_t gpuClockCount() {
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count() / 10);
}

std::vector<std::byte> copyDataSource(std::span<const std::uint32_t> packet, std::size_t bytes) {
std::uint32_t registerBankBase(std::uint32_t offset) {
if (offset - ShaderRegisterBase < 0x400u) return ShaderRegisterBase;
if (offset - ContextRegisterBase < 0x400u) return ContextRegisterBase;
if (offset - UserConfigRegisterBase < 0x4000u) return UserConfigRegisterBase;
return 0;
}

std::vector<std::byte> copyDataSource(std::span<const std::uint32_t> packet, const QueueState& queue, std::size_t bytes) {
const auto source = CopyDataSource(packet);
if (source == CopyDataRegisterSource) {
std::vector<std::byte> data(bytes);
for (std::size_t word = 0; word < bytes / 4; ++word) {
const auto offset = packet[2] + static_cast<std::uint32_t>(word);
const auto base = registerBankBase(offset);
const auto& bank = base == ShaderRegisterBase ? queue.shader : base == ContextRegisterBase ? queue.context : queue.userConfig;
if (base == 0 || !bank.contains(offset - base)) throw std::runtime_error("COPY_DATA source register 0x" + ToHex(offset) + " has no known value");
const auto value = bank.at(offset - base);
for (std::size_t i = 0; i < 4; ++i) data[word * 4 + i] = static_cast<std::byte>(value >> (i * 8));
}
return data;
}
if (source != CopyDataGpuClockSource) return copySource(address(packet[2], packet[3]), bytes, CopyDataImmediate(source));
const auto value = gpuClockCount();
std::vector<std::byte> data(bytes);
Expand Down Expand Up @@ -474,7 +496,8 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
break;
}
require(destination == 2 || destination == 4 || destination == 10, "COPY_DATA GDS or performance-counter destination is not implemented");
require(source == 2 || source == 4 || source == 5 || source == 10 || source == 11 || source == CopyDataGpuClockSource, "COPY_DATA register, GDS or reference-clock source is not implemented");
require(source == 2 || source == 4 || source == 5 || source == 10 || source == 11 || source == CopyDataGpuClockSource || source == CopyDataRegisterSource, "COPY_DATA GDS or reference-clock source is not implemented");
require(source != CopyDataRegisterSource || (packet[3] == 0 && registerBankBase(packet[2]) != 0 && registerBankBase(packet[2]) == registerBankBase(packet[2] + copyDataCount(packet) - 1)), "COPY_DATA from a register outside the shader, context and user config registers is not implemented");
require(source < 10 || source == CopyDataGpuClockSource || ((packet[1] & 0x10000u) == 0 && packet[3] == 0), "64-bit immediate COPY_DATA is not implemented");
break;
}
Expand Down Expand Up @@ -601,10 +624,11 @@ std::optional<StoreWrite> ResolveStore(std::span<const std::uint32_t> packet, co
switch ((packet[0] >> 8u) & 0xffu) {
case 0x40: {
if (packet.size() < 6 || CopyDataToRegister(packet)) return std::nullopt;
if (CopyDataSource(packet) == CopyDataRegisterSource) return std::nullopt;
const std::size_t bytes = CopyDataBytes(packet);
const auto destination = address(packet[4], packet[5]);
if (!fits(destination, bytes)) return std::nullopt;
return StoreWrite{destination, {}, copyDataSource(packet, bytes)};
return StoreWrite{destination, {}, copyDataSource(packet, queue, bytes)};
}
case 0x50: {
if (packet.size() < 7 || dmaDestination(packet) == DmaSelectGds) return std::nullopt;
Expand Down Expand Up @@ -899,6 +923,15 @@ std::vector<std::uint32_t> ReadIndirectRegisters(std::span<const std::uint32_t>
return pairs;
}

void NoteDispatchRegisters(std::span<const std::uint32_t> packet, QueueState& queue) {
const bool direct = ((packet[0] >> 8u) & 0xffu) == 0x15u;
queue.shader.insert_or_assign(0x200, packet.back());
for (std::uint32_t i = 0; i < 3; ++i) {
if (direct) queue.shader.insert_or_assign(0x201 + i, packet[1 + i]);
else queue.shader.erase(0x201 + i);
}
}

void ExecuteIndirectRegisters(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> pairs, QueueState& queue) {
const auto opcode = (packet[0] >> 8u) & 0xffu;
require(packet.size() == 5 && IndirectRegisterOpcode(opcode), "expected indirect register packet");
Expand Down Expand Up @@ -1071,7 +1104,7 @@ void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
return;
}
const auto destination = address(packet[4], packet[5]);
const auto data = copyDataSource(packet, CopyDataBytes(packet));
const auto data = copyDataSource(packet, queue, CopyDataBytes(packet));
GuestMemory::CheckRange(reinterpret_cast<void*>(destination), data.size(), 1, true);
GuestMemory::Write(destination, data);
return;
Expand Down
23 changes: 23 additions & 0 deletions core/libs/prx/libSceAgcDriver/tests/Pm4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,29 @@ void testCopies() {
const auto clockStore =AgcDriver::Pm4::ResolveStore(clockCopy(&clock[0]), state, 64);
check(clockStore.has_value() && clockStore->Bytes().size() == 8, "GPU clock COPY_DATA did not resolve as an 8-byte store");
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x40, {0x1020a, 0, 0, low(&clock[0]), high(&clock[0])}), 0); }, "reference-clock");
alignas(8) std::array<std::uint32_t, 2> fromRegister{0xdeadbeef, 0xdeadbeef};
const auto registerCopy = [&](std::uint32_t control, std::uint32_t offset) { return makePacket(0x40, {control, offset, 0, low(fromRegister.data()), high(fromRegister.data())}); };
execute(state, makePacket(0x76, {0x20c, 0x02754800, 0x2}));
execute(state, registerCopy(0x00100200, 0x2e0c));
check(fromRegister[0] == 0x02754800 && fromRegister[1] == 0xdeadbeef, "COPY_DATA from a shader register failed");
execute(state, registerCopy(0x00110200, 0x2e0c));
check(fromRegister[0] == 0x02754800 && fromRegister[1] == 0x2, "64-bit COPY_DATA from two shader registers failed");
execute(state, makePacket(0x69, {0x10, 0xabc}));
execute(state, registerCopy(0x00100200, 0xa010));
check(fromRegister[0] == 0xabc, "COPY_DATA from a context register failed");
check(!AgcDriver::Pm4::ResolveStore(registerCopy(0x00100200, 0x2e0c), state, 64).has_value(), "COPY_DATA from a register resolved before its turn");
AgcDriver::Pm4::NoteDispatchRegisters(makePacket(0x15, {4, 2, 1, 0x8041}), state);
execute(state, registerCopy(0x00100200, 0x2e00));
check(fromRegister[0] == 0x8041, "COPY_DATA did not read the last dispatch initiator");
execute(state, registerCopy(0x00100200, 0x2e01));
check(fromRegister[0] == 4, "COPY_DATA did not read the last direct dispatch's group count");
AgcDriver::Pm4::NoteDispatchRegisters(makePacket(0x16, {0, 0x8001}), state);
execute(state, registerCopy(0x00100200, 0x2e00));
check(fromRegister[0] == 0x8001, "COPY_DATA did not read the last indirect dispatch initiator");
expectFailure([&] { execute(state, registerCopy(0x00100200, 0x2e01)); }, "has no known value");
expectFailure([&] { execute(state, registerCopy(0x00100200, 0x2e3f)); }, "has no known value");
expectFailure([&] { AgcDriver::Pm4::Validate(registerCopy(0x00100200, 0x1000), 0); }, "outside the shader, context and user config");
expectFailure([&] { AgcDriver::Pm4::Validate(registerCopy(0x00110200, 0x2fff), 0); }, "outside the shader, context and user config");
execute(state, makePacket(0x50, {0x60000000, low(source.data()), high(source.data()), low(destination.data()), high(destination.data()), 16}));
check(source == destination, "DMA_DATA copy failed");
execute(state, makePacket(0x50, {0x40000000, 0x44332211, 0, low(destination.data()), high(destination.data()), 6}));
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/libSceAgcDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
- [8_SRGB color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) on such a device throw unless they are single-mip 2D surfaces with a resident image; linear targets have none.
- [1_5_5_5 color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) render into A1R5G5B5. AMD formats keep the first component in the least significant bits (X in bits 0-4, Y in 5-9, Z in 10-14, W in bit 15, as in the [fpPS4](https://github.com/red-prig/fpPS4) `kSurfaceFormat1_5_5_5` doc comment and as measured for textures in the packed 16-bit textures entry), while A1R5G5B5 keeps R in bits 10-14 and B in bits 0-4, so the pixel exports are written with X and Z exchanged, which makes the stored bits follow the guest layout. The decoded format, component mapping and write mask are tested, and an execution test draws 32_ABGR exports into tiled and linear targets and compares every stored texel with the guest layout on the host device; the export direction is not measured on a PS5. Only UNORM with the standard component swap is accepted. Constant blend colors go through the same component-swap handling as other targets with that export mapping (`0xc6`), which is not tested for this format. PPSA06699 renders into this format.
- [COPY_DATA from the GPU clock](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 9) writes the host steady clock in 10 ns ticks when the packet runs, the same counter as the `RELEASE_MEM` timestamp, not the console's GPU clock. The `COPY_DATA` cache policy fields are accepted and have no effect.
- [COPY_DATA from a register](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, `SRC_SEL` 0) copies the value the queue holds for a shader, context or user config register (`COUNT_SEL` two consecutive ones) and throws for a register with no value. A dispatch records `COMPUTE_DISPATCH_INITIATOR` and, for `DISPATCH_DIRECT`, `COMPUTE_DIM_X/Y/Z` in the shader registers; `DISPATCH_INDIRECT` clears the three counts. Neither the register values the CP writes nor the copy's ordering against in-flight dispatches have been measured on hardware.
- [INDEX_ATTRIBUTES_INDIRECT](../../../core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp) (libSceAgcDriver, opcode 0x91) loads INDEX_BASE, INDEX_BUFFER_SIZE (in indices) and INDEX_TYPE from the 16-byte record at `attribute_base + attribute_index * 16`, read when the packet runs. The record layout (address low, address high, size, type) is PAL's [`IndexAttribIndirect`](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9Chip.h#L60-L67) and the packet is the one [`BuildIndexAttributesIndirect`](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/hw/gfxip/gfx9/gfx9CmdUtil.cpp#L2586-L2613) writes; neither is measured on a PS5 or seen in a title. `indirect_mode` 1 (`addr_offset`, which only PAL's ExecuteIndirect path emits) throws, and so does a record whose index type is above 3, as INDEX_TYPE does.
- [16-bit RGBA color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) support the standard and the alternate (`COMP_SWAP` 1) component order. Alternate swaps red and blue in shader exports, write masks and constant-color blend factors, following `ac_translate_colorswap` in [Mesa 26.2.2 ac_formats.c](https://gitlab.freedesktop.org/mesa/mesa/-/blob/mesa-26.2.2/src/amd/common/ac_formats.c). The reverse orders (`COMP_SWAP` 2 and 3) keep throwing when blending, and simultaneous targets requiring conflicting blend constants throw. Console equivalence has not been measured on hardware.
- [UINT16_ABGR color exports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - export format 7 is accepted only into an unsigned integer target, and each 16-bit value reaches the target unchanged; a value beyond a narrower target's range (`R8_UINT`) is undefined in Vulkan, and whether the console clamps or truncates it is not confirmed. SINT16_ABGR (8) throws
Expand Down
Loading