feat(agc): packet sizes, register writes, GS/HS fusion, jump patches

- packet sizes for the existing builders and the tessellation ring
  config
- direct register writes and end-of-pipe action data, type and cache
  patches
- GS/HS shader halves fused by merging their registers, after Kyty
- jump target patches; reserved jumps are allowed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
STL1te 2026-09-29 07:53:50 +03:00
parent c206f7049a
commit fd4700b909
12 changed files with 178 additions and 81 deletions

View File

@ -17,15 +17,11 @@ std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuffer* buf, st
}
uint32_t APS5_VABI sceAgcCbSetShRegisterRangeDirectGetSize(uint32_t num_values) {
(void)num_values;
NotImplemented_nid_no_patch(__func__);
return 0;
return (num_values + 2u) * 4u; // header + offset + values
}
std::uint32_t APS5_VABI sceAgcCbSetUcRegisterRangeDirectGetSize(std::uint32_t numValues) {
(void)numValues;
NotImplemented_nid_no_patch(__func__);
return 0;
return (numValues + 2u) * 4u; // header + offset + values
}
std::uint32_t* APS5_VABI sceAgcCbSetShRegistersDirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs) {

View File

@ -15,9 +15,7 @@ std::uint32_t* APS5_VABI sceAgcCbNop_nid_postfix(CommandBuffer* buf, std::uint32
}
uint32_t APS5_VABI sceAgcCbNopGetSize(uint32_t size_in_dwords) {
(void)size_in_dwords;
NotImplemented_nid_no_patch(__func__);
return 0;
return size_in_dwords * 4u; // a NOP packet is exactly size_in_dwords dwords including its header
}
std::uint32_t APS5_VABI sceAgcCbQueueEndOfPipeActionGetSize() {

View File

@ -7,7 +7,9 @@ std::uint32_t* WriteJump(CommandBuffer* buffer, std::uint8_t chain, std::uint8_t
CheckBits(chain, 1, function);
CheckBits(cachePolicy, 3, function);
CheckBits(sizeInDwords, 0xfffffu, function);
CheckAddress(guestAddress, 4, function);
// Titles reserve a jump with a null target and write the address into the packet later.
Require((guestAddress & 3u) == 0, function, "misaligned jump target");
// 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);
}

View File

@ -32,8 +32,7 @@ std::uint32_t* APS5_VABI sceAgcDcbDrawIndexOffset(CommandBuffer* buf, std::uint3
}
uint32_t APS5_VABI sceAgcDcbDrawIndexOffsetGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 20; // DRAW_INDEX_OFFSET_2: header + 4 dwords
}
uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirect(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint64_t modifier) {

View File

@ -14,8 +14,7 @@ std::uint32_t* APS5_VABI sceAgcDcbSetIndexBuffer(CommandBuffer* buf, std::uint64
}
std::uint32_t APS5_VABI sceAgcDcbSetIndexBufferGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 12; // INDEX_BASE: header + 2 dwords
}
std::uint32_t* APS5_VABI sceAgcDcbSetIndexCount(CommandBuffer* buf, std::uint32_t indexCount) {
@ -33,8 +32,7 @@ std::uint32_t* APS5_VABI sceAgcDcbSetIndexSize(CommandBuffer* buf, std::uint8_t
}
std::uint32_t APS5_VABI sceAgcDcbSetIndexSizeGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 12; // SET_UCONFIG_REG_INDEX: header + 2 dwords
}
std::uint32_t* APS5_VABI sceAgcDcbSetIndexIndirectArgs(CommandBuffer* buf, std::uint32_t dataOffsetInBytes) {

View File

@ -42,9 +42,7 @@ std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t ev
}
std::uint64_t APS5_VABI sceAgcDcbEventWriteGetSize(std::uint8_t eventType) {
(void)eventType;
NotImplemented_nid_no_patch(__func__);
return 0;
return (eventType & 0xfeu) == 0x38u ? 16 : 8; // EVENT_WRITE, plus the address for the 0x38/0x39 events
}
std::uint32_t* APS5_VABI sceAgcDcbStallCommandBufferParser(CommandBuffer* buf) {

View File

@ -9,15 +9,11 @@
extern "C" {
uint32_t* APS5_VABI sceAgcDcbSetCxRegisterDirect(CommandBuffer* buf, ShaderRegister reg) {
(void)buf;
(void)reg;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteRegisterRange(buf, 0x69u, reg.offset, &reg.value, 1u, __func__);
}
uint32_t APS5_VABI sceAgcDcbSetCxRegisterDirectGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return 12;
}
std::uint32_t* APS5_VABI sceAgcDcbSetCxRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs) {
@ -31,15 +27,11 @@ std::uint32_t APS5_VABI sceAgcDcbSetCxRegistersIndirectGetSize(std::uint32_t num
}
uint32_t* APS5_VABI sceAgcDcbSetShRegisterDirect(CommandBuffer* buf, ShaderRegister reg) {
(void)buf;
(void)reg;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteRegisterRange(buf, 0x76u, reg.offset, &reg.value, 1u, __func__);
}
std::uint32_t APS5_VABI sceAgcDcbSetShRegisterDirectGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 12;
}
std::uint32_t* APS5_VABI sceAgcDcbSetShRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs) {
@ -52,15 +44,11 @@ std::uint32_t APS5_VABI sceAgcDcbSetShRegistersIndirectGetSize(std::uint32_t num
}
uint32_t* APS5_VABI sceAgcDcbSetUcRegisterDirect(CommandBuffer* buf, ShaderRegister reg) {
(void)buf;
(void)reg;
NotImplemented_nid_no_patch(__func__);
return nullptr;
return Agc::Command::WriteRegisterRange(buf, 0x79u, reg.offset, &reg.value, 1u, __func__);
}
std::uint32_t APS5_VABI sceAgcDcbSetUcRegisterDirectGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
return 12;
}
std::uint32_t* APS5_VABI sceAgcDcbSetUcRegistersIndirect(CommandBuffer* buf, const volatile ShaderRegister* regs, std::uint32_t numRegs) {

View File

@ -1,33 +1,116 @@
#include "prx/libSceAgc/Misc/include/ShaderFusion.hpp"
#include <algorithm>
#include <cstring>
#include <stdexcept>
#include <string>
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
struct SizeAlign {
uint64_t m_size;
size_t m_align;
};
namespace {
using namespace ShaderRegs;
constexpr int GRAPHICS5_ERROR_INVALID_SHADER_HALVES = static_cast<int>(0x8a6c0008u);
constexpr std::uint32_t SPI_SHADER_PGM_CHKSUM_GS = 0x080u;
constexpr std::uint32_t SPI_SHADER_PGM_RSRC1_GS = 0x08Au;
constexpr std::uint32_t SPI_SHADER_PGM_RSRC2_GS = 0x08Bu;
constexpr std::uint32_t SPI_SHADER_PGM_CHKSUM_HS = 0x100u;
constexpr std::uint32_t SPI_SHADER_PGM_RSRC1_HS = 0x10Au;
constexpr std::uint32_t SPI_SHADER_PGM_RSRC2_HS = 0x10Bu;
bool ValidHalves(const Shader* front, const Shader* back) {
const auto frontType = static_cast<ShaderBinaryType>(front->type);
const auto backType = static_cast<ShaderBinaryType>(back->type);
return (frontType == ShaderBinaryType::GsFront && backType == ShaderBinaryType::GsBack) ||
(frontType == ShaderBinaryType::HsFront && backType == ShaderBinaryType::HsBack);
}
ShaderRegister& FindRegister(ShaderRegister* regs, std::uint32_t count, std::uint32_t offset, std::uint32_t occurrence = 0) {
for (std::uint32_t i = 0; regs != nullptr && i < count; ++i) {
if (regs[i].offset == offset && occurrence-- == 0) return regs[i];
}
throw std::runtime_error("sceAgcUnknownFuseShaderHalves: shader half lacks register " + std::to_string(offset));
}
void MergeMax(ShaderRegister& dst, const ShaderRegister& src, std::uint32_t shift, std::uint32_t mask) {
const auto field = std::max((dst.value >> shift) & mask, (src.value >> shift) & mask);
dst.value = (dst.value & ~(mask << shift)) | (field << shift);
}
}
extern "C" {
APS5_EXPORT("fd5Bp5tGTgo", sceAgcUnknownFuseShaderHalves);
int APS5_VABI sceAgcUnknownFuseShaderHalves(Shader* fused_result, const Shader* front, const Shader* back, void* scratch_mem) {
(void)fused_result;
(void)front;
(void)back;
(void)scratch_mem;
NotImplemented_nid_no_patch(__func__);
if (fused_result == nullptr || front == nullptr || back == nullptr) APS5_INVALID_ARG_EX;
if (!ValidHalves(front, back)) return GRAPHICS5_ERROR_INVALID_SHADER_HALVES;
const bool isGs = static_cast<ShaderBinaryType>(front->type) == ShaderBinaryType::GsFront;
const std::uint32_t stageBit = isGs ? (1u << 22u) : (1u << 21u);
if (((front->specials->vgt_shader_stages_en.value ^ back->specials->vgt_shader_stages_en.value) & stageBit) != 0) {
return GRAPHICS5_ERROR_INVALID_SHADER_HALVES;
}
*fused_result = *back;
fused_result->type = static_cast<std::uint8_t>(isGs ? ShaderBinaryType::Gs : ShaderBinaryType::Hs);
if (scratch_mem != nullptr) {
auto* regs = static_cast<ShaderRegister*>(scratch_mem);
std::memcpy(regs, back->sh_registers, std::size_t{back->num_sh_registers} * sizeof(ShaderRegister));
fused_result->sh_registers = regs;
}
ShaderRegister* fused = fused_result->sh_registers;
const std::uint32_t fusedCount = fused_result->num_sh_registers;
const std::uint32_t frontCount = front->num_sh_registers;
const std::uint32_t checksum = isGs ? SPI_SHADER_PGM_CHKSUM_GS : SPI_SHADER_PGM_CHKSUM_HS;
for (std::uint32_t occurrence = 0; occurrence < 2; ++occurrence) {
FindRegister(fused, fusedCount, checksum, occurrence).value = FindRegister(front->sh_registers, frontCount, checksum, occurrence).value;
}
const auto& frontRsrc1 = FindRegister(front->sh_registers, frontCount, isGs ? SPI_SHADER_PGM_RSRC1_GS : SPI_SHADER_PGM_RSRC1_HS);
const auto& frontRsrc2 = FindRegister(front->sh_registers, frontCount, isGs ? SPI_SHADER_PGM_RSRC2_GS : SPI_SHADER_PGM_RSRC2_HS);
auto& fusedRsrc1 = FindRegister(fused, fusedCount, isGs ? SPI_SHADER_PGM_RSRC1_GS : SPI_SHADER_PGM_RSRC1_HS);
auto& fusedRsrc2 = FindRegister(fused, fusedCount, isGs ? SPI_SHADER_PGM_RSRC2_GS : SPI_SHADER_PGM_RSRC2_HS);
// Both halves run in one wave, so VGPRs are the larger of the two and shared VGPRs are reallocated (after Kyty).
const std::uint32_t frontVgprs = ((frontRsrc1.value & 0x3fu) + 1u) * 4u;
const std::uint32_t backVgprs = ((fusedRsrc1.value & 0x3fu) + 1u) * 4u;
const std::uint32_t frontTotal = frontVgprs + (frontRsrc2.value >> 28u) * 8u;
const std::uint32_t backTotal = backVgprs + (fusedRsrc2.value >> 28u) * 8u;
const std::uint32_t maxTotal = std::max(frontTotal, backTotal);
const std::uint32_t shared = std::max(frontVgprs, backVgprs) >= maxTotal ? 0u : (maxTotal - std::min(frontTotal, backTotal) + 7u) / 64u;
fusedRsrc2.value = (fusedRsrc2.value & 0x0fffffffu) | ((shared & 0xfu) << 28u);
MergeMax(fusedRsrc1, frontRsrc1, 0, 0x3fu);
if (isGs) {
MergeMax(fusedRsrc1, frontRsrc1, 29, 0x3u);
MergeMax(fusedRsrc2, frontRsrc2, 16, 0x3u);
fusedRsrc2.value = (fusedRsrc2.value & 0xfffbffffu) | (frontRsrc2.value & 0x00040000u);
} else {
MergeMax(fusedRsrc1, frontRsrc1, 28, 0x3u);
}
fusedRsrc2.value = (fusedRsrc2.value & 0xf7ffffc1u) | (frontRsrc2.value & 0x0800003eu);
// The fused program starts at the front half; its address goes in the ES/LS program registers.
const std::uint32_t lo = isGs ? SPI_SHADER_PGM_LO_ES : SPI_SHADER_PGM_LO_LS;
auto& loRegister = FindRegister(fused, fusedCount, lo);
auto& hiRegister = FindRegister(fused, fusedCount, lo + 1u);
const auto address = reinterpret_cast<std::uint64_t>(front->code);
loRegister.value = static_cast<std::uint32_t>(address >> 8u);
hiRegister.value = (hiRegister.value & 0xffffff00u) | static_cast<std::uint32_t>((address >> 40u) & 0xffu);
fused_result->user_data = nullptr;
return 0;
}
APS5_EXPORT("dolOmWH+huQ", sceAgcUnknownGetFusedShaderSize);
int APS5_VABI sceAgcUnknownGetFusedShaderSize(SizeAlign* dst, const Shader* front, const Shader* back) {
(void)dst;
(void)front;
(void)back;
NotImplemented_nid_no_patch(__func__);
if (dst == nullptr || front == nullptr || back == nullptr) APS5_INVALID_ARG_EX;
if (!ValidHalves(front, back)) return GRAPHICS5_ERROR_INVALID_SHADER_HALVES;
dst->m_size = std::uint64_t{back->num_sh_registers} * sizeof(ShaderRegister);
dst->m_align = 4;
return 0;
}

View File

@ -7,14 +7,35 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
// INDIRECT_BUFFER as sceAgcDcbJump writes it: [1..2] target, [3] size in 19:0, chain in 20, cache policy in 29:28.
void PatchJump(std::uint32_t* cmd, const volatile std::uint32_t* target, std::uint32_t sizeInDwords, const char* function) {
Agc::Command::ValidatePacket(cmd, 0x3fu, 4, function);
const auto address = reinterpret_cast<std::uintptr_t>(target);
Agc::Command::CheckAddress(address, 4, function);
Agc::Command::CheckBits(sizeInDwords, 0xfffffu, function);
cmd[1] = static_cast<std::uint32_t>(address);
cmd[2] = static_cast<std::uint32_t>(address >> 32u);
cmd[3] = (cmd[3] & ~0xfffffu) | sizeInDwords;
}
}
extern "C" {
int APS5_VABI sceAgcJumpPatchSetTarget(uint32_t* cmd, const volatile uint32_t* target, uint32_t size_in_dwords) {
(void)cmd;
(void)target;
(void)size_in_dwords;
NotImplemented_nid_no_patch(__func__);
return 0;
PatchJump(cmd, target, size_in_dwords, __func__);
return 0;
}
// Named after Kyty's reading: a jump patch that also sets the cache policy.
APS5_EXPORT("Ikfdt-rIqCE", sceAgcUnknown_Ikfdt_MrIqCE);
int APS5_VABI sceAgcUnknown_Ikfdt_MrIqCE(uint32_t* cmd, uint64_t cache_policy, const volatile uint32_t* target, uint32_t size_in_dwords) {
Agc::Command::CheckBits(cache_policy, 3u, __func__);
PatchJump(cmd, target, size_in_dwords, __func__);
cmd[3] = (cmd[3] & ~(3u << 28u)) | (static_cast<std::uint32_t>(cache_policy) << 28u);
return 0;
}
}

View File

@ -31,25 +31,37 @@ int APS5_VABI sceAgcQueueEndOfPipeActionPatchAddress(std::uint32_t* cmd, const v
}
int APS5_VABI sceAgcQueueEndOfPipeActionPatchData(uint32_t* cmd, uint32_t context_id, uint32_t data_sel, uint64_t data) {
(void)cmd;
(void)context_id;
(void)data_sel;
(void)data;
NotImplemented_nid_no_patch(__func__);
return 0;
// RELEASE_MEM as built by sceAgcCbReleaseMem: [2] dst | interrupt << 24 | data select << 29, [5..6] data, [7] interrupt context.
Agc::Command::ValidatePacket(cmd, 0x49u, 8, __func__);
Agc::Command::Require(data_sel <= 3, __func__, "invalid release data selector");
Agc::Command::CheckBits(context_id, 0x7ffffffu, __func__);
if (data_sel == 1) {
Agc::Command::CheckBits(data, 0xffffffffu, __func__);
}
Agc::Command::Require(((cmd[2] >> 24u) & 7u) != 4u || data_sel == 0, __func__, "interrupt-only release cannot write data");
cmd[2] = (cmd[2] & 0x1fffffffu) | (data_sel << 29u);
cmd[5] = static_cast<std::uint32_t>(data);
cmd[6] = static_cast<std::uint32_t>(data >> 32u);
cmd[7] = context_id;
return 0;
}
int APS5_VABI sceAgcQueueEndOfPipeActionPatchGcrCntl(std::uint32_t* cmd, std::uint16_t gcrControl) {
(void)cmd;
(void)gcrControl;
NotImplemented_nid_no_patch(__func__);
Agc::Command::ValidatePacket(cmd, 0x49u, 8, __func__);
Agc::Command::CheckBits(gcrControl, 0xfffu, __func__);
std::uint32_t control = gcrControl;
if ((control & 0x300u) == 0x100u) {
control |= 0x200u;
}
cmd[1] = (cmd[1] & ~(0xfffu << 12u)) | (control << 12u);
return 0;
}
int APS5_VABI sceAgcQueueEndOfPipeActionPatchType(std::uint32_t* cmd, std::uint8_t action) {
(void)cmd;
(void)action;
NotImplemented_nid_no_patch(__func__);
Agc::Command::ValidatePacket(cmd, 0x49u, 8, __func__);
Agc::Command::CheckBits(action, 0x3fu, __func__);
const std::uint32_t eventIndex = action >= 0x2fu ? 6u : 5u;
cmd[1] = (cmd[1] & ~0xf3fu) | action | (eventIndex << 8u);
return 0;
}

View File

@ -2,25 +2,33 @@
#include "prx/libSceAgc/Command/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include <atomic>
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
namespace {
int APS5_VABI sceAgcRewindPatchSetRewindState(uint32_t* cmd, uint8_t state) {
(void)cmd;
(void)state;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcAsyncRewindPatchSetRewindState(std::uint32_t* cmd, std::uint8_t state) {
(void)cmd;
(void)state;
NotImplemented_nid_no_patch(__func__);
// The valid bit (31) releases a queue stalled on the REWIND; the release store publishes the commands
// the title wrote behind it before setting the bit.
int SetRewindState(std::uint32_t* cmd, std::uint8_t state, const char* function) {
Agc::Command::ValidatePacket(cmd, 0x59u, 2, function);
Agc::Command::CheckBits(state, 1, function);
std::atomic_ref<std::uint32_t>(cmd[1]).store(static_cast<std::uint32_t>(state) << 31u, std::memory_order_release);
return 0;
}
}
extern "C" {
int APS5_VABI sceAgcRewindPatchSetRewindState(uint32_t* cmd, uint8_t state) {
return SetRewindState(cmd, state, __func__);
}
int APS5_VABI sceAgcAsyncRewindPatchSetRewindState(std::uint32_t* cmd, std::uint8_t state) {
return SetRewindState(cmd, state, __func__);
}
}

View File

@ -121,12 +121,6 @@ int APS5_VABI sceAgcUnknown_ICkECTBxrMw() {
return 0;
}
APS5_EXPORT("Ikfdt-rIqCE", sceAgcUnknown_Ikfdt_MrIqCE);
int APS5_VABI sceAgcUnknown_Ikfdt_MrIqCE() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("RTpj-tIlvZc", sceAgcUnknown_RTpj_MtIlvZc);
int APS5_VABI sceAgcUnknown_RTpj_MtIlvZc() {
NotImplemented_nid_no_patch(__func__);