diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 35c5467efb7..58fde0469bf 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -194,6 +194,7 @@ std::vector DecodeStores(std::span 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 packet, const QueueState& queue); +void NoteDispatchRegisters(std::span packet, QueueState& queue); std::array ReadDispatchArguments(std::uint64_t arguments, std::uint32_t initiator); std::array PartialGroupThreads(const std::array& groups, const std::array& numThreads); std::array ResolveDispatch(std::span packet, const QueueState& queue); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp index 508bbbfcb11..441cbdf3c78 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp @@ -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; 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 492a2e4095e..8daba37b06a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -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) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index e7b12a29d83..fa0b1a21485 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -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 packet) { return (packet[1] & 0x10000u) != 0 ? 2u : 1u; @@ -142,8 +145,27 @@ std::uint64_t gpuClockCount() { return static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count() / 10); } -std::vector copyDataSource(std::span 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 copyDataSource(std::span packet, const QueueState& queue, std::size_t bytes) { const auto source = CopyDataSource(packet); + if (source == CopyDataRegisterSource) { + std::vector data(bytes); + for (std::size_t word = 0; word < bytes / 4; ++word) { + const auto offset = packet[2] + static_cast(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(value >> (i * 8)); + } + return data; + } if (source != CopyDataGpuClockSource) return copySource(address(packet[2], packet[3]), bytes, CopyDataImmediate(source)); const auto value = gpuClockCount(); std::vector data(bytes); @@ -474,7 +496,8 @@ void Validate(std::span 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; } @@ -601,10 +624,11 @@ std::optional ResolveStore(std::span 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; @@ -899,6 +923,15 @@ std::vector ReadIndirectRegisters(std::span return pairs; } +void NoteDispatchRegisters(std::span 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 packet, std::span pairs, QueueState& queue) { const auto opcode = (packet[0] >> 8u) & 0xffu; require(packet.size() == 5 && IndirectRegisterOpcode(opcode), "expected indirect register packet"); @@ -1071,7 +1104,7 @@ void Execute(std::span 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(destination), data.size(), 1, true); GuestMemory::Write(destination, data); return; diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index 44a2886d86a..196f5d0a1c1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -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 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})); diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index ca8db69c907..358c130fa30 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -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