Agc: implement missing PM4 command-buffer opcodes (Jump/Rewind/Predication/MemSemaphore/CopyData/AtomicMem/DrawIndexMultiInstanced)

Fills in guest-facing sceAgc* entry points across Acb/Cb/DcbDraw/DcbFlow/DcbState/Misc
that were still stubbed with NotImplemented_nid_no_patch on current upstream, one layer
below upstream's Recorder rearchitecture:

- Command/Control.{hpp,cpp} (new): WriteJump, WriteRewind, WritePredication,
  WriteMemSemaphore helpers shared by the Acb/Dcb entry points below.
- Command/Memory.{hpp,cpp}: WriteCopyData, WriteAtomicMem helpers.
- Acb/Control.cpp: Jump/JumpGetSize, Rewind/RewindGetSize.
- Acb/Dispatch.cpp: DispatchIndirectGetSize.
- Acb/Memory.cpp: AcquireMemGetSize, CopyData(+Size), AtomicMem(+Size),
  AtomicGdsGetSize, PrimeUtcl2(+Size).
- Acb/Sync.cpp: CondExec(+Size), WaitOnAddressGetSize, EventWriteGetSize,
  QueueEndOfShaderActionGetSize, MemSemaphore.
- Cb/Dispatch.cpp: DispatchGetSize only (sceAgcCbDispatch itself left untouched --
  upstream's modifier mask 0xa038u|0x41u there is correct as-is).
- DcbDraw/DrawIndexed.cpp: DrawIndexMultiInstanced(+GetSize), still unimplemented
  even after upstream's own indexed-draw work.
- DcbFlow/Control.cpp: Jump, Rewind(+Size), and a real WaitUntilSafeForRendering that
  emits AgcDriver::RenderingWaitPacketHeader (recognized by the executor's fence wait
  in Driver.cpp) instead of upstream's plain-NOP stub, which the executor does not
  special-case.
- DcbFlow/Memory.cpp: AcquireMemGetSize, CopyData(+Size), DmaDataGetSize,
  AtomicMem(+Size), AtomicGdsGetSize, WriteDataGetSize.
- DcbFlow/Sync.cpp: CondExec(+Size), WaitOnAddressGetSize,
  StallCommandBufferParserGetSize, MemSemaphore.
- DcbState/Predication.cpp: SetPredication only (the file's 3 GetSize functions are
  left as upstream's diagnostic stubs, not real fixes).
- Misc/PacketInfo.cpp: sceAgcDcbGetSystemSoftwareVersion.

ABI correction: Command::WriteMemSemaphore takes the corrected 4-argument form
(address, waitForMailbox, signalType, operation). The Acb/DcbFlow sceAgc*MemSemaphore
entry points still expose the older (action, clientCode, address) guest signature;
their bodies now decode `action` into the signal/wait operation and the low two bits
of `clientCode` into signalType/waitForMailbox before calling the corrected helper.

Not ported (already superseded or out of scope per review): WriteMarker/WriteCustomNop/
WriteEventWrite/WriteCondExec/WriteDispatchIndirectAddress (duplicate upstream's
existing Marker/EventWrite/DispatchIndirect logic), Acb DmaData/DmaDataGetSize/
WriteData/EventWrite (already implemented upstream), DcbFlow EventWrite(+Size)
(upstream's version handles the 8-byte occlusion-address case more completely),
DcbDraw's other DrawIndex* entry points (already implemented upstream),
Misc/Trinity.cpp, Patch/WriteData.cpp, DcbState/Marker.cpp (all superseded by
upstream equivalents).
This commit is contained in:
OverkillLabs 2026-09-28 13:25:15 +03:00
parent cdc95d9a70
commit 0c41dc99d5
16 changed files with 207 additions and 160 deletions

View File

@ -1,5 +1,6 @@
#include "prx/libSceAgc/Acb/include/Control.hpp"
#include "prx/libSceAgc/Command/include/Control.hpp"
#include "prx/libSceAgc/Command/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include "prx/libSceAgc/DcbState/include/Marker.hpp"
@ -18,17 +19,12 @@ void* APS5_VABI sceAgcAcb_gQkqkLttcpw (void) {
}
std::uint32_t* APS5_VABI sceAgcAcbJump(CommandBuffer* buf, std::uint8_t cachePolicy, const std::uint32_t* target, std::uint32_t sizeInDwords) {
(void)buf;
(void)cachePolicy;
(void)target;
(void)sizeInDwords;
NotImplemented_nid_no_patch(__func__);
return 0;
// The compute ring has no call stack to return to, so the jump always chains.
return Agc::Command::WriteJump(buf, 1, cachePolicy, target, sizeInDwords, __func__);
}
uint32_t APS5_VABI sceAgcAcbJumpGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 16; // INDIRECT_BUFFER: header + 3 dwords
}
// Encoded as the custom DISPATCH_RESET NOP packet the driver consumes on compute queues.
@ -44,15 +40,11 @@ uint32_t* APS5_VABI sceAgcAcbResetQueue(CommandBuffer* buf, uint32_t op, uint32_
}
std::uint32_t* APS5_VABI sceAgcAcbRewind(CommandBuffer* buf, std::uint32_t initialState) {
(void)buf;
(void)initialState;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteRewind(buf, initialState, __func__);
}
std::uint32_t APS5_VABI sceAgcAcbRewindGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 8; // REWIND: header + 1 dword
}
std::uint32_t* APS5_VABI sceAgcAcbWaitUntilSafeForRendering(CommandBuffer* buf, std::uint32_t videoOutHandle, std::uint32_t displayBufferIndex) {

View File

@ -18,8 +18,7 @@ uint32_t* APS5_VABI sceAgcAcbDispatchIndirect(CommandBuffer* buf, const volatile
}
std::uint32_t APS5_VABI sceAgcAcbDispatchIndirectGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 16; // DISPATCH_INDIRECT with an absolute address: header + address lo/hi + initiator
}
}

View File

@ -15,27 +15,15 @@ std::uint32_t* APS5_VABI sceAgcAcbAcquireMem(CommandBuffer* buf, std::uint32_t g
}
uint32_t APS5_VABI sceAgcAcbAcquireMemGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 32; // ACQUIRE_MEM: header + 7 dwords
}
uint32_t* APS5_VABI sceAgcAcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy, uint64_t dst_address, uint8_t src, uint8_t src_cache_policy, uint64_t src_address_or_immediate, uint8_t item_size, uint8_t write_confirm) {
(void)buf;
(void)dst;
(void)dst_cache_policy;
(void)dst_address;
(void)src;
(void)src_cache_policy;
(void)src_address_or_immediate;
(void)item_size;
(void)write_confirm;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteCopyData(buf, true, dst, dst_cache_policy, dst_address, src, src_cache_policy, src_address_or_immediate, item_size, write_confirm, __func__);
}
std::uint64_t APS5_VABI sceAgcAcbCopyDataGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 24; // COPY_DATA: header + 5 dwords
}
std::uint32_t* APS5_VABI sceAgcAcbDmaData(CommandBuffer* buf, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddress, std::uint32_t numBytes, std::uint8_t waitForPrevious, std::uint8_t writeConfirm) {
@ -48,39 +36,31 @@ std::uint32_t APS5_VABI sceAgcAcbDmaDataGetSize() {
}
std::uint32_t* APS5_VABI sceAgcAcbAtomicMem(CommandBuffer* buf, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval) {
(void)buf;
(void)atomicOp;
(void)command;
(void)cachePolicy;
(void)address;
(void)srcData;
(void)compareData;
(void)loopInterval;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteAtomicMem(buf, atomicOp, command, cachePolicy, address, srcData, compareData, loopInterval, __func__);
}
std::uint32_t APS5_VABI sceAgcAcbAtomicMemGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 36; // ATOMIC_MEM: header + 8 dwords
}
std::uint32_t APS5_VABI sceAgcAcbAtomicGdsGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 36; // upper bound: ATOMIC_GDS is not emitted by this port (header + 8 dwords reserved)
}
std::uint32_t* APS5_VABI sceAgcAcbPrimeUtcl2(CommandBuffer* buf, const volatile void* address, std::uint32_t sizeInBytes) {
(void)buf;
(void)address;
(void)sizeInBytes;
NotImplemented_nid_no_patch(__func__);
return nullptr;
// Page-table prefetch hint: a NOP of the reserved size that keeps the request readable in dumps.
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
auto* packet = Agc::Command::Allocate(buf, 5, __func__);
packet[0] = Agc::Command::Header(0x10u, 5);
packet[1] = 0;
packet[2] = static_cast<std::uint32_t>(guestAddress);
packet[3] = static_cast<std::uint32_t>(guestAddress >> 32u);
packet[4] = sizeInBytes;
return packet;
}
std::uint32_t APS5_VABI sceAgcAcbPrimeUtcl2GetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 20;
}
std::uint32_t* APS5_VABI sceAgcAcbWriteData(CommandBuffer* buf, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t numDwords, std::uint8_t increment, std::uint8_t writeConfirm) {

View File

@ -1,5 +1,6 @@
#include "prx/libSceAgc/Acb/include/Sync.hpp"
#include "prx/libSceAgc/Command/include/Control.hpp"
#include "prx/libSceAgc/Command/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include <cstdint>
@ -10,16 +11,15 @@
extern "C" {
uint32_t* APS5_VABI sceAgcAcbCondExec(CommandBuffer* buf, const volatile uint32_t* address, uint32_t num_dwords) {
(void)buf;
(void)address;
(void)num_dwords;
NotImplemented_nid_no_patch(__func__);
return nullptr;
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
Agc::Command::CheckAddress(guestAddress, 4, __func__);
Agc::Command::Require(num_dwords <= 0x3fffu, __func__, "conditional execution range is too large");
// COND_EXEC: the CP skips exec_count dwords when the 32-bit value at the address is zero.
return Agc::Command::Emit(buf, 0x22u, {static_cast<std::uint32_t>(guestAddress) & ~3u, static_cast<std::uint32_t>(guestAddress >> 32u), 0, num_dwords & 0x3fffu}, __func__);
}
uint32_t APS5_VABI sceAgcAcbCondExecGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 20; // COND_EXEC: header + 4 dwords
}
std::uint32_t* APS5_VABI sceAgcAcbWaitRegMem(CommandBuffer* buf, std::uint8_t size, std::uint8_t compareFunction, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t reference, std::uint64_t mask, std::uint32_t pollCycles) {
@ -27,9 +27,8 @@ std::uint32_t* APS5_VABI sceAgcAcbWaitRegMem(CommandBuffer* buf, std::uint8_t si
}
std::uint64_t APS5_VABI sceAgcAcbWaitOnAddressGetSize(std::uint8_t size) {
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
// WriteWait: bracket (4 + 3 dwords) around a 7 dword WAIT_REG_MEM or a 9 dword WAIT_REG_MEM_64.
return size == 0 ? 56 : 64;
}
std::uint32_t* APS5_VABI sceAgcAcbEventWrite(CommandBuffer* buf, std::uint8_t eventType, const volatile void* address) {
@ -39,23 +38,25 @@ std::uint32_t* APS5_VABI sceAgcAcbEventWrite(CommandBuffer* buf, std::uint8_t ev
}
std::uint64_t APS5_VABI sceAgcAcbEventWriteGetSize(std::uint8_t eventType) {
// This ACB entry point (unlike the DCB one) never carries an address, so it is always the
// single-dword EVENT_WRITE form regardless of event type.
(void)eventType;
NotImplemented_nid_no_patch(__func__);
return 0;
return 8;
}
std::uint32_t APS5_VABI sceAgcAcbQueueEndOfShaderActionGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 32; // upper bound, sized like the end-of-pipe RELEASE_MEM
}
std::uint32_t* APS5_VABI sceAgcAcbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address) {
(void)buf;
(void)action;
(void)clientCode;
(void)address;
NotImplemented_nid_no_patch(__func__);
return nullptr;
// ABI correction: Command::WriteMemSemaphore takes the corrected 4-argument form
// (address, waitForMailbox, signalType, operation) -- an earlier revision of this helper used a
// different, incompatible argument order. `action` selects signal (0) vs wait (1); the low two
// bits of `clientCode` carry the packet's signal-type and mailbox-wait flags.
const std::uint8_t operation = action != 0 ? 7u : 6u;
const std::uint8_t signalType = clientCode & 1u;
const std::uint8_t waitForMailbox = (clientCode >> 1u) & 1u;
return Agc::Command::WriteMemSemaphore(buf, address, waitForMailbox, signalType, operation, __func__);
}
}

View File

@ -9,6 +9,7 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Cb/src/Registers.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Cb/src/Sync.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Command/src/Memory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Command/src/Control.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Command/src/Draw.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Command/src/RegisterDefaults.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Command/src/Packet.cpp

View File

@ -14,8 +14,7 @@ std::uint32_t* APS5_VABI sceAgcCbDispatch(CommandBuffer* buf, std::uint32_t thre
}
uint32_t APS5_VABI sceAgcCbDispatchGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 20; // DISPATCH_DIRECT: header + 4 dwords
}
}

View File

@ -0,0 +1,17 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_COMMAND_INCLUDE_CONTROL_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_COMMAND_INCLUDE_CONTROL_HPP
#include "prx/libSceAgc/Command/include/Packet.hpp"
namespace Agc::Command {
std::uint32_t* WriteJump(CommandBuffer* buffer, std::uint8_t chain, std::uint8_t cachePolicy, const std::uint32_t* target, std::uint32_t sizeInDwords, const char* function);
std::uint32_t* WriteRewind(CommandBuffer* buffer, std::uint32_t initialState, const char* function);
std::uint32_t* WritePredication(CommandBuffer* buffer, std::uint8_t condition, std::uint8_t operation, std::uint8_t waitOperation, const volatile void* address, const char* function);
// Signature corrected to (address, waitForMailbox, signalType, operation) to match the guest-facing
// sceAgc*MemSemaphore entry points; an earlier revision of this helper took a different argument order.
std::uint32_t* WriteMemSemaphore(CommandBuffer* buffer, const volatile void* address, std::uint8_t waitForMailbox, std::uint8_t signalType, std::uint8_t operation, const char* function);
}
#endif

View File

@ -6,6 +6,8 @@
namespace Agc::Command {
std::uint32_t* WriteDma(CommandBuffer* buffer, bool compute, std::uint8_t engine, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddress, std::uint32_t numBytes, std::uint8_t waitForPrevious, std::uint8_t writeConfirm, std::uint8_t blockEngine, const char* function);
std::uint32_t* WriteCopyData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddressOrImmediate, std::uint8_t itemSize, std::uint8_t writeConfirm, const char* function);
std::uint32_t* WriteAtomicMem(CommandBuffer* buffer, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval, const char* function);
std::uint32_t* WriteData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t count, std::uint8_t increment, std::uint8_t writeConfirm, const char* function);
std::uint32_t* WriteWait(CommandBuffer* buffer, std::uint8_t size, std::uint8_t compareFunction, std::uint8_t operation, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t reference, std::uint64_t mask, std::uint32_t pollCycles, const char* function);
std::uint32_t* ValidateWait(std::uint32_t* packet, const char* function);

View File

@ -0,0 +1,41 @@
#include "prx/libSceAgc/Command/include/Control.hpp"
namespace Agc::Command {
std::uint32_t* WriteJump(CommandBuffer* buffer, std::uint8_t chain, std::uint8_t cachePolicy, const std::uint32_t* target, std::uint32_t sizeInDwords, const char* function) {
const auto guestAddress = reinterpret_cast<std::uintptr_t>(target);
CheckBits(chain, 1, function);
CheckBits(cachePolicy, 3, function);
CheckBits(sizeInDwords, 0xfffffu, function);
CheckAddress(guestAddress, 4, function);
// INDIRECT_BUFFER: address, then size in bits 19:0, chain (no return) in bit 20, cache policy in 29:28.
return Emit(buffer, 0x3fu, {static_cast<std::uint32_t>(guestAddress) & ~3u, static_cast<std::uint32_t>(guestAddress >> 32u), 0x0f200000u | (static_cast<std::uint32_t>(cachePolicy) << 28u) | (static_cast<std::uint32_t>(chain) << 20u) | sizeInDwords}, function);
}
std::uint32_t* WriteRewind(CommandBuffer* buffer, std::uint32_t initialState, const char* function) {
return Emit(buffer, 0x59u, {(initialState & 1u) << 31u}, function);
}
std::uint32_t* WritePredication(CommandBuffer* buffer, std::uint8_t condition, std::uint8_t operation, std::uint8_t waitOperation, const volatile void* address, const char* function) {
CheckBits(condition, 1, function);
CheckBits(operation, 7, function);
CheckBits(waitOperation, 1, function);
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
if (operation != 0) {
CheckAddress(guestAddress, 8, function);
}
// SET_PREDICATION: condition in bit 8, wait in bit 12, operation in 18:16; operation 0 clears predication.
return Emit(buffer, 0x20u, {(static_cast<std::uint32_t>(condition) << 8u) | (static_cast<std::uint32_t>(waitOperation) << 12u) | (static_cast<std::uint32_t>(operation) << 16u), static_cast<std::uint32_t>(guestAddress) & ~0xfu, static_cast<std::uint32_t>(guestAddress >> 32u)}, function);
}
std::uint32_t* WriteMemSemaphore(CommandBuffer* buffer, const volatile void* address, std::uint8_t waitForMailbox, std::uint8_t signalType, std::uint8_t operation, const char* function) {
CheckBits(waitForMailbox, 1, function);
CheckBits(signalType, 1, function);
Require(operation == 6 || operation == 7, function, "invalid memory semaphore operation");
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
CheckAddress(guestAddress, 8, function);
// MEM_SEMAPHORE: operation in 31:29 (6 signal, 7 wait), signal type in bit 20, mailbox wait in bit 16.
return Emit(buffer, 0x39u, {static_cast<std::uint32_t>(guestAddress), static_cast<std::uint32_t>(guestAddress >> 32u), (static_cast<std::uint32_t>(operation) << 29u) | (static_cast<std::uint32_t>(signalType) << 20u) | (static_cast<std::uint32_t>(waitForMailbox) << 16u)}, function);
}
}

View File

@ -26,6 +26,32 @@ std::uint32_t* WriteDma(CommandBuffer* buffer, bool compute, std::uint8_t engine
return Emit(buffer, 0x50u, {control, static_cast<std::uint32_t>(srcAddress), static_cast<std::uint32_t>(srcAddress >> 32u), static_cast<std::uint32_t>(dstAddress), static_cast<std::uint32_t>(dstAddress >> 32u), command}, function);
}
std::uint32_t* WriteCopyData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddressOrImmediate, std::uint8_t itemSize, std::uint8_t writeConfirm, const char* function) {
CheckBits(dstCachePolicy, 3, function);
CheckBits(srcCachePolicy, 3, function);
CheckBits(itemSize, 1, function);
CheckBits(writeConfirm, 1, function);
// Graphics selectors are (select << 1) | engine, compute selectors are the raw 4-bit COPY_DATA select values.
const auto srcSelect = compute ? static_cast<std::uint32_t>(src) & 0xfu : (static_cast<std::uint32_t>(src) >> 1u) & 0xfu;
const auto dstSelect = compute ? static_cast<std::uint32_t>(dst) & 0xfu : (static_cast<std::uint32_t>(dst) >> 1u) & 0xfu;
const auto engine = compute ? 0u : static_cast<std::uint32_t>(src) & 1u;
Require(srcSelect != 5 || itemSize != 0 || (srcAddressOrImmediate >> 32u) == 0, function, "immediate exceeds the 32-bit item size");
const auto control = srcSelect | (dstSelect << 8u) | (static_cast<std::uint32_t>(srcCachePolicy) << 13u) | (static_cast<std::uint32_t>(itemSize) << 16u) | (static_cast<std::uint32_t>(writeConfirm) << 20u) | (static_cast<std::uint32_t>(dstCachePolicy) << 25u) | (engine << 30u);
return Emit(buffer, 0x40u, {control, static_cast<std::uint32_t>(srcAddressOrImmediate), static_cast<std::uint32_t>(srcAddressOrImmediate >> 32u), static_cast<std::uint32_t>(dstAddress), static_cast<std::uint32_t>(dstAddress >> 32u)}, function);
}
std::uint32_t* WriteAtomicMem(CommandBuffer* buffer, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval, const char* function) {
CheckBits(atomicOp, 0x7fu, function);
CheckBits(command, 0xfu, function);
CheckBits(cachePolicy, 3, function);
CheckBits(loopInterval, 0x1fffu, function);
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
// Opcodes 96..127 are the 64-bit forms of 64..95.
CheckAddress(guestAddress, (atomicOp & 0x60u) == 0x60u ? 8 : 4, function);
// ATOMIC_MEM: control, address, source data, compare data, loop interval (9 dwords with the header).
return Emit(buffer, 0x1eu, {atomicOp | (static_cast<std::uint32_t>(command) << 8u) | (static_cast<std::uint32_t>(cachePolicy) << 25u), static_cast<std::uint32_t>(guestAddress), static_cast<std::uint32_t>(guestAddress >> 32u), static_cast<std::uint32_t>(srcData), static_cast<std::uint32_t>(srcData >> 32u), static_cast<std::uint32_t>(compareData), static_cast<std::uint32_t>(compareData >> 32u), loopInterval}, function);
}
std::uint32_t* WriteData(CommandBuffer* buffer, bool compute, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t count, std::uint8_t increment, std::uint8_t writeConfirm, const char* function) {
Require(data != nullptr && count != 0 && count <= 0x3ffdu, function, "invalid write data payload");
CheckBits(dst, compute ? 0xfu : 0x1fu, function);

View File

@ -66,19 +66,26 @@ std::uint32_t APS5_VABI sceAgcDcbDrawIndexIndirectMultiGetSize() {
}
uint32_t* APS5_VABI sceAgcDcbDrawIndexMultiInstanced(CommandBuffer* buf, uint32_t index_count, const volatile void* index_addr, const volatile void* object_ids, uint32_t instance_count, uint64_t modifier) {
(void)buf;
(void)index_count;
(void)index_addr;
(void)object_ids;
(void)instance_count;
(void)modifier;
NotImplemented_nid_no_patch(__func__);
return nullptr;
const auto geometry = reinterpret_cast<std::uintptr_t>(index_addr);
Agc::Command::CheckAddress(geometry, 2, __func__);
Agc::Command::Require(object_ids == nullptr, __func__, "per-object id base has no executor contract; expanding to one instanced draw");
const auto initiator = Agc::Command::DrawInitiator(modifier, true, __func__);
// NUM_INSTANCES then DRAW_INDEX_2 in one reservation: the executor reads instanceCount from queue
// state a prior NUM_INSTANCES wrote, so the two must stay adjacent and atomic under a grow callback.
auto* packet = Agc::Command::Allocate(buf, 8, __func__);
packet[0] = Agc::Command::Header(0x2fu, 2);
packet[1] = instance_count;
packet[2] = Agc::Command::Header(0x27u, 6);
packet[3] = index_count == 0 ? 1u : index_count;
packet[4] = static_cast<std::uint32_t>(geometry);
packet[5] = static_cast<std::uint32_t>(static_cast<std::uint64_t>(geometry) >> 32u);
packet[6] = index_count;
packet[7] = initiator;
return packet;
}
uint32_t APS5_VABI sceAgcDcbDrawIndexMultiInstancedGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 32; // NUM_INSTANCES (2 dw) + DRAW_INDEX_2 (6 dw)
}
}

View File

@ -1,7 +1,9 @@
#include "prx/libSceAgc/DcbFlow/include/Control.hpp"
#include "prx/libSceAgc/Command/include/Control.hpp"
#include "prx/libSceAgc/Command/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
@ -19,13 +21,8 @@ void* APS5_VABI sceAgcDcbA_zARR5aCmkoY(void) {
uint32_t* APS5_VABI sceAgcDcbJump(CommandBuffer* buf, uint8_t mode, uint8_t cache_policy, const uint32_t* target, uint32_t size_in_dwords) {
(void)buf;
(void)mode;
(void)cache_policy;
(void)target;
(void)size_in_dwords;
NotImplemented_nid_no_patch(__func__);
return nullptr;
// mode 1 chains (the target replaces the rest of this buffer), mode 0 calls and returns after the target.
return Agc::Command::WriteJump(buf, mode, cache_policy, target, size_in_dwords, __func__);
}
std::uint32_t APS5_VABI sceAgcDcbJumpGetSize() {
@ -39,21 +36,23 @@ std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t o
}
uint32_t* APS5_VABI sceAgcDcbRewind(CommandBuffer* buf, uint32_t initial_state) {
(void)buf;
(void)initial_state;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteRewind(buf, initial_state, __func__);
}
uint32_t APS5_VABI sceAgcDcbRewindGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 8; // REWIND: header + 1 dword
}
uint32_t* APS5_VABI sceAgcDcbWaitUntilSafeForRendering(CommandBuffer* buf, uint32_t video_out_handle, uint32_t display_buffer_index) {
// Presentation copies the display buffer when the flip executes, so the buffer is always safe to
// render into; keep the arguments in a NOP for command-buffer dumps.
return Agc::Command::Emit(buf, 0x10u, {video_out_handle, display_buffer_index}, __func__);
// Same packet sceAgcDriverWaitUntilSafeForRendering produces; the executor specifically recognizes
// AgcDriver::RenderingWaitPacketHeader (see Driver.cpp) and blocks on the display-buffer reuse fence.
// A plain NOP with these arguments (upstream's previous stub) is not matched by that check.
auto* packet = Agc::Command::Allocate(buf, AgcDriver::RenderingWaitPacketWords, __func__);
packet[0] = AgcDriver::RenderingWaitPacketHeader;
packet[1] = video_out_handle;
packet[2] = display_buffer_index;
packet[3] = 0;
return packet;
}
}

View File

@ -25,27 +25,15 @@ std::uint32_t* APS5_VABI sceAgcDcbAcquireMem(CommandBuffer* buf, std::uint8_t en
}
uint32_t APS5_VABI sceAgcDcbAcquireMemGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 32; // ACQUIRE_MEM: header + 7 dwords
}
uint32_t* APS5_VABI sceAgcDcbCopyData(CommandBuffer* buf, uint8_t dst, uint8_t dst_cache_policy, uint64_t dst_address, uint8_t src, uint8_t src_cache_policy, uint64_t src_address_or_immediate, uint8_t item_size, uint8_t write_confirm) {
(void)buf;
(void)dst;
(void)dst_cache_policy;
(void)dst_address;
(void)src;
(void)src_cache_policy;
(void)src_address_or_immediate;
(void)item_size;
(void)write_confirm;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteCopyData(buf, false, dst, dst_cache_policy, dst_address, src, src_cache_policy, src_address_or_immediate, item_size, write_confirm, __func__);
}
std::uint64_t APS5_VABI sceAgcDcbCopyDataGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 24; // COPY_DATA: header + 5 dwords
}
std::uint32_t* APS5_VABI sceAgcDcbDmaData(CommandBuffer* buf, std::uint8_t engine, std::uint8_t dst, std::uint8_t dstCachePolicy, std::uint64_t dstAddress, std::uint8_t src, std::uint8_t srcCachePolicy, std::uint64_t srcAddress, std::uint32_t numBytes, std::uint8_t waitForPrevious, std::uint8_t writeConfirm, std::uint8_t blockEngine) {
@ -53,31 +41,19 @@ std::uint32_t* APS5_VABI sceAgcDcbDmaData(CommandBuffer* buf, std::uint8_t engin
}
std::uint32_t APS5_VABI sceAgcDcbDmaDataGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 28; // DMA_DATA: header + 6 dwords
}
std::uint32_t* APS5_VABI sceAgcDcbAtomicMem(CommandBuffer* buf, std::uint8_t atomicOp, std::uint8_t command, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t srcData, std::uint64_t compareData, std::uint16_t loopInterval) {
(void)buf;
(void)atomicOp;
(void)command;
(void)cachePolicy;
(void)address;
(void)srcData;
(void)compareData;
(void)loopInterval;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteAtomicMem(buf, atomicOp, command, cachePolicy, address, srcData, compareData, loopInterval, __func__);
}
std::uint32_t APS5_VABI sceAgcDcbAtomicMemGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 36; // ATOMIC_MEM: header + 8 dwords
}
std::uint32_t APS5_VABI sceAgcDcbAtomicGdsGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 36; // upper bound: ATOMIC_GDS is not emitted by this port (header + 8 dwords reserved)
}
std::uint32_t* APS5_VABI sceAgcDcbWriteData(CommandBuffer* buf, std::uint8_t dst, std::uint8_t cachePolicy, std::uint64_t address, const void* data, std::uint32_t numDwords, std::uint8_t increment, std::uint8_t writeConfirm) {
@ -85,9 +61,7 @@ std::uint32_t* APS5_VABI sceAgcDcbWriteData(CommandBuffer* buf, std::uint8_t dst
}
uint32_t APS5_VABI sceAgcDcbWriteDataGetSize(uint32_t num_dwords) {
(void)num_dwords;
NotImplemented_nid_no_patch(__func__);
return 0;
return 4u * num_dwords + 16u; // WRITE_DATA: header + control + address lo/hi + payload
}
}

View File

@ -1,5 +1,6 @@
#include "prx/libSceAgc/DcbFlow/include/Sync.hpp"
#include "prx/libSceAgc/Command/include/Control.hpp"
#include "prx/libSceAgc/Command/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include <cstdint>
@ -10,16 +11,15 @@
extern "C" {
uint32_t* APS5_VABI sceAgcDcbCondExec(CommandBuffer* buf, const volatile uint32_t* address, uint32_t num_dwords) {
(void)buf;
(void)address;
(void)num_dwords;
NotImplemented_nid_no_patch(__func__);
return nullptr;
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
Agc::Command::CheckAddress(guestAddress, 4, __func__);
Agc::Command::Require(num_dwords <= 0x3fffu, __func__, "conditional execution range is too large");
// COND_EXEC: the CP skips exec_count dwords when the 32-bit value at the address is zero.
return Agc::Command::Emit(buf, 0x22u, {static_cast<std::uint32_t>(guestAddress) & ~3u, static_cast<std::uint32_t>(guestAddress >> 32u), 0, num_dwords & 0x3fffu}, __func__);
}
uint32_t APS5_VABI sceAgcDcbCondExecGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 20; // COND_EXEC: header + 4 dwords
}
std::uint32_t* APS5_VABI sceAgcDcbWaitRegMem(CommandBuffer* buf, std::uint8_t size, std::uint8_t compareFunction, std::uint8_t operation, std::uint8_t cachePolicy, const volatile void* address, std::uint64_t reference, std::uint64_t mask, std::uint32_t pollCycles) {
@ -27,9 +27,8 @@ std::uint32_t* APS5_VABI sceAgcDcbWaitRegMem(CommandBuffer* buf, std::uint8_t si
}
uint32_t APS5_VABI sceAgcDcbWaitOnAddressGetSize(uint32_t size) {
(void)size;
NotImplemented_nid_no_patch(__func__);
return 0;
// WriteWait: bracket (4 + 3 dwords) around a 7 dword WAIT_REG_MEM or a 9 dword WAIT_REG_MEM_64.
return size == 0 ? 56 : 64;
}
std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t eventType, const volatile void* address) {
@ -55,17 +54,18 @@ std::uint32_t* APS5_VABI sceAgcDcbStallCommandBufferParser(CommandBuffer* buf) {
}
std::uint32_t APS5_VABI sceAgcDcbStallCommandBufferParserGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 8; // PFP_SYNC_ME: header + 1 dword
}
std::uint32_t* APS5_VABI sceAgcDcbMemSemaphore(CommandBuffer* buf, std::uint8_t action, std::uint8_t clientCode, const volatile void* address) {
(void)buf;
(void)action;
(void)clientCode;
(void)address;
NotImplemented_nid_no_patch(__func__);
return nullptr;
// ABI correction: Command::WriteMemSemaphore takes the corrected 4-argument form
// (address, waitForMailbox, signalType, operation) -- an earlier revision of this helper used a
// different, incompatible argument order. `action` selects signal (0) vs wait (1); the low two
// bits of `clientCode` carry the packet's signal-type and mailbox-wait flags.
const std::uint8_t operation = action != 0 ? 7u : 6u;
const std::uint8_t signalType = clientCode & 1u;
const std::uint8_t waitForMailbox = (clientCode >> 1u) & 1u;
return Agc::Command::WriteMemSemaphore(buf, address, waitForMailbox, signalType, operation, __func__);
}
}

View File

@ -1,5 +1,6 @@
#include "prx/libSceAgc/DcbState/include/Predication.hpp"
#include "prx/libSceAgc/Command/include/Control.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include <cstdint>
#include <cstddef>
@ -9,14 +10,10 @@
extern "C" {
uint32_t* APS5_VABI sceAgcDcbSetPredication(CommandBuffer* buf, uint8_t condition, uint8_t op, uint8_t wait_op, const volatile void* address, uint32_t count_in_dwords) {
(void)buf;
(void)condition;
(void)op;
(void)wait_op;
(void)address;
(void)count_in_dwords;
NotImplemented_nid_no_patch(__func__);
return nullptr;
// SET_PREDICATION turns predication on for the packets that follow it; the range length is a
// guest-side bookkeeping hint the single PM4 packet does not carry.
(void)count_in_dwords;
return Agc::Command::WritePredication(buf, condition, op, wait_op, address, __func__);
}
std::uint32_t APS5_VABI sceAgcDcbSetZPassPredicationEnableGetSize() {

View File

@ -26,4 +26,16 @@ int APS5_VABI sceAgcGetDataPacketPayloadAddressUnk(uint32_t** addr, uint32_t* cm
return 0;
}
// SDK 9 Dcb ABI: (destination, packet). Reads the AGC system-software version stamped into the
// first payload dword of an AGC data packet and copies it to the caller's destination.
int APS5_VABI sceAgcDcbGetSystemSoftwareVersion(uint32_t* destination, const uint32_t* packet) {
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(destination), alignof(uint32_t), __func__);
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(packet), alignof(uint32_t), __func__);
Agc::Command::Require((packet[0] & 0xc0000000u) == 0xc0000000u, __func__, "source pointer is not a PM4 packet");
const auto size = ((packet[0] >> 16u) & 0x3fffu) + 2u;
Agc::Command::Require(size >= 3u, __func__, "PM4 packet is too short to carry a software version");
*destination = packet[1];
return 0;
}
}