feat(agc): add draw packets and register defaults with strict validation

This commit is contained in:
boykopovar 2026-09-13 18:14:51 +03:00
parent 8e95b27583
commit bdff4874de
23 changed files with 3404 additions and 143 deletions

View File

@ -1,4 +1,5 @@
#include "prx/libSceAgc/Acb/include/Memory.hpp"
#include "prx/libSceAgc/DcbFlow/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
@ -9,14 +10,8 @@
extern "C" {
uint32_t* APS5_VABI sceAgcAcbAcquireMem(CommandBuffer* buf, uint32_t gcr_cntl, const volatile void* base, uint64_t size_bytes, uint32_t poll_cycles) {
(void)buf;
(void)gcr_cntl;
(void)base;
(void)size_bytes;
(void)poll_cycles;
NotImplemented_nid_no_patch(__func__);
return nullptr;
std::uint32_t* APS5_VABI sceAgcAcbAcquireMem(CommandBuffer* buf, std::uint32_t gcrControl, const volatile void* base, std::uint64_t sizeBytes, std::uint32_t pollCycles) {
return sceAgcDcbAcquireMem(buf, 1, 0, gcrControl, base, sizeBytes, pollCycles);
}
uint32_t APS5_VABI sceAgcAcbAcquireMemGetSize(void) {

View File

@ -8,6 +8,8 @@ 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/Draw.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Command/src/RegisterDefaults.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Command/src/Packet.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DcbDraw/src/DrawIndexed.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DcbDraw/src/DrawNonIndexed.cpp
@ -53,3 +55,11 @@ option(APS5_AGC_CREATE_LOG "Logging sceAgcCreateShader Ok status" ON)
target_compile_definitions(libSceAgc PRIVATE
APS5_AGC_CREATE_LOG=$<BOOL:${APS5_AGC_CREATE_LOG}>
)
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
endforeach()
add_executable(agc_command_tests EXCLUDE_FROM_ALL tests/Commands.cpp)
target_include_directories(agc_command_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_command_tests PRIVATE libSceAgc libc)

View File

@ -18,26 +18,40 @@ uint32_t APS5_VABI sceAgcCbNopGetSize(uint32_t size_in_dwords) {
return 0;
}
uint32_t APS5_VABI sceAgcCbQueueEndOfPipeActionGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcCbQueueEndOfPipeActionGetSize() {
return 32;
}
uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer* buf, uint8_t action, uint16_t gcr_cntl, uint8_t dst, uint8_t cache_policy, const volatile Label* address, uint8_t data_sel, uint64_t data, uint16_t gds_offset, uint16_t gds_size, uint8_t interrupt, uint32_t interrupt_ctx_id) {
(void)buf;
(void)action;
(void)gcr_cntl;
(void)dst;
(void)cache_policy;
(void)address;
(void)data_sel;
(void)data;
(void)gds_offset;
(void)gds_size;
(void)interrupt;
(void)interrupt_ctx_id;
NotImplemented_nid_no_patch(__func__);
return nullptr;
std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer* buf, std::uint8_t action, std::uint16_t gcrControl, std::uint8_t dst, std::uint8_t cachePolicy, const volatile Label* address, std::uint8_t dataSelect, std::uint64_t data, std::uint16_t gdsOffset, std::uint16_t gdsSize, std::uint8_t interrupt, std::uint32_t interruptContextId) {
Agc::Command::CheckBits(action, 0x3fu, __func__);
Agc::Command::CheckBits(gcrControl, 0xfffu, __func__);
Agc::Command::CheckBits(dst, 1, __func__);
Agc::Command::CheckBits(cachePolicy, 3, __func__);
Agc::Command::Require(dataSelect <= 3 || dataSelect == 5, __func__, "invalid release data selector");
Agc::Command::Require(interrupt <= 4, __func__, "invalid release interrupt selector");
Agc::Command::CheckBits(interruptContextId, 0x7ffffffu, __func__);
auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
auto value = data;
if (interrupt == 4) {
Agc::Command::Require(guestAddress == 0 && value == 0, __func__, "interrupt-only release cannot write data");
} else if (dataSelect == 5) {
Agc::Command::Require(data == 0, __func__, "GDS release cannot use immediate data");
value = gdsOffset | (static_cast<std::uint64_t>(gdsSize) << 16u);
} else {
Agc::Command::Require(gdsOffset == 0 && gdsSize == 0, __func__, "GDS parameters supplied for a non-GDS release");
}
if (dataSelect != 0 && interrupt != 4) {
Agc::Command::CheckAddress(guestAddress, dataSelect == 2 || dataSelect == 3 ? 8 : 4, __func__);
}
if (dataSelect == 1) {
Agc::Command::CheckBits(data, 0xffffffffu, __func__);
}
std::uint32_t control = gcrControl;
if ((control & 0x300u) == 0x100u) {
control |= 0x200u;
}
const auto eventIndex = action >= 0x2fu ? 6u : 5u;
return Agc::Command::Emit(buf, 0x49u, {action | (eventIndex << 8u) | (control << 12u) | (static_cast<std::uint32_t>(cachePolicy) << 25u), (static_cast<std::uint32_t>(dst) << 16u) | (static_cast<std::uint32_t>(interrupt) << 24u) | (static_cast<std::uint32_t>(dataSelect) << 29u), static_cast<std::uint32_t>(guestAddress), static_cast<std::uint32_t>(guestAddress >> 32u), static_cast<std::uint32_t>(value), static_cast<std::uint32_t>(value >> 32u), interruptContextId}, __func__);
}
}

View File

@ -0,0 +1,14 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_COMMAND_INCLUDE_DRAW_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_COMMAND_INCLUDE_DRAW_HPP
#include "prx/libSceAgc/Command/include/Packet.hpp"
namespace Agc::Command {
std::uint32_t DrawInitiator(std::uint64_t modifier, bool indexed, const char* function);
std::uint64_t DrawPatchOffsets(std::uint64_t modifier, const char* function);
std::uint32_t DrawIndexLocation(std::uint64_t modifier);
}
#endif

View File

@ -0,0 +1,12 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_COMMAND_INCLUDE_REGISTERDEFAULTS_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_COMMAND_INCLUDE_REGISTERDEFAULTS_HPP
#include <cstdint>
namespace Agc::Command {
void* GetRegisterDefaults(std::uint32_t version, bool internal, const char* function);
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
#include "prx/libSceAgc/Command/include/Draw.hpp"
namespace Agc::Command {
namespace {
std::uint32_t sgprBase(std::uint64_t modifier) {
const auto stage = static_cast<std::uint32_t>(modifier) >> 29u;
return stage == 3 || stage == 5 ? 0x10cu : 0x8cu;
}
}
std::uint32_t DrawInitiator(std::uint64_t modifier, bool indexed, const char* function) {
CheckBits(modifier, 0x1ffffffffull, function);
const auto flags = (modifier & 0x100000000ull) != 0 ? 0u : static_cast<std::uint32_t>((modifier >> 3u) & 0x20u);
return flags | (indexed ? 0u : 2u);
}
std::uint64_t DrawPatchOffsets(std::uint64_t modifier, const char* function) {
CheckBits(modifier, 0x1ffffffffull, function);
const auto base = sgprBase(modifier);
const auto vertex = (modifier & 1u) != 0 ? base + static_cast<std::uint32_t>((modifier >> 9u) & 0x1fu) : 0x280u;
const auto instance = (modifier & 4u) != 0 ? base + static_cast<std::uint32_t>((modifier >> 19u) & 0x1fu) : 0x280u;
return vertex | (static_cast<std::uint64_t>(instance) << 32u);
}
std::uint32_t DrawIndexLocation(std::uint64_t modifier) {
return (modifier & 8u) != 0 ? sgprBase(modifier) + static_cast<std::uint32_t>((modifier >> 24u) & 0x1fu) : 0x280u;
}
}

View File

@ -0,0 +1,79 @@
#include "prx/libSceAgc/Command/include/RegisterDefaults.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <mutex>
#include <vector>
namespace Agc::Command {
namespace {
struct RegisterDefaults {
ShaderRegister** tables[4];
std::uint32_t registerCounts[4];
std::uint32_t* types;
std::uint32_t count;
};
static_assert(offsetof(RegisterDefaults, count) == 0x38);
struct CompactBank {
ShaderRegister* registers;
std::uint32_t registerCount;
const std::uint16_t* offsets;
std::uint32_t pointerCount;
};
struct CompactRegisterDefaults {
CompactBank banks[4];
std::uint32_t* types;
std::uint32_t count;
};
#include "prx/libSceAgc/Command/include/RegisterDefaultsData.hpp"
struct Storage {
RegisterDefaults defaults{};
std::array<std::vector<ShaderRegister*>, 4> pointers;
bool initialized = false;
};
std::mutex defaultsMutex;
std::array<std::array<Storage, 14>, 2> storage;
}
void* GetRegisterDefaults(std::uint32_t version, bool internal, const char* function) {
Require(version < 14, function, "unsupported register defaults version");
const auto* source = internal ? agcInternalRegDefaultsByVersion[version] : agcPublicRegDefaultsByVersion[version];
std::lock_guard lock(defaultsMutex);
auto& destination = storage[internal ? 1 : 0][version];
if (!destination.initialized) {
std::array<std::vector<ShaderRegister*>, 4> pointers;
RegisterDefaults defaults{};
for (std::size_t bank = 0; bank < pointers.size(); ++bank) {
const auto& input = source->banks[bank];
Require(input.pointerCount == 0 || (input.registers != nullptr && input.offsets != nullptr), function, "invalid register defaults bank");
auto& table = pointers[bank];
table.reserve(input.pointerCount);
for (std::uint32_t index = 0; index < input.pointerCount; ++index) {
Require(input.offsets[index] < input.registerCount, function, "register defaults pointer exceeds bank");
table.push_back(input.registers + input.offsets[index]);
}
defaults.tables[bank] = table.empty() ? nullptr : table.data();
defaults.registerCounts[bank] = input.registerCount;
}
Require(source->count == 0 || source->types != nullptr, function, "missing register defaults types");
defaults.types = source->types;
defaults.count = source->count;
destination.pointers = std::move(pointers);
destination.defaults = defaults;
destination.initialized = true;
}
return &destination.defaults;
}
}

View File

@ -1,3 +1,4 @@
#include "prx/libSceAgc/Command/include/Draw.hpp"
#include "prx/libSceAgc/DcbDraw/include/DrawIndexed.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
@ -17,31 +18,20 @@ uint32_t* APS5_VABI sceAgcDcbDrawIndex(CommandBuffer* buf, uint32_t index_count,
return nullptr;
}
uint32_t APS5_VABI sceAgcDcbDrawIndexGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbDrawIndexGetSize() {
return 24;
}
uint32_t* APS5_VABI sceAgcDcbDrawIndexAuto(CommandBuffer* buf, uint32_t index_count, uint64_t modifier) {
(void)buf;
(void)index_count;
(void)modifier;
NotImplemented_nid_no_patch(__func__);
return nullptr;
std::uint32_t* APS5_VABI sceAgcDcbDrawIndexAuto(CommandBuffer* buf, std::uint32_t indexCount, std::uint64_t modifier) {
return Agc::Command::Emit(buf, 0x2du, {indexCount, Agc::Command::DrawInitiator(modifier, false, __func__)}, __func__);
}
uint32_t APS5_VABI sceAgcDcbDrawIndexAutoGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbDrawIndexAutoGetSize() {
return 12;
}
uint32_t* APS5_VABI sceAgcDcbDrawIndexOffset(CommandBuffer* buf, uint32_t index_offset, uint32_t index_count, uint64_t modifier) {
(void)buf;
(void)index_offset;
(void)index_count;
(void)modifier;
NotImplemented_nid_no_patch(__func__);
return nullptr;
std::uint32_t* APS5_VABI sceAgcDcbDrawIndexOffset(CommandBuffer* buf, std::uint32_t indexOffset, std::uint32_t indexCount, std::uint64_t modifier) {
return Agc::Command::Emit(buf, 0x35u, {indexCount == 0 ? 1u : indexCount, indexOffset, indexCount, Agc::Command::DrawInitiator(modifier, true, __func__)}, __func__);
}
uint32_t APS5_VABI sceAgcDcbDrawIndexOffsetGetSize(void) {
@ -57,9 +47,8 @@ uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirect(CommandBuffer* buf, uint32_t data
return nullptr;
}
uint32_t APS5_VABI sceAgcDcbDrawIndexIndirectGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbDrawIndexIndirectGetSize() {
return 20;
}
uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirectMulti(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint32_t count_indirect, uint32_t max_count_or_count, const volatile void* count_addr, uint32_t stride_in_bytes, uint64_t modifier) {
@ -74,9 +63,8 @@ uint32_t* APS5_VABI sceAgcDcbDrawIndexIndirectMulti(CommandBuffer* buf, uint32_t
return nullptr;
}
uint32_t APS5_VABI sceAgcDcbDrawIndexIndirectMultiGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbDrawIndexIndirectMultiGetSize() {
return 40;
}
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) {

View File

@ -1,3 +1,4 @@
#include "prx/libSceAgc/Command/include/Draw.hpp"
#include "prx/libSceAgc/DcbDraw/include/DrawNonIndexed.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
@ -8,34 +9,51 @@
extern "C" {
uint32_t* APS5_VABI sceAgcDcbDrawIndirect(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint64_t modifier) {
(void)buf;
(void)data_offset_in_bytes;
(void)modifier;
NotImplemented_nid_no_patch(__func__);
return nullptr;
std::uint32_t* APS5_VABI sceAgcDcbDrawIndirect(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint64_t modifier) {
Agc::Command::Require((dataOffsetInBytes & 3u) == 0, __func__, "misaligned indirect argument offset");
const auto offsets = Agc::Command::DrawPatchOffsets(modifier, __func__);
return Agc::Command::Emit(buf, 0x24u, {dataOffsetInBytes, static_cast<std::uint32_t>(offsets), static_cast<std::uint32_t>(offsets >> 32u), Agc::Command::DrawInitiator(modifier, false, __func__)}, __func__);
}
uint32_t APS5_VABI sceAgcDcbDrawIndirectGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbDrawIndirectGetSize() {
return 20;
}
uint32_t* APS5_VABI sceAgcDcbDrawIndirectMulti(CommandBuffer* buf, uint32_t data_offset_in_bytes, uint32_t count_indirect, uint32_t max_count_or_count, const volatile void* count_addr, uint32_t stride_in_bytes, uint64_t modifier) {
(void)buf;
(void)data_offset_in_bytes;
(void)count_indirect;
(void)max_count_or_count;
(void)count_addr;
(void)stride_in_bytes;
(void)modifier;
NotImplemented_nid_no_patch(__func__);
return nullptr;
std::uint32_t* APS5_VABI sceAgcDcbDrawIndirectMulti(CommandBuffer* buf, std::uint32_t dataOffsetInBytes, std::uint32_t countIndirect, std::uint32_t maxCountOrCount, const volatile void* countAddress, std::uint32_t strideInBytes, std::uint64_t modifier) {
Agc::Command::CheckBits(countIndirect, 1, __func__);
Agc::Command::Require((dataOffsetInBytes & 3u) == 0 && (strideInBytes & 3u) == 0 && strideInBytes >= 16, __func__, "invalid indirect draw offset or stride");
const auto address = reinterpret_cast<std::uintptr_t>(countAddress);
if (countIndirect != 0) {
Agc::Command::CheckAddress(address, 4, __func__);
} else {
Agc::Command::Require(address == 0, __func__, "count address supplied for a direct draw count");
}
const auto offsets = Agc::Command::DrawPatchOffsets(modifier, __func__);
const auto initiator = Agc::Command::DrawInitiator(modifier, false, __func__);
const auto low = static_cast<std::uint32_t>(modifier);
const auto control = Agc::Command::DrawIndexLocation(modifier) | ((low & 0x10u) << 23u) | (countIndirect << 30u) | ((low & 8u) << 28u);
auto* packet = Agc::Command::Allocate(buf, 16, __func__);
packet[0] = Agc::Command::Header(0x79u, 3, 1);
packet[1] = 0x342u;
packet[2] = 0xc6000008u;
packet[3] = Agc::Command::Header(0x2cu, 10);
packet[4] = dataOffsetInBytes;
packet[5] = static_cast<std::uint32_t>(offsets);
packet[6] = static_cast<std::uint32_t>(offsets >> 32u);
packet[7] = control;
packet[8] = maxCountOrCount;
packet[9] = static_cast<std::uint32_t>(address);
packet[10] = static_cast<std::uint32_t>(address >> 32u);
packet[11] = strideInBytes;
packet[12] = initiator;
packet[13] = Agc::Command::Header(0x79u, 3, 1);
packet[14] = 0x342u;
packet[15] = 0xc6000000u;
return packet;
}
uint32_t APS5_VABI sceAgcDcbDrawIndirectMultiGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbDrawIndirectMultiGetSize() {
return 64;
}
}

View File

@ -1,4 +1,8 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBFLOW_INCLUDE_MEMORY_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_DCBFLOW_INCLUDE_MEMORY_HPP
#include "SceTypes.hpp"
extern "C" std::uint32_t* APS5_VABI sceAgcDcbAcquireMem(CommandBuffer* buf, std::uint8_t engine, std::uint32_t cbDbOp, std::uint32_t gcrControl, const volatile void* base, std::uint64_t sizeBytes, std::uint32_t pollCycles);
#endif

View File

@ -1,4 +1,5 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_DCBFLOW_INCLUDE_SYNC_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_DCBFLOW_INCLUDE_SYNC_HPP
#endif

View File

@ -6,7 +6,7 @@
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "shader/recompilier/Recompiler.hpp"
extern "C" {
@ -20,27 +20,14 @@ uint32_t* APS5_VABI sceAgcDcbJump(CommandBuffer* buf, uint8_t mode, uint8_t cach
return nullptr;
}
uint32_t APS5_VABI sceAgcDcbJumpGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbJumpGetSize() {
return 16;
}
uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, uint32_t op, uint32_t state) {
(void)buf;
(void)op;
(void)state;
const std::array<std::uint32_t, 1> code = {0xbf810000u};
const std::array<std::uint32_t, 1> capabilities = {1u};
const std::array<ShaderRecompiler::RegisterValue, 3> shaderRegisters = {{{0x207u, 1u}, {0x208u, 1u}, {0x209u, 1u}}};
const std::array<ShaderRecompiler::MemoryRegion, 1> memory = {{{0x10000u, std::as_bytes(std::span(code))}}};
const ShaderRecompiler::RecompileRequest request{
{ShaderRecompiler::ShaderStage::Compute, 0x10000u, code, 0, {}},
{64, 0, {}, shaderRegisters, {}, {}, memory},
{0x00403000u, 0x00010600u, 64, capabilities, {}, {1024, 1024, 64}, 1024, 32768},
{0, 0, 0, 0}
};
(void)ShaderRecompiler::Recompile(request);
return nullptr;
std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t op, std::uint32_t state) {
Agc::Command::CheckBits(op, 0xfffu, __func__);
Agc::Command::CheckBits(state, 0xfu, __func__);
return Agc::Command::Emit(buf, 0x12u, {state}, __func__);
}
uint32_t* APS5_VABI sceAgcDcbRewind(CommandBuffer* buf, uint32_t initial_state) {

View File

@ -18,9 +18,8 @@ uint32_t* APS5_VABI sceAgcDcbDispatchIndirect(CommandBuffer* buf, uint32_t data_
return nullptr;
}
uint32_t APS5_VABI sceAgcDcbDispatchIndirectGetSize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbDispatchIndirectGetSize() {
return 12;
}
}

View File

@ -10,16 +10,16 @@
extern "C" {
uint32_t* APS5_VABI sceAgcDcbAcquireMem(CommandBuffer* buf, uint8_t engine, uint32_t cb_db_op, uint32_t gcr_cntl, const volatile void* base, uint64_t size_bytes, uint32_t poll_cycles) {
(void)buf;
(void)engine;
(void)cb_db_op;
(void)gcr_cntl;
(void)base;
(void)size_bytes;
(void)poll_cycles;
NotImplemented_nid_no_patch(__func__);
return nullptr;
std::uint32_t* APS5_VABI sceAgcDcbAcquireMem(CommandBuffer* buf, std::uint8_t engine, std::uint32_t cbDbOp, std::uint32_t gcrControl, const volatile void* base, std::uint64_t sizeBytes, std::uint32_t pollCycles) {
Agc::Command::CheckBits(engine, 1, __func__);
Agc::Command::CheckBits(cbDbOp, 0x7fffffffu, __func__);
Agc::Command::CheckBits(gcrControl, 0x7ffffu, __func__);
const auto address = reinterpret_cast<std::uintptr_t>(base);
Agc::Command::Require((address & 0xffu) == 0 && (address >> 40u) == 0, __func__, "invalid acquire memory base address");
const auto wholeAddressSpace = sizeBytes == 0xffffffffffffffffull;
Agc::Command::Require(wholeAddressSpace || ((sizeBytes & 0xffu) == 0 && (sizeBytes >> 40u) == 0), __func__, "invalid acquire memory range size");
Agc::Command::Require(pollCycles / 40u <= 0xffffu, __func__, "acquire poll interval overflow");
return Agc::Command::Emit(buf, 0x58u, {(static_cast<std::uint32_t>(engine) << 31u) | cbDbOp, wholeAddressSpace ? 0u : static_cast<std::uint32_t>(sizeBytes >> 8u), 0, static_cast<std::uint32_t>(address >> 8u), 0, pollCycles / 40u, gcrControl}, __func__);
}
uint32_t APS5_VABI sceAgcDcbAcquireMemGetSize(void) {

View File

@ -29,14 +29,12 @@ uint32_t* APS5_VABI sceAgcDcbContextStateAnotherOp(CommandBuffer* buf, uint32_t
return nullptr;
}
uint32_t APS5_VABI sceAgcDcbSetBaseDispatchIndirectArgsGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbSetBaseDispatchIndirectArgsGetSize() {
return 16;
}
uint32_t APS5_VABI sceAgcDcbSetBaseDrawIndirectArgsGetSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint32_t APS5_VABI sceAgcDcbSetBaseDrawIndirectArgsGetSize() {
return 16;
}
uint32_t APS5_VABI sceAgcDcbQueueEndOfShaderActionGetSize() {

View File

@ -1,3 +1,5 @@
#include "prx/libSceAgc/Command/include/RegisterDefaults.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include "prx/libSceAgc/Misc/include/RegisterDefaults.hpp"
#include <cstdint>
@ -7,16 +9,12 @@
extern "C" {
void* APS5_VABI sceAgcGetRegisterDefaults2(uint32_t ver) {
(void)ver;
NotImplemented_nid_no_patch(__func__);
return nullptr;
void* APS5_VABI sceAgcGetRegisterDefaults2(std::uint32_t version) {
return Agc::Command::GetRegisterDefaults(version, false, __func__);
}
void* APS5_VABI sceAgcGetRegisterDefaults2Internal(uint32_t ver) {
(void)ver;
NotImplemented_nid_no_patch(__func__);
return nullptr;
void* APS5_VABI sceAgcGetRegisterDefaults2Internal(std::uint32_t version) {
return Agc::Command::GetRegisterDefaults(version, true, __func__);
}
}

View File

@ -9,11 +9,25 @@
extern "C" {
int APS5_VABI sceAgcQueueEndOfPipeActionPatchAddress(uint32_t* cmd, const volatile Label* address) {
(void)cmd;
(void)address;
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAgcQueueEndOfPipeActionPatchAddress(std::uint32_t* cmd, const volatile Label* address) {
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(cmd), 4, __func__);
const auto guestAddress = reinterpret_cast<std::uintptr_t>(address);
const auto opcode = (cmd[0] >> 8u) & 0xffu;
if (opcode == 0x49u) {
Agc::Command::ValidatePacket(cmd, opcode, 8, __func__);
const auto dataSelect = cmd[2] >> 29u;
Agc::Command::CheckAddress(guestAddress, dataSelect == 2 || dataSelect == 3 ? 8 : 4, __func__);
cmd[3] = static_cast<std::uint32_t>(guestAddress);
cmd[4] = static_cast<std::uint32_t>(guestAddress >> 32u);
} else {
Agc::Command::ValidatePacket(cmd, 0x47u, 6, __func__);
const auto dataSelect = cmd[3] >> 29u;
Agc::Command::CheckAddress(guestAddress, dataSelect == 2 || dataSelect == 3 ? 8 : 4, __func__);
Agc::Command::CheckBits(guestAddress, 0xffffffffffffull, __func__);
cmd[2] = static_cast<std::uint32_t>(guestAddress);
cmd[3] = (cmd[3] & 0xffff0000u) | static_cast<std::uint32_t>(guestAddress >> 32u);
}
return 0;
}
int APS5_VABI sceAgcQueueEndOfPipeActionPatchData(uint32_t* cmd, uint32_t context_id, uint32_t data_sel, uint64_t data) {

View File

@ -28,6 +28,16 @@ int APS5_VABI sceAgcCreateShader(Shader** dst, void* header, const volatile void
auto* h = static_cast<Shader*>(header);
if (h->file_header != ShaderRegs::SHADER_FILE_HEADER_MAGIC || h->version != ShaderRegs::SHADER_VERSION) {
throw std::runtime_error(std::string(fn) + ": invalid shader header or version");
}
const auto base = reinterpret_cast<std::uint64_t>(code);
if ((base & ShaderRegs::SHADER_BASE_ALIGN_MASK) != 0 || h->shader_size == 0 || (h->shader_size & 3u) != 0) {
throw std::runtime_error(std::string(fn) + ": invalid shader code address or size");
}
std::uint32_t programOffset = 0;
(void)GetProgramAddressRegisterOffset(h->type, programOffset);
ResolveRelativePtr(h->cx_registers);
ResolveRelativePtr(h->sh_registers);
ResolveRelativePtr(h->user_data);
@ -45,19 +55,6 @@ int APS5_VABI sceAgcCreateShader(Shader** dst, void* header, const volatile void
h->code = code;
if (h->file_header != ShaderRegs::SHADER_FILE_HEADER_MAGIC) {
throw std::runtime_error(std::string(fn) + ": invalid file_header magic");
}
if (h->version != ShaderRegs::SHADER_VERSION) {
throw std::runtime_error(std::string(fn) + ": unsupported shader version");
}
const auto base = reinterpret_cast<std::uint64_t>(code);
if ((base & ShaderRegs::SHADER_BASE_ALIGN_MASK) != 0) {
throw std::runtime_error(std::string(fn) + ": code address violates alignment mask");
}
int result = PatchProgramAddressRegister(h->sh_registers, h->num_sh_registers, h->type, base);
if (result != 0) {
return result;

View File

@ -18,6 +18,20 @@ int APS5_VABI sceAgcCreatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_
throw std::runtime_error(std::string(__func__) + ": gs is null");
}
if (gs->type != static_cast<std::uint8_t>(ShaderRegs::ShaderBinaryType::Gs) || gs->specials == nullptr || (hs != nullptr && (hs->type != static_cast<std::uint8_t>(ShaderRegs::ShaderBinaryType::Hs) || hs->specials == nullptr))) {
throw std::runtime_error(std::string(__func__) + ": invalid shader type or missing special registers");
}
(void)GraphicsPrimTypeToGsOut(prim_type);
if (cx_regs != nullptr) {
const auto valid = [](const Shader* shader) { return shader->specials->vgt_shader_stages_en.offset == ShaderRegs::VGT_SHADER_STAGES_EN && shader->specials->vgt_gs_out_prim_type.offset == ShaderRegs::VGT_GS_OUT_PRIM_TYPE; };
if (!valid(gs) || (hs != nullptr && !valid(hs))) {
throw std::runtime_error(std::string(__func__) + ": invalid context register offsets");
}
}
if (uc_regs != nullptr && (gs->specials->ge_cntl.offset != ShaderRegs::GE_CNTL || gs->specials->ge_user_vgpr_en.offset != ShaderRegs::GE_USER_VGPR_EN || (hs != nullptr && hs->specials->ge_user_vgpr_en.offset != ShaderRegs::GE_USER_VGPR_EN))) {
throw std::runtime_error(std::string(__func__) + ": invalid user configuration register offsets");
}
if (cx_regs != nullptr) {
cx_regs[0] = gs->specials->vgt_shader_stages_en;
if ((cx_regs[0].value & ShaderRegs::VGT_SHADER_STAGES_GS_BIT) != 0) {

View File

@ -11,7 +11,12 @@ bool GetProgramAddressRegisterOffset(std::uint8_t type, std::uint32_t& loOffset)
case ShaderBinaryType::Hs: loOffset = SPI_SHADER_PGM_LO_LS; return true;
case ShaderBinaryType::GsBack: loOffset = SPI_SHADER_PGM_LO_GS; return true;
case ShaderBinaryType::HsBack: loOffset = SPI_SHADER_PGM_LO_HS; return true;
default: return false;
case ShaderBinaryType::GsFront:
case ShaderBinaryType::HsFront:
case ShaderBinaryType::Fs:
return false;
default:
throw std::runtime_error(std::string(__func__) + ": invalid shader binary type");
}
}
@ -34,6 +39,9 @@ int PatchProgramAddressRegister(ShaderRegister* regs, std::uint32_t numRegs, std
const std::uint64_t shaderOffset =
(static_cast<std::uint64_t>(regs[i].value) << 8u) |
((static_cast<std::uint64_t>(regs[hiIndex].value) & 0xFFu) << 40u);
if (base > 0xffffffffffffull || shaderOffset > 0xffffffffffffull - base) {
throw std::runtime_error(std::string(__func__) + ": shader program address overflow");
}
const std::uint64_t addr = base + shaderOffset;
regs[i].value = static_cast<std::uint32_t>((addr >> 8u) & 0xFFFFFFFFu);
regs[hiIndex].value &= 0xFFFFFF00u;
@ -57,8 +65,19 @@ std::uint32_t GraphicsPrimTypeToGsOut(std::uint32_t primType) {
return static_cast<std::uint32_t>(GsOutputPrimitiveType::Rectangle2D);
case PrimitiveType::RectListLegacy:
return static_cast<std::uint32_t>(GsOutputPrimitiveType::RectList);
default:
case PrimitiveType::None:
case PrimitiveType::TriList:
case PrimitiveType::TriFan:
case PrimitiveType::TriStrip:
case PrimitiveType::Patch:
case PrimitiveType::TriListAdjacency:
case PrimitiveType::TriStripAdjacency:
case PrimitiveType::QuadListLegacy:
case PrimitiveType::QuadStripLegacy:
case PrimitiveType::Polygon:
return static_cast<std::uint32_t>(GsOutputPrimitiveType::Triangles);
default:
throw std::runtime_error(std::string(__func__) + ": invalid primitive type");
}
}

View File

@ -0,0 +1,126 @@
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include "prx/libSceAgc/Command/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/RegisterDefaults.hpp"
#include <algorithm>
#include <array>
#include <cstdio>
#include <stdexcept>
#include <string>
extern "C" std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t op, std::uint32_t state);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbDrawIndexAuto(CommandBuffer* buf, std::uint32_t indexCount, std::uint64_t modifier);
extern "C" int APS5_VABI sceAgcWaitRegMemPatchReference(std::uint32_t* cmd, std::uint64_t reference);
namespace {
void check(bool condition, const char* message) {
if (!condition) {
throw std::runtime_error(message);
}
}
template <typename TAction>
void expectFailure(TAction action) {
try {
action();
} catch (const std::runtime_error& error) {
check(error.what()[0] != '\0', "empty exception message");
return;
}
throw std::runtime_error("expected an exception");
}
struct Storage {
std::array<std::uint32_t, 64> words{};
CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0};
};
bool APS5_VABI grow(CommandBuffer* buffer, std::uint32_t count, void* userData) {
auto& storage = *static_cast<Storage*>(userData);
check(count == 5, "incorrect callback allocation including reserved words");
buffer->bottom = storage.words.data();
buffer->top = storage.words.data() + storage.words.size();
buffer->cursor_up = buffer->bottom;
buffer->cursor_down = buffer->top;
return true;
}
void testPackets() {
Storage storage;
sceAgcDcbResetQueue(&storage.buffer, 0, 3);
sceAgcDcbDrawIndexAuto(&storage.buffer, 17, 0);
const std::array<std::uint32_t, 5> expected{0xc0001200u, 3, 0xc0012d00u, 17, 2};
check(std::equal(expected.begin(), expected.end(), storage.words.begin()), "reset or draw packet mismatch");
check(storage.buffer.cursor_up == storage.words.data() + expected.size(), "incorrect packet cursor advance");
const auto before = storage.words;
expectFailure([&] { sceAgcDcbResetQueue(&storage.buffer, 0, 16); });
check(storage.words == before, "invalid reset modified packet memory");
Storage destination;
CommandBuffer empty{nullptr, nullptr, nullptr, nullptr, grow, &destination, 0};
Agc::Command::Emit(&empty, 0x15u, {1, 1, 1, 0x41u}, __func__);
check(empty.cursor_up == destination.words.data() + 5, "guest ABI allocation callback failed");
Storage exhausted;
exhausted.buffer.cursor_down = exhausted.words.data() + 2;
expectFailure([&] { Agc::Command::WriteNop(&exhausted.buffer, 3, __func__); });
check(exhausted.buffer.cursor_up == exhausted.words.data(), "failed allocation advanced cursor");
}
void testRegisters() {
Storage storage;
const std::array<ShaderRegister, 3> registers{{{0x10, 7}, {0x11, 8}, {0x20, 9}}};
Agc::Command::WriteRegisters(&storage.buffer, 0x76u, registers.data(), registers.size(), true, __func__);
const std::array<std::uint32_t, 7> expected{0xc0027600u, 0x10, 7, 8, 0xc0017600u, 0x20, 9};
check(std::equal(expected.begin(), expected.end(), storage.words.begin()), "register run packet mismatch");
auto* packet = Agc::Command::WriteIndirectRegisters(&storage.buffer, 0x63u, registers.data(), 0x3ffeu, __func__);
Agc::Command::PatchIndirectCount(packet, 0x63u, 1, __func__);
check(packet[4] == 0x3fffu, "indirect register count mismatch");
const auto before = storage.words;
expectFailure([&] { Agc::Command::PatchIndirectCount(packet, 0x63u, 1, __func__); });
expectFailure([&] { Agc::Command::PatchIndirectAddress(packet, 0x64u, registers.data(), __func__); });
check(storage.words == before, "invalid indirect patch modified memory");
}
void testMemory() {
Storage storage;
Agc::Command::WriteDma(&storage.buffer, false, 1, 0, 0, 0x2000, 2, 0, 0x12345678, 16, 0, 1, 1, __func__);
const std::array<std::uint32_t, 7> expected{0xc0055000u, 0xc0000001u, 0x12345678, 0, 0x2000, 0, 0x80000010u};
check(std::equal(expected.begin(), expected.end(), storage.words.begin()), "DMA packet mismatch");
std::uint64_t value = 0;
auto* packet = Agc::Command::WriteWait(&storage.buffer, 1, 3, 0, 0, &value, 0x1122334455667788ull, 0xffffffffffffffffull, 32, __func__);
check(packet[0] == 0xc0027901u && packet[4] == 0xc0079300u && packet[5] == 0x13u, "wait packet header mismatch");
check(packet[8] == 0x55667788u && packet[9] == 0x11223344u && packet[12] == 2u, "wait reference or poll interval mismatch");
sceAgcWaitRegMemPatchReference(packet, 7);
check(packet[8] == 7 && packet[9] == 0x11223344u, "reference patch changed the high word");
const auto before = storage.words;
expectFailure([&] { sceAgcWaitRegMemPatchReference(packet, 0x100000000ull); });
expectFailure([&] { Agc::Command::WriteWait(&storage.buffer, 0, 3, 0, 0, &value, 0x100000000ull, 0, 32, __func__); });
check(storage.words == before, "invalid memory operation modified packet memory");
}
void testDefaults() {
for (std::uint32_t version = 0; version < 14; ++version) {
for (const bool internal : {false, true}) {
auto* first = Agc::Command::GetRegisterDefaults(version, internal, __func__);
check(first != nullptr && first == Agc::Command::GetRegisterDefaults(version, internal, __func__), "unstable register defaults pointer");
}
}
expectFailure([] { Agc::Command::GetRegisterDefaults(14, false, __func__); });
expectFailure([] { Agc::Command::GetRegisterDefaults(0xffffffffu, true, __func__); });
}
}
int main() {
try {
testPackets();
testRegisters();
testMemory();
testDefaults();
std::puts("AGC command tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}

View File

@ -2,6 +2,7 @@
#include <cstdint>
#include <cstddef>
#include <limits>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
@ -14,9 +15,19 @@ uint32_t APS5_VABI sceAgcDriverGetEqContextId(const KernelEvent* ev) {
}
int APS5_VABI sceAgcDriverGetEqEventType(const KernelEvent* ev) {
(void)ev;
NotImplemented_nid_no_patch(__func__);
return 0;
if (ev == nullptr) {
throw std::runtime_error(std::string(__func__) + ": null event");
}
if (ev->filter == -14) {
if (ev->ident > static_cast<std::uintptr_t>(std::numeric_limits<int>::max())) {
throw std::runtime_error(std::string(__func__) + ": graphics event type overflow");
}
return static_cast<int>(ev->ident);
}
if (ev->data < std::numeric_limits<int>::min() || ev->data > std::numeric_limits<int>::max()) {
throw std::runtime_error(std::string(__func__) + ": event type overflow");
}
return static_cast<int>(ev->data);
}
}