feat(agcDriver): add 54 PM4 execution and indirect dispatch support
This commit is contained in:
parent
990ae6a0ff
commit
699df8d5fa
|
|
@ -1,5 +1,7 @@
|
|||
add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/GuestMemory.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Pm4.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/VulkanDevice.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Eq/src/Event.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Eq/src/Query.cpp
|
||||
|
|
@ -39,3 +41,9 @@ target_include_directories(agc_driver_flip_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
|||
target_link_libraries(agc_driver_flip_tests PRIVATE libSceAgcDriver libc)
|
||||
set_target_properties(agc_driver_flip_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_flip_tests)
|
||||
|
||||
add_executable(agc_driver_pm4_tests EXCLUDE_FROM_ALL tests/Pm4.cpp)
|
||||
target_include_directories(agc_driver_pm4_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(agc_driver_pm4_tests PRIVATE libSceAgcDriver libc)
|
||||
set_target_properties(agc_driver_pm4_tests PROPERTIES CXX_EXTENSIONS OFF)
|
||||
configure_windows_unwind(agc_driver_pm4_tests)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_GUESTMEMORY_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_GUESTMEMORY_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
namespace AgcDriver::GuestMemory {
|
||||
|
||||
void CheckRange(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable = false);
|
||||
void Read(std::uint64_t address, std::span<std::byte> destination, std::size_t alignment = 1);
|
||||
void Write(std::uint64_t address, std::span<const std::byte> source, std::size_t alignment = 1);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PM4_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PM4_HPP
|
||||
|
||||
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4Opcodes.hpp"
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
namespace AgcDriver::Pm4 {
|
||||
|
||||
std::string Name(std::uint32_t header);
|
||||
std::string_view UnsupportedReason(std::uint32_t header);
|
||||
void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue);
|
||||
void Execute(std::span<const std::uint32_t> packet, QueueState& queue);
|
||||
bool AccessesMemory(std::uint32_t header);
|
||||
std::array<std::uint32_t, 5> ResolveDispatch(std::span<const std::uint32_t> packet, const QueueState& queue);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PM4OPCODES_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PM4OPCODES_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace AgcDriver::Pm4 {
|
||||
|
||||
struct Opcode {
|
||||
std::uint32_t value;
|
||||
std::string_view name;
|
||||
};
|
||||
|
||||
inline constexpr Opcode Opcodes[] = {
|
||||
{0x10, "NOP"},
|
||||
{0x11, "SET_BASE"},
|
||||
{0x12, "CLEAR_STATE"},
|
||||
{0x13, "INDEX_BUFFER_SIZE"},
|
||||
{0x15, "DISPATCH_DIRECT"},
|
||||
{0x16, "DISPATCH_INDIRECT"},
|
||||
{0x20, "SET_PREDICATION"},
|
||||
{0x22, "COND_EXEC"},
|
||||
{0x24, "DRAW_INDIRECT"},
|
||||
{0x25, "DRAW_INDEX_INDIRECT"},
|
||||
{0x26, "INDEX_BASE"},
|
||||
{0x27, "DRAW_INDEX_2"},
|
||||
{0x28, "CONTEXT_CONTROL"},
|
||||
{0x2A, "INDEX_TYPE"},
|
||||
{0x2C, "DRAW_INDIRECT_MULTI"},
|
||||
{0x2D, "DRAW_INDEX_AUTO"},
|
||||
{0x2F, "NUM_INSTANCES"},
|
||||
{0x33, "INDIRECT_BUFFER_CNST"},
|
||||
{0x35, "DRAW_INDEX_OFFSET_2"},
|
||||
{0x37, "WRITE_DATA"},
|
||||
{0x39, "MEM_SEMAPHORE"},
|
||||
{0x38, "DRAW_INDEX_INDIRECT_MULTI"},
|
||||
{0x3A, "DISPATCH_DRAW_PREAMBLE"},
|
||||
{0x3C, "WAIT_REG_MEM"},
|
||||
{0x3F, "INDIRECT_BUFFER"},
|
||||
{0x40, "COPY_DATA"},
|
||||
{0x41, "CP_DMA"},
|
||||
{0x42, "PFP_SYNC_ME"},
|
||||
{0x43, "SURFACE_SYNC"},
|
||||
{0x46, "EVENT_WRITE"},
|
||||
{0x47, "EVENT_WRITE_EOP"},
|
||||
{0x48, "EVENT_WRITE_EOS"},
|
||||
{0x49, "RELEASE_MEM"},
|
||||
{0x50, "DMA_DATA"},
|
||||
{0x58, "ACQUIRE_MEM"},
|
||||
{0x59, "REWIND"},
|
||||
{0x63, "SET_SH_REG_INDIRECT"},
|
||||
{0x64, "SET_UCONFIG_REG_INDIRECT"},
|
||||
{0x68, "SET_CONFIG_REG"},
|
||||
{0x69, "SET_CONTEXT_REG"},
|
||||
{0x76, "SET_SH_REG"},
|
||||
{0x78, "SET_QUEUE_REG"},
|
||||
{0x79, "SET_UCONFIG_REG"},
|
||||
{0x7A, "SET_UCONFIG_REG_INDEX"},
|
||||
{0x81, "WRITE_CONST_RAM"},
|
||||
{0x83, "DUMP_CONST_RAM"},
|
||||
{0x84, "INCREMENT_CE_COUNTER"},
|
||||
{0x85, "INCREMENT_DE_COUNTER"},
|
||||
{0x86, "WAIT_ON_CE_COUNTER"},
|
||||
{0x88, "WAIT_ON_DE_COUNTER_DIFF"},
|
||||
{0x8D, "DISPATCH_DRAW"},
|
||||
{0x8E, "GET_LOD_STATS"},
|
||||
{0x93, "WAIT_REG_MEM_64"},
|
||||
{0x9F, "SET_CONTEXT_REG_INDIRECT"},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
|
|
@ -12,6 +16,15 @@ struct QueueState {
|
|||
Registers shader;
|
||||
Registers context;
|
||||
Registers userConfig;
|
||||
std::optional<Registers> savedContext;
|
||||
std::array<std::uint32_t, 0x3000> constantRam{};
|
||||
std::uint64_t indexBase = 0;
|
||||
std::uint64_t drawIndirectBase = 0;
|
||||
std::uint64_t dispatchIndirectBase = 0;
|
||||
std::uint32_t indexBufferSize = 0;
|
||||
std::uint32_t indexType = 0;
|
||||
std::uint32_t instanceCount = 1;
|
||||
std::vector<std::string> markers;
|
||||
|
||||
void ClearContext() {
|
||||
context.clear();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
|
||||
|
|
@ -18,15 +20,6 @@
|
|||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
namespace AgcDriver {
|
||||
namespace {
|
||||
|
|
@ -37,42 +30,6 @@ void require(bool condition, const char* reason) {
|
|||
}
|
||||
}
|
||||
|
||||
void checkRange(const void* pointer, std::size_t bytes, std::size_t alignment) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
require(address != 0 && address % alignment == 0, "null or misaligned address");
|
||||
require(bytes <= std::numeric_limits<std::uintptr_t>::max() - address, "address range overflow");
|
||||
auto cursor = address;
|
||||
const auto end = address + bytes;
|
||||
#ifdef _WIN32
|
||||
while (cursor < end) {
|
||||
MEMORY_BASIC_INFORMATION memory{};
|
||||
require(VirtualQuery(reinterpret_cast<const void*>(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query guest memory");
|
||||
require(memory.State == MEM_COMMIT && (memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) == 0, "guest memory is not readable");
|
||||
const auto protection = memory.Protect & 0xffu;
|
||||
require(protection == PAGE_READONLY || protection == PAGE_READWRITE || protection == PAGE_WRITECOPY || protection == PAGE_EXECUTE_READ || protection == PAGE_EXECUTE_READWRITE || protection == PAGE_EXECUTE_WRITECOPY, "guest memory has no read permission");
|
||||
const auto base = reinterpret_cast<std::uintptr_t>(memory.BaseAddress);
|
||||
require(memory.RegionSize <= std::numeric_limits<std::uintptr_t>::max() - base && base + memory.RegionSize > cursor, "invalid guest memory mapping");
|
||||
cursor = std::min(end, base + memory.RegionSize);
|
||||
}
|
||||
#else
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
require(maps.is_open(), "cannot query guest memory maps");
|
||||
std::string line;
|
||||
while (cursor < end && std::getline(maps, line)) {
|
||||
std::istringstream fields(line);
|
||||
std::uintptr_t first = 0;
|
||||
std::uintptr_t last = 0;
|
||||
char separator = 0;
|
||||
std::string permissions;
|
||||
require(static_cast<bool>(fields >> std::hex >> first >> separator >> last >> permissions) && separator == '-' && first < last && !permissions.empty(), "invalid guest memory map entry");
|
||||
if (last <= cursor) continue;
|
||||
require(first <= cursor && permissions[0] == 'r', "guest memory is not readable");
|
||||
cursor = std::min(end, last);
|
||||
}
|
||||
require(cursor == end, "guest address range is not mapped");
|
||||
#endif
|
||||
}
|
||||
|
||||
struct ShaderSnapshot {
|
||||
std::uint64_t codeAddress;
|
||||
std::uint64_t headerAddress;
|
||||
|
|
@ -138,14 +95,14 @@ public:
|
|||
void Submit(const Packet* packet, std::uint32_t queue) {
|
||||
CheckFailure();
|
||||
require(queue == 0 || (queue >= 0x20 && queue < 0x58), "unsupported compute queue");
|
||||
checkRange(packet, sizeof(Packet), alignof(Packet));
|
||||
GuestMemory::CheckRange(packet, sizeof(Packet), alignof(Packet));
|
||||
const auto descriptor = *packet;
|
||||
require(descriptor.flags == 0, "nonzero submission flags are not implemented");
|
||||
Submission submission{};
|
||||
submission.queue = queue;
|
||||
if (descriptor.dw_num != 0) {
|
||||
require(descriptor.dw_num <= std::numeric_limits<std::size_t>::max() / sizeof(std::uint32_t), "command size overflow");
|
||||
checkRange(descriptor.addr, static_cast<std::size_t>(descriptor.dw_num) * sizeof(std::uint32_t), alignof(std::uint32_t));
|
||||
GuestMemory::CheckRange(descriptor.addr, static_cast<std::size_t>(descriptor.dw_num) * sizeof(std::uint32_t), alignof(std::uint32_t));
|
||||
submission.commands.assign(descriptor.addr, descriptor.addr + descriptor.dw_num);
|
||||
}
|
||||
validate(submission.commands, queue);
|
||||
|
|
@ -264,13 +221,13 @@ public:
|
|||
|
||||
void RegisterShader(const Shader* shader) {
|
||||
CheckFailure();
|
||||
checkRange(shader, sizeof(Shader), alignof(Shader));
|
||||
GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader));
|
||||
require(shader->file_header == 0x34333231u && shader->version == 0x18u, "invalid shader header");
|
||||
require(shader->header_size >= sizeof(Shader), "shader header is smaller than its fixed fields");
|
||||
require(shader->shader_size != 0 && (shader->shader_size & 3u) == 0, "invalid shader size");
|
||||
checkRange(shader, shader->header_size, alignof(Shader));
|
||||
GuestMemory::CheckRange(shader, shader->header_size, alignof(Shader));
|
||||
const auto* code = const_cast<const void*>(shader->code);
|
||||
checkRange(code, shader->shader_size, 256);
|
||||
GuestMemory::CheckRange(code, shader->shader_size, 256);
|
||||
ShaderSnapshot snapshot{reinterpret_cast<std::uintptr_t>(code), reinterpret_cast<std::uintptr_t>(shader), shader->type, {}, {}};
|
||||
snapshot.code.resize(shader->shader_size / sizeof(std::uint32_t));
|
||||
std::memcpy(snapshot.code.data(), code, shader->shader_size);
|
||||
|
|
@ -320,36 +277,10 @@ private:
|
|||
require((header & 0xc0000000u) == 0xc0000000u, "unsupported PM4 packet type");
|
||||
const auto count = static_cast<std::size_t>((header >> 16u) & 0x3fffu) + 2;
|
||||
require(count <= commands.size() - cursor, "truncated PM4 packet");
|
||||
if (header == FlipPacketHeader) {
|
||||
require(count == FlipPacketWords && queue == 0, "invalid flip packet size or queue");
|
||||
cursor += count;
|
||||
continue;
|
||||
}
|
||||
require((header & 0xffu) == 0, "PM4 header flags or internal extension are not implemented");
|
||||
const auto packet = commands.subspan(cursor, count);
|
||||
const auto opcode = (header >> 8u) & 0xffu;
|
||||
switch (opcode) {
|
||||
case 0x12:
|
||||
require(queue == 0, "CLEAR_STATE in compute queue");
|
||||
require(count == 2, "invalid CLEAR_STATE packet size");
|
||||
require((packet[1] & ~0xfu) == 0, "unsupported CLEAR_STATE payload bits");
|
||||
break;
|
||||
case 0x10:
|
||||
require((packet[1] & 0xffff0000u) != 0x68750000u, "marker NOP is not implemented");
|
||||
break;
|
||||
case 0x69:
|
||||
case 0x76:
|
||||
case 0x79:
|
||||
require(count >= 3, "register packet has no values");
|
||||
require(packet[1] <= 0xffffu && count - 2 <= 0x10000u - packet[1], "register range overflow or unsupported register index");
|
||||
require(queue == 0 || opcode == 0x76, "graphics register packet in compute queue");
|
||||
break;
|
||||
case 0x15:
|
||||
require(count == 5, "invalid dispatch packet size");
|
||||
require((packet[4] & ~0x8000u) == 0x41u, "dispatch modifiers are not implemented");
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("AGC driver: unsupported PM4 opcode " + std::to_string(opcode) + " at DWORD " + std::to_string(cursor));
|
||||
try {
|
||||
Pm4::Validate(commands.subspan(cursor, count), queue);
|
||||
} catch (const std::exception& error) {
|
||||
throw std::runtime_error("AGC driver: " + Pm4::Name(header) + " at DWORD " + std::to_string(cursor) + ": " + error.what());
|
||||
}
|
||||
cursor += count;
|
||||
}
|
||||
|
|
@ -404,35 +335,21 @@ private:
|
|||
const auto header = submission.commands[cursor];
|
||||
const auto count = static_cast<std::size_t>((header >> 16u) & 0x3fffu) + 2;
|
||||
const auto packet = std::span(submission.commands).subspan(cursor, count);
|
||||
switch ((header >> 8u) & 0xffu) {
|
||||
case 0x12:
|
||||
queue.ClearContext();
|
||||
break;
|
||||
case 0x10:
|
||||
if (header == FlipPacketHeader) {
|
||||
{
|
||||
std::lock_guard gpuLock(gpuMutex);
|
||||
if (device != nullptr) device->WaitIdle();
|
||||
}
|
||||
CheckFailure();
|
||||
submission.flips.at(cursor)->GpuReady();
|
||||
}
|
||||
break;
|
||||
case 0x69:
|
||||
case 0x76:
|
||||
case 0x79: {
|
||||
const auto opcode = (header >> 8u) & 0xffu;
|
||||
auto& registers = opcode == 0x69 ? queue.context : opcode == 0x76 ? queue.shader : queue.userConfig;
|
||||
for (std::size_t i = 2; i < count; ++i) {
|
||||
registers.insert_or_assign(packet[1] + static_cast<std::uint32_t>(i - 2), packet[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x15:
|
||||
dispatch(queue, packet, submission);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("AGC driver: validated packet has no executor");
|
||||
const auto opcode = (header >> 8u) & 0xffu;
|
||||
if (Pm4::AccessesMemory(header) || opcode == 0x42 || header == FlipPacketHeader) {
|
||||
std::lock_guard gpuLock(gpuMutex);
|
||||
if (device != nullptr) device->WaitIdle();
|
||||
}
|
||||
if (header == FlipPacketHeader) {
|
||||
CheckFailure();
|
||||
submission.flips.at(cursor)->GpuReady();
|
||||
} else if (opcode == 0x15) {
|
||||
dispatch(queue, packet, submission);
|
||||
} else if (opcode == 0x16) {
|
||||
const auto direct = Pm4::ResolveDispatch(packet, queue);
|
||||
dispatch(queue, direct, submission);
|
||||
} else if (opcode != 0x42) {
|
||||
Pm4::Execute(packet, queue);
|
||||
}
|
||||
cursor += count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
namespace AgcDriver::GuestMemory {
|
||||
namespace {
|
||||
void require(bool condition, const char* reason) {
|
||||
if (!condition) throw std::runtime_error(std::string("AGC driver: ") + reason);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckRange(const void* pointer, std::size_t bytes, std::size_t alignment, bool writable) {
|
||||
require(alignment != 0, "zero guest memory alignment");
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(pointer);
|
||||
require(address != 0 && address % alignment == 0, "null or misaligned address");
|
||||
require(bytes <= std::numeric_limits<std::uintptr_t>::max() - address, "address range overflow");
|
||||
auto cursor = address;
|
||||
const auto end = address + bytes;
|
||||
#ifdef _WIN32
|
||||
while (cursor < end) {
|
||||
MEMORY_BASIC_INFORMATION memory{};
|
||||
require(VirtualQuery(reinterpret_cast<const void*>(cursor), &memory, sizeof(memory)) == sizeof(memory), "cannot query guest memory");
|
||||
require(memory.State == MEM_COMMIT && (memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) == 0, "guest memory is not readable");
|
||||
const auto protection = memory.Protect & 0xffu;
|
||||
require(protection == PAGE_READONLY || protection == PAGE_READWRITE || protection == PAGE_WRITECOPY || protection == PAGE_EXECUTE_READ || protection == PAGE_EXECUTE_READWRITE || protection == PAGE_EXECUTE_WRITECOPY, "guest memory has no read permission");
|
||||
const auto base = reinterpret_cast<std::uintptr_t>(memory.BaseAddress);
|
||||
require(memory.RegionSize <= std::numeric_limits<std::uintptr_t>::max() - base && base + memory.RegionSize > cursor, "invalid guest memory mapping");
|
||||
require(!writable || protection == PAGE_READWRITE || protection == PAGE_WRITECOPY || protection == PAGE_EXECUTE_READWRITE || protection == PAGE_EXECUTE_WRITECOPY, "guest memory has no write permission");
|
||||
cursor = std::min(end, base + memory.RegionSize);
|
||||
}
|
||||
#else
|
||||
std::ifstream maps("/proc/self/maps");
|
||||
require(maps.is_open(), "cannot query guest memory maps");
|
||||
std::string line;
|
||||
while (cursor < end && std::getline(maps, line)) {
|
||||
std::istringstream fields(line);
|
||||
std::uintptr_t first = 0;
|
||||
std::uintptr_t last = 0;
|
||||
char separator = 0;
|
||||
std::string permissions;
|
||||
require(static_cast<bool>(fields >> std::hex >> first >> separator >> last >> permissions) && separator == '-' && first < last && !permissions.empty(), "invalid guest memory map entry");
|
||||
if (last <= cursor) continue;
|
||||
require(first <= cursor && permissions[0] == 'r', "guest memory is not readable");
|
||||
require(!writable || (permissions.size() > 1 && permissions[1] == 'w'), "guest memory has no write permission");
|
||||
cursor = std::min(end, last);
|
||||
}
|
||||
require(cursor == end, "guest address range is not mapped");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Read(std::uint64_t address, std::span<std::byte> destination, std::size_t alignment) {
|
||||
if (destination.empty()) return;
|
||||
const auto* source = reinterpret_cast<const void*>(address);
|
||||
CheckRange(source, destination.size(), alignment);
|
||||
std::memcpy(destination.data(), source, destination.size());
|
||||
}
|
||||
|
||||
void Write(std::uint64_t address, std::span<const std::byte> source, std::size_t alignment) {
|
||||
if (source.empty()) return;
|
||||
auto* destination = reinterpret_cast<void*>(address);
|
||||
CheckRange(destination, source.size(), alignment, true);
|
||||
std::memcpy(destination, source.data(), source.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace AgcDriver::Pm4 {
|
||||
namespace {
|
||||
|
||||
void require(bool condition, const char* reason) {
|
||||
if (!condition) throw std::runtime_error(reason);
|
||||
}
|
||||
|
||||
std::uint64_t address(std::uint32_t low, std::uint32_t high) {
|
||||
return low | (static_cast<std::uint64_t>(high) << 32u);
|
||||
}
|
||||
|
||||
std::uint32_t registerOffset(std::uint32_t value) {
|
||||
require(value != 0xffffffffu, "indirect register sentinel semantics are not implemented");
|
||||
const auto offset = value & ~0x70000000u;
|
||||
require(offset <= 0xffffu, "extended register semantics are not implemented");
|
||||
return offset;
|
||||
}
|
||||
|
||||
Registers& registersFor(QueueState& queue, std::uint32_t opcode) {
|
||||
if (opcode == 0x69 || opcode == 0x9f) return queue.context;
|
||||
if (opcode == 0x76 || opcode == 0x63) return queue.shader;
|
||||
return queue.userConfig;
|
||||
}
|
||||
|
||||
void writeRegister(QueueState& queue, std::uint32_t opcode, std::uint32_t offset, std::uint32_t value) {
|
||||
registersFor(queue, opcode).insert_or_assign(offset, value);
|
||||
if ((opcode == 0x64 || opcode == 0x79 || opcode == 0x7a) && offset == 0x243) queue.indexType = value & 3u;
|
||||
}
|
||||
|
||||
bool memorySelector(std::uint32_t selector) {
|
||||
return selector == 0 || selector == 3;
|
||||
}
|
||||
|
||||
std::uint32_t dmaSource(std::span<const std::uint32_t> packet) {
|
||||
return ((packet[1] >> 29u) & 3u) | ((packet[6] >> 24u) & 4u) | ((packet[6] >> 25u) & 8u);
|
||||
}
|
||||
|
||||
std::uint32_t dmaDestination(std::span<const std::uint32_t> packet) {
|
||||
return ((packet[1] >> 20u) & 3u) | ((packet[6] >> 25u) & 4u) | ((packet[6] >> 26u) & 8u);
|
||||
}
|
||||
|
||||
void copyMemory(std::uint64_t source, std::uint64_t destination, std::size_t bytes, bool immediate) {
|
||||
if (bytes == 0) return;
|
||||
GuestMemory::CheckRange(reinterpret_cast<void*>(destination), bytes, 1, true);
|
||||
std::vector<std::byte> data(bytes);
|
||||
if (immediate) {
|
||||
const auto value = static_cast<std::uint32_t>(source);
|
||||
for (std::size_t i = 0; i < bytes; ++i) data[i] = static_cast<std::byte>(value >> ((i % 4) * 8));
|
||||
} else {
|
||||
GuestMemory::Read(source, data);
|
||||
}
|
||||
GuestMemory::Write(destination, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string Name(std::uint32_t header) {
|
||||
const auto opcode = (header >> 8u) & 0xffu;
|
||||
if (opcode == 0x10 && (header & 0xfcu) != 0) {
|
||||
switch ((header >> 2u) & 0x3fu) {
|
||||
case 0x05: return "DRAW_RESET";
|
||||
case 0x06: return "WAIT_FLIP_DONE";
|
||||
case 0x09: return "DISPATCH_RESET";
|
||||
case 0x0b: return "PUSH_MARKER";
|
||||
case 0x0c: return "POP_MARKER";
|
||||
case 0x14: return "ACQUIRE_MEM_CUSTOM";
|
||||
case 0x15: return "WRITE_DATA_CUSTOM";
|
||||
case 0x17: return "FLIP";
|
||||
case 0x18: return "RELEASE_MEM_CUSTOM";
|
||||
case 0x19: return "DMA_DATA_CUSTOM";
|
||||
case 0x1a: return "CONTEXT_STATE";
|
||||
default: return "UNKNOWN_CUSTOM";
|
||||
}
|
||||
}
|
||||
for (const auto& entry : Opcodes) if (entry.value == opcode) return std::string(entry.name);
|
||||
char text[24]{};
|
||||
std::snprintf(text, sizeof(text), "UNKNOWN_0x%02x", opcode);
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string_view UnsupportedReason(std::uint32_t header) {
|
||||
const auto opcode = (header >> 8u) & 0xffu;
|
||||
if (opcode == 0x10) {
|
||||
switch ((header >> 2u) & 0x3fu) {
|
||||
case 0: case 0x09: case 0x0b: case 0x0c: case 0x17: case 0x1a: return {};
|
||||
case 0x06: return "VideoOut buffer-completion wait interface is not implemented";
|
||||
case 0x14: case 0x18: return "guest cache actions and GPU release events are not implemented";
|
||||
default: return "custom packet has no implemented contract in the reference dispatch table";
|
||||
}
|
||||
}
|
||||
switch (opcode) {
|
||||
case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26:
|
||||
case 0x2a: case 0x2f: case 0x37: case 0x40: case 0x42: case 0x50:
|
||||
case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a:
|
||||
case 0x81: case 0x83: case 0x9f: return {};
|
||||
case 0x24: case 0x25: case 0x27: case 0x2c: case 0x2d: case 0x35: case 0x38: case 0x3a: case 0x8d:
|
||||
return "graphics draw, shader stages and guest render-target materialization are not implemented";
|
||||
case 0x20: return "GPU query predication is not implemented";
|
||||
case 0x22: return "conditional command execution and conditional flip reservation are not implemented";
|
||||
case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented";
|
||||
case 0x39: case 0x3c: case 0x59: case 0x93:
|
||||
return "cooperative command-queue waits are not implemented";
|
||||
case 0x84: case 0x85: case 0x86: case 0x88:
|
||||
return "separate CE/DE execution and counter synchronization are not implemented";
|
||||
case 0x43: case 0x46: case 0x47: case 0x48: case 0x49: case 0x58:
|
||||
return "guest cache actions, GPU events and interrupt delivery are not implemented";
|
||||
case 0x8e: return "GPU LOD statistics are not implemented; synthetic results are forbidden";
|
||||
case 0x28: case 0x41: case 0x68: case 0x78:
|
||||
return "opcode is named but has no handler in the reference dispatch table";
|
||||
default: return "opcode is not known in the reference";
|
||||
}
|
||||
}
|
||||
|
||||
void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
|
||||
require(packet.size() >= 2, "truncated PM4 header or payload");
|
||||
const auto header = packet[0];
|
||||
require((header & 0xc0000000u) == 0xc0000000u, "unsupported PM4 packet type");
|
||||
require(packet.size() == ((header >> 16u) & 0x3fffu) + 2u, "invalid PM4 packet size");
|
||||
const auto opcode = (header >> 8u) & 0xffu;
|
||||
const auto size = [&](std::size_t count) { require(packet.size() == count, "invalid packet size"); };
|
||||
const auto graphics = [&] { require(queue == 0, "graphics packet in compute queue"); };
|
||||
const auto reason = UnsupportedReason(header);
|
||||
if (!reason.empty()) throw std::runtime_error(std::string(reason));
|
||||
if (opcode == 0x10) {
|
||||
require((header & 3u) == 0, "unsupported NOP header flags");
|
||||
switch ((header >> 2u) & 0x3fu) {
|
||||
case 0:
|
||||
require((packet[1] & 0xffff0000u) != 0x68750000u, "typed user-data and legacy flip markers are not implemented");
|
||||
break;
|
||||
case 0x09: size(2); break;
|
||||
case 0x0b: {
|
||||
const auto data = std::as_bytes(packet.subspan(1));
|
||||
require(std::find(data.begin(), data.end(), std::byte{}) != data.end(), "unterminated marker text");
|
||||
break;
|
||||
}
|
||||
case 0x0c: break;
|
||||
case 0x17: graphics(); size(6); break;
|
||||
case 0x1a:
|
||||
graphics();
|
||||
require(packet.size() == 3 || packet.size() == 5, "invalid context-state packet size");
|
||||
require(packet[1] <= 3, "unknown context-state operation");
|
||||
require(std::all_of(packet.begin() + 2, packet.end(), [](auto value) { return value == 0; }), "context-state trailing fields are not implemented");
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
require((header & 0xffu) == 0 || (opcode == 0x11 && (header & 0xffu) == 2), "PM4 header flags are not implemented");
|
||||
switch (opcode) {
|
||||
case 0x11:
|
||||
size(4);
|
||||
require(packet[1] == 1 && (packet[2] & 7u) == 0 && packet[3] <= 0xffffu, "unsupported indirect base index, alignment or address bits");
|
||||
if ((header & 2u) == 0) graphics();
|
||||
break;
|
||||
case 0x12: graphics(); size(2); require((packet[1] & ~0xfu) == 0, "unsupported CLEAR_STATE payload bits"); break;
|
||||
case 0x13: case 0x2f: graphics(); size(2); break;
|
||||
case 0x26: graphics(); size(3); break;
|
||||
case 0x2a: graphics(); size(2); require(packet[1] <= 3, "unsupported index-type modifiers"); break;
|
||||
case 0x15: size(5); require((packet[4] & ~0x8000u) == 0x41u, "dispatch modifiers are not implemented"); break;
|
||||
case 0x16:
|
||||
require(packet.size() == 3 || packet.size() == 4, "invalid indirect dispatch size");
|
||||
require((packet.back() & ~0x8000u) == 0x41u, "indirect dispatch modifiers are not implemented");
|
||||
break;
|
||||
case 0x42: size(2); require(packet[1] == 0, "unsupported PFP_SYNC_ME payload"); break;
|
||||
case 0x63: case 0x64: case 0x9f:
|
||||
if (opcode != 0x63) graphics();
|
||||
size(5);
|
||||
require((packet[1] & 3u) == 0 && packet[3] == 0x80000000u && packet[4] <= 0x3fffu, "unsupported indirect-register address or control fields");
|
||||
break;
|
||||
case 0x69: case 0x76: case 0x79: case 0x7a: {
|
||||
if (opcode != 0x76) graphics();
|
||||
require(packet.size() >= 3, "register packet has no values");
|
||||
if (opcode == 0x7a) require((packet[1] & 0xf0000000u) == 0 || (packet.size() == 3 && packet[1] == 0x20000243u), "indexed register bank selection is not implemented");
|
||||
const auto offset = registerOffset(packet[1]);
|
||||
require(packet.size() - 2 <= 0x10000u - offset, "register range overflow");
|
||||
break;
|
||||
}
|
||||
case 0x81:
|
||||
graphics();
|
||||
require(packet[1] <= 0xbffcu && (packet[1] & 3u) == 0 && packet.size() - 2 <= 0x3000u - packet[1] / 4u, "constant RAM write range overflow or misalignment");
|
||||
break;
|
||||
case 0x83:
|
||||
graphics(); size(5);
|
||||
require(packet[1] <= 0xbffcu && (packet[1] & 3u) == 0 && packet[2] <= 0x3000u - packet[1] / 4u, "constant RAM dump range overflow or misalignment");
|
||||
break;
|
||||
case 0x37: {
|
||||
require(packet.size() >= 5, "WRITE_DATA has no data");
|
||||
require((packet[1] & ~0x00110f00u) == 0, "WRITE_DATA engine, cache or reserved fields are not implemented");
|
||||
const auto destination = (packet[1] >> 8u) & 0xfu;
|
||||
require(destination == 1 || destination == 2 || (queue != 0 && destination == 5), "WRITE_DATA register or GDS destination is not implemented");
|
||||
require((packet[2] & 3u) == 0, "misaligned WRITE_DATA destination");
|
||||
break;
|
||||
}
|
||||
case 0x40: {
|
||||
size(6);
|
||||
require((packet[1] & ~0x40110f0fu) == 0, "COPY_DATA engine, cache or reserved fields are not implemented");
|
||||
const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u);
|
||||
const auto destination = ((packet[1] >> 8u) & 0xfu) << 1u;
|
||||
require(destination == 2 || destination == 4, "COPY_DATA register or GDS destination is not implemented");
|
||||
require(source == 2 || source == 4 || source == 5 || source == 10 || source == 11, "COPY_DATA register, GDS or reference-clock source is not implemented");
|
||||
require(source < 10 || ((packet[1] & 0x10000u) == 0 && packet[3] == 0), "64-bit immediate COPY_DATA is not implemented");
|
||||
break;
|
||||
}
|
||||
case 0x50:
|
||||
size(7);
|
||||
require((packet[1] & ~0xe0300001u) == 0, "DMA_DATA cache or reserved fields are not implemented");
|
||||
require(memorySelector(dmaDestination(packet)), "DMA_DATA register, GDS or prefetch destination is not implemented");
|
||||
require(memorySelector(dmaSource(packet)) || dmaSource(packet) == 2, "DMA_DATA register or GDS source is not implemented");
|
||||
require(dmaSource(packet) != 2 || packet[3] == 0, "DMA_DATA immediate exceeds 32 bits");
|
||||
break;
|
||||
default: throw std::runtime_error("known packet has no validator");
|
||||
}
|
||||
}
|
||||
|
||||
bool AccessesMemory(std::uint32_t header) {
|
||||
switch ((header >> 8u) & 0xffu) {
|
||||
case 0x16: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 5> ResolveDispatch(std::span<const std::uint32_t> packet, const QueueState& queue) {
|
||||
std::uint64_t source = 0;
|
||||
if (packet.size() == 4) source = address(packet[1], packet[2]);
|
||||
else {
|
||||
require(queue.dispatchIndirectBase != 0, "indirect dispatch base has not been set");
|
||||
require(packet[1] <= std::numeric_limits<std::uint64_t>::max() - queue.dispatchIndirectBase, "indirect dispatch address overflow");
|
||||
source = queue.dispatchIndirectBase + packet[1];
|
||||
}
|
||||
std::array<std::uint32_t, 5> result{0xc0031500u, 0, 0, 0, packet.back()};
|
||||
GuestMemory::Read(source, std::as_writable_bytes(std::span(result).subspan(1, 3)), 4);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
|
||||
const auto opcode = (packet[0] >> 8u) & 0xffu;
|
||||
switch (opcode) {
|
||||
case 0x10:
|
||||
switch ((packet[0] >> 2u) & 0x3fu) {
|
||||
case 0: return;
|
||||
case 0x09: queue = QueueState{}; return;
|
||||
case 0x0b: queue.markers.emplace_back(reinterpret_cast<const char*>(packet.data() + 1)); return;
|
||||
case 0x0c:
|
||||
require(!queue.markers.empty(), "marker stack underflow");
|
||||
queue.markers.pop_back();
|
||||
return;
|
||||
case 0x1a:
|
||||
switch (packet[1]) {
|
||||
case 0: queue.ClearContext(); break;
|
||||
case 1: case 3:
|
||||
require(!queue.savedContext.has_value(), "context state is already pushed");
|
||||
queue.savedContext = queue.context;
|
||||
if (packet[1] == 3) queue.ClearContext();
|
||||
break;
|
||||
case 2:
|
||||
require(queue.savedContext.has_value(), "context state has not been pushed");
|
||||
queue.context = std::move(*queue.savedContext);
|
||||
queue.savedContext.reset();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
default: throw std::runtime_error("custom packet requires driver execution");
|
||||
}
|
||||
case 0x11:
|
||||
((packet[0] & 2u) == 0 ? queue.drawIndirectBase : queue.dispatchIndirectBase) = address(packet[2], packet[3]);
|
||||
return;
|
||||
case 0x12: queue.ClearContext(); return;
|
||||
case 0x13: queue.indexBufferSize = packet[1]; return;
|
||||
case 0x26: queue.indexBase = address(packet[1], packet[2]); return;
|
||||
case 0x2a: queue.indexType = packet[1]; return;
|
||||
case 0x2f: queue.instanceCount = packet[1]; return;
|
||||
case 0x63: case 0x64: case 0x9f: {
|
||||
std::vector<std::uint32_t> pairs(static_cast<std::size_t>(packet[4]) * 2);
|
||||
GuestMemory::Read(address(packet[1], packet[2]), std::as_writable_bytes(std::span(pairs)), 4);
|
||||
for (std::size_t i = 0; i < pairs.size(); i += 2) registerOffset(pairs[i]);
|
||||
for (std::size_t i = 0; i < pairs.size(); i += 2) writeRegister(queue, opcode, registerOffset(pairs[i]), pairs[i + 1]);
|
||||
return;
|
||||
}
|
||||
case 0x69: case 0x76: case 0x79: case 0x7a: {
|
||||
const auto offset = registerOffset(packet[1]);
|
||||
for (std::size_t i = 2; i < packet.size(); ++i) writeRegister(queue, opcode, offset + static_cast<std::uint32_t>(i - 2), packet[i]);
|
||||
return;
|
||||
}
|
||||
case 0x81:
|
||||
std::copy(packet.begin() + 2, packet.end(), queue.constantRam.begin() + packet[1] / 4);
|
||||
return;
|
||||
case 0x83:
|
||||
GuestMemory::Write(address(packet[3], packet[4]), std::as_bytes(std::span(queue.constantRam).subspan(packet[1] / 4, packet[2])), 4);
|
||||
return;
|
||||
case 0x37: {
|
||||
const auto destination = address(packet[2], packet[3]);
|
||||
if ((packet[1] & 0x10000u) != 0) {
|
||||
for (const auto& value : packet.subspan(4)) GuestMemory::Write(destination, std::as_bytes(std::span(&value, 1)), 4);
|
||||
} else GuestMemory::Write(destination, std::as_bytes(packet.subspan(4)), 4);
|
||||
return;
|
||||
}
|
||||
case 0x40: {
|
||||
const auto source = ((packet[1] & 0xfu) << 1u) | ((packet[1] >> 30u) & 1u);
|
||||
copyMemory(address(packet[2], packet[3]), address(packet[4], packet[5]), (packet[1] & 0x10000u) != 0 ? 8 : 4, source >= 10);
|
||||
return;
|
||||
}
|
||||
case 0x50:
|
||||
copyMemory(address(packet[2], packet[3]), address(packet[4], packet[5]), packet[6] & 0x3ffffffu, dmaSource(packet) == 2);
|
||||
return;
|
||||
default: throw std::runtime_error("packet requires driver execution");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void check(bool condition, const char* reason) {
|
||||
if (!condition) throw std::runtime_error(reason);
|
||||
}
|
||||
|
||||
template<typename TAction>
|
||||
void expectFailure(TAction action, const char* text) {
|
||||
try { action(); }
|
||||
catch (const std::runtime_error& error) {
|
||||
check(std::string(error.what()).find(text) != std::string::npos, error.what());
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error("expected PM4 rejection");
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> makePacket(std::uint32_t opcode, std::initializer_list<std::uint32_t> payload, std::uint32_t flags = 0) {
|
||||
std::vector<std::uint32_t> result{0xc0000000u | (static_cast<std::uint32_t>(payload.size() - 1) << 16u) | (opcode << 8u) | flags};
|
||||
result.insert(result.end(), payload);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::uint32_t low(const void* pointer) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer)); }
|
||||
std::uint32_t high(const void* pointer) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(pointer) >> 32u); }
|
||||
|
||||
void execute(AgcDriver::QueueState& state, const std::vector<std::uint32_t>& packet) {
|
||||
AgcDriver::Pm4::Validate(packet, 0);
|
||||
AgcDriver::Pm4::Execute(packet, state);
|
||||
}
|
||||
|
||||
void testCatalog() {
|
||||
std::set<std::uint32_t> values;
|
||||
for (const auto& opcode : AgcDriver::Pm4::Opcodes) {
|
||||
check(values.insert(opcode.value).second, "duplicate PM4 opcode");
|
||||
const auto packet = makePacket(opcode.value, {0});
|
||||
check(AgcDriver::Pm4::Name(packet[0]) == opcode.name, "opcode name mismatch");
|
||||
const auto reason = AgcDriver::Pm4::UnsupportedReason(packet[0]);
|
||||
if (!reason.empty()) expectFailure([&] { AgcDriver::Pm4::Validate(packet, 0); }, std::string(reason).c_str());
|
||||
}
|
||||
check(values.size() == 54, "reference opcode catalog is incomplete");
|
||||
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0xff, {0}), 0); }, "not known");
|
||||
const std::array<std::pair<std::uint32_t, const char*>, 11> custom{{
|
||||
{5, "DRAW_RESET"}, {6, "WAIT_FLIP_DONE"}, {9, "DISPATCH_RESET"}, {11, "PUSH_MARKER"},
|
||||
{12, "POP_MARKER"}, {20, "ACQUIRE_MEM_CUSTOM"}, {21, "WRITE_DATA_CUSTOM"}, {23, "FLIP"},
|
||||
{24, "RELEASE_MEM_CUSTOM"}, {25, "DMA_DATA_CUSTOM"}, {26, "CONTEXT_STATE"}
|
||||
}};
|
||||
for (const auto& [id, name] : custom) check(AgcDriver::Pm4::Name(makePacket(0x10, {0}, id << 2)[0]) == name, "custom opcode name mismatch");
|
||||
}
|
||||
|
||||
void testRegisters() {
|
||||
AgcDriver::QueueState state;
|
||||
const std::array<std::uint32_t, 3> indirectOpcodes{0x9f, 0x63, 0x64};
|
||||
for (auto opcode : indirectOpcodes) {
|
||||
std::array<std::uint32_t, 6> pairs{0x10, 41, 0x11, 42, 0x10, 43};
|
||||
auto packet = makePacket(opcode, {low(pairs.data()), high(pairs.data()), 0x80000000, 3});
|
||||
execute(state, packet);
|
||||
const auto& registers = opcode == 0x9f ? state.context : opcode == 0x63 ? state.shader : state.userConfig;
|
||||
check(registers.at(0x10) == 43 && registers.at(0x11) == 42, "indirect register order or bank lost");
|
||||
pairs[0] = 0x12;
|
||||
pairs[4] = 0xffffffffu;
|
||||
expectFailure([&] { execute(state, packet); }, "sentinel");
|
||||
check(!registers.contains(0x12), "invalid indirect packet partially changed state");
|
||||
packet[1] = 0x1000;
|
||||
packet[2] = 0;
|
||||
expectFailure([&] { execute(state, packet); }, "guest");
|
||||
packet[3] = 0;
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(packet, 0); }, "control");
|
||||
}
|
||||
execute(state, makePacket(0x69, {0x11, 50, 51}));
|
||||
check(state.context.at(0x11) == 50 && state.context.at(0x12) == 51, "direct registers not sequential");
|
||||
execute(state, makePacket(0x7a, {0x10, 60}));
|
||||
check(state.userConfig.at(0x10) == 60, "uconfig index zero failed");
|
||||
execute(state, makePacket(0x7a, {0x20000243, 0x441}));
|
||||
check(state.indexType == 1 && state.userConfig.at(0x243) == 0x441, "indexed VGT_INDEX_TYPE write lost state");
|
||||
expectFailure([&] { execute(state, makePacket(0x7a, {0x10000010, 1})); }, "bank selection");
|
||||
expectFailure([&] { execute(state, makePacket(0x69, {0xffff, 1, 2})); }, "overflow");
|
||||
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x9f, {0, 0, 0x80000000, 0}), 0x20); }, "compute");
|
||||
}
|
||||
|
||||
void testContextAndBases() {
|
||||
AgcDriver::QueueState state;
|
||||
execute(state, makePacket(0x69, {0x10, 17}));
|
||||
execute(state, makePacket(0x76, {0x20c, 2}));
|
||||
execute(state, makePacket(0x10, {3, 0}, 0x68));
|
||||
check(state.context.empty() && state.shader.at(0x20c) == 2, "push-clear reset wrong state");
|
||||
expectFailure([&] { execute(state, makePacket(0x10, {1, 0}, 0x68)); }, "already pushed");
|
||||
execute(state, makePacket(0x69, {0x10, 19}));
|
||||
execute(state, makePacket(0x10, {2, 0}, 0x68));
|
||||
check(state.context.at(0x10) == 17, "pop did not restore context");
|
||||
expectFailure([&] { execute(state, makePacket(0x10, {2, 0}, 0x68)); }, "not been pushed");
|
||||
alignas(8) std::array<std::uint32_t, 4> arguments{7, 8, 9, 0};
|
||||
execute(state, makePacket(0x11, {1, low(arguments.data()), high(arguments.data())}, 2));
|
||||
auto packet = makePacket(0x16, {0, 0x8041});
|
||||
AgcDriver::Pm4::Validate(packet, 0);
|
||||
auto resolved = AgcDriver::Pm4::ResolveDispatch(packet, state);
|
||||
check(resolved == std::array<std::uint32_t, 5>{0xc0031500, 7, 8, 9, 0x8041}, "base-relative dispatch arguments changed");
|
||||
packet = makePacket(0x16, {low(arguments.data()), high(arguments.data()), 0x41});
|
||||
AgcDriver::Pm4::Validate(packet, 0x20);
|
||||
check(AgcDriver::Pm4::ResolveDispatch(packet, state)[3] == 9, "absolute indirect dispatch arguments changed");
|
||||
execute(state, makePacket(0x13, {32}));
|
||||
execute(state, makePacket(0x26, {0x1000, 1}));
|
||||
execute(state, makePacket(0x2a, {1}));
|
||||
execute(state, makePacket(0x2f, {3}));
|
||||
check(state.indexBufferSize == 32 && state.indexBase == 0x100001000ull && state.indexType == 1 && state.instanceCount == 3, "draw setup state lost");
|
||||
execute(state, makePacket(0x10, {0x00636261}, 0x2c));
|
||||
check(state.markers.back() == "abc", "marker text lost");
|
||||
execute(state, makePacket(0x10, {0}, 0x30));
|
||||
expectFailure([&] { execute(state, makePacket(0x10, {0}, 0x30)); }, "underflow");
|
||||
execute(state, makePacket(0x10, {0}, 0x24));
|
||||
check(state.shader.empty() && state.context.empty() && state.dispatchIndirectBase == 0 && state.indexBase == 0 && !state.savedContext, "dispatch reset retained state");
|
||||
}
|
||||
|
||||
void testMemory() {
|
||||
AgcDriver::QueueState state;
|
||||
std::array<std::uint32_t, 4> data{0, 0, 0, 0};
|
||||
execute(state, makePacket(0x37, {0x100, low(data.data()), high(data.data()), 11, 12}));
|
||||
check(data[0] == 11 && data[1] == 12, "WRITE_DATA increment failed");
|
||||
execute(state, makePacket(0x37, {0x10100, low(data.data()), high(data.data()), 21, 22}));
|
||||
check(data[0] == 22 && data[1] == 12, "WRITE_DATA fixed destination failed");
|
||||
execute(state, makePacket(0x81, {4, 31, 32}));
|
||||
execute(state, makePacket(0x83, {4, 2, low(data.data()), high(data.data())}));
|
||||
check(data[0] == 31 && data[1] == 32, "constant RAM round trip failed");
|
||||
expectFailure([&] { execute(state, makePacket(0x81, {0xbffc, 1, 2})); }, "overflow");
|
||||
expectFailure([&] { execute(state, makePacket(0x40, {0x10105, 0, 0, low(data.data()), high(data.data())})); }, "64-bit immediate");
|
||||
}
|
||||
|
||||
void testCopies() {
|
||||
AgcDriver::QueueState state;
|
||||
alignas(8) std::array<std::uint32_t, 4> source{11, 12, 13, 14};
|
||||
alignas(8) std::array<std::uint32_t, 4> destination{};
|
||||
execute(state, makePacket(0x40, {0x10101, low(source.data()), high(source.data()), low(destination.data()), high(destination.data())}));
|
||||
check(destination[0] == 11 && destination[1] == 12 && destination[2] == 0, "64-bit COPY_DATA failed");
|
||||
execute(state, makePacket(0x40, {0x105, 0x12345678, 0, low(destination.data()), high(destination.data())}));
|
||||
check(destination[0] == 0x12345678, "immediate COPY_DATA failed");
|
||||
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}));
|
||||
check(destination[0] == 0x44332211 && destination[1] == 0x00002211, "DMA_DATA byte fill failed");
|
||||
expectFailure([&] { execute(state, makePacket(0x50, {0x60100000, low(source.data()), high(source.data()), 0, 0, 4})); }, "GDS");
|
||||
expectFailure([&] { execute(state, makePacket(0x37, {0x100, 0x1000, 0, 1})); }, "guest");
|
||||
#ifdef _WIN32
|
||||
auto* memory = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
check(memory != nullptr, "VirtualAlloc failed");
|
||||
DWORD previous = 0;
|
||||
check(VirtualProtect(memory, 4096, PAGE_READONLY, &previous) != 0, "VirtualProtect failed");
|
||||
try {
|
||||
expectFailure([&] { execute(state, makePacket(0x37, {0x100, low(memory), high(memory), 1})); }, "write permission");
|
||||
} catch (...) { VirtualFree(memory, 0, MEM_RELEASE); throw; }
|
||||
check(VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
|
||||
#endif
|
||||
}
|
||||
|
||||
void testDriverSubmission() {
|
||||
std::array<std::uint32_t, 2> source{0x10, 73};
|
||||
std::array<std::uint32_t, 1> destination{};
|
||||
std::vector<std::uint32_t> commands;
|
||||
for (const auto& packet : {
|
||||
makePacket(0x9f, {low(source.data()), high(source.data()), 0x80000000, 1}),
|
||||
makePacket(0x81, {0, 83}),
|
||||
makePacket(0x42, {0}),
|
||||
makePacket(0x83, {0, 1, low(destination.data()), high(destination.data())})
|
||||
}) commands.insert(commands.end(), packet.begin(), packet.end());
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
check(sceAgcDriverSubmitDcb(&packet) == 0, "PM4 submission failed");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
check(destination[0] == 83, "worker did not execute PM4 memory operations");
|
||||
destination[0] = 0;
|
||||
const auto draw = makePacket(0x2d, {3, 2});
|
||||
commands.insert(commands.end(), draw.begin(), draw.end());
|
||||
packet = Packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }, "DRAW_INDEX_AUTO at DWORD");
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
check(destination[0] == 0, "rejected submission executed a prefix");
|
||||
}
|
||||
|
||||
void testAsyncMemoryFailure() {
|
||||
auto commands = makePacket(0x37, {0x100, 0x1000, 0, 1});
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
check(sceAgcDriverSubmitDcb(&packet) == 0, "memory packet was not submitted");
|
||||
expectFailure([] { AgcDriverWaitIdle_nid_postfix(); }, "guest");
|
||||
expectFailure([] { AgcDriverSuspendPoint_nid_postfix(); }, "guest");
|
||||
expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }, "guest");
|
||||
expectFailure([] { LibcRunShutdown_nid_postfix(); }, "guest");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
if (argc == 2 && std::string(argv[1]) == "failure") {
|
||||
testAsyncMemoryFailure();
|
||||
std::puts("PM4 asynchronous memory failure propagated to idle, suspend, submit and shutdown");
|
||||
return 0;
|
||||
}
|
||||
testCatalog();
|
||||
testRegisters();
|
||||
testContextAndBases();
|
||||
testMemory();
|
||||
testCopies();
|
||||
testDriverSubmission();
|
||||
LibcRunShutdown_nid_postfix();
|
||||
std::puts("PM4 catalog, registers, state, memory and submission tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::fprintf(stderr, "%s\n", error.what());
|
||||
try { LibcRunShutdown_nid_postfix(); } catch (...) {}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
int main() {
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
alignas(256) const std::array<std::uint32_t, 1> code{0xbf810000};
|
||||
Shader shader{};
|
||||
|
|
@ -23,6 +23,15 @@ int main() {
|
|||
0xc0031500, 1, 1, 1, 0x8041
|
||||
};
|
||||
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
|
||||
const std::array<std::uint32_t, 3> arguments{1, 1, 1};
|
||||
if (argc == 2 && std::string(argv[1]) == "indirect") {
|
||||
const auto argumentAddress = reinterpret_cast<std::uintptr_t>(arguments.data());
|
||||
commands[7] = 0xc0021600;
|
||||
commands[8] = static_cast<std::uint32_t>(argumentAddress);
|
||||
commands[9] = static_cast<std::uint32_t>(argumentAddress >> 32u);
|
||||
commands[10] = 0x8041;
|
||||
packet.dw_num = 11;
|
||||
}
|
||||
sceAgcDriverSubmitAcb(0x20, &packet);
|
||||
try {
|
||||
AgcDriverWaitIdle_nid_postfix();
|
||||
|
|
|
|||
Loading…
Reference in New Issue