mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
395 lines
15 KiB
C++
395 lines
15 KiB
C++
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
|
|
#include "prx/libSceJobManager/libSceJobManager.hpp"
|
|
#include "prx/libSceAgcDriver/libSceAgcDriver.hpp"
|
|
#include "prx/libSceAgc/libSceAgc.hpp"
|
|
#include "SceTypes.hpp"
|
|
#include "prx/libSceAgcDriver/tests/execution/VulkanTestDevice.hpp"
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <immintrin.h>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
|
|
namespace {
|
|
|
|
enum class Kind { Sequence, Job, Dispatch, Body };
|
|
|
|
struct Event {
|
|
Kind kind;
|
|
const char* name;
|
|
std::uint16_t manager;
|
|
std::uint16_t sequence;
|
|
std::uint16_t dispatch;
|
|
std::uint16_t value;
|
|
std::uint16_t event;
|
|
std::uint64_t entry;
|
|
std::uint64_t argument;
|
|
};
|
|
|
|
std::mutex eventLock;
|
|
std::vector<Event> events;
|
|
|
|
void Check(bool condition, int line) {
|
|
if (!condition) {
|
|
std::fprintf(stderr, "job manager profiler check failed at line %d\n", line);
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
#define Require(value) Check((value), __LINE__)
|
|
|
|
void Record(Event event) {
|
|
const std::lock_guard guard(eventLock);
|
|
events.push_back(event);
|
|
}
|
|
|
|
std::vector<Event> Snapshot() {
|
|
const std::lock_guard guard(eventLock);
|
|
return events;
|
|
}
|
|
|
|
void Clear() {
|
|
const std::lock_guard guard(eventLock);
|
|
events.clear();
|
|
}
|
|
|
|
struct Fixture {
|
|
alignas(256) std::array<std::uint8_t, 0x100000> memory{};
|
|
alignas(64) std::array<std::uint8_t, 0x100> object{};
|
|
ISequenceFactory* factory = nullptr;
|
|
std::uint16_t id = 0;
|
|
|
|
explicit Fixture(std::uint32_t workers = 1) {
|
|
_ZN3sce3Job10JobManagerC1Ev(object.data());
|
|
Require(_ZN3sce3Job10JobManager10initializeEPvmjjjmPKNS1_12ConfigParamsE(object.data(), memory.data(), memory.size(), workers, 2, 2, 0x122, nullptr) == 0);
|
|
factory = _ZN3sce3Job10JobManager34getUnifiedSequenceFactoryInterfaceENS1_8PriorityE(object.data(), 0);
|
|
id = static_cast<std::uint16_t>(_mm_crc32_u64(0, reinterpret_cast<std::uintptr_t>(memory.data())) >> 16);
|
|
Clear();
|
|
}
|
|
|
|
void Stop() {
|
|
Require(_ZN3sce3Job10JobManager8shutdownEv(object.data()) == 0);
|
|
}
|
|
|
|
ISequence* Create(const char* name, bool compute = false) {
|
|
ISequence* sequence = nullptr;
|
|
if (compute) Require(factory->CreateCompute(&sequence, 16, 8, 8, 4, name) == 0);
|
|
else Require(factory->Create(&sequence, 16, 8, 8, 4, name) == 0);
|
|
return sequence;
|
|
}
|
|
};
|
|
|
|
struct Context {
|
|
std::atomic<bool> entered{false};
|
|
std::atomic<std::uint32_t> calls{0};
|
|
std::atomic<bool> released{true};
|
|
};
|
|
|
|
void APS5_VABI Job(void* argument, std::uint32_t index) {
|
|
Record({Kind::Body, nullptr, 0, 0, 0, static_cast<std::uint16_t>(index), 0, 0, reinterpret_cast<std::uint64_t>(argument)});
|
|
auto& context = *static_cast<Context*>(argument);
|
|
++context.calls;
|
|
context.entered.store(true);
|
|
while (!context.released.load()) std::this_thread::yield();
|
|
}
|
|
|
|
void Await(const std::atomic<bool>& flag) {
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
|
while (!flag.load() && std::chrono::steady_clock::now() < deadline) std::this_thread::yield();
|
|
Require(flag.load());
|
|
}
|
|
|
|
struct Reader {
|
|
std::vector<Event> records = Snapshot();
|
|
std::size_t next = 0;
|
|
std::uint16_t manager;
|
|
|
|
explicit Reader(std::uint16_t id) : manager(id) {}
|
|
|
|
Event Take(Kind kind) {
|
|
Require(next < records.size());
|
|
const Event result = records[next++];
|
|
Require(result.kind == kind);
|
|
if (kind != Kind::Body) Require(result.manager == manager);
|
|
return result;
|
|
}
|
|
|
|
void Sequence(const char* name, std::uint16_t id, std::uint16_t event) {
|
|
const Event result = Take(Kind::Sequence);
|
|
Require(result.name == name && result.sequence == id && result.event == event);
|
|
}
|
|
|
|
std::uint16_t Dispatch(const char* name, Context& context, std::uint16_t count, std::uint16_t sequence = 0) {
|
|
std::uint16_t dispatch = 0;
|
|
for (std::uint16_t index = 0; index < count; ++index) {
|
|
const Event begin = Take(Kind::Job);
|
|
if (index == 0) dispatch = begin.dispatch;
|
|
Require(begin.dispatch == dispatch && begin.value == index && begin.event == 16);
|
|
const Event body = Take(Kind::Body);
|
|
Require(body.value == index && body.argument == reinterpret_cast<std::uint64_t>(&context));
|
|
const Event end = Take(Kind::Job);
|
|
Require(end.dispatch == dispatch && end.value == index && end.event == 32);
|
|
}
|
|
const Event result = Take(Kind::Dispatch);
|
|
Require(result.name == name && result.sequence == sequence && result.dispatch == dispatch && result.value == count);
|
|
Require(result.entry == reinterpret_cast<std::uint64_t>(&Job) && result.argument == reinterpret_cast<std::uint64_t>(&context));
|
|
return dispatch;
|
|
}
|
|
|
|
void Done() const { Require(next == records.size()); }
|
|
};
|
|
|
|
void EmptyAndIdentityCase() {
|
|
Fixture fixture;
|
|
const char* cpuName = "cpu";
|
|
const char* gpuName = "compute";
|
|
ISequence* cpu0 = fixture.Create(cpuName);
|
|
ISequence* cpu1 = fixture.Create(nullptr);
|
|
ISequence* gpu0 = fixture.Create(gpuName, true);
|
|
ISequence* gpu1 = fixture.Create(nullptr, true);
|
|
ISequence* unused = nullptr;
|
|
Require(fixture.factory->Create(&unused, 1, 1, 0, 1, "overflow") == static_cast<int>(0x81580006u));
|
|
Require(fixture.factory->Create(nullptr, 1, 1, 0, 1, "null") == static_cast<int>(0x81580001u));
|
|
for (ISequence* sequence : {cpu0, cpu1, gpu0, gpu1}) {
|
|
Require(sequence->SyncPoint() == 0);
|
|
Require(sequence->Wait() == 0);
|
|
}
|
|
Require(fixture.factory->Destroy(cpu0) == 0);
|
|
Require(fixture.factory->Destroy(cpu1) == 0);
|
|
Require(fixture.factory->DestroyCompute(gpu0) == 0);
|
|
Require(fixture.factory->DestroyCompute(gpu1) == 0);
|
|
ISequence* reused = fixture.Create(nullptr);
|
|
Require(fixture.factory->Destroy(reused) == 0);
|
|
fixture.Stop();
|
|
Reader reader(fixture.id);
|
|
reader.Sequence(cpuName, 0, 1);
|
|
reader.Sequence(nullptr, 1, 1);
|
|
reader.Sequence(gpuName, 0, 1);
|
|
reader.Sequence(nullptr, 1, 1);
|
|
reader.Sequence(cpuName, 0, 2);
|
|
reader.Sequence(nullptr, 1, 2);
|
|
reader.Sequence(gpuName, 0, 2);
|
|
reader.Sequence(nullptr, 1, 2);
|
|
reader.Sequence(nullptr, 0, 1);
|
|
reader.Sequence(nullptr, 0, 2);
|
|
reader.Done();
|
|
}
|
|
|
|
void BatchCase(bool sync, bool wait, bool compute = false) {
|
|
Fixture fixture;
|
|
const char* name = "sequence";
|
|
const char* firstName = "first";
|
|
const char* secondName = "second";
|
|
ISequence* sequence = fixture.Create(name, compute);
|
|
const std::uint16_t id = compute ? 2 : 0;
|
|
Context first;
|
|
Context second;
|
|
first.released.store(false);
|
|
Require(sequence->Dispatch(Job, &first, 1, firstName) == 0);
|
|
Await(first.entered);
|
|
if (sync) Require(sequence->SyncPoint() == 0);
|
|
if (wait) {
|
|
first.released.store(true);
|
|
Require(sequence->Wait() == 0);
|
|
}
|
|
Require(sequence->Dispatch(Job, &second, 4, secondName) == 0);
|
|
first.released.store(true);
|
|
Require(sequence->Wait() == 0);
|
|
if (compute) Require(fixture.factory->DestroyCompute(sequence) == 0);
|
|
else Require(fixture.factory->Destroy(sequence) == 0);
|
|
fixture.Stop();
|
|
Reader reader(fixture.id);
|
|
reader.Sequence(name, 0, 1);
|
|
reader.Sequence(name, id, 3);
|
|
const auto firstId = reader.Dispatch(firstName, first, 1, id);
|
|
if (sync || wait) {
|
|
reader.Sequence(name, id, 4);
|
|
reader.Sequence(name, id, 3);
|
|
}
|
|
const auto secondId = reader.Dispatch(secondName, second, 4, id);
|
|
if (!wait) Require(firstId != secondId);
|
|
reader.Sequence(name, id, 4);
|
|
reader.Sequence(name, 0, 2);
|
|
reader.Done();
|
|
}
|
|
|
|
void ConcurrentCase() {
|
|
Fixture fixture(4);
|
|
ISequence* sequence = fixture.Create(nullptr);
|
|
Context context;
|
|
context.released.store(false);
|
|
Require(sequence->Dispatch(Job, &context, 8, nullptr) == 0);
|
|
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
|
|
while (context.calls.load() < 4 && std::chrono::steady_clock::now() < deadline) std::this_thread::yield();
|
|
Require(context.calls.load() == 4);
|
|
context.released.store(true);
|
|
Require(sequence->Wait() == 0);
|
|
Require(fixture.factory->Destroy(sequence) == 0);
|
|
fixture.Stop();
|
|
Reader reader(fixture.id);
|
|
reader.Sequence(nullptr, 0, 1);
|
|
reader.Sequence(nullptr, 0, 3);
|
|
std::array<std::uint16_t, 8> states{};
|
|
std::uint16_t dispatch = 0;
|
|
for (std::size_t index = 0; index < states.size() * 3; ++index) {
|
|
Require(reader.next < reader.records.size());
|
|
const Event event = reader.records[reader.next++];
|
|
Require(event.value < states.size());
|
|
auto& state = states[event.value];
|
|
if (event.kind == Kind::Job) {
|
|
if (index == 0) dispatch = event.dispatch;
|
|
Require(event.manager == fixture.id && event.dispatch == dispatch);
|
|
Require((state == 0 && event.event == 16) || (state == 2 && event.event == 32));
|
|
} else {
|
|
Require(event.kind == Kind::Body && state == 1 && event.argument == reinterpret_cast<std::uint64_t>(&context));
|
|
}
|
|
++state;
|
|
}
|
|
for (const auto state : states) Require(state == 3);
|
|
const Event metadata = reader.Take(Kind::Dispatch);
|
|
Require(metadata.dispatch == dispatch && metadata.value == 8 && metadata.sequence == 0 && metadata.name == nullptr);
|
|
Require(metadata.entry == reinterpret_cast<std::uint64_t>(&Job) && metadata.argument == reinterpret_cast<std::uint64_t>(&context));
|
|
reader.Sequence(nullptr, 0, 4);
|
|
reader.Sequence(nullptr, 0, 2);
|
|
reader.Done();
|
|
}
|
|
|
|
void GpuBarrierCase(bool mixed, bool cpuAfter) {
|
|
Fixture fixture;
|
|
ISequence* sequence = fixture.Create(nullptr, true);
|
|
alignas(256) static const std::array<std::uint32_t, 1> code{0xbf810000u};
|
|
const auto address = reinterpret_cast<std::uint64_t>(code.data());
|
|
const std::array<ShaderRegister, 7> registers{{{0x20c, static_cast<std::uint32_t>(address >> 8)}, {0x20d, static_cast<std::uint32_t>(address >> 40) & 0xff}, {0x207, 1}, {0x208, 1}, {0x209, 1}, {0x212, 0}, {0x213, 0}}};
|
|
const std::array<std::uint32_t, 8> specials{0, 0, 0, 0, 1, 0, 0, 0};
|
|
const ShaderRegister* registerPointer = registers.data();
|
|
const std::uint32_t* specialPointer = specials.data();
|
|
std::array<std::uint8_t, 0x80> shader{};
|
|
std::memcpy(shader.data() + 0x20, ®isterPointer, sizeof(registerPointer));
|
|
std::memcpy(shader.data() + 0x28, &specialPointer, sizeof(specialPointer));
|
|
shader[0x5c] = static_cast<std::uint8_t>(registers.size());
|
|
alignas(64) std::uint32_t gate = 0;
|
|
alignas(256) std::array<std::uint32_t, 32> commands{};
|
|
CommandBuffer buffer{commands.data(), commands.data() + commands.size(), commands.data(), commands.data() + commands.size(), nullptr, nullptr, 0};
|
|
Require(sceAgcAcbWaitRegMem(&buffer, 0, 3, 3, &gate, 1, 0xffffffffu, 0x190) != nullptr);
|
|
const Packet packet{commands.data(), static_cast<std::uint32_t>(buffer.cursor_up - commands.data()), 0, {}};
|
|
Require(sceAgcDriverSubmitAcb(0x21, &packet) == 0);
|
|
Context context;
|
|
if (mixed) {
|
|
context.released.store(false);
|
|
Require(sequence->Dispatch(Job, &context, 1, nullptr) == 0);
|
|
Await(context.entered);
|
|
}
|
|
Require(sequence->DispatchCompute(shader.data(), nullptr, 1, 1, 1, nullptr) == 0);
|
|
Require(sequence->SyncPoint() == 0);
|
|
if (cpuAfter) {
|
|
Require(sequence->Dispatch(Job, &context, 1, nullptr) == 0);
|
|
Require(sequence->SyncPoint() == 0);
|
|
}
|
|
Require(sequence->DispatchCompute(shader.data(), nullptr, 1, 1, 1, nullptr) == 0);
|
|
context.released.store(true);
|
|
std::atomic_ref<std::uint32_t>(gate).store(1);
|
|
Require(sequence->Wait() == 0);
|
|
Require(fixture.factory->DestroyCompute(sequence) == 0);
|
|
fixture.Stop();
|
|
Reader reader(fixture.id);
|
|
reader.Sequence(nullptr, 0, 1);
|
|
reader.Sequence(nullptr, 2, 3);
|
|
if (cpuAfter) {
|
|
reader.Sequence(nullptr, 2, 4);
|
|
reader.Sequence(nullptr, 2, 3);
|
|
}
|
|
if (mixed || cpuAfter) {
|
|
reader.Dispatch(nullptr, context, 1, 2);
|
|
reader.Sequence(nullptr, 2, 4);
|
|
reader.Sequence(nullptr, 2, 3);
|
|
}
|
|
reader.Sequence(nullptr, 2, 4);
|
|
reader.Sequence(nullptr, 0, 2);
|
|
reader.Done();
|
|
}
|
|
|
|
void ShutdownCase(bool queued) {
|
|
Fixture fixture;
|
|
ISequence* sequence = fixture.Create(nullptr);
|
|
Context first;
|
|
Context canceled;
|
|
first.released.store(false);
|
|
Require(sequence->Dispatch(Job, &first, 1, nullptr) == 0);
|
|
Await(first.entered);
|
|
if (queued) {
|
|
Require(sequence->SyncPoint() == 0);
|
|
Require(sequence->Dispatch(Job, &canceled, 1, "canceled") == 0);
|
|
}
|
|
std::atomic<bool> stopping{false};
|
|
std::thread stopper([&] {
|
|
stopping.store(true);
|
|
fixture.Stop();
|
|
});
|
|
Await(stopping);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
first.released.store(true);
|
|
stopper.join();
|
|
Require(!canceled.entered.load());
|
|
Reader reader(fixture.id);
|
|
reader.Sequence(nullptr, 0, 1);
|
|
reader.Sequence(nullptr, 0, 3);
|
|
reader.Dispatch(nullptr, first, 1);
|
|
if (queued) {
|
|
reader.Sequence(nullptr, 0, 4);
|
|
reader.Sequence(nullptr, 0, 3);
|
|
}
|
|
reader.Done();
|
|
}
|
|
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
int APS5_VABI sceRazorCpuJobManagerSequence(const char* name, std::uint16_t manager, std::uint16_t sequence, std::uint16_t event) {
|
|
Record({Kind::Sequence, name, manager, sequence, 0, 0, event, 0, 0});
|
|
return -1;
|
|
}
|
|
|
|
int APS5_VABI sceRazorCpuJobManagerJob(std::uint16_t manager, std::uint16_t dispatch, std::uint16_t index, std::uint16_t event) {
|
|
Record({Kind::Job, nullptr, manager, 0, dispatch, index, event, 0, 0});
|
|
return -1;
|
|
}
|
|
|
|
int APS5_VABI sceRazorCpuJobManagerDispatch(const char* name, std::uint16_t manager, std::uint16_t sequence, std::uint16_t dispatch, std::uint16_t count, std::uint64_t entry, std::uint64_t argument) {
|
|
Record({Kind::Dispatch, name, manager, sequence, dispatch, count, 0, entry, argument});
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc == 2 && std::strcmp(argv[1], "gpu") == 0) {
|
|
auto device = OpenVulkanTestDevice();
|
|
if (!device) return VulkanTestSkipped;
|
|
device.reset();
|
|
GpuBarrierCase(false, false);
|
|
GpuBarrierCase(false, true);
|
|
GpuBarrierCase(true, false);
|
|
AgcDriverShutdown_nid_postfix();
|
|
std::puts("job manager GPU profiler tests passed");
|
|
return 0;
|
|
}
|
|
if (argc != 1) return 2;
|
|
EmptyAndIdentityCase();
|
|
BatchCase(false, false);
|
|
BatchCase(true, false);
|
|
BatchCase(false, true);
|
|
BatchCase(true, false, true);
|
|
ConcurrentCase();
|
|
ShutdownCase(false);
|
|
ShutdownCase(true);
|
|
std::puts("job manager profiler tests passed");
|
|
}
|