Merge commit '3c3b3cda739f9eb00a07fae50d94a3af5f6fb8f2' into HEAD

# Conflicts:
#	core/libs/prx/libc/src/Math.cpp
This commit is contained in:
Dean Galvin committed 2026-11-02 23:35:08 +13:00
commit 6ea7d0ee1c
360 files changed
+29382 -2979

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@ AnyPS5 relinks PS5 executables into native Linux or Windows programs that run ag
- New third-party code is a submodule at `3rdparty/<name>`: add its `.gitmodules` entry (`git submodule add <url> 3rdparty/<name>`) and build it from source with CMake. Never `find_package` or copy sources in.
- Known gaps go in `docs/dev/debt/<library>.md`; notes, reports and logs go in the pull request. Add no other files.
- Commit subjects and PR titles are Conventional Commits.
- PR body: copy [the template](.github/pull_request_template.md), keep every line, tick every box, and fill `What`, `Tested`, `Depends on` (#N or none), `Unblocks` (title ID and discussion, or none), `GPUs tested` (AGC, shader recompiler, VideoOut; otherwise n/a) and `AI-assisted` (yes or no).
- PR body: copy [the template](.github/pull_request_template.md), keep every line, tick every box, and fill `What`, `Tested`, `Depends on` (#N or none), `Fixes` (Fixes #N or none), `Unblocks` (title ID and discussion, or none), `GPUs tested` (AGC, shader recompiler, VideoOut; otherwise n/a) and `AI-assisted` (yes or no).
- Before pushing run `python3 tools/check_conventions.py --base origin/main`; before opening run `python3 tools/pr_overlap.py --branch <branch>`.
- Build and test: [BUILD](docs/dev/BUILD.md); a relinker-only change builds with `-DANYPS5_RELINKER_ONLY=ON`.
- A speed claim shows `tools/trace_digest.py` summaries of `main` and of the change from the same machine and scene.
+2 -2
View File
@@ -40,7 +40,7 @@ For pull requests, full Linux and Windows builds and tests run only when a colla
gh workflow run build.yml --repo boykopovar/AnyPS5 --ref main --field pr=1234
```
This manual run also builds and tests the relinker on Linux, Windows and macOS when the pull request changes a file the relinker-only build uses, regardless of the author's previous contributions. The relinker matrix also runs automatically when a pull request targeting `main` is opened, updated or reopened, if its author already has a commit in `main` and it changes a file the relinker-only build uses; full builds never run automatically for pull requests. Every job that runs automatically for pull requests runs only when the pull request changes its files: a `paths` filter on the `pull_request` trigger, or `needs: eligibility` with an `if` on the files output it sets in `build.yml`. The Conventions check fails a workflow change that adds a job without one. All build jobs build the selected pull request merged into the current `main` (GitHub's merge commit), so a pull request that no longer merges cleanly must be rebased first. New commits require another manual run for full builds.
This manual run also builds and tests the relinker on Linux, Windows and macOS when the pull request changes a file the relinker-only build uses, regardless of the author's previous contributions. The relinker matrix also runs automatically when a pull request targeting `main` is opened, updated or reopened, if its author already has a commit or a merged pull request in `main` and it changes a file the relinker-only build uses; full builds never run automatically for pull requests. Every job that runs automatically for pull requests runs only when the pull request changes its files: a `paths` filter on the `pull_request` trigger, or `needs: eligibility` with an `if` on the files output it sets in `build.yml`. The Conventions check fails a workflow change that adds a job without one. All build jobs check out the current `main` and merge the head of each selected pull request into it with `git merge --no-ff`, so a pull request that no longer merges cleanly must be rebased first. New commits require another manual run for full builds.
With `--field full=false`, the manual run skips the full builds and runs only the relinker matrix on Linux, Windows and macOS, if the pull request changes a file the relinker-only build uses, such as the Python tools and their tests in `tools/`.
@@ -70,7 +70,7 @@ The [pull request template](.github/pull_request_template.md) is the checklist f
- Fixes a title needs go to `main` as pull requests. A Game bring-up discussion lists the open pull requests and local changes the title still needs; update the list when one is merged, closed or replaced.
- The PR overlap comment lists open pull requests that change the same lines or exports; agree with their authors which one goes first and declare it with `Depends on`.
- Run the tests before opening the pull request and describe what was tested (OS, title or homebrew).
- A pull request that claims a change in speed shows the summary lines of `main` and of the change, from the same machine and scene, and attaches the traces, captured as the [Performance report](.github/ISSUE_TEMPLATE/performance.yml) issue form describes. A slow scene is reported with the same form. A pull request titled `perf(...)` or `perf:` is labeled `perf`, and `perf numbers missing` until its description holds two valid summary lines with different commits.
- A pull request that claims a change in speed shows the summary lines of `main` and of the change, from the same machine and scene, and attaches the traces, captured as the [Performance report](.github/ISSUE_TEMPLATE/performance.yml) issue form describes. A slow scene is reported with the same form. A pull request titled `perf(...)` or `perf:` is labeled `perf`, and `perf numbers missing` until its description holds two valid summary lines with different commits. A `perf(tools)`, `perf(ci)` or `perf(build)` pull request has no title to capture, so it is labeled `perf` only and reports its own before and after timings.
- Changes to the AGC driver, the shader recompiler or VideoOut run the same focused tests on `main` and the change with `VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation`, `ANYPS5_NO_SHADER_CACHE=1` and `ANYPS5_REQUIRE_VULKAN=1` using `ctest -V` from the same machine and GPU, confirm neither log reports `was not found but was requested by env var VK_INSTANCE_LAYERS`, confirm the change introduces no new `VUID-` validation errors compared with `main`, and name each GPU the GPU tests ran on (vendor, model, driver, OS) on the `GPUs tested` checklist line. CI runs these tests only on lavapipe, and hardware drivers fail where lavapipe and other vendors pass, so a reviewer can ask for a run on another vendor before merging.
- Investigation notes, reports, screenshots and logs go in the pull request, not in the repository. Images for documentation go in the [gist](https://gist.github.com/boykopovar/0e53f2e1426f29ecd41e3b51540b8a90) comments.
- Say whether the change was written with AI assistance. The author of the pull request is responsible for every line of it.
+1 -1
View File
@@ -185,7 +185,7 @@ foreach(LIB_DIR ${LIBS_DIRS})
target_link_options(${LIB_NAME} PRIVATE LINKER:-z,now)
endif()
if(WIN32 AND LIB_NAME STREQUAL "libSceNet")
target_link_libraries(${LIB_NAME} PRIVATE ws2_32)
target_link_libraries(${LIB_NAME} PRIVATE ws2_32 iphlpapi)
target_compile_definitions(${LIB_NAME} PRIVATE _WIN32_WINNT=0x0602)
elseif(WIN32 AND LIB_NAME STREQUAL "libSceNetCtl")
target_link_libraries(${LIB_NAME} PRIVATE ws2_32 iphlpapi)
@@ -18,7 +18,6 @@ extern "C" int APS5_VABI sceAgcGetFusedShaderSize_0080(SizeAlign* dst, const Sha
extern "C" {
int APS5_VABI sceAgcFuseShaderHalves_nid_postfix(Shader* fusedResult, const Shader* front, const Shader* back, void* scratchMem);
int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, const Shader* ps);
}
@@ -354,12 +354,4 @@ int APS5_VABI sceAgcGetFusedShaderSize_0080(SizeAlign* dst, const Shader* front,
return 0;
}
int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, const Shader* ps) {
(void)regs;
(void)vs;
(void)ps;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -7,6 +7,7 @@
extern "C" {
int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps);
int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, const Shader* ps);
int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps);
int APS5_VABI sceAgcUpdateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps);
int APS5_VABI sceAgcCreateInterpolantMapping_nid_postfix(ShaderRegister* regs, const Shader* gs, const Shader* ps);
@@ -58,6 +58,12 @@ std::uint32_t CreateInterpolantValueFlatDefault(std::uint32_t psWord, const Shad
return ((psWord & 0x00700000u) == 0x00400000u) ? (value | 0x00000020u) : value;
}
std::uint32_t ApplyInterpolantDefaultValue0100(std::uint32_t value, std::uint32_t psWord) {
value &= ~0x00000300u;
value |= ((psWord >> 28u) & 0x3u) << 8u;
return value;
}
int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue, bool fillIdentityTail) {
if (regs == nullptr) {
throw std::runtime_error(std::string(fn) + ": regs is null");
@@ -124,6 +130,37 @@ int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Sh
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue, true);
}
int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, const Shader* ps) {
if (regs == nullptr) {
throw std::runtime_error(std::string(__func__) + ": regs is null");
}
if (ps == nullptr) {
throw std::runtime_error(std::string(__func__) + ": ps is null");
}
if (ps->num_input_semantics > InterpolantRegisterCount) {
throw std::runtime_error(std::string(__func__) + ": input semantic count exceeds 32");
}
if (ps->num_input_semantics == 0) {
return 0;
}
if (ps->input_semantics == nullptr) {
throw std::runtime_error(std::string(__func__) + ": ps->input_semantics is null but num_input_semantics != 0");
}
if (vs == nullptr) {
throw std::runtime_error(std::string(__func__) + ": vs is null");
}
if (vs->num_output_semantics != 0 && vs->output_semantics == nullptr) {
throw std::runtime_error(std::string(__func__) + ": vs->output_semantics is null but num_output_semantics != 0");
}
for (std::uint32_t i = 0; i < ps->num_input_semantics; ++i) {
const ShaderSemantic& psSemantic = ps->input_semantics[i];
const std::uint32_t psWord = ShaderSemanticWord(psSemantic);
const ShaderSemantic* vsSemantic = FindOutputSemantic(vs, psSemantic.semantic);
SetInterpolantRegister(regs, i, ApplyInterpolantDefaultValue0100(CreateInterpolantValue(psWord, vsSemantic), psWord));
}
return 0;
}
int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps) {
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16, true);
}
@@ -162,6 +162,81 @@ void testUpdateMapping() {
check(update[1].value == 0x01380007u && update[3].value == 0x03680409u, "f16 DEFAULT_VAL does not come from the low default");
}
void testUpdate0100Mapping() {
const auto mappingsBefore = mappings;
const auto linksBefore = links;
Shader ps{};
auto regs = filled();
const auto untouched = filled();
check(sceAgcUpdateInterpolantMapping_0100(regs.data(), nullptr, &ps) == 0, "_0100 update without inputs failed");
check(std::memcmp(regs.data(), untouched.data(), sizeof(regs)) == 0, "_0100 update without inputs wrote registers");
std::array<ShaderSemantic, 4> inputs{semantic(1, 0, 0), semantic(2, 0, 1), semantic(3, 0, 2), semantic(4, 0, 3)};
inputs[0].is_custom = 1;
inputs[0].default_value = 2;
inputs[1].default_value = 1;
inputs[1].default_value_hi = 1;
inputs[2].default_value_hi = 2;
inputs[3].is_custom = 1;
inputs[3].default_value = 3;
inputs[3].default_value_hi = 3;
std::array<ShaderSemantic, 3> outputs{semantic(1, 5, 0), semantic(2, 7, 1), semantic(4, 9, 3)};
Shader vs{};
vs.output_semantics = outputs.data();
vs.num_output_semantics = static_cast<std::uint16_t>(outputs.size());
ps.input_semantics = inputs.data();
ps.num_input_semantics = static_cast<std::uint32_t>(inputs.size());
regs = filled();
check(sceAgcUpdateInterpolantMapping_0100(regs.data(), &vs, &ps) == 0, "_0100 update mapping failed");
check(regs[0].value == 0x00000625u, "_0100 update does not encode default value 2");
check(regs[1].value == 0x01380107u, "_0100 update does not encode default value 1");
check(regs[2].value == 0x02580020u, "_0100 unmatched interpolant update changed");
check(regs[3].value == 0x03680709u, "_0100 full f16 interpolant update changed");
for (std::uint32_t i = 0; i < inputs.size(); ++i) {
check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "_0100 update register offset changed");
}
const std::size_t written = inputs.size() * sizeof(ShaderRegister);
check(std::memcmp(regs.data() + inputs.size(), untouched.data(), sizeof(regs) - written) == 0, "_0100 update wrote past its inputs");
check(mappings == mappingsBefore && links == linksBefore, "_0100 update resolved an ABI");
}
void testUpdate0100Boundary() {
const auto mappingsBefore = mappings;
const auto linksBefore = links;
std::array<ShaderSemantic, 32> inputs{};
std::array<ShaderSemantic, 32> outputs{};
for (std::uint32_t i = 0; i < inputs.size(); ++i) {
inputs[i] = semantic(i + 1u, 0, 0);
inputs[i].default_value = i % 4u;
outputs[i] = semantic(inputs.size() - i, i, 0);
}
Shader ps{};
ps.input_semantics = inputs.data();
ps.num_input_semantics = static_cast<std::uint32_t>(inputs.size());
Shader vs{};
std::array<ShaderRegister, 33> regs{};
std::memset(regs.data(), 0xcc, sizeof(regs));
const auto untouched = regs;
check(sceAgcUpdateInterpolantMapping_0100(regs.data(), &vs, &ps) == 0, "_0100 update rejected a producer without outputs");
constexpr std::array<std::uint32_t, 4> defaults{0x000u, 0x100u, 0x200u, 0x300u};
for (std::uint32_t i = 0; i < inputs.size(); ++i) {
check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "_0100 full default mapping register offset changed");
check(regs[i].value == (0x20u | defaults[i % 4u]), "_0100 full default mapping changed");
}
check(std::memcmp(&regs.back(), &untouched.back(), sizeof(ShaderRegister)) == 0, "_0100 full default mapping wrote past 32 inputs");
vs.output_semantics = outputs.data();
vs.num_output_semantics = static_cast<std::uint16_t>(outputs.size());
regs = untouched;
check(sceAgcUpdateInterpolantMapping_0100(regs.data(), &vs, &ps) == 0, "_0100 update rejected 32 matched inputs");
for (std::uint32_t i = 0; i < inputs.size(); ++i) {
check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "_0100 full matched mapping register offset changed");
check(regs[i].value == ((31u - i) | defaults[i % 4u]), "_0100 full matched mapping changed");
}
check(std::memcmp(&regs.back(), &untouched.back(), sizeof(ShaderRegister)) == 0, "_0100 full matched mapping wrote past 32 inputs");
check(mappings == mappingsBefore && links == linksBefore, "_0100 full mapping resolved an ABI");
}
void testUnversionedMapping() {
std::array<ShaderSemantic, 5> inputs{
semantic(1, 0, 2), semantic(2, 0, 2), semantic(5, 0, 2), semantic(3, 0, 0), semantic(4, 0, 3)};
@@ -207,6 +282,7 @@ void testRejections() {
std::memset(regs.data(), 0xcc, sizeof(regs));
const auto saved = regs;
expectFailure([&] { sceAgcCreateInterpolantMapping_0100(regs.data(), &gs, &ps); });
expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), &gs, &ps); });
expectFailure([&] { sceAgcCreateInterpolantMapping_10000(regs.data(), &gs, &ps); });
expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(regs.data(), &gs, &ps); });
check(std::memcmp(regs.data(), saved.data(), sizeof(regs)) == 0, "rejected mapping wrote registers");
@@ -216,6 +292,15 @@ void testRejections() {
expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(regs.data(), nullptr, &ps); });
expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(nullptr, &gs, &ps); });
expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(regs.data(), &gs, nullptr); });
expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), nullptr, &ps); });
expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(nullptr, &gs, &ps); });
expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), &gs, nullptr); });
ps.input_semantics = nullptr;
expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), &gs, &ps); });
ps.input_semantics = inputs.data();
gs.output_semantics = nullptr;
gs.num_output_semantics = 1;
expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), &gs, &ps); });
}
}
@@ -238,6 +323,8 @@ int main() {
testIdentity();
testMapping();
testUpdateMapping();
testUpdate0100Mapping();
testUpdate0100Boundary();
testUnversionedMapping();
testRejections();
check(mappings == 10u && links == 8u, "mapping preparation call count changed");
@@ -400,6 +400,7 @@ set(agcZlibIncludeDir ${CMAKE_SOURCE_DIR}/3rdparty/libjpeg-turbo/src/spng/zlib)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/TraceFile.cpp PROPERTIES
INCLUDE_DIRECTORIES ${agcZlibIncludeDir}
)
set_property(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/VulkanDevice.cpp APPEND PROPERTY COMPILE_DEFINITIONS VK_ENABLE_BETA_EXTENSIONS)
add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL
${agcDriverSources}
@@ -7,7 +7,6 @@
#include <cstdlib>
#include <fstream>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
@@ -31,8 +30,11 @@ public:
if (!Enabled()) return;
std::array<char, 2048> line{};
const auto size = std::snprintf(line.data(), line.size(), format, args...);
if (size < 0 || static_cast<std::size_t>(size) >= line.size()) throw std::runtime_error("Capture trace event exceeds 2047 bytes");
static CaptureTrace& trace = instance();
if (size < 0 || static_cast<std::size_t>(size) >= line.size()) {
trace.drop();
return;
}
trace.append(std::string(line.data(), static_cast<std::size_t>(size)));
}
@@ -45,6 +47,8 @@ public:
}
private:
friend struct CaptureTraceTestAccess;
static CaptureTrace& instance() {
static CaptureTrace trace;
return trace;
@@ -57,7 +61,7 @@ private:
std::fprintf(stderr, "Capture trace open failed for %s\n", path);
return;
}
output.exceptions(std::ios::badbit | std::ios::failbit);
active = true;
worker = std::thread([this] { run(); });
}
@@ -71,33 +75,72 @@ private:
}
void append(std::string line) {
if (!output.is_open()) return;
const auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
std::lock_guard lock(mutex);
if (pending.size() + line.size() + 64 > 16 * 1024 * 1024) throw std::runtime_error("Capture trace queue exceeded 16 MiB");
pending += std::to_string(++sequence) + " " + std::to_string(elapsed) + "us " + line + "\n";
if (!active || stopping) return;
if (droppedPending != 0 || pending.size() + line.size() + 64 > 16 * 1024 * 1024) {
++droppedPending;
++droppedTotal;
return;
}
appendRecord(pending, line);
if (line.starts_with("blit ")) flushRequested = true;
if (pending.size() >= 65536 || flushRequested) changed.notify_one();
}
void drop() {
std::lock_guard lock(mutex);
if (!active || stopping) return;
++droppedPending;
++droppedTotal;
}
void appendRecord(std::string& destination, const std::string& line) {
const auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
destination += std::to_string(++sequence) + " " + std::to_string(elapsed) + "us " + line + "\n";
}
void fail(const char* reason) {
unsigned long long dropped;
{
std::lock_guard lock(mutex);
active = false;
pending.clear();
dropped = droppedTotal;
}
std::fprintf(stderr, "Capture trace failed: %s (dropped records: %llu)\n", reason, dropped);
}
void run() {
try {
for (;;) {
std::string batch;
{
std::unique_lock lock(mutex);
changed.wait_for(lock, std::chrono::milliseconds(100), [this] { return stopping || flushRequested || pending.size() >= 65536; });
if (stopping && pending.empty()) return;
batch.swap(pending);
flushRequested = false;
for (;;) {
std::string batch;
bool stop;
{
std::unique_lock lock(mutex);
changed.wait_for(lock, std::chrono::milliseconds(100), [this] { return stopping || flushRequested || pending.size() >= 65536; });
batch.swap(pending);
if (droppedPending != 0) {
appendRecord(batch, "capture-trace-dropped records=" + std::to_string(droppedPending));
droppedPending = 0;
}
flushRequested = false;
stop = stopping;
if (stop) {
appendRecord(batch, "capture-trace-summary dropped=" + std::to_string(droppedTotal));
active = false;
}
if (batch.empty()) continue;
output.write(batch.data(), static_cast<std::streamsize>(batch.size()));
output.flush();
}
} catch (const std::exception& error) {
std::fprintf(stderr, "Capture trace failed: %s\n", error.what());
std::terminate();
if (batch.empty()) continue;
output.write(batch.data(), static_cast<std::streamsize>(batch.size()));
if (!output) {
fail("write failed");
return;
}
output.flush();
if (!output) {
fail("flush failed");
return;
}
if (stop) return;
}
}
@@ -108,6 +151,9 @@ private:
std::thread worker;
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
unsigned long long sequence = 0;
unsigned long long droppedPending = 0;
unsigned long long droppedTotal = 0;
bool active = false;
bool stopping = false;
bool flushRequested = false;
};
@@ -329,20 +329,21 @@ private:
static void notePendingWait(std::span<const std::uint32_t> packet, std::uint32_t queue, std::uint64_t received);
void landPendingWaits(std::uint32_t queue);
struct ResolvedDispatch {
std::uint64_t address = 0, key = 0, indirectArguments = 0;
std::uint64_t address = 0, key = 0, indirectArguments = 0, forgetAtCapture = 0;
std::array<std::uint32_t, 5> packet{};
std::shared_ptr<const ShaderSnapshot> registeredShader;
std::shared_ptr<const ShaderRecompiler::RecompileResult> compiledResult;
std::shared_ptr<DispatchVariant> keepVariant, attachVariant;
std::shared_ptr<DispatchEntry> missedEntry;
std::shared_ptr<ShaderMemory> shaderMemory;
std::vector<ShaderRecompiler::MemoryRegion> captured;
std::vector<std::uint32_t> liveWords;
std::shared_ptr<const ShaderRecompiler::ResourceCapture> capture;
bool dataHit = false, cached = false, validated = false;
bool dataHit = false, cached = false, validated = false, missedDiffering = false, noDispatchCache = false;
std::chrono::steady_clock::time_point resolvedAt{};
};
struct GroupCaptureStats {
std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, memoryMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, deviceRejects = 0, argumentMismatches = 0;
std::uint64_t runs = 0, resolved = 0, resolvedHits = 0, adopted = 0, addressMismatches = 0, keyMismatches = 0, memoryMismatches = 0, failures = 0, stale = 0, labelOverlaps = 0, writtenBefore = 0, unknownWrites = 0, deviceRejects = 0, argumentMismatches = 0;
double resolveMs = 0, resolveMaxMs = 0, ageMs = 0, ageMaxMs = 0;
std::chrono::steady_clock::time_point reported = std::chrono::steady_clock::now();
};
@@ -3,6 +3,7 @@
#include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
#include <algorithm>
#include <array>
#include <optional>
#include <span>
@@ -17,6 +18,7 @@
#include <map>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
@@ -184,10 +186,102 @@ struct PreparedShaders : PreparedShaderState {
std::vector<std::size_t> convertedSamples;
};
struct RegisteredShaderState {
class SharedCode {
public:
SharedCode() = default;
SharedCode(std::vector<std::uint32_t> values) : words(std::make_shared<std::vector<std::uint32_t>>(std::move(values))) {}
SharedCode(std::initializer_list<std::uint32_t> values) : SharedCode(std::vector<std::uint32_t>(values)) {}
template<std::input_iterator TIterator>
SharedCode(TIterator first, TIterator last) : SharedCode(std::vector<std::uint32_t>(first, last)) {}
operator const std::vector<std::uint32_t>&() const { return Words(); }
[[nodiscard]] const std::vector<std::uint32_t>& Words() const { return words != nullptr ? *words : none(); }
[[nodiscard]] const std::uint32_t* data() const { return Words().data(); }
[[nodiscard]] std::size_t size() const { return Words().size(); }
[[nodiscard]] bool empty() const { return Words().empty(); }
[[nodiscard]] const std::uint32_t* begin() const { return Words().data(); }
[[nodiscard]] const std::uint32_t* end() const { return Words().data() + Words().size(); }
[[nodiscard]] std::uint32_t operator[](std::size_t index) const { return Words()[index]; }
[[nodiscard]] bool SharesWith(const SharedCode& other) const { return words != nullptr && words == other.words; }
bool operator==(const SharedCode& other) const { return words == other.words || Words() == other.Words(); }
bool operator==(const std::vector<std::uint32_t>& other) const { return Words() == other; }
std::vector<std::uint32_t>& Mutable() {
if (words == nullptr) words = std::make_shared<std::vector<std::uint32_t>>();
else if (words.use_count() > 1) words = std::make_shared<std::vector<std::uint32_t>>(*words);
return *words;
}
void resize(std::size_t count) { Mutable().resize(count); }
template<typename TIterator>
void assign(TIterator first, TIterator last) { Mutable().assign(first, last); }
private:
static const std::vector<std::uint32_t>& none() {
static const std::vector<std::uint32_t> none;
return none;
}
std::shared_ptr<std::vector<std::uint32_t>> words;
};
class RegisterList {
public:
using Entry = std::pair<std::uint32_t, std::uint32_t>;
using const_iterator = std::vector<Entry>::const_iterator;
RegisterList() = default;
explicit RegisterList(const Registers& registers) {
entries.reserve(registers.size());
registers.ForEach([&](std::uint32_t offset, std::uint32_t value) { entries.emplace_back(offset, value); });
}
[[nodiscard]] Registers Expand() const {
Registers registers;
if (!entries.empty()) registers.emplace(entries.back().first, entries.back().second);
for (const auto& [offset, value] : entries) registers.emplace(offset, value);
return registers;
}
[[nodiscard]] const_iterator begin() const { return entries.begin(); }
[[nodiscard]] const_iterator end() const { return entries.end(); }
[[nodiscard]] const_iterator find(std::uint32_t offset) const {
const auto found = std::lower_bound(entries.begin(), entries.end(), offset, [](const Entry& entry, std::uint32_t wanted) { return entry.first < wanted; });
return found != entries.end() && found->first == offset ? found : entries.end();
}
[[nodiscard]] std::uint32_t at(std::uint32_t offset) const {
const auto found = find(offset);
if (found == end()) throw std::out_of_range("register is not set");
return found->second;
}
[[nodiscard]] std::size_t size() const { return entries.size(); }
[[nodiscard]] bool empty() const { return entries.empty(); }
template<class TVisit>
void ForEach(TVisit&& visit) const {
for (const auto& [offset, value] : entries) visit(offset, value);
}
void emplace(std::uint32_t offset, std::uint32_t value) {
const auto found = std::lower_bound(entries.begin(), entries.end(), offset, [](const Entry& entry, std::uint32_t wanted) { return entry.first < wanted; });
if (found == entries.end() || found->first != offset) entries.insert(found, {offset, value});
}
bool operator==(const RegisterList&) const = default;
private:
std::vector<Entry> entries;
};
struct DecodedShaderState {
Registers shader;
Registers context;
Registers userConfig;
};
struct RegisteredShaderState {
RegisteredShaderState() = default;
RegisteredShaderState(const DecodedShaderState& decoded) : shader(decoded.shader), context(decoded.context), userConfig(decoded.userConfig) {}
RegisterList shader;
RegisterList context;
RegisterList userConfig;
bool operator==(const RegisteredShaderState&) const = default;
};
@@ -196,7 +290,7 @@ struct ShaderSnapshot {
std::uint64_t codeAddress;
std::uint64_t headerAddress;
std::uint8_t type;
std::vector<std::uint32_t> code;
SharedCode code;
std::vector<std::byte> header;
std::shared_ptr<PreparedShaders> prepared = std::make_shared<PreparedShaders>();
std::shared_ptr<const RegisteredShaderState> registeredState;
@@ -6,7 +6,7 @@
namespace AgcDriver {
inline bool SubgroupControlBarrierUsable(VkDriverId driver) {
return driver != VK_DRIVER_ID_AMD_PROPRIETARY;
return driver != VK_DRIVER_ID_AMD_PROPRIETARY && driver != VK_DRIVER_ID_MOLTENVK;
}
inline bool WorkgroupScopeFenceUsable(VkDriverId driver) {
@@ -15,6 +15,7 @@
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "Optimization/ResourceProgram.hpp"
#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <cstring>
#include <optional>
@@ -140,10 +141,12 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
if (resolving) {
if (indirectArguments == 0 && Graphics::MatchMultisampledSingleClear(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads)) return;
if (!partialLastGroup && matchesFillKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) return;
if (!partialLastGroup && indirectArguments == 0 && (MatchDwordPatternFill(packet, std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchConstantDwordFill(packet, std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchClampedGlobalFill(packet, std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchMemoryValueBufferFill(packet, std::span(snapshot.code).subspan(codeOffset), userData, compute))) return;
if (!partialLastGroup && indirectArguments == 0 && (matchesCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchesStreamCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchesIndexedCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute))) return;
} else {
if (indirectArguments == 0 && Graphics::TakeMultisampledSingleClear(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads)) {
pendingDispatchPhases().outcome = DispatchOutcome::FillHle;
if (resolvedAhead().empty()) gateOpened() = true;
return;
}
if (indirectArguments == 0 && (packet[4] & 0x20u) == 0) {
@@ -151,6 +154,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
}
if (!partialLastGroup && fillBuffer(queue, submission.queue, packet, std::span(snapshot.code).subspan(codeOffset), userData, compute, localDevice)) {
pendingDispatchPhases().outcome = DispatchOutcome::FillHle;
if (resolvedAhead().empty()) gateOpened() = true;
return;
}
if (DrawPipeline::Active() && !pipelineCandidate) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, 0x15);
@@ -166,6 +170,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
}
if (!partialLastGroup && indirectArguments == 0 && copyBuffer(queue, submission.queue, packet, std::span(snapshot.code).subspan(codeOffset), userData, compute, localDevice, address)) {
pendingDispatchPhases().outcome = DispatchOutcome::CopyHle;
if (resolvedAhead().empty()) gateOpened() = true;
return;
}
}
@@ -188,9 +193,9 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
{0, 0, 0, threadGroup != std::array<std::uint32_t, 3>{} ? 0u : 128u}
};
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
static double captureMs = 0, keyMs = 0, recompileMs = 0, deviceMs = 0;
static std::uint64_t cacheHits = 0;
static std::uint64_t dispatches = 0;
static std::atomic<double> captureMs{0}, keyMs{0}, recompileMs{0}, deviceMs{0};
static std::atomic<std::uint64_t> cacheHits{0};
static std::atomic<std::uint64_t> dispatches{0};
auto lap = std::chrono::steady_clock::now();
std::array<double, DriverPhaseCount> phaseMs{};
@@ -365,12 +370,12 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
}
if (!pipelined && DrawPipeline::Queue0().Busy()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, 0x15);
}
std::uint64_t forgetAtCapture = 0;
if (cached) {
require(keepVariant != nullptr && keepVariant->shader == registeredShader, "dispatch cache belongs to another registered shader");
captureMs += phaseTiming.Elapsed();
if (profile) captureMs.fetch_add(phaseTiming.Elapsed(), std::memory_order_relaxed);
} else if (!fromAhead) {
shaderMemory = std::make_shared<ShaderMemory>(memory, &queryPendingWrite, &observePendingWrite, hookWaitCounter(), &QueryPendingSpan);
std::uint64_t forgetAtCapture = 0;
static const bool dumpShaders = std::getenv("APS5_DUMP_SHADERS") != nullptr;
try {
@@ -386,7 +391,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
captured = shaderMemory->Regions();
request.context.memory = captured;
if (traceCapSync()) traceCapture("dispatch-capture", address, submission.queue, captured, Graphics::Recorder::ThreadWaitedMs() - waitedBefore);
captureMs += phaseTiming.Elapsed();
if (profile) captureMs.fetch_add(phaseTiming.Elapsed(), std::memory_order_relaxed);
phaseTiming.Phase(PhaseCapture);
if (dumpShaders) static_cast<void>(dumpRequest(address, request));
const auto started = std::chrono::steady_clock::now();
@@ -395,11 +400,13 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
timing.Mark("capture_resources");
compiledResult = invocation.Materialize(*capture);
timing.Mark("materialize");
if (compiledResult->cacheHit) ++cacheHits;
if (profile && compiledResult->cacheHit) cacheHits.fetch_add(1, std::memory_order_relaxed);
const auto elapsed = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - started).count();
static double totalMs = 0;
totalMs += elapsed;
if (profile && elapsed > 200) std::fprintf(stderr, "[gpu] compute shader 0x%llx recompile took %.0f ms (%zu SPIR-V words, %zu captured regions, total %.1f s)\n", static_cast<unsigned long long>(address), elapsed, compiledResult->spirv.size(), captured.size(), totalMs / 1000);
static std::atomic<double> totalMs{0};
if (profile) {
const auto total = totalMs.fetch_add(elapsed, std::memory_order_relaxed) + elapsed;
if (elapsed > 200) std::fprintf(stderr, "[gpu] compute shader 0x%llx recompile took %.0f ms (%zu SPIR-V words, %zu captured regions, total %.1f s)\n", static_cast<unsigned long long>(address), elapsed, compiledResult->spirv.size(), captured.size(), total / 1000);
}
} catch (const std::exception& error) {
std::string dump;
if (dumpShaders) {
@@ -416,9 +423,9 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
else std::snprintf(where, sizeof(where), "compute shader 0x%llx (%s): ", static_cast<unsigned long long>(address), dump.c_str());
throw std::runtime_error(where + reason);
}
recompileMs += phaseTiming.Elapsed();
if (profile) recompileMs.fetch_add(phaseTiming.Elapsed(), std::memory_order_relaxed);
phaseTiming.Phase(PhaseRecompile);
insertDispatch(address, key, noDispatchCache || !CacheableResult(*compiledResult), profile, registeredShader, forgetAtCapture, memory, shaderMemory, captured, capture, compiledResult, missedEntry, missedDiffering, attachVariant, phaseTiming);
if (!resolving) insertDispatch(address, key, noDispatchCache || !CacheableResult(*compiledResult), profile, registeredShader, forgetAtCapture, memory, shaderMemory, captured, capture, compiledResult, missedEntry, missedDiffering, attachVariant, phaseTiming);
}
if (resolving) {
auto& parked = resolvedAhead()[currentPacketOffset()];
@@ -426,11 +433,13 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
parked.address = address;
parked.key = key;
parked.indirectArguments = indirectArguments;
parked.forgetAtCapture = forgetAtCapture;
std::copy_n(packet.begin(), std::min(packet.size(), parked.packet.size()), parked.packet.begin());
parked.registeredShader = registeredShader;
parked.compiledResult = compiledResult;
parked.keepVariant = keepVariant;
parked.attachVariant = attachVariant;
parked.missedEntry = missedEntry;
parked.shaderMemory = shaderMemory;
parked.captured = std::move(captured);
parked.liveWords = std::move(liveWords);
@@ -438,6 +447,8 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
parked.dataHit = dataHit;
parked.cached = cached;
parked.validated = validated;
parked.missedDiffering = missedDiffering;
parked.noDispatchCache = noDispatchCache || (compiledResult != nullptr && !CacheableResult(*compiledResult));
parked.resolvedAt = std::chrono::steady_clock::now();
++groupStats.resolved;
if (cached) ++groupStats.resolvedHits;
@@ -673,7 +684,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
}
phaseTiming.Phase(PhaseDevice);
if (noteWrites && !writerKeyedEvidence()) noteWrittenBuffers(address, submission.queue, compiled);
deviceMs += phaseTiming.Elapsed();
if (profile) deviceMs.fetch_add(phaseTiming.Elapsed(), std::memory_order_relaxed);
phaseTiming.Phase(PhaseTail);
if (profile) {
auto& pending = pendingDispatchPhases();
@@ -684,7 +695,16 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
pending.ms = phaseMs;
pending.tailAt = phaseLap;
}
if (profile && ++dispatches % 100 == 0) AgcDriver::ProfilePrint_nid_no_patch( "[gpu] %llu dispatches (%llu dispatch cache hits, %llu evictions, %llu recompile cache hits): capture %.1f s, cache key %.1f s, recompile %.1f s, device %.1f s\n", static_cast<unsigned long long>(dispatches), static_cast<unsigned long long>(dispatchCacheHits), static_cast<unsigned long long>(dispatchCacheEvictions), static_cast<unsigned long long>(cacheHits), captureMs / 1000, keyMs / 1000, recompileMs / 1000, deviceMs / 1000);
if (profile && dispatches.fetch_add(1, std::memory_order_relaxed) % 100 == 99) {
std::uint64_t hits = 0;
std::uint64_t evictions = 0;
{
std::lock_guard cacheLock(dispatchCacheMutex);
hits = dispatchCacheHits;
evictions = dispatchCacheEvictions;
}
AgcDriver::ProfilePrint_nid_no_patch( "[gpu] %llu dispatches (%llu dispatch cache hits, %llu evictions, %llu recompile cache hits): capture %.1f s, cache key %.1f s, recompile %.1f s, device %.1f s\n", static_cast<unsigned long long>(dispatches.load(std::memory_order_relaxed)), static_cast<unsigned long long>(hits), static_cast<unsigned long long>(evictions), static_cast<unsigned long long>(cacheHits.load(std::memory_order_relaxed)), captureMs.load(std::memory_order_relaxed) / 1000, keyMs.load(std::memory_order_relaxed) / 1000, recompileMs.load(std::memory_order_relaxed) / 1000, deviceMs.load(std::memory_order_relaxed) / 1000);
}
}
}
@@ -2,6 +2,7 @@
#include "ThreadOwned.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/DrawWriteRanges.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp"
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
@@ -108,10 +109,6 @@ void NoteLabel(std::vector<RunLabel>& labels, std::uint64_t address, std::span<c
labels.push_back({address, bytes.size(), value});
}
bool WritesThroughPointers(const ShaderRecompiler::RecompileResult& compiled) {
return std::any_of(compiled.bindings.begin(), compiled.bindings.end(), [](const ShaderRecompiler::DescriptorBinding& binding) { return binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable; });
}
bool OverlapsLabel(std::span<const ShaderRecompiler::MemoryRegion> regions, const std::vector<RunLabel>& labels) {
for (const auto& label : labels) {
for (const auto& region : regions) {
@@ -136,7 +133,6 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l
QueueState scratch = live;
std::vector<RunLabel> labels;
std::vector<std::pair<std::uint64_t, std::uint64_t>> writes;
bool unknownWrites = false;
for (const auto& label : deferredLabels().labels) NoteLabel(labels, label.address, std::span<const std::byte>(label.bytes).first(label.size));
struct Resolving {
std::size_t offset;
@@ -277,7 +273,7 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l
const auto argumentsEnd = arguments + 3u * sizeof(std::uint32_t);
const bool written = std::any_of(writes.begin(), writes.end(), [&](const auto& range) { return range.first < argumentsEnd && arguments < range.second; })
|| std::any_of(labels.begin(), labels.end(), [&](const RunLabel& label) { return label.address < argumentsEnd && arguments < label.address + label.size; });
if ((unknownWrites || written) && indirectArgumentsPath(packet, arguments) != IndirectGpu) {
if (written && indirectArgumentsPath(packet, arguments) != IndirectGpu) {
++stats.writtenBefore;
break;
}
@@ -302,10 +298,31 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l
++stats.writtenBefore;
break;
}
if (found->second.compiledResult != nullptr) appendWrittenRanges(*found->second.compiledResult, writes);
if (found->second.compiledResult == nullptr || WritesThroughPointers(*found->second.compiledResult)) unknownWrites = true;
if (found->second.compiledResult == nullptr) {
++stats.unknownWrites;
break;
}
if (!found->second.cached && found->second.registeredShader != nullptr) {
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
const auto& snapshot = *found->second.registeredShader;
std::vector<ShaderRecompiler::MemoryRegion> memory{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}};
if (!snapshot.header.empty()) memory.push_back({snapshot.headerAddress, snapshot.header});
auto lap = std::chrono::steady_clock::now();
std::array<double, DriverPhaseCount> phaseMs{};
auto phaseLap = lap;
DispatchPhaseTiming phaseTiming{profile, lap, phaseMs, phaseLap};
insertDispatch(found->second.address, found->second.key, found->second.noDispatchCache, profile, found->second.registeredShader, found->second.forgetAtCapture, memory, found->second.shaderMemory, found->second.captured, found->second.capture, found->second.compiledResult, found->second.missedEntry, found->second.missedDiffering, found->second.attachVariant, phaseTiming);
}
bool bounded = false;
const auto itemWrites = DispatchWriteRanges(*found->second.compiledResult, bounded);
if (!bounded) {
++stats.unknownWrites;
break;
}
writes.insert(writes.end(), itemWrites.begin(), itemWrites.end());
} else {
unknownWrites = true;
++stats.unknownWrites;
break;
}
} else if (opcode == 0x37 || opcode == 0x49) {
const auto label = Pm4::DecodeLabelWrite(packet);
@@ -334,10 +351,10 @@ void Driver::reportGroupCapture(std::uint32_t queue) {
auto& stats = groupCaptureStats();
const auto now = std::chrono::steady_clock::now();
if (now - stats.reported < std::chrono::seconds(10)) return;
AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu memory %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu rejected by the device, %llu dropped because the argument re-read differed\n",
AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu memory %llu; %llu resolve failures, %llu stale, %llu label overlaps, %llu read an earlier dispatch's output, %llu stopped at unknown writes, %llu rejected by the device, %llu dropped because the argument re-read differed\n",
queue, static_cast<unsigned long long>(stats.runs), static_cast<unsigned long long>(stats.resolved), static_cast<unsigned long long>(stats.resolvedHits), stats.resolveMs, stats.resolveMaxMs,
static_cast<unsigned long long>(stats.adopted), stats.adopted != 0 ? stats.ageMs / static_cast<double>(stats.adopted) : 0.0, stats.ageMaxMs, static_cast<unsigned long long>(stats.addressMismatches), static_cast<unsigned long long>(stats.keyMismatches), static_cast<unsigned long long>(stats.memoryMismatches),
static_cast<unsigned long long>(stats.failures), static_cast<unsigned long long>(stats.stale), static_cast<unsigned long long>(stats.labelOverlaps), static_cast<unsigned long long>(stats.writtenBefore), static_cast<unsigned long long>(stats.deviceRejects), static_cast<unsigned long long>(stats.argumentMismatches));
static_cast<unsigned long long>(stats.failures), static_cast<unsigned long long>(stats.stale), static_cast<unsigned long long>(stats.labelOverlaps), static_cast<unsigned long long>(stats.writtenBefore), static_cast<unsigned long long>(stats.unknownWrites), static_cast<unsigned long long>(stats.deviceRejects), static_cast<unsigned long long>(stats.argumentMismatches));
stats = GroupCaptureStats{};
stats.reported = now;
}
@@ -128,7 +128,7 @@ void Driver::noteCopyWriter(std::uint64_t program, std::uint64_t begin, std::uin
knownValueEntries.fetch_add(1, std::memory_order_relaxed);
}
std::lock_guard lock(writtenBuffersMutex);
writtenBuffers.Push({program, begin, end, ++writtenBufferSerial, queue, false, std::move(known), generation});
writtenBuffers.Push({known || !copyKnownValues() ? program : 0, begin, end, ++writtenBufferSerial, queue, false, std::move(known), generation});
}
bool Driver::copyKnownValues() {
@@ -53,11 +53,11 @@ void Driver::noteWrittenBuffers(std::uint64_t program, std::uint32_t queue, cons
}
std::vector<std::pair<std::uint64_t, std::uint64_t>> DispatchWriteRanges(const ShaderRecompiler::RecompileResult& compiled, bool& bounded) {
bounded = compiled.bdaAbiVersion == 0;
bounded = compiled.bdaAbiVersion == 0 || std::any_of(compiled.bindings.begin(), compiled.bindings.end(), [](const ShaderRecompiler::DescriptorBinding& binding) { return binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer; });
std::vector<std::pair<std::uint64_t, std::uint64_t>> ranges;
constexpr std::uint64_t UnknownImageBytes = 64ull * 1024 * 1024;
for (const auto& binding : compiled.bindings) {
if (binding.role == ShaderRecompiler::DescriptorRole::Gds || binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer) bounded = false;
if (binding.role == ShaderRecompiler::DescriptorRole::Gds || binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable) bounded = false;
if (binding.role != ShaderRecompiler::DescriptorRole::GuestImages || binding.kind != ShaderRecompiler::DescriptorKind::StorageImage) continue;
for (std::uint32_t element = 0; element < binding.count; ++element) {
const bool written = element >= binding.imageWritten.size() || binding.imageWritten[element];
@@ -111,7 +111,7 @@ std::vector<std::pair<std::uint64_t, std::uint64_t>> DrawWriteRanges(const Graph
const bool written = element >= binding.imageWritten.size() || binding.imageWritten[element];
if (!written || binding.guestDescriptor.size() < (static_cast<std::size_t>(element) + 1) * 8) continue;
const auto words = std::span<const std::uint32_t>(binding.guestDescriptor).subspan(static_cast<std::size_t>(element) * 8, 8);
if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue;
if (Graphics::IsNullTextureResource(words)) continue;
const auto resource = Graphics::DecodeTextureResource(words);
if (resource.baseAddress == 0) continue;
const auto surfaceBytes = Graphics::DescribeSurface(resource).guestBytes;
@@ -144,6 +144,7 @@ void Driver::run(std::uint32_t id) noexcept {
StartWorkerSampler(id);
Submission submission;
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
static const bool idleStore = std::getenv("APS5_NO_IDLE_STORE") == nullptr;
auto& costs = submissionCosts(id);
try {
PerformanceTrace::BindThread(role);
@@ -183,7 +184,7 @@ void Driver::run(std::uint32_t id) noexcept {
&& std::all_of(workers.begin(), workers.end(), [](const auto& item) { return item.second.pending.empty(); });
};
const auto idleResultsPending = [&] {
return id == 0 && Graphics::StorageTexture::AnyPending() && allQueuesIdle() && !completionsPending();
return idleStore && id == 0 && Graphics::StorageTexture::AnyPending() && allQueuesIdle() && !completionsPending();
};
const auto poll = [&](const auto& done) {
#ifdef _WIN32
@@ -221,7 +222,7 @@ void Driver::run(std::uint32_t id) noexcept {
lock.lock();
if (ready()) break;
if (id == 0 && allQueuesIdle() && !completionsPending()
&& ((!storeIdleResults && Graphics::StorageTexture::AnyPending()) || idleEpoch.load(std::memory_order_acquire) != idleEpochAtCheck)) continue;
&& ((idleStore && !storeIdleResults && Graphics::StorageTexture::AnyPending()) || idleEpoch.load(std::memory_order_acquire) != idleEpochAtCheck)) continue;
if (id == 0) queue0Dormant.store(true, std::memory_order_relaxed);
const auto idleEpochAtWait = idleEpoch.load(std::memory_order_acquire);
changed.wait(lock, [&] { return ready() || (id == 0 && (idleEpoch.load(std::memory_order_acquire) != idleEpochAtWait || completionsPending())); });
@@ -124,7 +124,7 @@ std::shared_ptr<const ShaderSnapshot> ReadRawShader(std::uint64_t address) {
snapshot.code.resize(std::min(available, std::max<std::size_t>(64, previous * 2)));
drain(address + previous * sizeof(std::uint32_t), (snapshot.code.size() - previous) * sizeof(std::uint32_t));
GuestMemory::Read(address + previous * sizeof(std::uint32_t),
std::as_writable_bytes(std::span(snapshot.code).subspan(previous)), alignof(std::uint32_t));
std::as_writable_bytes(std::span(snapshot.code.Mutable()).subspan(previous)), alignof(std::uint32_t));
try {
const auto decoded = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(snapshot.code);
if (const auto tail = ShaderRecompiler::FindUserDataTail(decoded, 0u, 106u)) snapshot.userDataTailRegister = tail->userDataRegister;
@@ -1205,7 +1205,8 @@ std::shared_ptr<const ShaderRecompiler::ResourceCapture> CaptureInvocation(Shade
}
namespace {
std::optional<ShaderRecompiler::ShaderFloatMode> DecodeFloatMode(const Registers& registers, std::uint8_t type) {
template<typename TRegisters>
std::optional<ShaderRecompiler::ShaderFloatMode> DecodeFloatMode(const TRegisters& registers, std::uint8_t type) {
std::uint32_t rsrc1;
std::uint32_t fp16OverflowBit;
switch (type) {
@@ -1253,7 +1254,7 @@ Shader ReadHeader(const ShaderSnapshot& snapshot) {
return header;
}
RegisteredShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot, RegisteredShaderState state = {{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}}) {
DecodedShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot, DecodedShaderState state = {{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}}) {
const auto header = ReadHeader(snapshot);
for (const auto reg : ReadHeaderArray(snapshot, header.sh_registers, header.num_sh_registers)) {
state.shader.insert_or_assign(reg.offset, reg.value);
@@ -1501,9 +1502,9 @@ PreparedAtUse PrepareDeferredAbis(const ShaderSnapshot& snapshot, std::unique_lo
const auto device = abi.device.lock();
if (device == nullptr || snapshot.registeredState == nullptr) continue;
QueueState state{};
state.shader = snapshot.registeredState->shader;
state.context = snapshot.registeredState->context;
state.userConfig = snapshot.registeredState->userConfig;
state.shader = snapshot.registeredState->shader.Expand();
state.context = snapshot.registeredState->context.Expand();
state.userConfig = snapshot.registeredState->userConfig.Expand();
for (const auto& [offset, value] : abi.context) state.context[offset] = value;
for (const auto& [offset, value] : abi.primitive) state.userConfig[offset] = value;
try {
@@ -2209,9 +2210,9 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegist
}
require(snapshot->registeredState != nullptr, "registered shader state is missing");
QueueState state{};
state.shader = snapshot->registeredState->shader;
state.context = snapshot->registeredState->context;
state.userConfig = snapshot->registeredState->userConfig;
state.shader = snapshot->registeredState->shader.Expand();
state.context = snapshot->registeredState->context.Expand();
state.userConfig = snapshot->registeredState->userConfig.Expand();
for (const auto reg : context) state.context[reg.offset] = reg.value;
for (const auto reg : primitive) state.userConfig[reg.offset] = reg.value;
const auto localDevice = device.Load();
@@ -2462,9 +2463,9 @@ ShaderSnapshot PrepareNullPixelProgram(const VulkanDevice& device) {
nullRegisteredState.context.insert_or_assign(0x1b4u, 0x2u);
null.registeredState = std::make_shared<const RegisteredShaderState>(std::move(nullRegisteredState));
QueueState nullState{};
nullState.shader = null.registeredState->shader;
nullState.context = null.registeredState->context;
nullState.userConfig = null.registeredState->userConfig;
nullState.shader = null.registeredState->shader.Expand();
nullState.context = null.registeredState->context.Expand();
nullState.userConfig = null.registeredState->userConfig.Expand();
null.prepared->entries = PrepareRegistered(null, device, nullState, true);
return null;
}
@@ -2511,7 +2512,7 @@ void Driver::RegisterShader(const Shader* shader) {
require(!RawGraphicsDrawnIn(reinterpret_cast<std::uintptr_t>(code), reinterpret_cast<std::uintptr_t>(code) + fields.shader_size), "a shader was registered over a graphics program that was drawn without registration");
ShaderSnapshot snapshot{reinterpret_cast<std::uintptr_t>(code), reinterpret_cast<std::uintptr_t>(shader), fields.type, {}, {}};
snapshot.code.resize(fields.shader_size / sizeof(std::uint32_t));
std::memcpy(snapshot.code.data(), code, fields.shader_size);
std::memcpy(snapshot.code.Mutable().data(), code, fields.shader_size);
snapshot.header.resize(fields.header_size);
std::memcpy(snapshot.header.data(), static_cast<const void*>(shader), fields.header_size);
@@ -2546,7 +2547,9 @@ void Driver::RegisterShader(const Shader* shader) {
if (shaders != nullptr) {
if (const auto found = shaders->find(snapshot.codeAddress); found != shaders->end()) {
for (const auto& current : found->second) {
if (current->registeredState == nullptr || current->type != snapshot.type || current->code != snapshot.code || *current->registeredState != *snapshot.registeredState) continue;
if (current->code != snapshot.code) continue;
snapshot.code = current->code;
if (current->registeredState == nullptr || current->type != snapshot.type || *current->registeredState != *snapshot.registeredState) continue;
snapshot.registeredState = current->registeredState;
break;
}
@@ -2554,9 +2557,9 @@ void Driver::RegisterShader(const Shader* shader) {
}
}
QueueState registered{};
registered.shader = snapshot.registeredState->shader;
registered.context = snapshot.registeredState->context;
registered.userConfig = snapshot.registeredState->userConfig;
registered.shader = snapshot.registeredState->shader.Expand();
registered.context = snapshot.registeredState->context.Expand();
registered.userConfig = snapshot.registeredState->userConfig.Expand();
std::shared_ptr<RegisteredPreparation> preparation;
if (LazyShaderPreparation()) {
static_cast<void>(PlanRegistered(snapshot, *localDevice, registered, true, &snapshot.prepared->deferred));
@@ -1181,6 +1181,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
APS5_LOG_OUT("VulkanDevice constructor window=%p", static_cast<const void*>(window));
#ifdef _WIN32
state->library = SDL_LoadObject("vulkan-1.dll");
#elif defined(__APPLE__)
state->library = SDL_LoadObject("libvulkan.1.dylib");
#else
state->library = SDL_LoadObject("libvulkan.so.1");
#endif
@@ -1221,9 +1223,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
instanceExtensions.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME);
state->swapchainColorSpaces = true;
}
create.enabledExtensionCount = static_cast<std::uint32_t>(instanceExtensions.size());
create.ppEnabledExtensionNames = instanceExtensions.data();
}
#ifdef __APPLE__
instanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
create.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
#endif
create.enabledExtensionCount = static_cast<std::uint32_t>(instanceExtensions.size());
create.ppEnabledExtensionNames = instanceExtensions.empty() ? nullptr : instanceExtensions.data();
const VkResult instanceResult = state->InstanceFunction<PFN_vkCreateInstance>("vkCreateInstance")(&create, nullptr, &state->instance);
if (instanceResult == VK_ERROR_INCOMPATIBLE_DRIVER) throw VulkanUnavailable("vkCreateInstance: Vulkan result " + std::to_string(instanceResult));
check(instanceResult, "vkCreateInstance");
@@ -1700,6 +1706,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
if (state->nonSeamlessCubeMap) deviceExtensions.push_back(VK_EXT_NON_SEAMLESS_CUBE_MAP_EXTENSION_NAME);
}
nonSeamlessCubeFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NON_SEAMLESS_CUBE_MAP_FEATURES_EXT};
VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR};
const bool portabilitySubset = hasExtension(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
if (portabilitySubset) {
VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &portabilityFeatures};
state->InstanceFunction<PFN_vkGetPhysicalDeviceFeatures2>("vkGetPhysicalDeviceFeatures2")(selected, &features);
deviceExtensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
}
nonSeamlessCubeFeatures.nonSeamlessCubeMap = VK_TRUE;
VkPhysicalDeviceCoverageReductionModeFeaturesNV coverageReductionFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV};
if (hasExtension(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME) && hasExtension(VK_NV_COVERAGE_REDUCTION_MODE_EXTENSION_NAME)) {
@@ -1988,6 +2001,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
nonSeamlessCubeFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &nonSeamlessCubeFeatures;
}
if (portabilitySubset) {
portabilityFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &portabilityFeatures;
}
if (!state->mixedAttachmentSamples && (state->mergedCoverageSampleCounts != 0 || state->truncatedCoverageSampleCounts != 0)) {
coverageReductionFeatures.pNext = const_cast<void*>(deviceInfo.pNext);
deviceInfo.pNext = &coverageReductionFeatures;
@@ -2073,10 +2090,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
byteFeatures.pNext = &subgroupSizeFeatures;
APS5_LOG_OUT("Wave32 programs run on subgroups of %u: compute %d, mesh %d", 32u, state->computeWave32, state->meshWave32);
}
if (std::getenv("APS5_SPANNED_WAVE") != nullptr) {
state->spannedWaves = state->computeFullSubgroups;
APS5_LOG_OUT("APS5_SPANNED_WAVE: single-wave wave64 compute spans two full 32-lane subgroups: %d", state->spannedWaves);
}
state->spannedWaves = state->computeFullSubgroups && std::getenv("APS5_NO_SPANNED_WAVE") == nullptr;
APS5_LOG_OUT("single-wave wave64 compute spans two full 32-lane subgroups: %d", state->spannedWaves);
timelineFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR};
timelineFeatures.timelineSemaphore = VK_TRUE;
if (state->timelineSemaphores) {
@@ -2498,7 +2513,7 @@ void VulkanDevice::StoreIdleResults(bool reapFirst, const std::function<bool()>&
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush);
std::lock_guard gpuLock(GuestMemory::GpuMutex());
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
static_cast<void>(Graphics::StorageTexture::FlushAllPendingUntil("idle", stop));
static_cast<void>(Graphics::StorageTexture::FlushAllPendingUntil("idle", stop, state->device));
SubmitRecorded(reapFirst);
}
@@ -2575,7 +2590,7 @@ bool VulkanDevice::StoresPendingOver(std::uint64_t address, std::size_t bytes) c
int VulkanDevice::WriteLabelOnGpu(std::uint64_t address, std::span<const std::byte> bytes, std::uint64_t stamp, std::uint32_t queue, bool reapFirst) {
static const bool storeAtLabel = Graphics::StorageTexture::StoreAtLabelRequested(std::getenv("APS5_STORE_AT_LABEL"));
if (storeAtLabel) Graphics::StorageTexture::FlushAllPending("label");
if (storeAtLabel) Graphics::StorageTexture::FlushAllPending("label", state->device);
if (queue == 0) static_cast<void>(Graphics::StorageTexture::StoreDrawnLinear("label"));
Graphics::StorageTexture::StoreReadbackBeforeLabel("readback before label");
if (!state->recorder || bytes.empty() || bytes.size() > 65536 || address % 4 != 0 || bytes.size() % 4 != 0) return 4;
@@ -3539,7 +3554,7 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer, const Displ
}
}
static const bool storeAtFlip = Graphics::StorageTexture::StoreAtFlipRequested(std::getenv("APS5_STORE_AT_FLIP"));
if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip");
if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip", state->device);
else Graphics::StorageTexture::FlushDumpSelected("flip");
auto& dumps = Dumps();
const bool passthrough = state->surfaceColorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT;
@@ -144,10 +144,7 @@ struct DeviceFunctions {
};
// Per-thread count of vkGetDeviceProcAddr lookups made through Context::Function (the [vk] line).
inline std::uint64_t& DeviceProcLookups() {
thread_local std::uint64_t count = 0;
return count;
}
std::uint64_t& DeviceProcLookups();
inline constexpr std::size_t EmptyBufferBytes = 16;
@@ -109,6 +109,7 @@ struct GuestTextureResource {
std::uint32_t gcnPitch = 0;
bool gcnPow2Pad = false;
bool gcnLinearGeneral = false;
std::uint32_t linearPitch = 0;
};
constexpr bool IsGcnMicroTiled(TextureTileMode mode) {
@@ -174,7 +174,7 @@ public:
// on the open batch, no snapshot: the only reader holds the mutex. `kind` names the reader for
// the [recorder] hit counters. APS5_COPY_READ_TRACKING=0 notes nothing (ReadTracking() is then
// false and the CPU copy falls back to Idle()).
enum class ReadKind : std::uint8_t { DispatchElement = 0, GpuCopy, AddressBased, Indirect, StorageUpload, CopySource, DrawInput, Count };
enum class ReadKind : std::uint8_t { DispatchElement = 0, GpuCopy, AddressBased, Indirect, StorageUpload, CopySource, DrawInput, AddressBasedReadOnly, Count };
static bool ReadTracking();
void NotePendingRead(std::uint64_t address, std::size_t bytes, ReadKind kind);
void NotePendingReads(std::span<const std::pair<std::uint64_t, std::uint64_t>> ranges, ReadKind kind);
@@ -295,9 +295,9 @@ public:
// any unit was: a reader of the import must sync for a publish as for a store. Only images
// with a pending unit inside the range are stored (APS5_NO_FLUSH_PRETEST=1 lists every
// overlapping pending image, as before).
static bool FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr, const char* reason = "memory access", PublishScope scope = PublishScope::Whole, bool* published = nullptr, bool waited = false);
static void FlushAllPending(const char* reason);
static bool FlushAllPendingUntil(const char* reason, const std::function<bool()>& stop);
static bool FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr, const char* reason = "memory access", PublishScope scope = PublishScope::Whole, bool* published = nullptr, bool waited = false, VkDevice device = VK_NULL_HANDLE);
static void FlushAllPending(const char* reason, VkDevice device = VK_NULL_HANDLE);
static bool FlushAllPendingUntil(const char* reason, const std::function<bool()>& stop, VkDevice device = VK_NULL_HANDLE);
static bool WaitForFlushing(std::uint64_t address, std::size_t bytes, const StorageTexture* except = nullptr);
static void SaveBeforeUnwatch(std::uint64_t address, std::size_t bytes);
static bool UnwatchHookLocksGpu(std::span<const std::pair<std::uint64_t, std::uint64_t>> ranges);
@@ -26,7 +26,7 @@ struct TileMipLayout {
std::uint32_t gcnSplitStride = 0;
};
std::vector<TileMipLayout> ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount);
std::vector<TileMipLayout> ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount, std::uint32_t linearPitch = 0u);
std::uint64_t ComputeSurfaceSize(const std::vector<TileMipLayout>& mips, std::uint32_t arrayLayers);
// Mip chain of an uncompressed surface given its element size (color targets have no texture format).
std::vector<TileMipLayout> ComputeElementMipLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount);
@@ -13,6 +13,7 @@
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp"
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"
#include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp"
#include <algorithm>
#include <array>
@@ -3033,8 +3034,10 @@ VkImageAspectFlags HtileFillClears(std::uint32_t pattern, bool stencilInHtile) {
}
std::uint64_t HtileMetadataBytes(VkExtent2D extent) {
const auto blocks = static_cast<std::uint64_t>((extent.width + 1023u) / 1024u) * ((extent.height + 511u) / 512u);
return blocks * 32768u;
const bool gcn = ShaderRecompiler::GcnGuestShaders();
const std::uint32_t blockWidth = gcn ? 512u : 1024u;
const auto blocks = static_cast<std::uint64_t>((extent.width + blockWidth - 1u) / blockWidth) * ((extent.height + 511u) / 512u);
return blocks * (gcn ? 16384u : 32768u);
}
bool HtileFillCovers(std::uint64_t htile, VkExtent2D extent, std::uint64_t address, std::size_t bytes) {
@@ -91,8 +91,8 @@ GuestSamplerResource DecodeSamplerResource(std::span<const std::uint32_t> words,
Require(!forceUnormCoords || unnormalizedProven, "guest sampler descriptor uses unnormalized coordinates which are not implemented");
if (forceUnormCoords) {
Require(!isAnisoFilter(xyMagFilter) && !isAnisoFilter(xyMinFilter), "guest sampler descriptor uses unnormalized coordinates with anisotropic filtering, which is not implemented");
Require(clampX == 0u || clampX == 2u || clampX == 6u, "guest sampler descriptor uses unnormalized coordinates with clamp mode " + std::to_string(clampX) + " on X; only clamp-to-last-texel and clamp-to-border are implemented");
Require(clampY == 0u || clampY == 2u || clampY == 6u, "guest sampler descriptor uses unnormalized coordinates with clamp mode " + std::to_string(clampY) + " on Y; only clamp-to-last-texel and clamp-to-border are implemented");
Require(clampX <= 3u || clampX == 6u, "guest sampler descriptor uses unnormalized coordinates with clamp mode " + std::to_string(clampX) + " on X; only wrap, mirror, clamp-to-last-texel, mirror-once and clamp-to-border are implemented");
Require(clampY <= 3u || clampY == 6u, "guest sampler descriptor uses unnormalized coordinates with clamp mode " + std::to_string(clampY) + " on Y; only wrap, mirror, clamp-to-last-texel, mirror-once and clamp-to-border are implemented");
Require(!mcCoordTrunc, "guest sampler descriptor uses unnormalized coordinates with MC_COORD_TRUNC, which is not implemented");
} else {
Require(!unnormalizedProven, "guest sampler descriptor is bound as unnormalized without FORCE_UNNORMALIZED");
@@ -247,15 +247,19 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> descri
Require(baseAddress != 0, "guest texture descriptor has a null base address");
}
std::uint32_t linearPitch = 0;
switch (dimension) {
case TextureDimension::k1D:
Require(height == 1 && depth == 0 && baseArray == 0, "guest 1D texture descriptor has a nonzero height, depth or base array");
break;
case TextureDimension::k2D:
if (depth != 0 && tileMode == TextureTileMode::kLinear) {
Require(!IsBlockCompressed(format) && maxMip == 0 && baseArray == 0, "guest 2D linear texture descriptor sets a pitch on a block-compressed, mipmapped or base-array surface, which is not implemented" + describe());
const auto elementsPerPitchUnit = 256u / BytesPerElement(format);
const auto defaultPitch = (width + elementsPerPitchUnit - 1u) / elementsPerPitchUnit * elementsPerPitchUnit;
Require(!IsBlockCompressed(format) && maxMip == 0 && baseArray == 0 && depth + 1u == defaultPitch, "guest 2D linear texture descriptor sets a custom pitch other than its default pitch, which is not implemented" + describe());
Require(!ShaderRecompiler::GcnGuestShaders() || depth + 1u == defaultPitch, "guest 2D linear texture descriptor of a PS4 guest sets a custom pitch other than its default pitch, which is not implemented" + describe());
const auto pitch = (std::max(depth + 1u, width) + elementsPerPitchUnit - 1u) / elementsPerPitchUnit * elementsPerPitchUnit;
linearPitch = pitch == defaultPitch ? 0u : pitch;
depth = 0;
}
Require(depth == 0 && baseArray == 0, "guest 2D texture descriptor has a nonzero depth or base array");
@@ -314,6 +318,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> descri
result.gcnLinearGeneral = ShaderRecompiler::GcnImageTilingIndex(gcnTiling) == 31u;
Require(result.gcnPitch >= width, "PS4 texture descriptor has a pitch below its width" + describe());
}
result.linearPitch = linearPitch;
if (baseLevel > maxMip) {
const bool fits = LevelsFitAllocation(result, lastLevel + 1u);
if (!fits && ZeroTail(words)) {
@@ -328,7 +333,7 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> descri
std::array<std::uint32_t, 8> EncodeTextureResource(const GuestTextureResource& resource) {
const bool multisampled = resource.dimension == TextureDimension::k2DMsaa;
Require((resource.dimension == TextureDimension::k2D && resource.samples == 1u) || (multisampled && resource.samples >= 2u && resource.samples <= 16u && std::has_single_bit(resource.samples)), "only 2D and multisampled 2D surfaces have a texture descriptor encoding");
Require(resource.mipCount == 1u && resource.allocatedMipCount <= 1u && resource.baseLevel == 0u && resource.lastLevel == 0u && resource.depthOrLastArray == 0u && resource.baseArray == 0u && resource.dccAddress == 0u && resource.minLod == 0u && !resource.iterate256 && resource.bcSwizzle == 0u, "only single-level, single-layer surfaces without metadata have a texture descriptor encoding");
Require(resource.mipCount == 1u && resource.allocatedMipCount <= 1u && resource.baseLevel == 0u && resource.lastLevel == 0u && resource.depthOrLastArray == 0u && resource.baseArray == 0u && resource.dccAddress == 0u && resource.minLod == 0u && !resource.iterate256 && resource.bcSwizzle == 0u && resource.linearPitch == 0u, "only single-level, single-layer surfaces without metadata have a texture descriptor encoding");
Require(resource.width >= 1u && resource.width <= 0x4000u && resource.height >= 1u && resource.height <= 0x4000u && resource.format < 0x200u, "the surface's extent or format has no texture descriptor encoding");
const auto xorMask = resource.tileMode == TextureTileMode::kS4KBX || resource.tileMode == TextureTileMode::kD4KBX ? 0xfffull : 0xffffull;
Require(XorSwizzleMode(resource.tileMode) != 0 ? (resource.baseAddress & xorMask) == 0 && (resource.pipeBankXor & ~xorMask) == 0 : resource.pipeBankXor == 0, "the surface's pipe and bank XOR has no texture descriptor encoding");
@@ -4364,7 +4364,7 @@ void Recorder::finish(std::unique_ptr<Batch> batch, bool wait, int source, bool
AgcDriver::ProfilePrint_nid_no_patch("[recorder] completion label stores: %llu run, %llu skipped (no CPU write-back overlapped them); %llu counted pending at a write-back; %llu write-backs over a tracked label; %llu write-back completions pending\n", static_cast<unsigned long long>(completionStoresRun.load()), static_cast<unsigned long long>(completionStoresSkipped.load()), static_cast<unsigned long long>(completionLabelsCountedLate.load()), static_cast<unsigned long long>(writeBacksOverLabels.load()), static_cast<unsigned long long>(writeBackCompletions.load()));
AgcDriver::ProfilePrint_nid_no_patch("[recorder] submits %llu, vkQueueSubmit mean %.1f us, max %.1f us; command buffers freed at batch release %llu, fences kept for reuse %llu\n", static_cast<unsigned long long>(submitCount), submitCount != 0 ? submitUs / static_cast<double>(submitCount) : 0.0, submitMaxUs, static_cast<unsigned long long>(commandBuffersFreed.load()), static_cast<unsigned long long>(fencesKept.load()));
const auto reads = Recorder::ReadCounts();
AgcDriver::ProfilePrint_nid_no_patch("[recorder] in-place reads: %llu noted, %llu queries, hits by reader: dispatch element %llu, gpu copy %llu, address-based %llu, indirect %llu, storage upload %llu, copy source %llu, draw input %llu; %llu hits on signaled batches ignored\n", static_cast<unsigned long long>(reads.noted), static_cast<unsigned long long>(reads.queries), static_cast<unsigned long long>(reads.hits[0]), static_cast<unsigned long long>(reads.hits[1]), static_cast<unsigned long long>(reads.hits[2]), static_cast<unsigned long long>(reads.hits[3]), static_cast<unsigned long long>(reads.hits[4]), static_cast<unsigned long long>(reads.hits[5]), static_cast<unsigned long long>(reads.hits[6]), static_cast<unsigned long long>(reads.staleIgnored));
AgcDriver::ProfilePrint_nid_no_patch("[recorder] in-place reads: %llu noted, %llu queries, hits by reader: dispatch element %llu, gpu copy %llu, address-based %llu, indirect %llu, storage upload %llu, copy source %llu, draw input %llu, address-based read-only %llu; %llu hits on signaled batches ignored\n", static_cast<unsigned long long>(reads.noted), static_cast<unsigned long long>(reads.queries), static_cast<unsigned long long>(reads.hits[0]), static_cast<unsigned long long>(reads.hits[1]), static_cast<unsigned long long>(reads.hits[2]), static_cast<unsigned long long>(reads.hits[3]), static_cast<unsigned long long>(reads.hits[4]), static_cast<unsigned long long>(reads.hits[5]), static_cast<unsigned long long>(reads.hits[6]), static_cast<unsigned long long>(reads.hits[7]), static_cast<unsigned long long>(reads.staleIgnored));
}
}
} report{profile, source, waitStart, *this, signaledAtStart};
@@ -5171,6 +5171,11 @@ std::uint32_t SamplerTableCapacityFor(const Context& context, bool updateAfterBi
}
std::uint64_t& DeviceProcLookups() {
thread_local std::uint64_t count = 0;
return count;
}
std::uint32_t ImageTableCapacity(const Context& context) {
const auto capacity = ImageTableCapacityFor(context, false);
return capacity != 0u ? capacity : ImageTableCapacityFor(context, true);
@@ -5990,7 +5995,8 @@ void ShaderResources::MarkGpuWrites(Recorder& recorder) {
// The ranges this use reads in place through their host imports (read-only and written elements
// alike, and an address-based build's whole leased heaps), before the writes: a CPU store into
// one of them (the copy HLE) must not land before the recorded work read it.
const auto kind = guestMemory.HoldsLease() ? Recorder::ReadKind::AddressBased : Recorder::ReadKind::DispatchElement;
static const bool readOnlyLeaseSkip = std::getenv("APS5_NO_READ_ONLY_LEASE_SKIP") == nullptr;
const auto kind = !guestMemory.HoldsLease() ? Recorder::ReadKind::DispatchElement : readOnlyLeaseSkip && usesBda && bdaReadOnly ? Recorder::ReadKind::AddressBasedReadOnly : Recorder::ReadKind::AddressBased;
if (auto split = guestMemory.SplitInPlaceReads()) {
recorder.NotePendingReadList(std::move(split->space), kind);
recorder.NotePendingReads(split->own, kind);
@@ -67,6 +67,11 @@ void OwnedImage::Allocate(const Context& device, const VkMemoryAllocateInfo& all
namespace {
Recorder* recorderFor(const Context& context) {
auto* recorder = Recorder::Active();
return recorder != nullptr && recorder->Device() == context.device ? recorder : nullptr;
}
// APS5_PROFILE_DRAW: accumulate texture setup phases and report every 200 textures.
struct TextureProfile {
double allocate = 0, read = 0, gpu = 0, view = 0;
@@ -1105,7 +1110,7 @@ bool Texture::CanCopyFrom(const StorageTexture& source, const GuestTextureResour
// Same memory, same layout, same texel size: the GPU copy reinterprets the texels exactly as a
// guest read through the sampled descriptor would.
if (IsRepackedTextureFormat(descriptor.format)) return false;
return descriptor.baseAddress == from.baseAddress && descriptor.pipeBankXor == from.pipeBankXor && descriptor.width == from.width && descriptor.height == from.height && ArrayLayersServeView(from, descriptor) && descriptor.tileMode == from.tileMode && descriptor.mipCount <= from.mipCount && BytesPerElement(descriptor.format) == BytesPerElement(from.format) && BlockWidth(descriptor.format) == BlockWidth(from.format);
return descriptor.baseAddress == from.baseAddress && descriptor.pipeBankXor == from.pipeBankXor && descriptor.width == from.width && descriptor.height == from.height && descriptor.linearPitch == from.linearPitch && ArrayLayersServeView(from, descriptor) && descriptor.tileMode == from.tileMode && descriptor.mipCount <= from.mipCount && BytesPerElement(descriptor.format) == BytesPerElement(from.format) && BlockWidth(descriptor.format) == BlockWidth(from.format);
}
Texture::Texture(const Context& context, const std::shared_ptr<StorageTexture>& source, const GuestTextureResource& descriptor, VkComponentMapping components) : context(context), storageSource(source) {
@@ -1322,21 +1327,21 @@ VkFormat StorageFormatOrUndefined(const Context& context, VkFormat format) {
};
VkFormat storage = VK_FORMAT_UNDEFINED;
const auto packed = PackedStorageViewFormat(format);
VkFormat linear = VK_FORMAT_UNDEFINED;
switch (format) {
case VK_FORMAT_R8G8B8A8_SRGB: linear = VK_FORMAT_R8G8B8A8_UNORM; break;
case VK_FORMAT_B8G8R8A8_SRGB: linear = VK_FORMAT_B8G8R8A8_UNORM; break;
case VK_FORMAT_A8B8G8R8_SRGB_PACK32: linear = VK_FORMAT_A8B8G8R8_UNORM_PACK32; break;
case VK_FORMAT_R8_SRGB: linear = VK_FORMAT_R8_UNORM; break;
case VK_FORMAT_R8G8_SRGB: linear = VK_FORMAT_R8G8_UNORM; break;
default: break;
}
if (packed != VK_FORMAT_UNDEFINED) {
if (supports(packed)) storage = format;
} else if (linear != VK_FORMAT_UNDEFINED) {
if (supports(linear)) storage = linear;
} else if (supports(format)) {
storage = format;
} else {
VkFormat linear = VK_FORMAT_UNDEFINED;
switch (format) {
case VK_FORMAT_R8G8B8A8_SRGB: linear = VK_FORMAT_R8G8B8A8_UNORM; break;
case VK_FORMAT_B8G8R8A8_SRGB: linear = VK_FORMAT_B8G8R8A8_UNORM; break;
case VK_FORMAT_A8B8G8R8_SRGB_PACK32: linear = VK_FORMAT_A8B8G8R8_UNORM_PACK32; break;
case VK_FORMAT_R8_SRGB: linear = VK_FORMAT_R8_UNORM; break;
case VK_FORMAT_R8G8_SRGB: linear = VK_FORMAT_R8G8_UNORM; break;
default: break;
}
if (linear != VK_FORMAT_UNDEFINED && supports(linear)) storage = linear;
}
std::lock_guard lock(table.mutex);
table.formats.emplace(key, storage);
@@ -3803,7 +3808,7 @@ std::uint64_t StorageTexture::writeBackWindows(std::span<const ImportSegment> se
const bool scratchUsed = std::any_of(directRanges.begin(), directRanges.end(), [](const auto& range) { return range.first == range.second; }) || std::any_of(slabPieces.begin(), slabPieces.end(), [](const SlabPieces& pieces) { return !pieces.copies.empty(); }) || std::any_of(importCopies.begin(), importCopies.end(), [](const auto& copies) { return !copies.empty(); }) || !saves.empty() || !padding.copies.empty() || !seeds.empty();
auto tiledScratch = scratchUsed ? std::make_shared<DeviceBuffer>(context, static_cast<std::size_t>(scratchTotal), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) : nullptr;
detiler.BeginBatch();
auto* recorder = Recorder::Active();
auto* recorder = recorderFor(context);
std::unique_ptr<CommandBatch> batch;
VkCommandBuffer commands = VK_NULL_HANDLE;
VkAccessFlags covered = 0;
@@ -4481,7 +4486,7 @@ void waitForIdlePublish(const char* reason) {
}
bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except, const char* reason, PublishScope scope, bool* published, bool waited) {
bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except, const char* reason, PublishScope scope, bool* published, bool waited, VkDevice device) {
static_cast<void>(FlushDepthPlanes(address, bytes));
if (published != nullptr) *published = false;
const auto nothingToStore = [&] {
@@ -4522,7 +4527,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons
const auto* exempt = refreshing;
for (auto it = pending.textures.MayOverlap(address, bytes) ? pending.textures.begin() : pending.textures.end(); it != pending.textures.end();) {
auto* texture = *it;
if (texture != except && texture != exempt && texture->overlaps(address, bytes)) {
if (texture != except && texture != exempt && (device == VK_NULL_HANDLE || texture->context.device == device) && texture->overlaps(address, bytes)) {
if (!texture->pendingUnitInside(address, bytes)) {
pretestSkipped.fetch_add(1, std::memory_order_relaxed);
++it;
@@ -4788,7 +4793,7 @@ void StorageTexture::commitStoredSave(const std::vector<std::uint32_t>& units) {
if (units.empty()) return;
const auto ticket = ++lastSaveTicket;
for (const auto unit : units) savedTicket[unit] = ticket;
auto* recorder = Recorder::Active();
auto* recorder = recorderFor(context);
if (recorder == nullptr) {
applyStoredSave(ticket, units);
return;
@@ -4829,7 +4834,8 @@ void StorageTexture::awaitStoredSave(const std::vector<bool>* layers) {
last = std::max(last, layerBegin(unit) + layerBytes(unit));
}
if (last <= first) return;
auto* recorder = Recorder::Active();
auto* recorder = recorderFor(context);
if (recorder == nullptr) recorder = context.recorder;
Require(recorder != nullptr, "a write-back's saved guest bytes are pending without a recorder");
recorder->SyncThrough(first, static_cast<std::size_t>(last - first));
const auto outstanding = [&] {
@@ -4961,26 +4967,28 @@ void StorageTexture::FlushDumpSelected(const char* reason) {
for (const auto& [address, bytes] : ranges) static_cast<void>(FlushPending(address, bytes, nullptr, reason));
}
void StorageTexture::FlushAllPending(const char* reason) {
void StorageTexture::FlushAllPending(const char* reason, VkDevice device) {
std::optional<PublishOnCpuScope> onCpu;
if (reason != nullptr && std::strcmp(reason, "idle") == 0) onCpu.emplace();
static_cast<void>(FlushPending(0, std::numeric_limits<std::size_t>::max(), nullptr, reason));
static_cast<void>(FlushPending(0, std::numeric_limits<std::size_t>::max(), nullptr, reason, PublishScope::Whole, nullptr, false, device));
}
bool StorageTexture::FlushAllPendingUntil(const char* reason, const std::function<bool()>& stop) {
bool StorageTexture::FlushAllPendingUntil(const char* reason, const std::function<bool()>& stop, VkDevice device) {
std::optional<PublishOnCpuScope> onCpu;
if (reason != nullptr && std::strcmp(reason, "idle") == 0) onCpu.emplace();
std::vector<std::pair<std::uint64_t, std::size_t>> ranges;
{
auto& pending = Pending();
std::lock_guard lock(pending.mutex);
for (const auto* texture : pending.textures) ranges.emplace_back(texture->descriptor.baseAddress, static_cast<std::size_t>(texture->guestBytes));
for (const auto* texture : pending.textures) {
if (device == VK_NULL_HANDLE || texture->context.device == device) ranges.emplace_back(texture->descriptor.baseAddress, static_cast<std::size_t>(texture->guestBytes));
}
}
constexpr std::size_t piece = std::size_t{4} << 20u;
for (const auto& [address, bytes] : ranges) {
for (std::size_t offset = 0; offset < bytes; offset += piece) {
if (stop()) return false;
static_cast<void>(FlushPending(address + offset, std::min(piece, bytes - offset), nullptr, reason));
static_cast<void>(FlushPending(address + offset, std::min(piece, bytes - offset), nullptr, reason, PublishScope::Whole, nullptr, false, device));
}
}
return true;
@@ -5898,7 +5906,7 @@ std::shared_ptr<StorageTexture> StorageTexture::FindLive(std::uint64_t address,
bool StorageTexture::SameSurfaceShape(const StorageTexture& other) const {
const auto& mine = descriptor;
const auto& theirs = other.descriptor;
return guestBytes == other.guestBytes && mine.width == theirs.width && mine.height == theirs.height && mine.depthOrLastArray == theirs.depthOrLastArray && mine.baseArray == theirs.baseArray && mine.mipCount == theirs.mipCount && mine.tileMode == theirs.tileMode && mine.dimension == theirs.dimension && mine.format == theirs.format && storageFormat == other.storageFormat && geometry.imageLayers == other.geometry.imageLayers && geometry.imageDepth == other.geometry.imageDepth;
return guestBytes == other.guestBytes && mine.width == theirs.width && mine.height == theirs.height && mine.linearPitch == theirs.linearPitch && mine.depthOrLastArray == theirs.depthOrLastArray && mine.baseArray == theirs.baseArray && mine.mipCount == theirs.mipCount && mine.tileMode == theirs.tileMode && mine.dimension == theirs.dimension && mine.format == theirs.format && storageFormat == other.storageFormat && geometry.imageLayers == other.geometry.imageLayers && geometry.imageDepth == other.geometry.imageDepth;
}
bool StorageTexture::ImageCoversGuestBytes() const {
@@ -6640,7 +6648,7 @@ void StorageTexture::writeBackLayers(const std::vector<bool>& layers) {
padding = {};
}
detiler.BeginBatch();
auto* recorder = Recorder::Active();
auto* recorder = recorderFor(context);
std::unique_ptr<CommandBatch> batch;
VkCommandBuffer commands = VK_NULL_HANDLE;
auto timing = Recorder::NoTiming;
@@ -38,7 +38,7 @@ std::shared_ptr<Texture> TextureCache::Get(std::span<const std::uint32_t> words,
entries.erase(it);
break;
}
const auto mips = ComputeMipLayout(resource.tileMode, resource.format, resource.width, resource.height, resource.mipCount);
const auto mips = ComputeMipLayout(resource.tileMode, resource.format, resource.width, resource.height, resource.mipCount, resource.linearPitch);
const auto layers = resource.dimension == TextureDimension::k2DArray || resource.dimension == TextureDimension::k1DArray || resource.dimension == TextureDimension::kCube ? resource.depthOrLastArray + 1u : 1u;
const auto bytes = ComputeSurfaceSize(mips, layers);
Require(bytes != 0 && bytes <= std::numeric_limits<std::size_t>::max(), "texture cache surface size overflow");
@@ -256,9 +256,9 @@ void TextureDetiler::Dispatch(VkCommandBuffer commands, TextureTileMode tileMode
writes[1].descriptorCount = 1;
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
writes[1].pBufferInfo = &destinationInfo;
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target);
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr);
context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
context.Resolved(&DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target);
context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr);
Push push{};
push.srcBase = static_cast<std::uint32_t>(sourceBase);
push.dstBase = static_cast<std::uint32_t>(destinationBase);
@@ -279,10 +279,10 @@ void TextureDetiler::Dispatch(VkCommandBuffer commands, TextureTileMode tileMode
push.rowBegin = window.rowBegin;
push.pipeBankXor = window.pipeBankXor ^ layerBankXor(tileMode, elementBytes, layout, slice);
push.splitStride = layout.gcnSplitStride;
context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Push), &push);
context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Push), &push);
const auto groupsX = (columnEnd - window.columnBegin + 7u) / 8u;
const auto groupsY = (rowEnd - window.rowBegin + 7u) / 8u;
context.Function<PFN_vkCmdDispatch>("vkCmdDispatch")(commands, groupsX, groupsY, 1);
context.Resolved(&DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, groupsX, groupsY, 1);
}
void TextureDetiler::DispatchRepack(VkCommandBuffer commands, VkBuffer source, std::uint64_t sourceOffset, VkBuffer destination, std::uint64_t destinationOffset, std::uint64_t bytes) {
@@ -322,12 +322,12 @@ void TextureDetiler::DispatchRepack(VkCommandBuffer commands, VkBuffer source, s
writes[index].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
writes[index].pBufferInfo = index == 0 ? &sourceInfo : &destinationInfo;
}
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, repackPipeline);
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr);
context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
context.Resolved(&DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, repackPipeline);
context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr);
const std::array<std::uint32_t, 4> push{static_cast<std::uint32_t>(sourceOffset - sourceDescriptorOffset), static_cast<std::uint32_t>(destinationOffset - destinationDescriptorOffset), static_cast<std::uint32_t>(words), static_cast<std::uint32_t>(std::min(words, rowWords))};
context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data());
context.Function<PFN_vkCmdDispatch>("vkCmdDispatch")(commands, static_cast<std::uint32_t>((std::min(words, rowWords) + 63u) / 64u), static_cast<std::uint32_t>(rows), 1);
context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data());
context.Resolved(&DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, static_cast<std::uint32_t>((std::min(words, rowWords) + 63u) / 64u), static_cast<std::uint32_t>(rows), 1);
}
std::uint32_t TextureDetiler::CmaskErrors() {
@@ -395,24 +395,24 @@ void TextureDetiler::DispatchCmaskClear(VkCommandBuffer commands, VkBuffer cmask
writes[0].pBufferInfo = &cmaskInfo;
writes[1].pBufferInfo = &errorInfo;
writes[2].pImageInfo = &imageInfo;
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
const auto barrier = [&](VkPipelineStageFlags source, VkPipelineStageFlags destination, VkAccessFlags sourceAccess, VkAccessFlags destinationAccess) {
VkMemoryBarrier memory{VK_STRUCTURE_TYPE_MEMORY_BARRIER};
memory.srcAccessMask = sourceAccess;
memory.dstAccessMask = destinationAccess;
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, source, destination, 0, 1, &memory, 0, nullptr, 0, nullptr);
context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier")(commands, source, destination, 0, 1, &memory, 0, nullptr, 0, nullptr);
};
constexpr VkAccessFlags anyWrite = VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT;
constexpr VkAccessFlags anyAccess = anyWrite | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
barrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, anyWrite, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT);
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, cmaskPipeline);
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, cmaskPipelineLayout, 0, 1, &set, 0, nullptr);
context.Resolved(&DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, cmaskPipeline);
context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, cmaskPipelineLayout, 0, 1, &set, 0, nullptr);
const std::uint32_t mask = elementBytes >= 4 ? 0xffffffffu : (1u << (elementBytes * 8u)) - 1u;
const std::array<std::uint32_t, 7> push{static_cast<std::uint32_t>(base), width, height, (width + 1023u) / 1024u, clearWords[0] & mask, elementBytes == 8 ? clearWords[1] : 0u, write ? 1u : 0u};
context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants")(commands, cmaskPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data());
context.Function<PFN_vkCmdDispatch>("vkCmdDispatch")(commands, (width + 7u) / 8u, (height + 7u) / 8u, 1);
context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, cmaskPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data());
context.Resolved(&DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, (width + 7u) / 8u, (height + 7u) / 8u, 1);
barrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT, anyAccess);
context.Function<PFN_vkCmdFillBuffer>("vkCmdFillBuffer")(commands, cmask, cmaskOffset, cmaskBytes, 0xffffffffu);
context.Resolved(&DeviceFunctions::cmdFillBuffer, "vkCmdFillBuffer")(commands, cmask, cmaskOffset, cmaskBytes, 0xffffffffu);
barrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, anyAccess);
}
@@ -447,9 +447,9 @@ void TextureDetiler::DispatchImage(VkCommandBuffer commands, TextureTileMode til
writes[1].descriptorCount = 1;
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
writes[1].pImageInfo = &imageInfo;
context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target);
context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, imagePipelineLayout, 0, 1, &set, 0, nullptr);
context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast<std::uint32_t>(writes.size()), writes.data(), 0, nullptr);
context.Resolved(&DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target);
context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, imagePipelineLayout, 0, 1, &set, 0, nullptr);
Push push{};
push.srcBase = static_cast<std::uint32_t>(retile ? 0 : base);
push.dstBase = static_cast<std::uint32_t>(retile ? base : 0);
@@ -470,10 +470,10 @@ void TextureDetiler::DispatchImage(VkCommandBuffer commands, TextureTileMode til
push.rowBegin = window.rowBegin;
push.pipeBankXor = window.pipeBankXor ^ layerBankXor(tileMode, elementBytes, layout, slice);
push.splitStride = layout.gcnSplitStride;
context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants")(commands, imagePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Push), &push);
context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, imagePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Push), &push);
const auto groupsX = (columnEnd - window.columnBegin + 7u) / 8u;
const auto groupsY = (rowEnd - window.rowBegin + 7u) / 8u;
context.Function<PFN_vkCmdDispatch>("vkCmdDispatch")(commands, groupsX, groupsY, 1);
context.Resolved(&DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, groupsX, groupsY, 1);
}
}
@@ -58,7 +58,7 @@ VkDescriptorSet TextureDetiler::allocateFrom(DescriptorRing& ring, VkDescriptorS
allocation.descriptorSetCount = 1;
allocation.pSetLayouts = &layout;
VkDescriptorSet set = VK_NULL_HANDLE;
Check(context.Function<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets")(context.device, &allocation, &set), allocateWhat);
Check(context.Resolved(&DeviceFunctions::allocateDescriptorSets, "vkAllocateDescriptorSets")(context.device, &allocation, &set), allocateWhat);
++ring.used;
ring.serials[ring.current] = recorder != nullptr ? recorder->OpenSerial() : std::numeric_limits<std::uint64_t>::max();
return set;
@@ -163,8 +163,9 @@ std::uint32_t TexelLevelDimension(std::uint32_t guestDimension, std::uint32_t le
return std::max((std::max(guestDimension >> level, 1u) + texelScale - 1u) / texelScale, 1u);
}
std::vector<TileMipLayout> ComputeLinearMipLayout(std::uint32_t bytesPerElement, std::uint32_t texelWidth, std::uint32_t texelHeight, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) {
std::vector<TileMipLayout> ComputeLinearMipLayout(std::uint32_t bytesPerElement, std::uint32_t texelWidth, std::uint32_t texelHeight, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount, std::uint32_t linearPitch) {
const auto compressed = texelWidth != 1u || texelHeight != 1u;
Require(linearPitch == 0u || (!compressed && mipCount == 1u && linearPitch >= width), "a linear pitch needs a single-level uncompressed surface no wider than the pitch");
const auto elementsWidth0 = (width + texelWidth - 1u) / texelWidth;
const auto elementsHeight0 = (height + texelHeight - 1u) / texelHeight;
const auto blockWidth = CalcLinearBlockWidth(bytesPerElement);
@@ -174,7 +175,7 @@ std::vector<TileMipLayout> ComputeLinearMipLayout(std::uint32_t bytesPerElement,
for (auto level = mipCount; level-- > 0;) {
const auto elementsLevelWidth = std::max(ShiftCeil(elementsWidth0, level), 1u);
const auto elementsLevelHeight = std::max(ShiftCeil(elementsHeight0, level), 1u);
const auto paddedElementsWidth = AlignUp(elementsLevelWidth, blockWidth);
const auto paddedElementsWidth = linearPitch != 0u ? linearPitch : AlignUp(elementsLevelWidth, blockWidth);
const auto size = static_cast<std::uint64_t>(paddedElementsWidth) * elementsLevelHeight * bytesPerElement;
Require(size != 0, "computed a zero-sized linear texture mip level");
@@ -277,7 +278,7 @@ std::vector<TileMipLayout> ComputeTiledMipLayout(TextureTileMode tileMode, std::
}
std::vector<TileMipLayout> ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) {
std::vector<TileMipLayout> ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount, std::uint32_t linearPitch) {
Require(width != 0 && height != 0, "cannot compute mip layout for a zero-sized texture");
Require(mipCount != 0 && mipCount <= 16u, "texture mip count is out of range");
@@ -286,7 +287,7 @@ std::vector<TileMipLayout> ComputeMipLayout(TextureTileMode tileMode, std::uint3
const auto texelWidth = BlockWidth(format);
const auto texelHeight = BlockHeight(format);
if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, texelWidth, texelHeight, width, height, mipCount);
if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, texelWidth, texelHeight, width, height, mipCount, linearPitch);
return ComputeTiledMipLayout(tileMode, bytesPerElement, texelWidth, texelHeight, width, height, mipCount);
}
@@ -635,7 +636,7 @@ SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) {
geometry.mips.push_back({0u, geometry.guestBytes, 0u, geometry.sliceLinearBytes, descriptor.width, descriptor.height, static_cast<std::uint32_t>(align(descriptor.width, blockWidth) / blockWidth), descriptor.width * descriptor.samples * bytesPerElement, false, 0u, 0u});
return geometry;
}
geometry.mips = ComputeMipLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, descriptor.mipCount);
geometry.mips = ComputeMipLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, descriptor.mipCount, descriptor.linearPitch);
const bool layered = descriptor.dimension == TextureDimension::k2DArray || descriptor.dimension == TextureDimension::k1DArray || descriptor.dimension == TextureDimension::kCube;
geometry.layers = layered ? descriptor.depthOrLastArray + 1u : 1u;
geometry.imageLayers = geometry.layers;
@@ -709,7 +710,7 @@ GuestTextureResource ArrayLayerWindow(const GuestTextureResource& viewed, std::u
std::vector<TileMipLayout> ComputeElementMipLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) {
Require(width != 0 && height != 0 && mipCount != 0 && mipCount <= 16u, "invalid surface mip chain");
Require(!IsGcnTiled(tileMode), "PS4 surfaces are laid out by ComputeGcnMipLayout from their pitch");
if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, 1u, 1u, width, height, mipCount);
if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, 1u, 1u, width, height, mipCount, 0u);
return ComputeTiledMipLayout(tileMode, bytesPerElement, 1u, 1u, width, height, mipCount);
}
@@ -4,15 +4,30 @@
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <thread>
#include <vector>
namespace AgcDriver {
struct CaptureTraceTestAccess {
static void QueueDrop(bool shutdownOnly, bool cumulative = false);
};
}
namespace {
@@ -127,6 +142,82 @@ void BestEffortOverflow() {
std::remove(configured);
}
void FormatOverflow() {
const char* configured = RequiredPathVariable();
const std::string oversized(4096, 'x');
AgcDriver::CaptureTrace::Log("capture-trace-format payload=%s", oversized.c_str());
Check(WaitForRecord(configured, "capture-trace-dropped records=1\n"),
"an oversized record must be counted instead of throwing or writing a partial record");
AgcDriver::CaptureTrace::Log("%s", "capture-trace-format-recovered");
Check(WaitForRecord(configured, "capture-trace-format-recovered\n"),
"capture tracing did not recover after an oversized record");
Check(ReadFile(configured).find("capture-trace-format payload=") == std::string::npos,
"the trace contains a truncated oversized record");
std::remove(configured);
}
class PausedTraceBuffer final : public std::streambuf {
public:
bool WaitForWrite() {
std::unique_lock lock(mutex);
return changed.wait_for(lock, std::chrono::seconds(2), [this] { return entered; });
}
void Release() {
{
std::lock_guard lock(mutex);
released = true;
}
changed.notify_all();
}
void Pause() {
std::lock_guard lock(mutex);
entered = false;
released = false;
}
std::string Contents() {
std::lock_guard lock(mutex);
return contents;
}
bool WaitForRecord(const std::string& record) {
std::unique_lock lock(mutex);
return changed.wait_for(lock, std::chrono::seconds(2), [this, &record] {
return contents.find(record) != std::string::npos;
});
}
protected:
std::streamsize xsputn(const char* text, std::streamsize size) override {
std::unique_lock lock(mutex);
entered = true;
changed.notify_all();
changed.wait(lock, [this] { return released; });
contents.append(text, static_cast<std::size_t>(size));
changed.notify_all();
return size;
}
int sync() override {
return 0;
}
private:
std::mutex mutex;
std::condition_variable changed;
std::string contents;
bool entered = false;
bool released = false;
};
std::string QueueRecord(std::size_t producer, std::size_t item) {
char prefix[64];
std::snprintf(prefix, sizeof(prefix), "queue producer=%02zu item=%08zu payload=", producer, item);
return std::string(prefix) + std::string(1900, 'x');
}
void Disabled() {
RemoveEnvironmentVariable("APS5_CAPTURE_TRACE");
Check(!AgcDriver::CaptureTrace::Enabled(),
@@ -138,6 +229,163 @@ void Disabled() {
}
void AgcDriver::CaptureTraceTestAccess::QueueDrop(bool shutdownOnly, bool cumulative) {
constexpr std::size_t Capacity = 16u * 1024u * 1024u;
constexpr std::size_t Producers = 4;
const char* configured = RequiredPathVariable();
PausedTraceBuffer sink;
std::exception_ptr failure;
std::size_t initialItems = 0;
std::atomic<bool> bounded = true;
const std::size_t rounds = cumulative ? 2 : 1;
std::size_t lostTotal = 0;
std::vector<std::string> expectedMarkers;
std::vector<std::size_t> queuedByRound;
for (std::size_t round = 0; round < rounds; ++round) {
const auto lost = Producers * (round == 0 ? 64u : 17u) + (shutdownOnly ? 0u : 1u);
lostTotal += lost;
expectedMarkers.push_back("capture-trace-dropped records=" + std::to_string(lost));
}
const auto summary = "capture-trace-summary dropped=" + std::to_string(lostTotal);
{
CaptureTrace trace;
Check(trace.output.is_open(), "the queue test trace path must be writable");
auto& output = static_cast<std::ostream&>(trace.output);
output.rdbuf(&sink);
auto append = [&](std::size_t producer, std::size_t item) {
trace.append(QueueRecord(producer, item));
std::lock_guard lock(trace.mutex);
if (trace.pending.size() > Capacity) bounded.store(false);
};
for (std::size_t round = 0; round < rounds && !failure; ++round) {
if (round != 0) sink.Pause();
const std::size_t itemsPerProducer = round == 0 ? 64 : 17;
std::vector<std::thread> producers;
std::vector<std::exception_ptr> producerErrors(Producers);
std::mutex stateMutex;
std::condition_variable stateChanged;
std::size_t attempted = 0;
std::size_t finished = 0;
try {
for (std::size_t item = 0; item < 64; ++item) append(0, initialItems++);
Check(sink.WaitForWrite(), "the trace worker did not reach the paused sink");
for (;;) {
const auto line = QueueRecord(0, initialItems);
{
std::lock_guard lock(trace.mutex);
if (trace.pending.size() + line.size() + 64 > Capacity) break;
}
append(0, initialItems++);
}
queuedByRound.push_back(initialItems);
for (std::size_t producer = 0; producer < Producers; ++producer) {
producers.emplace_back([&, producer] {
{
std::lock_guard lock(stateMutex);
++attempted;
}
stateChanged.notify_all();
try {
for (std::size_t item = 0; item < itemsPerProducer; ++item) append(producer + 1, item);
} catch (...) {
producerErrors[producer] = std::current_exception();
}
{
std::lock_guard lock(stateMutex);
++finished;
}
stateChanged.notify_all();
});
}
{
std::unique_lock lock(stateMutex);
Check(stateChanged.wait_for(lock, std::chrono::seconds(2), [&] { return attempted == Producers; }),
"the queue producers did not start");
Check(stateChanged.wait_for(lock, std::chrono::seconds(2), [&] { return finished == Producers; }),
"a full capture queue must return from every caller while its writer is paused");
}
if (!shutdownOnly) trace.append("queue-short-during-loss");
} catch (...) {
failure = std::current_exception();
}
sink.Release();
for (auto& producer : producers) producer.join();
if (!failure) {
for (const auto& error : producerErrors) {
if (error) {
failure = error;
break;
}
}
}
if (!failure && !shutdownOnly) {
try {
Check(sink.WaitForRecord(expectedMarkers[round] + "\n"),
"the recovered writer did not report the exact dropped-record count");
append(Producers + 1, round);
Check(sink.WaitForRecord(QueueRecord(Producers + 1, round) + "\n"),
"the recovered writer did not accept a new record");
} catch (...) {
failure = std::current_exception();
}
}
}
}
if (failure) std::rethrow_exception(failure);
Check(bounded.load(), "the pending capture queue exceeded 16 MiB");
std::istringstream records(sink.Contents());
std::string record;
std::size_t sequence = 0;
std::size_t queued = 0;
std::size_t recovered = 0;
std::size_t markers = 0;
std::size_t summaries = 0;
while (std::getline(records, record)) {
std::istringstream fields(record);
unsigned long long foundSequence = 0;
std::string elapsed;
std::string label;
std::string producerField;
std::string itemField;
fields >> foundSequence >> elapsed >> label >> producerField >> itemField;
Check(foundSequence == ++sequence, "the recovered trace lost or reordered sequence numbers");
Check(elapsed.ends_with("us"), "the recovered trace lost its elapsed-time field");
Check(summaries == 0, "the trace contains a record after its shutdown summary");
const auto prefix = std::to_string(foundSequence) + " " + elapsed + " ";
const auto body = record.substr(prefix.size());
if (body.starts_with("capture-trace-dropped records=")) {
Check(markers < rounds && body == expectedMarkers[markers],
"the trace did not report each dropped-record batch exactly once");
Check(queued == queuedByRound[markers] && recovered == markers,
"the dropped marker did not follow the queued records and precede recovery");
++markers;
continue;
}
if (body == summary) {
Check(markers == rounds, "the shutdown total appeared before the loss markers");
++summaries;
continue;
}
Check(label == "queue" && producerField.starts_with("producer=") && itemField.starts_with("item="),
"the recovered trace contains an incomplete record");
const auto producer = std::stoull(producerField.substr(9));
const auto item = std::stoull(itemField.substr(5));
if (producer == 0) {
Check(markers == recovered && item == queued++, "the trace lost or reordered an accepted queued record");
} else {
Check(!shutdownOnly && producer == Producers + 1 && item == recovered && markers == recovered + 1,
"the trace retained a dropped record or lost recovery ordering");
++recovered;
}
Check(record == prefix + QueueRecord(producer, item), "the recovered trace changed a record's payload");
}
Check(queued == initialItems, "the recovered trace lost the initial accepted records");
Check(recovered == (shutdownOnly ? 0u : rounds), "the trace lost or duplicated its recovery records");
Check(markers == rounds && summaries == 1, "the trace did not report each loss marker and one shutdown total");
Check(sequence == initialItems + recovered + rounds + 1, "the trace contains an unexpected number of records");
std::remove(configured);
}
int main(int argc, char** argv) {
try {
if (argc != 2) throw std::runtime_error("expected exactly one test mode argument");
@@ -149,6 +397,10 @@ int main(int argc, char** argv) {
else if (mode == "default-writes") DefaultWrites();
else if (mode == "open-failure") OpenFailure();
else if (mode == "best-effort-overflow") BestEffortOverflow();
else if (mode == "format-overflow") FormatOverflow();
else if (mode == "queue-drop") AgcDriver::CaptureTraceTestAccess::QueueDrop(false);
else if (mode == "queue-drop-shutdown") AgcDriver::CaptureTraceTestAccess::QueueDrop(true);
else if (mode == "queue-drop-cumulative") AgcDriver::CaptureTraceTestAccess::QueueDrop(false, true);
else if (mode == "disabled") Disabled();
else throw std::runtime_error("unknown capture trace test mode: " + mode);
std::cout << "capture trace tests passed\n";
@@ -0,0 +1,45 @@
if(NOT CMAKE_ARGC EQUAL 6)
message(FATAL_ERROR "Expected fixture, mode and output root arguments")
endif()
set(FIXTURE "${CMAKE_ARGV3}")
set(MODE "${CMAKE_ARGV4}")
set(OUTPUT_ROOT "${CMAKE_ARGV5}")
foreach(required IN ITEMS FIXTURE MODE OUTPUT_ROOT)
if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "")
message(FATAL_ERROR "Missing ${required}")
endif()
endforeach()
if(NOT MODE STREQUAL "write" AND NOT MODE STREQUAL "flush")
message(FATAL_ERROR "Expected write or flush mode, got ${MODE}")
endif()
string(RANDOM LENGTH 16 ALPHABET 0123456789abcdef runId)
set(runDirectory "${OUTPUT_ROOT}/capture-trace-${MODE}-${runId}")
file(MAKE_DIRECTORY "${runDirectory}")
set(ENV{APS5_CAPTURE_TRACE} "1")
set(ENV{APS5_CAPTURE_TRACE_PATH} "${runDirectory}/trace.log")
execute_process(
COMMAND "${FIXTURE}" "${MODE}"
WORKING_DIRECTORY "${runDirectory}"
RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error TIMEOUT 8
)
file(WRITE "${runDirectory}/failure.log" "result=${result}\n${output}\n${error}")
set(armed "capture-trace-failure armed mode=${MODE} queueFull=1 attempted=4 finished=4")
if(MODE STREQUAL "write")
set(sinkFailure "capture-trace-failure sink-short-write")
else()
set(sinkFailure "capture-trace-failure sink-flush-failed")
endif()
set(survived "capture-trace-failure survived mode=${MODE} queueFull=1 attempted=4 finished=4 inactive=1 empty=1 sinkFailed=1")
string(FIND "${error}" "${armed}" armedAt)
string(FIND "${error}" "${sinkFailure}" sinkAt)
string(FIND "${error}" "Capture trace failed:" failedAt)
string(FIND "${error}" "${survived}" survivedAt)
string(REGEX MATCHALL "Capture trace failed:" failureMessages "${error}")
list(LENGTH failureMessages failureCount)
if(NOT result STREQUAL "0"
OR error MATCHES "setup-failed|watchdog|unexpected-terminate"
OR NOT failureCount EQUAL 1
OR armedAt LESS 0 OR NOT sinkAt GREATER armedAt OR NOT failedAt GREATER sinkAt OR NOT survivedAt GREATER failedAt)
message(FATAL_ERROR "Capture trace ${MODE} failure did not stop only tracing and exit normally: ${result}\n${output}\n${error}")
endif()
message(STATUS "Capture trace ${MODE} failure stopped tracing, released all callers before the sink failed and exited normally")
@@ -0,0 +1,223 @@
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <mutex>
#include <streambuf>
#include <string>
#include <thread>
#include <vector>
namespace AgcDriver {
struct CaptureTraceTestAccess {
static void WriterFailure(bool flush);
};
}
namespace {
using namespace std::chrono_literals;
[[noreturn]] void SetupFailure(const char* message) {
std::fprintf(stderr, "capture-trace-failure setup-failed: %s\n", message);
std::fflush(stderr);
std::_Exit(87);
}
void Check(bool condition, const char* message) {
if (!condition) SetupFailure(message);
}
class FailingBuffer final : public std::streambuf {
public:
explicit FailingBuffer(bool failFlush) : failFlush(failFlush) {}
bool WaitForWriter() {
std::unique_lock lock(mutex);
return changed.wait_for(lock, 2s, [this] { return entered; });
}
void Fail() {
{
std::lock_guard lock(mutex);
released = true;
}
changed.notify_all();
}
unsigned WriteCalls() const {
return writeCalls.load();
}
unsigned FlushCalls() const {
return flushCalls.load();
}
bool Failed() const {
return failed.load();
}
protected:
std::streamsize xsputn(const char*, std::streamsize size) override {
writeCalls.fetch_add(1);
if (failFlush) return size;
wait();
failed.store(true);
std::fputs("capture-trace-failure sink-short-write\n", stderr);
std::fflush(stderr);
return 0;
}
int sync() override {
flushCalls.fetch_add(1);
if (!failFlush) return 0;
wait();
failed.store(true);
std::fputs("capture-trace-failure sink-flush-failed\n", stderr);
std::fflush(stderr);
return -1;
}
private:
void wait() {
std::unique_lock lock(mutex);
entered = true;
changed.notify_all();
changed.wait(lock, [this] { return released; });
}
std::mutex mutex;
std::condition_variable changed;
const bool failFlush;
std::atomic<unsigned> writeCalls{0};
std::atomic<unsigned> flushCalls{0};
std::atomic<bool> failed{false};
bool entered = false;
bool released = false;
};
std::string Record(unsigned producer, unsigned item) {
char prefix[64];
std::snprintf(prefix, sizeof(prefix), "failure producer=%02u item=%08u payload=", producer, item);
return std::string(prefix) + std::string(1900, 'x');
}
}
void AgcDriver::CaptureTraceTestAccess::WriterFailure(bool flush) {
constexpr std::size_t Capacity = 16u * 1024u * 1024u;
FailingBuffer sink(flush);
{
CaptureTrace trace;
Check(trace.output.is_open(), "the trace output did not open");
auto& output = static_cast<std::ostream&>(trace.output);
auto* original = output.rdbuf(&sink);
unsigned item = 0;
for (; item < 64; ++item) trace.append(Record(0, item));
Check(sink.WaitForWriter(), "the writer did not reach the failing sink");
const auto line = Record(1, 0);
for (;;) {
{
std::lock_guard lock(trace.mutex);
if (trace.pending.size() + line.size() + 64 > Capacity) break;
}
trace.append(Record(0, item++));
}
std::size_t queuedBytes = 0;
{
std::lock_guard lock(trace.mutex);
Check(trace.pending.size() <= Capacity, "the queue exceeded its bound");
Check(trace.droppedTotal == 0, "filling the queue unexpectedly dropped a record");
queuedBytes = trace.pending.size();
}
std::atomic<unsigned> attempted{0};
std::mutex stateMutex;
std::condition_variable stateChanged;
unsigned finished = 0;
std::vector<std::thread> producers;
for (unsigned producer = 1; producer <= 4; ++producer) {
producers.emplace_back([&, producer] {
attempted.fetch_add(1);
try {
trace.append(Record(producer, 0));
} catch (...) {
SetupFailure("a producer threw before the writer failed");
}
{
std::lock_guard lock(stateMutex);
++finished;
}
stateChanged.notify_all();
});
}
{
std::unique_lock lock(stateMutex);
Check(stateChanged.wait_for(lock, 2s, [&] { return finished == 4; }),
"full-queue producers did not return while the sink was paused");
}
for (auto& producer : producers) producer.join();
{
std::lock_guard lock(trace.mutex);
Check(trace.pending.size() == queuedBytes, "a full-queue producer added data to the pending queue");
Check(trace.droppedTotal == 4, "the four rejected records were not counted exactly once");
}
Check(attempted.load() == 4, "the four producers did not run");
Check(!sink.Failed(), "the sink failed before the full-queue producers returned");
std::fprintf(stderr, "capture-trace-failure armed mode=%s queueFull=1 attempted=4 finished=4\n", flush ? "flush" : "write");
std::fflush(stderr);
sink.Fail();
trace.worker.join();
Check(sink.Failed(), "the writer did not reach the requested stream failure");
{
std::lock_guard lock(trace.mutex);
Check(!trace.active, "tracing remained active after the stream failure");
Check(trace.pending.empty(), "failed tracing retained queued records");
}
const auto writes = sink.WriteCalls();
const auto flushes = sink.FlushCalls();
Check(writes == 1, "the failed sink received another write");
Check(flushes == (flush ? 1u : 0u), "the failed sink received an unexpected flush");
for (unsigned producer = 1; producer <= 4; ++producer) trace.append(Record(producer, 1));
{
std::lock_guard lock(trace.mutex);
Check(!trace.active && trace.pending.empty(), "logging after failure reactivated or queued tracing");
Check(trace.droppedTotal == 4, "logging after failure changed the queue-loss count");
}
Check(sink.WriteCalls() == writes && sink.FlushCalls() == flushes,
"logging after failure attempted more stream I/O");
output.exceptions(std::ios::goodbit);
output.clear();
output.rdbuf(original);
}
std::fprintf(stderr, "capture-trace-failure survived mode=%s queueFull=1 attempted=4 finished=4 inactive=1 empty=1 sinkFailed=1\n",
flush ? "flush" : "write");
std::fflush(stderr);
}
int main(int argc, char** argv) {
std::set_terminate([] {
std::fputs("capture-trace-failure unexpected-terminate\n", stderr);
std::fflush(stderr);
std::_Exit(86);
});
std::thread([] {
std::this_thread::sleep_for(6s);
std::fputs("capture-trace-failure watchdog\n", stderr);
std::fflush(stderr);
std::_Exit(99);
}).detach();
Check(argc == 2, "expected write or flush mode");
const std::string mode = argv[1];
Check(mode == "write" || mode == "flush", "unknown failure mode");
Check(AgcDriver::CaptureTrace::Enabled(), "capture tracing must be enabled");
const char* path = std::getenv("APS5_CAPTURE_TRACE_PATH");
Check(path != nullptr && path[0] != '\0', "the test requires an explicit trace path");
AgcDriver::CaptureTraceTestAccess::WriterFailure(mode == "flush");
return 0;
}
@@ -1,4 +1,5 @@
#include "prx/libc/include/Api.hpp"
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"
#include "BdaTests.hpp"
#include "GraphicsTests.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
@@ -2513,6 +2514,10 @@ void DepthStencilTests() {
// 260, AMD PAL), so a fill of the words a linear layout would hold covers nothing.
Require(!HtileFillCovers(0x10000, {16, 16}, 0x10000, 16) && !HtileFillCovers(0x10000, {16, 16}, 0x10000, 264), "a fill of the linear HTILE size of a 16x16 surface misses its pipe-aligned words");
Require(AgcDriver::Graphics::HtileMetadataBytes({1024, 512}) == 0x8000u && AgcDriver::Graphics::HtileMetadataBytes({1025, 512}) == 0x10000u && AgcDriver::Graphics::HtileMetadataBytes({3840, 2160}) == 20u * 0x8000u, "the HTILE metadata is 32 KiB per 1024x512 block");
ShaderRecompiler::SetGcnGuestShaders(true);
Require(AgcDriver::Graphics::HtileMetadataBytes({512, 512}) == 0x4000u && AgcDriver::Graphics::HtileMetadataBytes({256, 256}) == 0x4000u && AgcDriver::Graphics::HtileMetadataBytes({2048, 1152}) == 0x30000u && AgcDriver::Graphics::HtileMetadataBytes({4096, 4096}) == 0x100000u, "a PS4 HTILE is 16 KiB per 512x512 block");
Require(HtileFillCovers(0x10000, {512, 512}, 0x10000, 0x4000) && !HtileFillCovers(0x10000, {512, 512}, 0x10000, 0x3ffc), "a 16 KiB fill covers the HTILE of a 512x512 PS4 depth target");
ShaderRecompiler::SetGcnGuestShaders(false);
}
Require(state.renderExtent.width == 4 && state.renderExtent.height == 2, "render extent ignores the depth surface");
Require(!state.depthTest && !state.depthWrite && state.stencilTest, "depth/stencil enables changed");
@@ -428,22 +428,20 @@ void RunGuestSamplerResourceTests() {
unnormalizedAniso.xyMagFilter = 2;
unnormalizedAniso.xyMinFilter = 2;
rejectUnnormalized(unnormalizedAniso, "unnormalized coordinates with anisotropic filtering");
for (const std::uint32_t clamp : {1u, 3u, 4u, 5u, 7u}) {
Fields clampX = unnormalizedBase;
clampX.clampX = clamp;
rejectUnnormalized(clampX, "unnormalized coordinates with clamp mode " + std::to_string(clamp) + " on X");
}
for (const std::uint32_t clamp : {1u, 3u, 4u, 5u, 7u}) {
Fields clampY = unnormalizedBase;
clampY.clampY = clamp;
rejectUnnormalized(clampY, "unnormalized coordinates with clamp mode " + std::to_string(clamp) + " on Y");
}
Fields clampZero = unnormalizedBase;
clampZero.clampX = 0;
clampZero.clampY = 0;
const auto zeroDecoded = DecodeSamplerResource(pack(clampZero), true);
requireUnnormalized(zeroDecoded, "unnormalized S# with clamp mode 0");
Require(zeroDecoded.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && zeroDecoded.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "unnormalized clamp mode 0 must decode to clamp-to-edge");
for (const std::uint32_t clamp : {1u, 3u}) {
Fields mirrored = unnormalizedBase;
mirrored.clampX = clamp;
mirrored.clampY = clamp;
const auto decoded = DecodeSamplerResource(pack(mirrored), true);
requireUnnormalized(decoded, clamp == 1u ? "unnormalized S# with clamp mode 1" : "unnormalized S# with clamp mode 3");
Require(decoded.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && decoded.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "unnormalized clamp mode " + std::to_string(clamp) + " must decode to clamp-to-edge after the shader mirrors the coordinate");
}
Fields unnormalizedBiased = unnormalizedBase;
unnormalizedBiased.lodBiasRaw = 256;
unnormalizedBiased.lodBiasSec = 8;
@@ -155,6 +155,9 @@ void RunGuestTextureResourceTests() {
Require(IsNullTextureResource(pack(nullType)), "a type-1 texture with a nonzero base was not classified as null");
nullType.typeRaw = 2;
Require(!IsNullTextureResource(pack(nullType)), "a reserved type-2 texture was classified as null");
const std::array<std::uint32_t, 8> reportedStorage{0x04efd000u, 0x00040011u, 0x00017a00u, 0x00005204u, 0x0af00000u, 0u, 0x00000612u, 0x00fff000u};
Require(IsNullTextureResource(reportedStorage), "the type-0 words of a written storage slot were not classified as null");
reject([&] { static_cast<void>(DecodeTextureResource(reportedStorage)); }, "guest texture descriptor");
auto result = DecodeTextureResource(pack(base));
Require(result.baseAddress == (base.base40 << 8u), "decoded base address changed");
Require(result.width == 16 && result.height == 16, "decoded width or height changed");
@@ -286,7 +289,8 @@ void RunGuestTextureResourceTests() {
Fields twoD = base;
twoD.depth = 1;
rejectFields(twoD, "custom pitch other than its default pitch");
result = DecodeTextureResource(pack(twoD));
Require(result.depthOrLastArray == 0 && result.linearPitch == 0, "linear 2D descriptor with a pitch below its width did not keep the default pitch");
twoD.tileModeRaw = 0x09;
rejectFields(twoD, "nonzero depth or base array");
Fields pitched = base;
@@ -294,18 +298,35 @@ void RunGuestTextureResourceTests() {
pitched.height = 72;
pitched.depth = 127;
result = DecodeTextureResource(pack(pitched));
Require(result.dimension == TextureDimension::k2D && result.width == 72 && result.depthOrLastArray == 0, "linear 2D descriptor with its default pitch in DEPTH decoded incorrectly");
Require(result.dimension == TextureDimension::k2D && result.width == 72 && result.depthOrLastArray == 0 && result.linearPitch == 0, "linear 2D descriptor with its default pitch in DEPTH decoded incorrectly");
pitched.width = 8;
pitched.height = 8;
pitched.depth = 63;
result = DecodeTextureResource(pack(pitched));
Require(result.width == 8 && result.depthOrLastArray == 0, "linear 2D descriptor with a one-unit default pitch decoded incorrectly");
pitched.depth = 127;
rejectFields(pitched, "custom pitch other than its default pitch");
Require(result.width == 8 && result.depthOrLastArray == 0 && result.linearPitch == 0, "linear 2D descriptor with a one-unit default pitch decoded incorrectly");
for (const auto& [depth, pitch] : std::array<std::pair<std::uint32_t, std::uint32_t>, 4>{{{64u, 128u}, {127u, 128u}, {128u, 192u}, {319u, 320u}}}) {
pitched.depth = depth;
result = DecodeTextureResource(pack(pitched));
Require(result.width == 8 && result.depthOrLastArray == 0 && result.linearPitch == pitch, "linear 2D descriptor did not round its DEPTH pitch up to 256 bytes");
const auto geometry = DescribeSurface(result);
Require(geometry.mips.size() == 1 && geometry.mips[0].pitchBytes == pitch * 4u && geometry.mips[0].blocksPerRow == pitch && geometry.guestBytes == pitch * 4u * 8u, "linear 2D surface with a custom pitch has the wrong layout");
auto defaultPitch = result;
defaultPitch.linearPitch = 0;
static_cast<void>(EncodeTextureResource(defaultPitch));
}
Fields narrow = base;
narrow.format = 5;
narrow.width = 300;
narrow.depth = 511;
Require(DecodeTextureResource(pack(narrow)).linearPitch == 0, "linear 2D 8-bit descriptor with its default pitch in DEPTH set a custom pitch");
narrow.depth = 767;
Require(DecodeTextureResource(pack(narrow)).linearPitch == 768, "linear 2D 8-bit descriptor did not keep its DEPTH pitch");
narrow.depth = 768;
Require(DecodeTextureResource(pack(narrow)).linearPitch == 1024, "linear 2D 8-bit descriptor did not round its DEPTH pitch up to 256 bytes");
pitched.depth = 63;
pitched.maxMip = 1;
pitched.lastLevel = 1;
rejectFields(pitched, "custom pitch other than its default pitch");
rejectFields(pitched, "pitch on a block-compressed, mipmapped or base-array surface");
twoD = base;
twoD.baseArray = 1;
rejectFields(twoD, "nonzero depth or base array");
@@ -9049,6 +9049,120 @@ void refreshProofTests(const Device& device, Recorder& recorder) {
recorder.Sync();
}
bool repeatedNeighbourStoreTests(const Device& device, Recorder& recorder) {
const auto& base = device.GetContext();
constexpr std::uint32_t width = 1024;
constexpr std::uint32_t height = 512;
constexpr std::size_t surfaceBytes = std::size_t{width} * height * 4;
constexpr std::uint64_t offset = 0x2000;
constexpr std::size_t neighbourBytes = 0x6000;
constexpr std::size_t bytes = 35 * 65536;
constexpr std::array<std::uint8_t, 4> red{255, 0, 0, 255};
const auto pattern = [](std::size_t i) { return static_cast<std::uint8_t>(i * 131u + 7u); };
bool ran = false;
for (const bool imported : {false, true}) {
#ifdef _WIN32
if (imported) {
std::cout << "repeated neighbour stores over a watched host import: Linux write watch only\n";
continue;
}
#endif
if (imported && base.hostImportAlignment == 0) {
std::cout << "host imports unavailable: repeated neighbour stores over a watched host import not tested\n";
continue;
}
const auto decided = PrepareImportWatch(base);
if (imported) SetImportWatch(base, ImportWatch::Watch);
struct Restore {
const Context& context;
ImportWatch decided;
~Restore() { SetImportWatch(context, decided); }
} restore{base, decided};
const std::string what = imported ? "over a watched host import" : "over watched memory";
void* block = AllocateWatched(bytes, 65536);
if (block == nullptr) {
std::cout << "no write watching: repeated neighbour stores " << what << " not tested\n";
continue;
}
auto* memory = static_cast<std::uint8_t*>(block);
for (std::size_t i = 0; i < bytes; ++i) memory[i] = pattern(i);
const auto blockAddress = reinterpret_cast<std::uint64_t>(block);
const auto address = blockAddress + offset;
{
GuestAllocations::Mutation mutation;
mutation.Add(block, bytes, true, true, true);
}
struct Unregister {
const Context& context;
void* block;
std::uint64_t address;
std::size_t bytes;
~Unregister() {
{
GuestAllocations::Mutation mutation;
mutation.Remove(block);
}
HostImportFor(context, address, bytes);
ReleaseWatched(block, bytes);
}
} unregister{base, block, blockAddress, bytes};
AgcDriver::GuestMemory::CollectWritesUncached(blockAddress, bytes);
TextureDetiler detiler(base);
auto context = base;
if (!imported) context.hostImportAlignment = 0;
context.detiler = &detiler;
GuestTextureResource resource{};
resource.baseAddress = address;
resource.width = width;
resource.height = height;
resource.mipCount = 1;
resource.tileMode = TextureTileMode::kLinear;
resource.dimension = TextureDimension::k2D;
resource.format = 56;
resource.dstSelX = 4;
resource.dstSelY = 5;
resource.dstSelZ = 6;
resource.dstSelW = 7;
Require(DescribeSurface(resource).guestBytes == surfaceBytes, "(n) unexpected surface size");
{
auto image = StorageTexture::Create(context, detiler, resource, 0);
image->Refresh();
Require(image->Refresh(), "(n) " + what + ": the image does not settle after its upload");
const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
const auto commands = recorder.Commands();
recorder.Keep(image);
const VkClearColorValue value{{1.0f, 0.0f, 0.0f, 1.0f}};
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
context.Function<PFN_vkCmdClearColorImage>("vkCmdClearColorImage")(commands, image->Image(), VK_IMAGE_LAYOUT_GENERAL, &value, 1, &range);
RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
image->MarkDirty();
for (int store = 0; store < 5; ++store) AgcDriver::GuestMemory::MarkWritten(address + surfaceBytes, neighbourBytes);
Require(AgcDriver::GuestMemory::Watched(address, surfaceBytes) && (HostImportFor(context, address, surfaceBytes) != nullptr) == imported, "(n) " + what + ": the image is not on the path under test");
image->WriteBack();
recorder.Submit();
device.WaitQueue();
recorder.Sync();
std::vector<std::byte> read(bytes);
AgcDriver::GuestMemory::Read(blockAddress, read);
std::size_t dropped = 0;
for (std::size_t at = offset; at < offset + surfaceBytes; at += 4) {
for (std::size_t c = 0; c < 4; ++c) {
if (std::to_integer<std::uint8_t>(read[at + c]) != red[c]) {
++dropped;
break;
}
}
}
if (dropped != 0) throw std::runtime_error("repeated neighbour stores " + what + ": " + std::to_string(dropped) + " of " + std::to_string(surfaceBytes / 4) + " texels the GPU wrote were not stored");
for (std::size_t at = offset + surfaceBytes; at < offset + surfaceBytes + neighbourBytes; ++at) Require(std::to_integer<std::uint8_t>(read[at]) == pattern(at), "(n) " + what + ": the write-back stored over the neighbour's bytes");
}
recorder.Sync();
std::cout << "repeated neighbour stores " << what << ": ok\n";
ran = true;
}
return ran;
}
void unchangedCpuStampTests(const Device& device, Recorder& recorder) {
const auto& base = device.GetContext();
constexpr std::uint32_t side = 256;
@@ -13845,6 +13959,7 @@ int main(int argc, char** argv) {
idleWriteBackRaceTests(device, recorder);
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--repeated-neighbour-stores-only") return repeatedNeighbourStoreTests(device, recorder) ? 0 : 77;
if (argc == 2 && std::string_view(argv[1]) == "--kept-unit-settle-only") {
keptUnitSettleTests(device, recorder);
return 0;
@@ -14173,6 +14288,7 @@ int main(int argc, char** argv) {
singlePassTests(device, recorder, false);
singlePassTests(device, recorder, true);
unchangedCpuStampTests(device, recorder);
repeatedNeighbourStoreTests(device, recorder);
keptUnitSettleTests(device, recorder);
settledBaselineTests(device, recorder);
importWatchTests(device);
@@ -56,6 +56,7 @@
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#ifdef _WIN32
@@ -1257,6 +1258,39 @@ void verifyMayEvaluate() {
}
}
void verifyMayDecideSharedConditions() {
using namespace ShaderRecompiler;
IrResourcePlan plan;
std::vector<std::unique_ptr<IrValue>> values;
const auto make = [&](IrOpcode opcode, IrType type, std::initializer_list<IrValue*> arguments = {}) -> IrValue& {
values.push_back(std::make_unique<IrValue>(opcode, type, static_cast<std::uint32_t>(values.size())));
for (auto* argument : arguments) values.back()->AddArgument(argument);
return *values.back();
};
auto& scalar = make(IrOpcode::Void, IrType::ScalarReg);
scalar.SetRegister({RegisterBank::Scalar, 0});
auto& vector = make(IrOpcode::Void, IrType::VectorReg);
vector.SetRegister({RegisterBank::Vector, 0});
auto& input = make(IrOpcode::GetUserData, IrType::U32, {&scalar});
auto& lane = make(IrOpcode::GetRegister, IrType::U32, {&vector});
const auto varying = [&] { return &make(IrOpcode::IEqual32, IrType::Bool, {&input, &lane}); };
const auto chain = [&](IrValue* first) {
auto* condition = first;
for (int level = 0; level < 64; ++level) {
auto& left = make(IrOpcode::LogicalAnd, IrType::Bool, {condition, varying()});
auto& right = make(IrOpcode::LogicalAnd, IrType::Bool, {condition, varying()});
condition = &make(IrOpcode::LogicalOr, IrType::Bool, {&left, &right});
}
return condition;
};
auto* undecided = chain(varying());
auto* decided = chain(&make(IrOpcode::LogicalAnd, IrType::Bool, {varying(), &make(IrOpcode::IEqual32, IrType::Bool, {&input, &input})}));
require(!Detail::Evaluator::MayDecide(plan, undecided), "MayDecide decided 64 levels of lane conditions that share their operands");
require(Detail::Evaluator::MayDecide(plan, decided), "MayDecide did not decide 64 levels of shared conditions over a uniform conjunct");
std::unordered_map<const IrValue*, bool> memo;
require(!Detail::Evaluator::MayDecide(plan, undecided, memo) && Detail::Evaluator::MayDecide(plan, decided, memo), "MayDecide answered differently through a shared memo");
}
void verifyEvaluatedValues() {
using namespace ShaderRecompiler;
std::vector<std::unique_ptr<IrValue>> values;
@@ -1343,7 +1377,7 @@ void verifySignedSrtComparison() {
void verifyTypeZeroImageMaterialization() {
using namespace ShaderRecompiler;
for (const std::uint32_t type : {0u, 1u}) {
for (const std::uint32_t type : {0u, 1u, 2u}) {
IrResourcePlan plan;
plan.srtPlanComplete = true;
plan.resourceTrackingComplete = true;
@@ -1371,19 +1405,29 @@ void verifyTypeZeroImageMaterialization() {
plan.info.runtimeImageModes.push_back(ResourceMaterializer::RuntimeImageModes(image));
ResourceSnapshot snapshot;
ResourceMaterializer{}.Materialize(plan, SrtRuntime{}, snapshot);
require(snapshot.images.size() == 1u && snapshot.images[0].dwordCount == 8u && snapshot.images[0].dwords == words,
type == 0u ? "TYPE=0 image descriptor with a nonzero base was rejected or altered during materialization" : "TYPE=1 image descriptor with a nonzero base was rejected or altered during materialization");
const auto materialize = [&] { ResourceMaterializer{}.Materialize(plan, SrtRuntime{}, snapshot); };
if (type == 2u) {
expectFailure(materialize, "runtime image descriptor is invalid", "a reserved TYPE=2 read-only image descriptor was accepted");
} else {
materialize();
require(snapshot.images.size() == 1u && snapshot.images[0].dwordCount == 8u && snapshot.images[0].dwords == words,
type == 0u ? "TYPE=0 image descriptor with a nonzero base was rejected or altered during materialization" : "TYPE=1 image descriptor with a nonzero base was rejected or altered during materialization");
}
for (const bool atomic : {false, true}) {
for (const bool read : {false, true}) {
image.resourceClass = ImageResourceClass::Storage;
image.read = false;
image.written = !atomic;
image.atomic = atomic;
image.read = read;
image.written = true;
plan.info.images[0] = image;
plan.info.runtimeImageModes[0] = ResourceMaterializer::RuntimeImageModes(image);
expectFailure([&] { ResourceMaterializer{}.Materialize(plan, SrtRuntime{}, snapshot); }, "runtime image descriptor is invalid",
atomic ? "TYPE=0 or 1 atomic image descriptor was accepted without hardware evidence" : "TYPE=0 or 1 writable image descriptor was accepted without hardware evidence");
if (type == 2u) {
expectFailure(materialize, "runtime image descriptor is invalid", "a reserved TYPE=2 storage image descriptor was accepted");
} else {
snapshot = {};
materialize();
require(snapshot.images.size() == 1u && snapshot.images[0].dwordCount == 8u && snapshot.images[0].dwords == words,
read ? "TYPE=0 or 1 read-write storage image descriptor was not materialized as a null storage image" : "TYPE=0 or 1 written storage image descriptor was not materialized as a null storage image");
}
}
}
@@ -8252,11 +8296,16 @@ void verifyInt64AtomicCapabilities() {
const auto texelAddress = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(texels.data()));
const std::array<std::uint32_t, 16> userData{
0u, 0u, 0u, 0u,
static_cast<std::uint32_t>(bufferAddress), static_cast<std::uint32_t>((bufferAddress >> 32u) & 0xffffu), 256u, 0x01016facu,
static_cast<std::uint32_t>(bufferAddress), static_cast<std::uint32_t>((bufferAddress >> 32u) & 0xffffu), 256u, 0x31016facu,
static_cast<std::uint32_t>(texelAddress >> 8u), static_cast<std::uint32_t>((texelAddress >> 40u) & 0xffu) | (Format32_32UInt << 20u) | (3u << 30u), 7u | (7u << 14u), 0xfacu | (Type2D << 28u),
0u, 0u, 0u, 0u};
const std::vector<std::uint32_t> bufferAtomic{0xe1705000u, 0x80012803u, 0xbf810000u};
const std::vector<std::uint32_t> imageAtomic{0xf03c0308u, 0x00020a08u, 0xbf810000u};
const std::vector<std::uint32_t> bufferOr{0xe1685000u, 0x80010003u, 0xbf810000u};
const std::vector<std::uint32_t> bufferAnd{0xe1645000u, 0x80010003u, 0xbf810000u};
const std::vector<std::uint32_t> bufferXor{0xe16c5000u, 0x80010003u, 0xbf810000u};
const std::vector<std::uint32_t> bufferOrTwice{0xe1685000u, 0x80010003u, 0xe1685000u, 0x80010203u, 0xbf810000u};
const std::vector<std::uint32_t> bufferOrAnd{0xe1685000u, 0x80010003u, 0xe1645000u, 0x80010203u, 0xbf810000u};
const auto compile = [&](std::span<const std::uint32_t> code, spv::Capability added) {
const std::array<std::uint32_t, 4> capabilities{spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, static_cast<std::uint32_t>(added)};
RecompileRequest request{};
@@ -8285,8 +8334,54 @@ void verifyInt64AtomicCapabilities() {
}
return false;
};
const auto verifySplit = [](std::span<const std::uint32_t> words, spv::Op atomic) {
std::map<std::uint32_t, std::size_t> definitions;
std::map<std::uint32_t, std::uint32_t> constants;
std::uint32_t uintType = 0;
for (std::size_t cursor = 5; cursor < words.size();) {
const auto count = words[cursor] >> 16u;
require(count != 0 && count <= words.size() - cursor, "64-bit atomics: truncated SPIR-V instruction");
const auto op = words[cursor] & 0xffffu;
if (op == spv::OpTypeInt && words[cursor + 2] == 32u && words[cursor + 3] == 0u) uintType = words[cursor + 1];
if (op == spv::OpConstant && count == 4u) constants[words[cursor + 2]] = words[cursor + 3];
if (op == spv::OpAccessChain || op == spv::OpIAdd || op == spv::OpUConvert || op == spv::OpVariable) definitions[words[cursor + 2]] = cursor;
cursor += count;
}
const auto opcodeOf = [&](std::uint32_t id) { return definitions.contains(id) ? words[definitions.at(id)] & 0xffffu : 0xffffffffu; };
std::vector<std::size_t> halves;
for (std::size_t cursor = 5; cursor < words.size();) {
const auto count = words[cursor] >> 16u;
if ((words[cursor] & 0xffffu) == static_cast<std::uint32_t>(atomic) && opcodeOf(words[cursor + 3]) == spv::OpAccessChain) {
const auto chain = definitions.at(words[cursor + 3]);
if ((words[chain] >> 16u) == 6u && opcodeOf(words[chain + 3]) == spv::OpAccessChain) halves.push_back(cursor);
}
cursor += count;
}
require(halves.size() == 2u, "64-bit atomics: an unused-result bitwise buffer atomic was not emitted as two 32-bit atomics");
const auto low = halves[0];
const auto high = halves[1];
require(words[low + 1] == uintType && words[high + 1] == uintType, "64-bit atomics: a half of a split buffer atomic is not 32 bits wide");
const auto lowChain = definitions.at(words[low + 3]);
const auto highChain = definitions.at(words[high + 3]);
require(words[lowChain + 3] == words[highChain + 3], "64-bit atomics: the halves of a split buffer atomic use different buffers");
const auto highIndex = words[highChain + 5];
require(opcodeOf(highIndex) == spv::OpIAdd, "64-bit atomics: the high half of a split buffer atomic does not address the next dword");
const auto add = definitions.at(highIndex);
require(words[add + 3] == words[lowChain + 5] && constants.contains(words[add + 4]) && constants.at(words[add + 4]) == 1u, "64-bit atomics: the high half of a split buffer atomic is not one dword after the low half");
require(words[low + 6] != words[high + 6] && opcodeOf(words[low + 6]) == spv::OpUConvert && opcodeOf(words[high + 6]) == spv::OpUConvert, "64-bit atomics: the halves of a split buffer atomic do not take the low and high dwords of the operand");
};
expectFailure([&] { static_cast<void>(compile(bufferAtomic, spv::CapabilityShader)); }, "64-bit buffer atomics need shaderBufferInt64Atomics", "64-bit atomics: a buffer_atomic_inc_x2 compiled without shaderBufferInt64Atomics");
require(declares(compile(bufferAtomic, spv::CapabilityInt64Atomics), spv::CapabilityInt64Atomics), "64-bit atomics: a buffer_atomic_inc_x2 does not declare Int64Atomics");
const std::array<std::pair<const std::vector<std::uint32_t>*, spv::Op>, 3> bitwise{{{&bufferOr, spv::OpAtomicOr}, {&bufferAnd, spv::OpAtomicAnd}, {&bufferXor, spv::OpAtomicXor}}};
for (const auto& [code, atomic] : bitwise) {
const auto split = compile(*code, spv::CapabilityShader);
require(!declares(split, spv::CapabilityInt64Atomics), "64-bit atomics: an unused-result bitwise buffer atomic declares Int64Atomics on a device without it");
verifySplit(split, atomic);
require(declares(compile(*code, spv::CapabilityInt64Atomics), spv::CapabilityInt64Atomics), "64-bit atomics: an unused-result bitwise buffer atomic does not use the 64-bit atomic where the device has it");
}
require(!declares(compile(bufferOrTwice, spv::CapabilityShader), spv::CapabilityInt64Atomics), "64-bit atomics: two buffer_atomic_or_x2 of one program declare Int64Atomics on a device without it");
expectFailure([&] { static_cast<void>(compile(bufferOrAnd, spv::CapabilityShader)); }, "64-bit buffer atomics need shaderBufferInt64Atomics", "64-bit atomics: a buffer_atomic_or_x2 and a buffer_atomic_and_x2 of one program compiled without shaderBufferInt64Atomics");
require(declares(compile(bufferOrAnd, spv::CapabilityInt64Atomics), spv::CapabilityInt64Atomics), "64-bit atomics: a buffer_atomic_or_x2 and a buffer_atomic_and_x2 of one program do not use the 64-bit atomics where the device has them");
expectFailure([&] { static_cast<void>(compile(imageAtomic, spv::CapabilityShader)); }, "64-bit image atomics need VK_EXT_shader_image_atomic_int64", "64-bit atomics: an image_atomic_swap on a 32_32 image compiled without shaderImageInt64Atomics");
require(declares(compile(imageAtomic, spv::CapabilityInt64ImageEXT), spv::CapabilityInt64ImageEXT), "64-bit atomics: an image_atomic_swap on a 32_32 image does not declare Int64ImageEXT");
}
@@ -10578,6 +10673,7 @@ int main(int argc, char** argv) {
verifyOutOfRangeConstantReads();
verifySignedMinimum();
verifyMayEvaluate();
verifyMayDecideSharedConditions();
verifyPureFlatSlots();
verifyPlanConditionPruning();
verifyPlanRetention();
@@ -16,18 +16,19 @@ namespace {
struct Case {
VkDriverId driver;
bool usable;
bool controlBarrier;
bool workgroupFence;
};
constexpr Case Cases[]{
{VK_DRIVER_ID_AMD_PROPRIETARY, false},
{VK_DRIVER_ID_AMD_OPEN_SOURCE, true},
{VK_DRIVER_ID_MESA_RADV, true},
{VK_DRIVER_ID_NVIDIA_PROPRIETARY, true},
{VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, true},
{VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, true},
{VK_DRIVER_ID_MESA_LLVMPIPE, true},
{VK_DRIVER_ID_MOLTENVK, true},
{VK_DRIVER_ID_AMD_PROPRIETARY, false, false},
{VK_DRIVER_ID_AMD_OPEN_SOURCE, true, true},
{VK_DRIVER_ID_MESA_RADV, true, true},
{VK_DRIVER_ID_NVIDIA_PROPRIETARY, true, true},
{VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, true, true},
{VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, true, true},
{VK_DRIVER_ID_MESA_LLVMPIPE, true, true},
{VK_DRIVER_ID_MOLTENVK, false, true},
};
constexpr std::array<std::uint32_t, 19> BranchedLdsStep{
@@ -120,8 +121,8 @@ Barriers recompile(std::uint32_t threads, bool waveLdsMemoryBarriers) {
void verifyDriverQuirk() {
for (const auto& item : Cases) {
require(AgcDriver::SubgroupControlBarrierUsable(item.driver) == item.usable, "driver " + std::to_string(static_cast<int>(item.driver)) + ": expected subgroup control barriers to be " + (item.usable ? "usable" : "unusable"));
require(AgcDriver::WorkgroupScopeFenceUsable(item.driver) == item.usable, "driver " + std::to_string(static_cast<int>(item.driver)) + ": expected workgroup-scope fences to be " + (item.usable ? "usable" : "unusable"));
require(AgcDriver::SubgroupControlBarrierUsable(item.driver) == item.controlBarrier, "driver " + std::to_string(static_cast<int>(item.driver)) + ": expected subgroup control barriers to be " + (item.controlBarrier ? "usable" : "unusable"));
require(AgcDriver::WorkgroupScopeFenceUsable(item.driver) == item.workgroupFence, "driver " + std::to_string(static_cast<int>(item.driver)) + ": expected workgroup-scope fences to be " + (item.workgroupFence ? "usable" : "unusable"));
}
}
@@ -859,6 +859,23 @@ void testHeaderWithoutProgramAddress() {
AgcDriverRegisterShader_nid_postfix(&header.shader);
}
void testRegisterList() {
using AgcDriver::DriverDetail::RegisterList;
const AgcDriver::Registers userConfig{{0x24a, 1}, {0x24b, 2}, {0x25b, 3}};
const RegisterList list(userConfig);
check(list.size() == 3 && list.Expand() == userConfig, "a register list does not hold its registers' offsets and values");
check(list.find(0x24b) != list.end() && list.find(0x24b)->second == 2 && list.find(0x24c) == list.end() && list.find(0x200) == list.end(), "a register list found the wrong register");
RegisterList added;
added.emplace(0x25b, 3);
added.emplace(0x24a, 1);
added.emplace(0x24b, 2);
added.emplace(0x24a, 9);
check(added == list, "a register list is not kept in offset order, or a second emplace replaced a value");
AgcDriver::DriverDetail::DecodedShaderState decoded{{{0x08a, 7}}, AgcDriver::InitialContextRegisters(), userConfig};
const AgcDriver::DriverDetail::RegisteredShaderState state(decoded);
check(state.shader.Expand() == decoded.shader && state.context.Expand() == decoded.context && state.userConfig.Expand() == decoded.userConfig, "a registered state did not keep the decoded registers");
}
void testRegisteredFloatMode() {
using AgcDriver::DriverDetail::RegisteredFloatMode;
using AgcDriver::DriverDetail::ShaderSnapshot;
@@ -1227,6 +1244,7 @@ int main(int argc, char** argv) {
testLabelResetByItsQueueBeforeItLanded();
testWideLabelPartlyResetByItsQueueBeforeItLanded();
testSmallCopyBehindPendingSource();
testRegisterList();
testRegisteredFloatMode();
testDispatchFloatMode();
testReadOnlyResummarize();
@@ -209,6 +209,26 @@ void RunTextureDetilerTests(const Context& context, const TextureDetilerTestAcce
Require(access.descriptorPoolCount() == pools, "a full descriptor pool whose batches completed must be reset and reused, not joined by a new pool");
}
{
DeviceFunctions table{};
table.updateDescriptorSets = context.Function<PFN_vkUpdateDescriptorSets>("vkUpdateDescriptorSets");
table.allocateDescriptorSets = context.Function<PFN_vkAllocateDescriptorSets>("vkAllocateDescriptorSets");
table.cmdBindPipeline = context.Function<PFN_vkCmdBindPipeline>("vkCmdBindPipeline");
table.cmdBindDescriptorSets = context.Function<PFN_vkCmdBindDescriptorSets>("vkCmdBindDescriptorSets");
table.cmdPushConstants = context.Function<PFN_vkCmdPushConstants>("vkCmdPushConstants");
table.cmdDispatch = context.Function<PFN_vkCmdDispatch>("vkCmdDispatch");
auto tabled = context;
tabled.functions = &table;
TextureDetiler cached(tabled);
cached.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 20, destination, 40, layout, 0);
const auto before = DeviceProcLookups();
cached.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 20, destination, 40, layout, 0);
const auto lookups = DeviceProcLookups() - before;
Require(lookups == 0, "a detile dispatch looked up " + std::to_string(lookups) + " Vulkan functions by name while the device's function table holds them");
const auto tabledCapture = access.lastDispatch();
Require(tabledCapture.groupsX == 3 && tabledCapture.groupsY == 2 && tabledCapture.groupsZ == 1, "a detile dispatch through the device's function table recorded the wrong group counts");
}
reject([&] { detiler.Dispatch(VK_NULL_HANDLE, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, layout, 0); }, "active command buffer");
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, VK_NULL_HANDLE, 0, destination, 0, layout, 0); }, "source and destination buffers");
reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, VK_NULL_HANDLE, 0, layout, 0); }, "source and destination buffers");
@@ -201,6 +201,50 @@ void CheckStoredOverUsesPageEvidence() {
Require(!StoredOver(base + Page, Page, before), "a page-local CPU write made an untouched page in the same block count as stored over");
}
void CheckRepeatedNeighbourStores() {
void* memory = AllocateWatched(3 * Block);
const auto base = reinterpret_cast<std::uint64_t>(memory);
const auto boundary = base + 2 * Block - 0x9000;
const auto sharedFirst = boundary - (base + Block);
const auto neighbourBytes = static_cast<std::size_t>(base + 3 * Block - boundary);
std::memset(memory, 0x11, 3 * Block);
const auto synced = CollectWrites(base, 3 * Block);
Require(synced != 0, "the test block is not collected");
for (int store = 0; store < 6; ++store) MarkWritten(boundary, neighbourBytes);
Require(!StoredOver(base + Block, static_cast<std::size_t>(sharedFirst), synced), "repeated stores of the neighbour's bytes count for the first surface's part of the shared block");
Require(StoredAllSince_nid_no_patch(boundary, neighbourBytes, synced), "repeated stores of the neighbour's bytes forget that they covered them");
const auto pieces = TrackerGeneration();
for (int store = 0; store < 3; ++store) MarkWritten(boundary + static_cast<std::uint64_t>(store) * 0x100, 4);
MarkWritten(boundary, 0x400);
Require(StoredOver(boundary + 0x200, 4, pieces) && !StoredOver(boundary + 0x400, 4, pieces), "a store over earlier pieces of the block answers for other bytes than its own");
MarkWritten(boundary + 0x800, 4);
MarkWritten(boundary + 0x900, 4);
Require(!StoredOver(base + Block, static_cast<std::size_t>(sharedFirst), pieces), "a store over earlier pieces counts for the first surface's part of the shared block");
void* fresh = AllocateWatched(2 * Block);
const auto freshBase = reinterpret_cast<std::uint64_t>(fresh);
std::memset(fresh, 0x11, 2 * Block);
const auto freshSynced = CollectWrites(freshBase, 2 * Block);
const auto neighbour = freshBase + Block - 0x9000;
MarkWritten(neighbour, 0x100);
const auto secondStored = MarkWritten(neighbour + 0x1000, 0x100);
const auto thirdStored = MarkWritten(neighbour + 0x2000, 0x100);
MarkWritten(neighbour + 0x3000, 0x100);
const auto settled = TrackerGeneration();
MarkWritten(neighbour, 0x100);
MarkWritten(neighbour, 0x100);
MarkWritten(neighbour + 0x5000, 0x100);
Require(!StoredOver(freshBase, static_cast<std::size_t>(neighbour - freshBase), secondStored), "a full exact ring evicted a newer piece instead of its oldest");
Require(StoredOver(freshBase, static_cast<std::size_t>(neighbour - freshBase), freshSynced), "a forgotten store is not taken as a store over the rest of the block");
const auto partial = TrackerGeneration();
MarkWritten(neighbour + 0x5080, 0x100);
Require(StoredOver(neighbour + 0x5000, 0x10, settled) && StoredOver(neighbour + 0x5100, 0x10, partial), "a store that overlaps an earlier piece without containing it replaces it");
Require(!StoredOver(freshBase, static_cast<std::size_t>(neighbour - freshBase), thirdStored), "a store that overlaps without containing evicted a newer piece instead of the oldest");
}
void CheckStoredAllSince() {
constexpr std::size_t bytes = 4 * Block;
auto* memory = static_cast<std::uint8_t*>(AllocateWatched(bytes));
@@ -2276,6 +2320,7 @@ int main() {
#endif
CheckSharedBlock();
CheckStoredOverUsesPageEvidence();
CheckRepeatedNeighbourStores();
CheckStoredAllSince();
CheckSharedBlockProofs();
CheckRepeatedPossibleStores();
@@ -8,10 +8,18 @@ if(BUILD_TESTING)
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/explicit")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/failure")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/best-effort")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/format")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/queue")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/queue-shutdown")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/queue-cumulative")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/disabled")
set(agcCaptureTraceExplicitPath "${agcCaptureTraceDirectory}/explicit/trace.log")
set(agcCaptureTraceFailurePath "${agcCaptureTraceDirectory}/failure/missing-parent/trace.log")
set(agcCaptureTraceBestEffortPath "${agcCaptureTraceDirectory}/best-effort/trace.log")
set(agcCaptureTraceFormatPath "${agcCaptureTraceDirectory}/format/trace.log")
set(agcCaptureTraceQueuePath "${agcCaptureTraceDirectory}/queue/trace.log")
set(agcCaptureTraceQueueShutdownPath "${agcCaptureTraceDirectory}/queue-shutdown/trace.log")
set(agcCaptureTraceQueueCumulativePath "${agcCaptureTraceDirectory}/queue-cumulative/trace.log")
add_test(NAME agc_capture_trace_path_default COMMAND agc_capture_trace_tests path-default)
add_test(NAME agc_capture_trace_path_empty COMMAND agc_capture_trace_tests path-empty)
set_tests_properties(agc_capture_trace_path_empty PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE_PATH=")
@@ -25,13 +33,47 @@ if(BUILD_TESTING)
set_tests_properties(agc_capture_trace_open_failure PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceFailurePath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/failure")
add_test(NAME agc_capture_trace_best_effort_overflow COMMAND agc_capture_trace_tests best-effort-overflow)
set_tests_properties(agc_capture_trace_best_effort_overflow PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceBestEffortPath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/best-effort")
add_test(NAME agc_capture_trace_format_overflow COMMAND agc_capture_trace_tests format-overflow)
set_tests_properties(agc_capture_trace_format_overflow PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceFormatPath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/format" TIMEOUT 15)
add_test(NAME agc_capture_trace_queue_drop COMMAND agc_capture_trace_tests queue-drop)
set_tests_properties(agc_capture_trace_queue_drop PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceQueuePath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/queue" TIMEOUT 15)
add_test(NAME agc_capture_trace_queue_drop_shutdown COMMAND agc_capture_trace_tests queue-drop-shutdown)
set_tests_properties(agc_capture_trace_queue_drop_shutdown PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceQueueShutdownPath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/queue-shutdown" TIMEOUT 15)
add_test(NAME agc_capture_trace_queue_drop_cumulative COMMAND agc_capture_trace_tests queue-drop-cumulative)
set_tests_properties(agc_capture_trace_queue_drop_cumulative PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceQueueCumulativePath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/queue-cumulative" TIMEOUT 15)
add_test(NAME agc_capture_trace_disabled COMMAND agc_capture_trace_tests disabled)
set_tests_properties(agc_capture_trace_disabled PROPERTIES WORKING_DIRECTORY "${agcCaptureTraceDirectory}/disabled")
if(WIN32)
set_tests_properties(agc_capture_trace_path_default agc_capture_trace_path_empty
agc_capture_trace_path_explicit agc_capture_trace_explicit_writes
agc_capture_trace_default_writes agc_capture_trace_open_failure agc_capture_trace_best_effort_overflow
agc_capture_trace_default_writes agc_capture_trace_open_failure agc_capture_trace_best_effort_overflow agc_capture_trace_format_overflow agc_capture_trace_queue_drop agc_capture_trace_queue_drop_shutdown agc_capture_trace_queue_drop_cumulative
agc_capture_trace_disabled PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(agc_capture_trace_failure_tests tests/CaptureTraceFailure.cpp)
target_include_directories(agc_capture_trace_failure_tests PRIVATE ${LIBS_INCLUDE_DIR})
if(MINGW)
target_compile_options(agc_capture_trace_failure_tests PRIVATE -fasynchronous-unwind-tables)
endif()
add_test_executable(agc_capture_trace_runtime_failure_tests tests/CaptureTraceFailure.cpp)
target_include_directories(agc_capture_trace_runtime_failure_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_capture_trace_runtime_failure_tests PRIVATE libc)
configure_windows_unwind(agc_capture_trace_runtime_failure_tests)
foreach(mode IN ITEMS write flush)
add_test(NAME agc_capture_trace_${mode}_failure
COMMAND "${CMAKE_COMMAND}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/tests/CaptureTraceFailure.cmake"
"$<TARGET_FILE:agc_capture_trace_failure_tests>" "${mode}" "${agcCaptureTraceDirectory}")
set_tests_properties(agc_capture_trace_${mode}_failure PROPERTIES TIMEOUT 20)
add_test(NAME agc_capture_trace_${mode}_failure_runtime
COMMAND "${CMAKE_COMMAND}"
-P "${CMAKE_CURRENT_SOURCE_DIR}/tests/CaptureTraceFailure.cmake"
"$<TARGET_FILE:agc_capture_trace_runtime_failure_tests>" "${mode}" "${agcCaptureTraceDirectory}")
set_tests_properties(agc_capture_trace_${mode}_failure_runtime PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(agc_capture_trace_${mode}_failure_runtime PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
endif()
@@ -0,0 +1,8 @@
if(BUILD_TESTING)
add_test(NAME agc_driver_linear_target_draw_bindings_counted COMMAND agc_driver_linear_target_draw_bindings_tests)
set_tests_properties(agc_driver_linear_target_draw_bindings_counted PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_READ_ONLY_LEASE_SKIP=1" SKIP_RETURN_CODE 77 TIMEOUT 60 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_linear_target_draw_bindings_counted PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,8 @@
if(BUILD_TESTING)
add_test(NAME agc_driver_read_only_lease_writer_counted COMMAND agc_driver_read_only_lease_writer_tests)
set_tests_properties(agc_driver_read_only_lease_writer_counted PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_READ_ONLY_LEASE_SKIP=1" SKIP_RETURN_CODE 77 TIMEOUT 60 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_read_only_lease_writer_counted PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -157,6 +157,12 @@ if(BUILD_TESTING)
set_tests_properties(agc_partial_watch_restoration PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_repeated_neighbour_stores COMMAND agc_driver_recorder_tests --repeated-neighbour-stores-only)
set_tests_properties(agc_repeated_neighbour_stores PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_repeated_neighbour_stores PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_storage_kept_unit_settle COMMAND agc_driver_recorder_tests --kept-unit-settle-only)
add_test(NAME agc_storage_idle_writeback_race COMMAND agc_driver_recorder_tests --idle-writeback-race-only)
set_tests_properties(agc_storage_idle_writeback_race PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 60)
@@ -2,8 +2,13 @@ if(BUILD_TESTING)
add_test(NAME agc_driver_store_idle_results_wake COMMAND agc_driver_store_idle_results_tests --idle-epoch-wake)
set_tests_properties(agc_driver_store_idle_results_wake PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_driver_store_idle_results_disabled COMMAND agc_driver_store_idle_results_tests --disabled-idle-store)
set_tests_properties(agc_driver_store_idle_results_disabled PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_IDLE_STORE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_store_idle_results_wake PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_driver_store_idle_results_disabled PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,9 @@
if(BUILD_TESTING)
add_test(NAME agc_driver_two_device_pending_store_label COMMAND agc_driver_two_device_pending_store_tests --label)
set_tests_properties(agc_driver_two_device_pending_store_label PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_STORE_AT_LABEL=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_two_device_pending_store_label PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -1,7 +1,7 @@
add_test_executable(agc_pipeline_cache_allocation_tests EXCLUDE_FROM_ALL tests/PipelineCacheFailures.cpp
Graphics/src/PipelineCache.cpp tests/AllocationFailure.cpp)
target_include_directories(agc_pipeline_cache_allocation_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_pipeline_cache_allocation_tests PRIVATE shader_recompiler_agc_driver libc)
target_link_libraries(agc_pipeline_cache_allocation_tests PRIVATE shader_recompiler_agc_driver libSceAgcDriver libc)
set_target_properties(agc_pipeline_cache_allocation_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_pipeline_cache_allocation_tests)
@@ -9,6 +9,7 @@
#include <iostream>
#include <optional>
#include <span>
#include <spirv/unified1/spirv.hpp>
#include <stdexcept>
#include <string>
#include <vector>
@@ -48,6 +49,11 @@ alignas(256) constexpr std::array<std::uint32_t, 19> ScaleCode{
0xe0702004u, 0x80010b01u, 0xbf810000u,
};
alignas(256) constexpr std::array<std::uint32_t, 16> FmasCode{
0x34020082u, 0xe0302000u, 0x80000401u, 0xe0302004u, 0x80000501u, 0xe0302008u, 0x80000601u, 0xe030200cu,
0x80000701u, 0xbf8c3f70u, 0x7d8a0e80u, 0xd56f000au, 0x041a0b04u, 0xe0702000u, 0x80010a01u, 0xbf810000u,
};
struct ScaleVector {
std::uint32_t s0, s1, s2, flushed, kept, vcc;
};
@@ -621,6 +627,39 @@ constexpr DivisionVector DivisionVectors[] = {
{0x2210592fu, 0x0ac8fdd3u, 0x56b7dabeu},
};
struct FmasDenormalVector {
std::uint32_t s0, s1, s2, vcc, flushed, kept;
};
constexpr FmasDenormalVector FmasDenormalVectors[] = {
{0x00000001u, 0x80800000u, 0x1fea6146u, 1u, 0x00000000u, 0x003a9851u},
{0x8da51d6fu, 0x96a2a07au, 0x2004bac1u, 1u, 0x00000000u, 0x00425d61u},
{0xbec6b7ecu, 0x20000000u, 0x00800000u, 1u, 0x80000000u, 0x8018d6fdu},
{0x00000001u, 0x6a2932fau, 0x00400000u, 1u, 0x00000000u, 0x002a4cbfu},
{0x0becd7b0u, 0x2535a659u, 0x80000001u, 0u, 0x80000000u, 0x80000001u},
{0x20000000u, 0x08fe0996u, 0x807fffffu, 0u, 0x80000000u, 0x807fffffu},
{0x9d1996e9u, 0x03feab54u, 0x00400000u, 0u, 0x00000000u, 0x00400000u},
{0x80000000u, 0x9d47e6bfu, 0x00000001u, 0u, 0x00000000u, 0x00000001u},
{0x32d351d2u, 0x1f800000u, 0x9f800000u, 1u, 0x80000000u, 0x80200000u},
{0x1452d51eu, 0xb9246da0u, 0x9682e357u, 1u, 0x80000000u, 0x80000008u},
{0x84b5a818u, 0x2de2cedcu, 0x1f800000u, 1u, 0x00000000u, 0x00200000u},
{0x149e259bu, 0x80800000u, 0x1a3dc795u, 1u, 0x00000000u, 0x000005eeu},
{0xff800000u, 0x00000001u, 0x1e8b9c7bu, 0u, 0xff800000u, 0xff800000u},
{0x096fea1fu, 0x40000000u, 0x00400000u, 0u, 0x09efea2fu, 0x09efea2fu},
{0xb8c9817au, 0x0adcc633u, 0x007fffffu, 0u, 0x842cc747u, 0x842cc747u},
{0x807fffffu, 0xff7fffffu, 0x3f000000u, 0u, 0x408fffffu, 0x408fffffu},
{0xff800000u, 0x00000001u, 0x1e8b9c7bu, 1u, 0xff800000u, 0xff800000u},
{0x807fffffu, 0xff7fffffu, 0x3f000000u, 1u, 0x208fffffu, 0x208fffffu},
{0x802927c2u, 0x7f7fffffu, 0x80000000u, 1u, 0x9fa49f07u, 0x9fa49f07u},
{0x007fffffu, 0xde01282au, 0x25a1ba53u, 1u, 0x05a1b64au, 0x05a1b64au},
{0x00400000u, 0x14afa671u, 0x807fffffu, 0u, 0x80000000u, 0x807fffffu},
{0x95928216u, 0x80000001u, 0x807fffffu, 0u, 0x80000000u, 0x807fffffu},
{0x807fffffu, 0x3f800000u, 0x00000000u, 0u, 0x80000000u, 0x807fffffu},
{0x9e2d4ac9u, 0x80000000u, 0x80000001u, 0u, 0x80000000u, 0x80000001u},
{0x007fffffu, 0xff800000u, 0x15e81bf3u, 0u, 0xff800000u, 0xff800000u},
{0x007fffffu, 0xff800000u, 0x15e81bf3u, 1u, 0xff800000u, 0xff800000u},
};
struct FmasNanVector {
std::uint32_t s0, s1, s2, noIeee, ieee;
};
@@ -773,6 +812,30 @@ void CheckFmasNan(AgcDriver::VulkanDevice& device, const std::optional<ShaderRec
}
}
bool HasFloat64(AgcDriver::VulkanDevice& device) {
const auto& capabilities = device.Target().supportedCapabilities;
return std::find(capabilities.begin(), capabilities.end(), static_cast<std::uint32_t>(spv::CapabilityFloat64)) != capabilities.end();
}
void CheckFmasDenormals(AgcDriver::VulkanDevice& device) {
static_assert(std::size(FmasDenormalVectors) <= Threads);
for (const std::uint32_t mode : {0xc0u, 0xd0u, 0xe0u, 0xf0u}) {
Shader shader(device, FmasCode, ShaderRecompiler::ShaderFloatMode{mode, true, false, false});
Input.fill(0u);
for (std::uint32_t lane = 0; lane < std::size(FmasDenormalVectors); ++lane) {
const auto& vector = FmasDenormalVectors[lane];
std::copy_n(std::array<std::uint32_t, 4>{vector.s0, vector.s1, vector.s2, vector.vcc}.begin(), 4, Input.begin() + lane * Stride);
}
shader.Run();
for (std::uint32_t lane = 0; lane < std::size(FmasDenormalVectors); ++lane) {
const auto& vector = FmasDenormalVectors[lane];
const auto actual = Output[lane * Stride];
const auto expected = ((mode >> 4u) & 2u) != 0u ? vector.kept : vector.flushed;
Require(actual == expected, "division fmas denormals: FLOAT_MODE " + Hex(mode) + " (" + Hex(vector.s0) + ", " + Hex(vector.s1) + ", " + Hex(vector.s2) + ") vcc " + std::to_string(vector.vcc) + ": v_div_fmas_f32 is " + Hex(actual) + ", expected " + Hex(expected));
}
}
}
void CheckDivision(AgcDriver::VulkanDevice& device) {
Shader shader(device, DivisionCode);
constexpr std::uint32_t count = sizeof(DivisionVectors) / sizeof(DivisionVectors[0]);
@@ -805,6 +868,11 @@ int main() {
CheckFmasNan(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false});
CheckFmasNan(*device, ShaderRecompiler::ShaderFloatMode{0xf0u, false, false, false});
CheckFmasNan(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, false, true, false});
if (HasFloat64(*device)) {
CheckFmasDenormals(*device);
} else {
std::puts("the device has no Float64, so v_div_fmas_f32 denormal and scaling rows are not checked");
}
if (fused) {
CheckDivision(*device);
} else {
@@ -87,6 +87,23 @@ int main() {
Require(bounded, "a dispatch whose written images are a real one and a null one is not bounded");
Require(Contains(dispatchRanges, written, written + surfaceBytes), "a written image next to a null one is not a dispatch write range");
Require(!Touches(dispatchRanges, 0, surfaceBytes), "a null storage image is a dispatch write range");
auto typeZero = ImageWords(0x500000000ull, 256, 128);
typeZero[3] &= 0x0fffffffu;
auto typeOne = typeZero;
typeOne[3] |= 1u << 28u;
const std::array<std::uint32_t, 8> reported{0x04efd000u, 0x00040011u, 0x00017a00u, 0x00005204u, 0x0af00000u, 0u, 0x00000612u, 0x00fff000u};
for (const auto& [words, base] : {std::pair{typeZero, 0x500000000ull}, std::pair{typeOne, 0x500000000ull}, std::pair{reported, 0x1104efd00000ull}}) {
std::copy(words.begin(), words.end(), program.bindings[0].guestDescriptor.begin() + 8);
for (const bool exact : {false, true}) {
const auto ranges = DrawWriteRanges(state, stages, exact);
Require(Contains(ranges, written, written + surfaceBytes), "a written image next to a TYPE 0 or 1 one is not a write range");
Require(!Touches(ranges, base, base + surfaceBytes), "a TYPE 0 or 1 storage image is a write range");
}
bool typedBounded = false;
const auto typedRanges = AgcDriver::DriverDetail::DispatchWriteRanges(program, typedBounded);
Require(typedBounded && Contains(typedRanges, written, written + surfaceBytes), "a dispatch whose written images are a real one and a TYPE 0 or 1 one lost the real one");
Require(!Touches(typedRanges, base, base + surfaceBytes), "a TYPE 0 or 1 storage image is a dispatch write range");
}
AgcDriver::Graphics::State targets{};
targets.hasColorTarget = true;
auto& color = targets.color;
@@ -0,0 +1,248 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <iterator>
#include <optional>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 64;
constexpr std::uint32_t Inputs = 4;
constexpr std::uint32_t Results = 8;
constexpr std::uint32_t Halves = 0x10000u;
constexpr std::uint32_t PackedGroups = Halves / (Threads * 2u);
constexpr std::uint32_t UnaryGroups = Halves / Threads;
constexpr std::uint32_t Rows = UnaryGroups * Threads;
std::vector<std::uint32_t> Input(Rows * Inputs);
std::vector<std::uint32_t> Output(Rows * Results);
alignas(256) constexpr std::array<std::uint32_t, 30> PackedCode{
0x34020084u, 0x34060085u, 0x8f098a08u, 0x8f0a8b08u, 0x4a020209u, 0x4a06060au, 0xe0381000u, 0x80000401u, 0xbf8c3f70u,
0xcc0f400au, 0x18020b04u,
0xcc10400bu, 0x18020b04u,
0xcc0e400cu, 0x1c1a0b04u,
0xcc0f4a0du, 0x30020b04u,
0xcc10c00eu, 0x18020d04u,
0xcc0e330fu, 0x8c1a0b04u,
0x7e200306u, 0x78200b04u,
0xcc0ec011u, 0x3c1a0f04u,
0xe0781000u, 0x80010a03u, 0xe0781010u, 0x80010e03u, 0xbf810000u,
};
constexpr const char* PackedNames[Results] = {
"v_pk_add_f16 v10, v4, v5",
"v_pk_mul_f16 v11, v4, v5",
"v_pk_fma_f16 v12, v4, v5, v6",
"v_pk_add_f16 v13, v4, v5 op_sel:[1,0] op_sel_hi:[0,1] neg_lo:[1,0] neg_hi:[0,1]",
"v_pk_mul_f16 v14, v4, v6 clamp",
"v_pk_fma_f16 v15, v4, v5, v6 op_sel:[0,1,1] op_sel_hi:[1,0,0] neg_lo:[0,0,1] neg_hi:[1,1,0]",
"v_pk_fmac_f16 v16, v4, v5",
"v_pk_fma_f16 v17, v4, v7, v6 neg_lo:[1,0,0] clamp",
};
constexpr std::array<std::uint32_t, Results> UnaryOpcodes{0x54u, 0x55u, 0x56u, 0x57u, 0x58u, 0x5fu, 0x60u, 0x61u};
constexpr const char* UnaryNames[Results] = {
"v_rcp_f16", "v_sqrt_f16", "v_rsq_f16", "v_log_f16", "v_exp_f16", "v_fract_f16", "v_sin_f16", "v_cos_f16",
};
constexpr std::array<std::uint32_t, 34> MakeUnaryCode() {
std::array<std::uint32_t, 34> code{0x34020084u, 0x34060085u, 0x8f098a08u, 0x8f0a8b08u, 0x4a020209u, 0x4a06060au, 0xe0381000u, 0x80000401u, 0xbf8c3f70u};
for (std::uint32_t index = 0; index < Results; ++index) {
code[9u + index] = 0x7e000280u | ((10u + index) << 17u) | (1u << 9u);
code[17u + index] = 0x7e000104u | ((10u + index) << 17u) | (UnaryOpcodes[index] << 9u);
}
code[25u] = 0xe0781000u;
code[26u] = 0x80010a03u;
code[27u] = 0xe0781010u;
code[28u] = 0x80010e03u;
code[29u] = 0xbf810000u;
return code;
}
alignas(256) constexpr std::array<std::uint32_t, 34> UnaryCode = MakeUnaryCode();
struct Mode {
const char* name;
std::optional<ShaderRecompiler::ShaderFloatMode> floatMode;
};
const std::array<Mode, 9> PackedModes{{
{"no FLOAT_MODE", std::nullopt},
{"dx10 ieee ovfl", ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, true}},
{"dx10 ieee", ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}},
{"dx10 ovfl", ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, true}},
{"dx10", ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}},
{"ieee ovfl", ShaderRecompiler::ShaderFloatMode{0xc0u, false, true, true}},
{"ieee", ShaderRecompiler::ShaderFloatMode{0xc0u, false, true, false}},
{"ovfl", ShaderRecompiler::ShaderFloatMode{0xc0u, false, false, true}},
{"f16 denormals flushed", ShaderRecompiler::ShaderFloatMode{0x00u, true, true, false}},
}};
const std::array<Mode, 4> UnaryModes{{
{"no FLOAT_MODE", std::nullopt},
{"dx10 ieee", ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}},
{"dx10", ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}},
{"f16 denormals flushed", ShaderRecompiler::ShaderFloatMode{0x00u, true, true, false}},
}};
constexpr std::array<std::uint32_t, 26> Specials{
0x0000u, 0x8000u, 0x0001u, 0x8001u, 0x03ffu, 0x0400u, 0x3c00u, 0xbc00u, 0x3555u, 0x4000u, 0xc400u, 0x7bffu, 0xfbffu,
0x7c00u, 0xfc00u, 0x7c01u, 0xfc01u, 0x7d55u, 0xfdffu, 0x7e00u, 0xfe00u, 0x7e55u, 0xfeaau, 0x7fffu, 0xffffu, 0x5bffu,
};
std::uint64_t Seed = 0x9e3779b97f4a7c15ull;
std::uint32_t Next() {
Seed += 0x9e3779b97f4a7c15ull;
std::uint64_t z = Seed;
z = (z ^ (z >> 30u)) * 0xbf58476d1ce4e5b9ull;
z = (z ^ (z >> 27u)) * 0x94d049bb133111ebull;
return static_cast<std::uint32_t>(z ^ (z >> 31u));
}
std::uint32_t Other() {
const std::uint32_t bits = Next();
return (bits & 1u) != 0u ? Specials[(bits >> 1u) % Specials.size()] : (bits >> 16u);
}
bool IsNan(std::uint32_t half) {
return (half & 0x7fffu) > 0x7c00u;
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
std::uint32_t CountOpcode(std::span<const std::uint32_t> spirv, std::uint32_t wanted) {
std::uint32_t count = 0u;
for (std::size_t offset = 5u; offset < spirv.size();) {
const std::uint32_t words = spirv[offset] >> 16u;
Require(words != 0u && offset + words <= spirv.size(), "f16 unused NaN payloads: malformed SPIR-V");
if ((spirv[offset] & 0xffffu) == wanted) ++count;
offset += words;
}
return count;
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
const auto target = device->Target();
const auto supports = [&](spv::Capability capability) {
return std::ranges::find(target.supportedCapabilities, static_cast<std::uint32_t>(capability)) != target.supportedCapabilities.end();
};
if (!supports(spv::CapabilityFloat16) || !supports(spv::CapabilityRoundingModeRTE) || !supports(spv::CapabilityDenormPreserve) || !target.float16DenormPreserve) {
std::puts("f16 unused NaN payloads: skipped, the device has no native f16 conversions with RTE rounding and kept f16 denormals");
return VulkanTestSkipped;
}
std::vector<std::uint32_t> withoutFloat16;
std::ranges::copy_if(target.supportedCapabilities, std::back_inserter(withoutFloat16), [](std::uint32_t capability) { return capability != spv::CapabilityFloat16; });
auto softwareTarget = target;
softwareTarget.supportedCapabilities = withoutFloat16;
softwareTarget.float16DenormPreserve = false;
std::vector<std::uint32_t> userData(8, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {true, false, false}, false, 1};
const auto compile = [&](std::span<const std::uint32_t> code, const ShaderRecompiler::SpirvTarget& shaderTarget, const Mode& mode) {
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
shaderTarget,
{0, 0, 0, 128}
};
request.useCache = false;
request.context.floatMode = mode.floatMode;
return ShaderRecompiler::Recompile(request);
};
const auto run = [&](const auto& result, std::span<const std::uint32_t> code, std::uint32_t groups, std::uint32_t unwritten) {
std::ranges::fill(Output, unwritten);
device->Dispatch(result, groups, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device->WaitIdle();
return Output;
};
const auto compare = [&](const char* group, const Mode& mode, const char* const* names, const auto& fast, const auto& software, std::uint32_t rows) {
std::uint64_t nanResults = 0u;
for (std::uint32_t tid = 0; tid < rows; ++tid) {
const std::uint32_t* row = &Input[tid * Inputs];
for (std::uint32_t index = 0; index < Results; ++index) {
const std::uint32_t actual = fast[tid * Results + index];
const std::uint32_t expected = software[tid * Results + index];
if (actual != expected) Require(false, std::string("f16 unused NaN payloads: ") + group + ", " + mode.name + ", " + names[index] + " with v4=" + Hex(row[0]) + " v5=" + Hex(row[1]) + " v6=" + Hex(row[2]) + " v7=" + Hex(row[3]) + " is " + Hex(actual) + ", the software conversions give " + Hex(expected));
nanResults += (IsNan(expected & 0xffffu) ? 1u : 0u) + (IsNan(expected >> 16u) ? 1u : 0u);
}
}
return nanResults;
};
std::uint64_t compared = 0u;
std::uint64_t nanResults = 0u;
const std::span<const std::uint32_t> packedCode(PackedCode);
for (const auto& mode : PackedModes) {
const auto fast = compile(packedCode, target, mode);
const auto software = compile(packedCode, softwareTarget, mode);
if (mode.floatMode.has_value() && mode.floatMode->dx10Clamp) {
Require(CountOpcode(fast.spirv.Words(), spv::OpIsNan) == 0u, std::string("f16 unused NaN payloads: packed f16, ") + mode.name + " still rebuilds the NaN of an f16 input whose payload the result never uses");
}
constexpr std::uint32_t rows = PackedGroups * Threads;
for (std::uint32_t exhaustive = 0; exhaustive < Inputs; ++exhaustive) {
for (std::uint32_t tid = 0; tid < rows; ++tid) {
for (std::uint32_t index = 0; index < Inputs; ++index) {
const std::uint32_t low = index == exhaustive ? tid * 2u : Other();
const std::uint32_t high = index == exhaustive ? tid * 2u + 1u : Other();
Input[tid * Inputs + index] = low | (high << 16u);
}
}
const auto fastOutput = run(fast, packedCode, PackedGroups, 0xdeadbeefu);
const auto softwareOutput = run(software, packedCode, PackedGroups, 0xfeedfaceu);
nanResults += compare("packed f16", mode, PackedNames, fastOutput, softwareOutput, rows);
compared += rows * Results * 2u;
}
}
const std::span<const std::uint32_t> unaryCode(UnaryCode);
for (const auto& mode : UnaryModes) {
const auto fast = compile(unaryCode, target, mode);
const auto software = compile(unaryCode, softwareTarget, mode);
for (std::uint32_t tid = 0; tid < Rows; ++tid) {
Input[tid * Inputs] = tid | (Next() & 0xffff0000u);
for (std::uint32_t index = 1; index < Inputs; ++index) Input[tid * Inputs + index] = Next();
}
const auto fastOutput = run(fast, unaryCode, UnaryGroups, 0xdeadbeefu);
const auto softwareOutput = run(software, unaryCode, UnaryGroups, 0xfeedfaceu);
nanResults += compare("unary f16", mode, UnaryNames, fastOutput, softwareOutput, Rows);
compared += Rows * Results;
}
std::printf("f16 unused NaN payloads tests passed (%llu results, %llu NaN halves)\n", static_cast<unsigned long long>(compared), static_cast<unsigned long long>(nanResults));
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -51,11 +51,12 @@ constexpr std::uint32_t DivisionUlps = 3;
constexpr std::size_t BlockBytes = 65536;
constexpr std::size_t RoundTripOffset = 16384;
constexpr std::size_t StoreOffset = 32768;
constexpr std::uint32_t D16Width = 1024;
constexpr std::uint32_t D16Groups = D16Width / Threads;
constexpr std::array<std::uint32_t, 4> PointSampler{0x00000092u, 0x00fff000u, 0u, 0u};
constexpr std::array<std::uint32_t, 4> BilinearSampler{0x00000092u, 0x00fff000u, 0x00500000u, 0u};
constexpr std::array<std::uint32_t, 4> WrapBilinearSampler{0x00000090u, 0x00fff000u, 0x00500000u, 0u};
constexpr std::array<std::uint32_t, 4> WrapPointSampler{0x00000090u, 0x00fff000u, 0u, 0u};
constexpr std::array<std::uint32_t, 4> BorderSampler{0x000000b6u, 0x00fff000u, 0u, 0u};
constexpr std::uint32_t SampleStep = 0x3a200000u;
constexpr std::uint32_t OffGridStep = 0x3a206000u;
constexpr std::uint32_t SampleStart = 0x39c00000u;
@@ -72,6 +73,14 @@ constexpr std::array<std::uint32_t, 15> LoadCode(std::uint32_t dmask, std::uint3
};
}
constexpr std::array<std::uint32_t, 17> LoadStoreCode(std::uint32_t dmask, std::uint32_t d16) {
return {
0x7e080218u, 0x343c0885u, 0x4a3c3d00u, 0x34063c84u, 0x7e3e0280u, 0x7e140280u, 0x7e160280u, 0x7e180280u,
0x7e1a0280u, 0xf0001008u | (dmask << 8u), 0x00010a1eu | d16, 0xbf8c3f70u, 0xe0781000u, 0x80000a03u, 0xf0201008u | (dmask << 8u), 0x00010a1eu | d16,
0xbf810000u,
};
}
constexpr std::array<std::uint32_t, 9> StoreCode(std::uint32_t dmask, std::uint32_t d16) {
return {
0x34060084u, 0xe0381000u, 0x80030a03u, 0x7e3c0300u, 0x7e3e0280u, 0xbf8c3f70u, 0xf0201008u | (dmask << 8u), 0x00010a1eu | d16,
@@ -102,12 +111,12 @@ alignas(256) constexpr auto LoadXyzw = LoadCode(0xfu, 0u);
alignas(256) constexpr auto LoadXyz = LoadCode(0x7u, 0u);
alignas(256) constexpr auto LoadXy = LoadCode(0x3u, 0u);
alignas(256) constexpr auto LoadD16 = LoadCode(0xfu, D16);
alignas(256) constexpr auto LoadStoreD16 = LoadStoreCode(0x7u, D16);
alignas(256) constexpr auto StoreXyz = StoreCode(0x7u, 0u);
alignas(256) constexpr auto StoreXz = StoreCode(0x5u, 0u);
alignas(256) constexpr auto StoreD16 = StoreCode(0x7u, D16);
alignas(256) constexpr auto StoreXzD16 = StoreCode(0x5u, D16);
alignas(256) constexpr auto SampleLz = SamplerCode(0xf09c0f08u);
alignas(256) constexpr auto Gather4Lz = SamplerCode(0xf11c0108u);
alignas(256) constexpr auto GetLod = SamplerCode(0xf1800308u);
alignas(256) constexpr auto SampleAt = SampleAtCode(SampleStep, SampleStart);
alignas(256) constexpr auto SampleWrapped = SampleAtCode(SampleStep, WrappedStart);
alignas(256) constexpr auto SampleOffGrid = SampleAtCode(OffGridStep, SampleStart);
@@ -118,6 +127,28 @@ constexpr std::array<std::uint32_t, 11> StoreValues{
0x40000000u, 0x7f800000u, 0x7fc00000u, 0x000116c2u, 0x3f7fbe77u,
};
constexpr std::array<std::uint32_t, 11> HalfStoreValues{
0x0000u, 0x3c00u, 0x3800u, 0x3555u, 0x8000u, 0xbc00u,
0x4000u, 0x7c00u, 0x7e00u, 0x0001u, 0x3bffu,
};
std::uint32_t HalfToFloatBits(std::uint32_t half) {
const auto sign = (half & 0x8000u) << 16u;
const auto exponent = (half >> 10u) & 0x1fu;
const auto mantissa = half & 0x3ffu;
if (exponent == 31u) return sign | 0x7f800000u | (mantissa << 13u);
if (exponent != 0u) return sign | ((exponent + 112u) << 23u) | (mantissa << 13u);
return sign | std::bit_cast<std::uint32_t>(static_cast<float>(mantissa) * 0x1p-24f);
}
bool HalfNan(std::uint32_t half) {
return (half & 0x7c00u) == 0x7c00u && (half & 0x3ffu) != 0u;
}
std::uint32_t ResultHalf(std::uint32_t index, std::uint32_t component) {
return (Output[index * 4u + component / 2u] >> ((component % 2u) * 16u)) & 0xffffu;
}
class GuestBlock {
public:
GuestBlock() {
@@ -252,17 +283,23 @@ void CheckRoundTrip(AgcDriver::VulkanDevice& device, std::uint8_t* texels) {
}
}
void CheckStore(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::span<const std::uint32_t> code, std::uint32_t dmask, const std::string& what) {
void CheckStore(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::span<const std::uint32_t> code, std::uint32_t dmask, const std::string& what, bool d16 = false) {
std::fill(texels, texels + Threads * 4u, static_cast<std::uint8_t>(0xa5u));
Input.fill(0u);
std::array<std::uint32_t, Threads> expected{};
const auto& source = d16 ? HalfStoreValues : StoreValues;
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const std::array<std::uint32_t, 3> values{StoreValues[tid % 11u], StoreValues[(tid + 4u) % 11u], StoreValues[(tid + 8u) % 11u]};
const std::array<std::uint32_t, 3> values{source[tid % 11u], source[(tid + 4u) % 11u], source[(tid + 8u) % 11u]};
std::uint32_t slot = 0;
for (std::uint32_t component = 0; component < 3u; ++component) {
if (((dmask >> component) & 1u) == 0u) continue;
Input[tid * 4u + slot++] = values[component];
expected[tid] |= StoreField(values[component], component == 2u ? 1023u : 2047u) << (component * 11u);
if (d16) {
Input[tid * 4u + slot / 2u] |= values[component] << ((slot % 2u) * 16u);
} else {
Input[tid * 4u + slot] = values[component];
}
++slot;
expected[tid] |= StoreField(d16 ? HalfToFloatBits(values[component]) : values[component], component == 2u ? 1023u : 2047u) << (component * 11u);
}
}
Run(device, code, TextureDescriptor(texels, UnormFormat, SwizzleXYZ1, Threads), BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u)), 1);
@@ -431,6 +468,148 @@ void CheckSample(AgcDriver::VulkanDevice& device, const std::uint8_t* texels, st
}
}
void CheckUnormLoadD16(AgcDriver::VulkanDevice& device, const std::uint8_t* texels, std::uint32_t swizzle, const std::string& what) {
Run(device, LoadD16, TextureDescriptor(texels, UnormFormat, swizzle, LoadWidth), {}, LoadGroups);
for (std::uint32_t index = 0; index < LoadWidth; ++index) {
for (std::uint32_t component = 0; component < 4u; ++component) {
const auto actual = ResultHalf(index, component);
const auto expected = ChannelValue(LoadTexel(index), swizzle, component);
const auto tolerance = expected == 0.0 || expected == 1.0 ? 0.0 : std::ldexp(1.0, std::ilogb(expected) - 10);
const auto value = static_cast<double>(std::bit_cast<float>(HalfToFloatBits(actual)));
Require((actual & 0x8000u) == 0u && std::fabs(value - expected) <= tolerance, what + ": texel " + std::to_string(index) + " component " + std::to_string(component) + " is " + Hex(actual) + ", expected " + std::to_string(expected) + " within one half ULP");
}
Require(Output[index * 4u + 2u] == 0u && Output[index * 4u + 3u] == 0u, what + ": texel " + std::to_string(index) + " writes more than two result dwords");
}
}
struct FloatLayout {
std::uint32_t format;
std::array<std::uint32_t, 3> offsets;
std::array<std::uint32_t, 3> mantissas;
std::size_t loadOffset;
std::size_t storeOffset;
const char* name;
};
constexpr std::array<FloatLayout, 2> FloatLayouts{{
{FloatFormat, {0u, 10u, 21u}, {5u, 6u, 6u}, 49152, 57344, "R10_G11_B11_FLOAT"},
{NativeFloatFormat, {0u, 11u, 22u}, {6u, 6u, 5u}, 53248, 58368, "R11_G11_B10_FLOAT"},
}};
constexpr std::array<std::array<std::uint32_t, 3>, 8> HalfStoreEdges{{
{0xbc00u, 0x8000u, 0x7e00u},
{0x7c00u, 0x7bffu, 0xfc00u},
{0x3c01u, 0x3c10u, 0x3c30u},
{0x0001u, 0x03ffu, 0x0400u},
{0xfe00u, 0x7c01u, 0x7dffu},
{0x3bffu, 0x7800u, 0x7bf8u},
{0x0010u, 0x0200u, 0x3c08u},
{0x7bf0u, 0x1400u, 0x3c18u},
}};
std::uint32_t SmallFloatField(std::uint32_t index, std::uint32_t component, std::uint32_t mantissa) {
constexpr std::array<std::uint32_t, 8> exponents{0u, 0u, 0u, 1u, 15u, 30u, 31u, 31u};
const auto maximum = (1u << mantissa) - 1u;
const std::array<std::uint32_t, 8> mantissas{0u, 1u, maximum, 0u, 0u, maximum, 0u, 1u};
const auto special = (index + component * 3u) % 8u;
if (index < 64u) return (exponents[special] << mantissa) | mantissas[special];
return ((index * 2654435761u) >> (component * 7u + 3u)) & ((1u << (mantissa + 5u)) - 1u);
}
std::uint32_t ExactHalf(float value) {
if (value == 0.0f) return 0u;
const auto bits = std::bit_cast<std::uint32_t>(value);
return ((((bits >> 23u) & 0xffu) - 112u) << 10u) | ((bits & 0x7fffffu) >> 13u);
}
void CheckFloatLoadD16(AgcDriver::VulkanDevice& device, std::uint8_t* block, const FloatLayout& layout, bool storage) {
auto* texels = block + layout.loadOffset;
const auto texelAt = [&](std::uint32_t index) {
std::uint32_t texel = 0;
for (std::uint32_t component = 0; component < 3u; ++component) texel |= SmallFloatField(index, component, layout.mantissas[component]) << layout.offsets[component];
return texel;
};
for (std::uint32_t index = 0; index < D16Width; ++index) {
const auto texel = texelAt(index);
std::memcpy(texels + index * 4u, &texel, 4u);
}
const auto what = std::string(storage ? "image_load d16 then image_store d16 of " : "image_load d16 of ") + layout.name;
Run(device, storage ? std::span<const std::uint32_t>(LoadStoreD16) : std::span<const std::uint32_t>(LoadD16), TextureDescriptor(texels, layout.format, SwizzleXYZ1, D16Width), {}, D16Groups);
for (std::uint32_t index = 0; index < D16Width; ++index) {
for (std::uint32_t component = 0; component < 4u; ++component) {
const auto actual = ResultHalf(index, component);
auto expected = storage ? 0u : 0x3c00u;
if (component < 3u) {
const auto mantissa = layout.mantissas[component];
const auto field = SmallFloatField(index, component, mantissa);
expected = ((field >> mantissa) << 10u) | ((field & ((1u << mantissa) - 1u)) << (10u - mantissa));
}
Require(HalfNan(expected) ? HalfNan(actual) : actual == expected, what + ": texel " + std::to_string(index) + " component " + std::to_string(component) + " is " + Hex(actual) + ", expected " + Hex(expected));
}
Require(Output[index * 4u + 2u] == 0u && Output[index * 4u + 3u] == 0u, what + ": texel " + std::to_string(index) + " writes more than two result dwords");
}
if (!storage) return;
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(texels), D16Width * 4u, nullptr, "test");
device.WaitIdle();
for (std::uint32_t index = 0; index < D16Width; ++index) {
std::uint32_t actual = 0;
std::memcpy(&actual, texels + index * 4u, 4u);
for (std::uint32_t component = 0; component < 3u; ++component) {
const auto mantissa = layout.mantissas[component];
const auto mask = (1u << (mantissa + 5u)) - 1u;
const auto field = (actual >> layout.offsets[component]) & mask;
const auto expected = SmallFloatField(index, component, mantissa);
const auto nan = [&](std::uint32_t value) { return (value >> mantissa) == 31u && (value & ((1u << mantissa) - 1u)) != 0u; };
Require(nan(expected) ? nan(field) : field == expected, what + ": texel " + std::to_string(index) + " is " + Hex(actual) + " after the round trip, expected " + Hex(texelAt(index)));
}
}
}
void CheckFloatStoreD16(AgcDriver::VulkanDevice& device, std::uint8_t* block, const FloatLayout& layout, std::uint32_t dmask) {
auto* written = block + layout.storeOffset;
auto* reference = written + 256u;
std::array<std::array<std::uint32_t, 3>, Threads> halves{};
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
halves[tid] = tid < HalfStoreEdges.size() ? HalfStoreEdges[tid] : std::array<std::uint32_t, 3>{ExactHalf(FloatValues[tid % 11u]), ExactHalf(FloatValues[(tid + 4u) % 11u]), ExactHalf(FloatValues[(tid + 8u) % 11u])};
}
const auto store = [&](std::uint8_t* target, std::span<const std::uint32_t> code, bool d16) {
std::fill(target, target + Threads * 4u, static_cast<std::uint8_t>(0xa5u));
Input.fill(0u);
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
std::uint32_t slot = 0;
for (std::uint32_t component = 0; component < 3u; ++component) {
if (((dmask >> component) & 1u) == 0u) continue;
if (d16) {
Input[tid * 4u + slot / 2u] |= halves[tid][component] << ((slot % 2u) * 16u);
} else {
Input[tid * 4u + slot] = HalfToFloatBits(halves[tid][component]);
}
++slot;
}
}
Run(device, code, TextureDescriptor(target, layout.format, SwizzleXYZ1, Threads), BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u)), 1);
AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast<std::uintptr_t>(target), Threads * 4u, nullptr, "test");
device.WaitIdle();
};
store(reference, dmask == 0x7u ? std::span<const std::uint32_t>(StoreXyz) : std::span<const std::uint32_t>(StoreXz), false);
store(written, dmask == 0x7u ? std::span<const std::uint32_t>(StoreD16) : std::span<const std::uint32_t>(StoreXzD16), true);
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
std::uint32_t actual = 0;
std::uint32_t widened = 0;
std::memcpy(&actual, written + tid * 4u, 4u);
std::memcpy(&widened, reference + tid * 4u, 4u);
const auto what = std::string("image_store d16 dmask:") + Hex(dmask) + " of " + layout.name + ": texel " + std::to_string(tid);
if (tid >= HalfStoreEdges.size()) {
std::uint32_t expected = 0;
for (std::uint32_t component = 0; component < 3u; ++component) {
if (((dmask >> component) & 1u) != 0u) expected |= SmallFloat(FloatValues[(tid + component * 4u) % 11u], layout.mantissas[component]) << layout.offsets[component];
}
Require(actual == expected, what + " is " + Hex(actual) + ", expected " + Hex(expected));
}
Require(actual == widened, what + " is " + Hex(actual) + ", but the 32-bit store of the same values wrote " + Hex(widened));
}
}
void CheckConvertedPreparation(AgcDriver::VulkanDevice& device, const std::uint8_t* texels) {
std::vector<std::uint32_t> userData(24, 0u);
const auto texture = TextureDescriptor(texels, UnormFormat, SwizzleXYZ1, LoadWidth);
@@ -595,11 +774,16 @@ int main(int argc, char** argv) {
CheckSample(*device, texels, SampleOffGrid, OffGridStep, SampleStart, SwizzleZYX1, BilinearSampler, true, false, "off-grid bilinear image_sample_lz Z Y X 1");
CheckSample(*device, texels, SampleWrappedOffGrid, OffGridStep, WrappedStart, SwizzleXYZ1, WrapBilinearSampler, true, true, "off-grid wrapped bilinear image_sample_lz X Y Z 1");
CheckConvertedPreparation(*device, texels);
RequireRefused(*device, SampleLz, texels, SwizzleXYZ1, BorderSampler, "implemented only with wrap or clamp-to-edge addressing", "image_sample_lz with clamp-to-border addressing");
RequireRefused(*device, Gather4Lz, texels, SwizzleXYZ1, PointSampler, "sampling, gathering or querying LOD of a converted unorm image", "image_gather4_lz");
RequireRefused(*device, GetLod, texels, SwizzleXYZ1, PointSampler, "sampling, gathering or querying LOD of a converted unorm image", "image_get_lod");
RequireRefused(*device, LoadD16, texels, SwizzleXYZ1, {}, "converted unorm image with 16-bit data", "image_load d16");
RequireRefused(*device, StoreD16, texels + StoreOffset, SwizzleXYZ1, BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u)), "converted unorm image with 16-bit data", "image_store d16");
for (const auto& layout : FloatLayouts) {
CheckFloatStoreD16(*device, texels, layout, 0x7u);
CheckFloatStoreD16(*device, texels, layout, 0x5u);
CheckFloatLoadD16(*device, texels, layout, true);
CheckFloatLoadD16(*device, texels, layout, false);
}
CheckUnormLoadD16(*device, texels, SwizzleXYZ1, "image_load d16 X Y Z 1");
CheckUnormLoadD16(*device, texels, SwizzleZYX1, "image_load d16 Z Y X 1");
CheckStore(*device, texels + StoreOffset, StoreD16, 0x7u, "image_store d16 dmask:0x7", true);
CheckStore(*device, texels + StoreOffset, StoreXzD16, 0x5u, "image_store d16 dmask:0x5", true);
RequireRefused(*device, LoadXyzw, texels, SwizzleXYZW, {}, "selects a channel the converted image format does not have", "image_load with DST_SEL X Y Z W");
RequireRefused(*device, StoreXyz, texels + StoreOffset, SwizzleXYZW, BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u)), "selects a channel the converted image format does not have", "image_store with DST_SEL X Y Z W");
std::puts("image converted unorm tests passed");
@@ -80,7 +80,7 @@ alignas(256) constexpr std::array<std::uint32_t, Threads * Inputs> Input{
alignas(256) std::array<std::uint32_t, Wave64Threads * Inputs + Wave64Threads * Results> Buffer{};
alignas(256) std::array<float, Width * Height * Depth> Texels{};
alignas(256) constexpr std::array<std::uint32_t, 168> Code2D{
alignas(256) constexpr std::array<std::uint32_t, 118> Code2D{
0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201,
0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280,
0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280,
@@ -93,15 +93,25 @@ alignas(256) constexpr std::array<std::uint32_t, 168> Code2D{
0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1a00108, 0x00c40c40, 0xbefe031e, 0x7e80032c, 0x7e82032e,
0x7e840304, 0x7e860305, 0xf0880109, 0x00c40d40, 0x7e80032c, 0x7e82032e, 0x7e840304, 0x7e860305,
0xf1a00109, 0x00c40e40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880330, 0xf0880108,
0x40c40f40, 0x7e80032c, 0x7e82032e, 0x7e840330, 0xf0880109, 0x40c41040, 0x7e80033c, 0x7e820328,
0x7e840329, 0x7e860332, 0x7e880333, 0x7e8a0304, 0x7e8c0305, 0xf1b00108, 0x00c41140, 0x7e80033e,
0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xf0c80109, 0x00c41240, 0x7e80033c, 0x7e82032c,
0x7e84032e, 0x7e860304, 0x7e880305, 0xf1b00109, 0x00c41340, 0x7e80033c, 0x7e82032c, 0x7e84032e,
0x7e860330, 0xf0c80109, 0x40c41440, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305,
0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1b00109, 0x00c41540, 0xbefe031e, 0xbf8c3f70, 0xe0701600,
0x40c40f40, 0x7e80032c, 0x7e82032e, 0x7e840330, 0xf0880109, 0x40c41040, 0xbf8c3f70, 0xe0701600,
0x80000a03, 0xe0701604, 0x80000b03, 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610,
0x80000e03, 0xe0701614, 0x80000f03, 0xe0701618, 0x80001003, 0xe070161c, 0x80001103, 0xe0701620,
0x80001203, 0xe0701624, 0x80001303, 0xe0701628, 0x80001403, 0xe070162c, 0x80001503, 0xbf810000,
0x80000e03, 0xe0701614, 0x80000f03, 0xe0701618, 0x80001003, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 102> Code2DOffsets{
0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201,
0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280,
0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280,
0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328,
0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106,
0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6,
0x7e7c02ff, 0x00000101, 0x7e80033c, 0x7e820328, 0x7e840329, 0x7e860332, 0x7e880333, 0x7e8a0304,
0x7e8c0305, 0xf1b00108, 0x00c41140, 0x7e80033e, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305,
0xf0c80109, 0x00c41240, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xf1b00109,
0x00c41340, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860330, 0xf0c80109, 0x40c41440, 0x7e80033c,
0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1b00109,
0x00c41540, 0xbefe031e, 0xbf8c3f70, 0xe0701600, 0x80001103, 0xe0701604, 0x80001203, 0xe0701608,
0x80001303, 0xe070160c, 0x80001403, 0xe0701610, 0x80001503, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 122> Code3D{
@@ -137,57 +147,77 @@ alignas(256) constexpr std::array<std::uint32_t, 87> Code1D{
0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, 0x80000e03, 0xbf810000,
};
constexpr std::array<const char*, 12> Names2D{"image_sample_d", "image_sample_cd", "image_sample_cd (exec)", "image_sample_d_g16", "image_sample_cd_g16", "image_sample_d a16", "image_sample_d_g16 a16", "image_sample_cd_o", "image_sample_d_o_g16", "image_sample_cd_o_g16", "image_sample_d_o_g16 a16", "image_sample_cd_o_g16 (exec)"};
constexpr std::array<bool, 12> Masked2D{false, false, true, false, false, false, false, false, false, false, false, true};
constexpr std::array<std::uint32_t, Threads * 12> Expected2D{
0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x40100000,
0x42cc8000, 0x40100000, 0x40100000, 0x40100000, 0x42cc0000, 0x42cc8000, 0x42cc8000, 0x42cc0000,
0x42cc8000, 0x42cc0000, 0x42cc0000, 0x42ce8000, 0x434b0000, 0x42ce8000, 0x42ce0000, 0x42ce8000,
0x434b0000, 0x434b4000, 0x434b4000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b0000, 0x434c4000,
0x43980000, 0x434c4000, 0x434c0000, 0x434c4000, 0x43980000, 0x43982000, 0x43982000, 0x43980000,
0x43982000, 0x43980000, 0x43980000, 0x4398a000, 0x43988000, 0x4398a000, 0x43988000, 0x4398a000,
0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40c00000,
0x42d40000, 0x40c00000, 0x40c00000, 0x40c00000, 0x42d48000, 0x42d40000, 0x42d40000, 0x42d48000,
0x42d40000, 0x42d48000, 0x42d48000, 0x42d60000, 0x434f4000, 0x42d60000, 0x42d68000, 0x42d60000,
0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x43500000,
0x439a0000, 0x43500000, 0x43500000, 0x43500000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000,
0x439a0000, 0x439a0000, 0x439a0000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000,
0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41200000,
0x42dc0000, 0x41200000, 0x41200000, 0x41200000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000,
0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42de0000, 0x43530000, 0x42de0000, 0x42de0000, 0x42de0000,
0x43534000, 0x43530000, 0x43530000, 0x43534000, 0x43530000, 0x43534000, 0x43534000, 0x43540000,
0x439c2000, 0x43540000, 0x43544000, 0x43540000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000,
0x439c0000, 0x439c0000, 0x439c0000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000,
0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41600000,
0x42e40000, 0x41600000, 0x41600000, 0x41600000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000,
0x42e40000, 0x42e40000, 0x42e40000, 0x42e60000, 0x43570000, 0x42e60000, 0x42e60000, 0x42e60000,
0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43580000,
0x439e0000, 0x43580000, 0x43580000, 0x43580000, 0x439e2000, 0x439e0000, 0x439e0000, 0x439e2000,
0x439e0000, 0x439e2000, 0x439e2000, 0x439e8000, 0x439ea000, 0x439e8000, 0x439ea000, 0x439e8000,
0x41880000, 0x41880000, 0x00000000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41900000,
0x42ec0000, 0x41900000, 0x41900000, 0x00000000, 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000,
0x42ec0000, 0x42ec8000, 0x42ec8000, 0x42ee0000, 0x435b4000, 0x42ee0000, 0x42ee8000, 0x42ee8000,
0x435b0000, 0x435b0000, 0x435b4000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435c0000,
0x43a00000, 0x435c0000, 0x435c0000, 0x435c4000, 0x43a00000, 0x43a00000, 0x43a02000, 0x43a00000,
0x43a00000, 0x43a00000, 0x43a00000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a0a000,
0x41a80000, 0x41a80000, 0x00000000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41b00000,
0x42f40000, 0x41b00000, 0x41b00000, 0x00000000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000,
0x42f40000, 0x42f40000, 0x42f40000, 0x42f60000, 0x435f0000, 0x42f60000, 0x42f60000, 0x42f60000,
0x435f4000, 0x435f0000, 0x435f0000, 0x435f4000, 0x435f0000, 0x435f4000, 0x435f4000, 0x43600000,
0x43a22000, 0x43600000, 0x43604000, 0x43600000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000,
0x43a20000, 0x43a20000, 0x43a20000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000,
0x41c80000, 0x41c80000, 0x00000000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41d00000,
0x42fc0000, 0x41d00000, 0x41d00000, 0x00000000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000,
0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fe0000, 0x43630000, 0x42fe0000, 0x42fe0000, 0x42fe0000,
0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43640000,
0x43a40000, 0x43640000, 0x43640000, 0x43640000, 0x43a42000, 0x43a40000, 0x43a40000, 0x43a42000,
0x43a40000, 0x43a42000, 0x43a42000, 0x43a48000, 0x43a4a000, 0x43a48000, 0x43a4a000, 0x43a48000,
0x41e80000, 0x41e80000, 0x00000000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41f00000,
0x43020000, 0x41f00000, 0x41f00000, 0x00000000, 0x43024000, 0x43020000, 0x43024000, 0x43024000,
0x43020000, 0x43024000, 0x43024000, 0x43030000, 0x43674000, 0x43030000, 0x43034000, 0x43034000,
0x43670000, 0x43670000, 0x00000000, 0x43670000, 0x43670000, 0x43670000, 0x43670000, 0x43680000,
0x43a60000, 0x43680000, 0x43680000, 0x00000000, 0x43a60000, 0x43a60000, 0x43a62000, 0x43a60000,
0x43a60000, 0x43a60000, 0x43a60000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a6a000,
constexpr std::array<const char*, 7> Names2D{"image_sample_d", "image_sample_cd", "image_sample_cd (exec)", "image_sample_d_g16", "image_sample_cd_g16", "image_sample_d a16", "image_sample_d_g16 a16"};
constexpr std::array<bool, 7> Masked2D{false, false, true, false, false, false, false};
constexpr std::array<const char*, 5> Names2DOffsets{"image_sample_cd_o", "image_sample_d_o_g16", "image_sample_cd_o_g16", "image_sample_d_o_g16 a16", "image_sample_cd_o_g16 (exec)"};
constexpr std::array<bool, 5> Masked2DOffsets{false, false, false, false, true};
constexpr std::array<std::uint32_t, Threads * 7> Expected2D{
0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000,
0x42cc0000, 0x42cc8000, 0x42cc8000, 0x42cc0000, 0x42cc8000, 0x42cc0000, 0x42cc0000,
0x434b0000, 0x434b4000, 0x434b4000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b0000,
0x43980000, 0x43982000, 0x43982000, 0x43980000, 0x43982000, 0x43980000, 0x43980000,
0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000,
0x42d48000, 0x42d40000, 0x42d40000, 0x42d48000, 0x42d40000, 0x42d48000, 0x42d48000,
0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000,
0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000,
0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000,
0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000,
0x43534000, 0x43530000, 0x43530000, 0x43534000, 0x43530000, 0x43534000, 0x43534000,
0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000,
0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000,
0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000,
0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000,
0x439e2000, 0x439e0000, 0x439e0000, 0x439e2000, 0x439e0000, 0x439e2000, 0x439e2000,
0x41880000, 0x41880000, 0x00000000, 0x41880000, 0x41880000, 0x41880000, 0x41880000,
0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000,
0x435b0000, 0x435b0000, 0x435b4000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000,
0x43a00000, 0x43a00000, 0x43a02000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000,
0x41a80000, 0x41a80000, 0x00000000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000,
0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000,
0x435f4000, 0x435f0000, 0x435f0000, 0x435f4000, 0x435f0000, 0x435f4000, 0x435f4000,
0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000,
0x41c80000, 0x41c80000, 0x00000000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000,
0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000,
0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000,
0x43a42000, 0x43a40000, 0x43a40000, 0x43a42000, 0x43a40000, 0x43a42000, 0x43a42000,
0x41e80000, 0x41e80000, 0x00000000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000,
0x43024000, 0x43020000, 0x43024000, 0x43024000, 0x43020000, 0x43024000, 0x43024000,
0x43670000, 0x43670000, 0x00000000, 0x43670000, 0x43670000, 0x43670000, 0x43670000,
0x43a60000, 0x43a60000, 0x43a62000, 0x43a60000, 0x43a60000, 0x43a60000, 0x43a60000,
};
constexpr std::array<std::uint32_t, Threads * 5> Expected2DOffsets{
0x40100000, 0x42cc8000, 0x40100000, 0x40100000, 0x40100000,
0x42ce8000, 0x434b0000, 0x42ce8000, 0x42ce0000, 0x42ce8000,
0x434c4000, 0x43980000, 0x434c4000, 0x434c0000, 0x434c4000,
0x4398a000, 0x43988000, 0x4398a000, 0x43988000, 0x4398a000,
0x40c00000, 0x42d40000, 0x40c00000, 0x40c00000, 0x40c00000,
0x42d60000, 0x434f4000, 0x42d60000, 0x42d68000, 0x42d60000,
0x43500000, 0x439a0000, 0x43500000, 0x43500000, 0x43500000,
0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000,
0x41200000, 0x42dc0000, 0x41200000, 0x41200000, 0x41200000,
0x42de0000, 0x43530000, 0x42de0000, 0x42de0000, 0x42de0000,
0x43540000, 0x439c2000, 0x43540000, 0x43544000, 0x43540000,
0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000,
0x41600000, 0x42e40000, 0x41600000, 0x41600000, 0x41600000,
0x42e60000, 0x43570000, 0x42e60000, 0x42e60000, 0x42e60000,
0x43580000, 0x439e0000, 0x43580000, 0x43580000, 0x43580000,
0x439e8000, 0x439ea000, 0x439e8000, 0x439ea000, 0x439e8000,
0x41900000, 0x42ec0000, 0x41900000, 0x41900000, 0x00000000,
0x42ee0000, 0x435b4000, 0x42ee0000, 0x42ee8000, 0x42ee8000,
0x435c0000, 0x43a00000, 0x435c0000, 0x435c0000, 0x435c4000,
0x43a08000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a0a000,
0x41b00000, 0x42f40000, 0x41b00000, 0x41b00000, 0x00000000,
0x42f60000, 0x435f0000, 0x42f60000, 0x42f60000, 0x42f60000,
0x43600000, 0x43a22000, 0x43600000, 0x43604000, 0x43600000,
0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000,
0x41d00000, 0x42fc0000, 0x41d00000, 0x41d00000, 0x00000000,
0x42fe0000, 0x43630000, 0x42fe0000, 0x42fe0000, 0x42fe0000,
0x43640000, 0x43a40000, 0x43640000, 0x43640000, 0x43640000,
0x43a48000, 0x43a4a000, 0x43a48000, 0x43a4a000, 0x43a48000,
0x41f00000, 0x43020000, 0x41f00000, 0x41f00000, 0x00000000,
0x43030000, 0x43674000, 0x43030000, 0x43034000, 0x43034000,
0x43680000, 0x43a60000, 0x43680000, 0x43680000, 0x00000000,
0x43a68000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a6a000,
};
constexpr std::array<const char*, 6> Names3D{"image_sample_d 3D", "image_sample_cd 3D", "image_sample_d_g16 3D", "image_sample_cd_g16 3D", "image_sample_d a16 3D", "image_sample_d_g16 a16 3D"};
constexpr std::array<bool, 6> Masked3D{false, false, false, false, false, false};
@@ -329,12 +359,13 @@ int main() {
if (device->Target().subgroupSize < Threads) {
std::printf("EXEC-masked cases skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize);
}
const bool computedOffsets = device->Target().nonConstantImageOffsets;
if (!computedOffsets) std::puts("2D cases skipped, their computed texel offsets need VK_KHR_maintenance8");
const bool nonConstantOffsets = device->Target().nonConstantImageOffsets;
if (!nonConstantOffsets) std::puts("image_sample_*_o cases skipped without VK_KHR_maintenance8");
std::vector<std::uint32_t> waves{Threads};
if (device->Target().subgroupSize >= Threads) waves.push_back(Wave64Threads);
for (const auto threads : waves) {
if (computedOffsets) Run(*device, Code2D, threads, 9u, Height, 1u, Names2D, Masked2D, Expected2D);
Run(*device, Code2D, threads, 9u, Height, 1u, Names2D, Masked2D, Expected2D);
if (nonConstantOffsets) Run(*device, Code2DOffsets, threads, 9u, Height, 1u, Names2DOffsets, Masked2DOffsets, Expected2DOffsets);
Run(*device, Code3D, threads, 10u, Height, Depth, Names3D, Masked3D, Expected3D);
Run(*device, Code1D, threads, 8u, 1u, 1u, Names1D, Masked1D, Expected1D);
}
@@ -98,14 +98,19 @@ struct SamplerCase {
bool linear;
bool border;
bool atBaseLevel;
bool mirrorX = false;
bool mirrorY = false;
};
constexpr std::array<SamplerCase, 5> Samplers{{
constexpr std::array<SamplerCase, 8> Samplers{{
{"bilinear clamp-to-last-texel, point mips, MAX_LOD 0xfff", {0x00008092u, 0x00fff000u, 0x05500000u, 0u}, true, false, true},
{"point clamp-to-last-texel, point mips", {0x00008092u, 0x00fff000u, 0x04000000u, 0u}, false, false, true},
{"bilinear clamp-to-last-texel, linear mips, MIN_LOD 1, LOD bias 1.5", {0x00008092u, 0x00fff100u, 0x09500180u, 0u}, true, false, false},
{"bilinear clamp-to-border, transparent black", {0x000080b6u, 0x00fff000u, 0x05500000u, 0u}, true, true, true},
{"point clamp-to-border, no mips, transparent black", {0x000080b6u, 0u, 0u, 0u}, false, true, true},
{"bilinear mirror on X and Y, point mips", {0x00008089u, 0x00fff000u, 0x05500000u, 0u}, true, false, true, true, true},
{"point mirror-once on X, clamp-to-last-texel on Y", {0x00008093u, 0x00fff000u, 0x04000000u, 0u}, false, false, true, true, false},
{"bilinear clamp-to-last-texel on X, mirror-once on Y", {0x0000809au, 0x00fff000u, 0x05500000u, 0u}, true, false, true, false, true},
}};
struct Coordinate {
@@ -130,7 +135,7 @@ std::vector<Coordinate> Coordinates(std::uint32_t level) {
for (std::int32_t k = 0; k < width; ++k) {
for (const float fraction : {0.5f, 0.0f, 0.25f, 0.75f}) us.push_back(static_cast<float>(k) + fraction);
}
for (const float edge : {-1.0f, -0.5f, static_cast<float>(width), static_cast<float>(width + 1), static_cast<float>(width) - 0.25f}) us.push_back(edge);
for (const float edge : {-1.0f, -0.5f, static_cast<float>(width), static_cast<float>(width + 1), static_cast<float>(width) - 0.25f, -0.25f, -0.75f, -2.0f, -3.25f, -static_cast<float>(width) + 0.5f, -static_cast<float>(width) - 1.5f}) us.push_back(edge);
constexpr std::array<float, 3> fractions{0.5f, 0.0f, 0.75f};
std::vector<Coordinate> result;
for (std::size_t index = 0; index < us.size(); ++index) {
@@ -142,6 +147,8 @@ std::vector<Coordinate> Coordinates(std::uint32_t level) {
const float v = static_cast<float>(x % height) + 0.5f;
result.push_back({static_cast<float>(x) - 1.0f, v});
result.push_back({static_cast<float>(x) + 2.0f, v});
result.push_back({static_cast<float>(x) + 0.5f, -v});
result.push_back({-static_cast<float>(x) - 0.25f, -1.0f - v});
}
return result;
}
@@ -247,6 +254,8 @@ Samples Run(AgcDriver::VulkanDevice& device, const Program& program, const std::
std::array<double, 4> Fetch(const SamplerCase& sampler, std::uint32_t level, std::int32_t x, std::int32_t y) {
const auto width = LevelWidth(level);
const auto height = LevelHeight(level);
if (sampler.mirrorX && x < 0) x = -x - 1;
if (sampler.mirrorY && y < 0) y = -y - 1;
if (sampler.border && (x < 0 || x >= width || y < 0 || y >= height)) return {0.0, 0.0, 0.0, 0.0};
return TexelOf(level, std::clamp(x, 0, width - 1), std::clamp(y, 0, height - 1));
}
@@ -67,6 +67,7 @@ alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
alignas(256) constexpr std::array<std::uint32_t, 7> PixelOpaque{0x7e100280u, 0x7e120280u, 0x7e140280u, 0x7e1602f2u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 11> PixelRead{0x7e1a0280u, 0xdc308000u, 0x0c02000du, 0xbf8c3f70u, 0x7e10030cu, 0x7e120280u, 0x7e140280u, 0x7e160280u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 13> PixelReadStore{0x7e1a0280u, 0xdc308000u, 0x0c02000du, 0xbf8c3f70u, 0xdc708000u, 0x00020c0du, 0x7e10030cu, 0x7e120280u, 0x7e140280u, 0x7e160280u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 13> PixelCopy{0x7e040280u, 0xe0301000u, 0x80010102u, 0xbf8c3f70u, 0xe0701000u, 0x80020102u, 0x7e100301u, 0x7e120280u, 0x7e140280u, 0x7e1602f2u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u};
constexpr std::array<std::array<float, 4>, 3> Triangle{{
@@ -250,7 +251,7 @@ void ExpectTexels(const std::uint8_t* target, const char* what, std::uint32_t fi
}
}
bool Expect(AgcDriver::VulkanDevice& device, bool unwatch) {
bool Expect(AgcDriver::VulkanDevice& device, bool unwatch, bool readOnly) {
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device);
if (!context.bufferDeviceAddress) {
std::puts("skipped, the device lacks bufferDeviceAddress");
@@ -285,9 +286,11 @@ bool Expect(AgcDriver::VulkanDevice& device, bool unwatch) {
const bool watched = AgcDriver::GuestMemory::Watched(copied.address, TargetBytes);
Require(!unwatch || !watched, "the copied target is still write-watched after its block was taken out of the write watch");
const auto submitted = recorder->Submissions();
const std::uint64_t synced = readOnly && std::getenv("APS5_NO_READ_ONLY_LEASE_SKIP") == nullptr ? 0u : 1u;
const std::string reader = readOnly ? "the read-only address-based draw" : "the storing address-based draw";
const auto pointed = block.Address() + PointedOffset;
const std::array<std::uint32_t, 4> pointer{0u, 0u, static_cast<std::uint32_t>(pointed), static_cast<std::uint32_t>(pointed >> 32u)};
Draw(device, drawn, PixelRead, pointer, whole);
Draw(device, drawn, readOnly ? std::span<const std::uint32_t>(PixelRead) : std::span<const std::uint32_t>(PixelReadStore), pointer, whole);
Require(pending(drawn.address), "the address-based draw did not leave its linear target pending");
Require(recorder->Recording() && recorder->Submissions() == submitted, "the address-based draw did not stay in the open batch");
const auto setsBefore = context.descriptorCache->Counters().sets;
@@ -295,12 +298,12 @@ bool Expect(AgcDriver::VulkanDevice& device, bool unwatch) {
Draw(device, copied, PixelCopy, firstCopy, {{0, 0}, {Width / 2u, Height}});
Require(!pending(drawn.address), "the draw writing a copied buffer did not store the drawn linear target first");
Require(pending(copied.address), "the first copying draw did not leave its linear target pending");
Require(recorder->Recording() && recorder->Submissions() == submitted + 1, "storing the drawn linear target did not submit the batch the address-based draw read it in (" + std::to_string(recorder->Submissions() - submitted) + " submissions)");
Require(recorder->Recording() && recorder->Submissions() == submitted + synced, "storing the drawn linear target after " + reader + " made " + std::to_string(recorder->Submissions() - submitted) + " submissions, expected " + std::to_string(synced));
const auto setsBetween = context.descriptorCache->Counters().sets;
const auto secondCopy = CopyUserData(block.Address() + SecondSourceOffset, SecondOutput);
Draw(device, copied, PixelCopy, secondCopy, {{static_cast<std::int32_t>(Width / 2u), 0}, {Width / 2u, Height}});
if (watched) Require(recorder->Recording() && recorder->Submissions() == submitted + 1, "the second copying draw submitted recorded work");
else Require(recorder->Submissions() == submitted + 2, "the second copying draw into a stored target outside the write watch did not submit once to capture its bytes (" + std::to_string(recorder->Submissions() - submitted - 1) + " submissions)");
if (watched) Require(recorder->Recording() && recorder->Submissions() == submitted + synced, "the second copying draw submitted recorded work after " + reader);
else Require(recorder->Submissions() == submitted + synced + 1, "the second copying draw into a stored target outside the write watch did not submit once to capture its bytes after " + reader + " (" + std::to_string(recorder->Submissions() - submitted - synced) + " submissions)");
const auto setsAfter = context.descriptorCache->Counters().sets;
device.WaitIdle();
Require(FirstOutput[0] == FirstSource, "the first copying draw's output holds " + Hex(FirstOutput[0]) + ", not its source's " + Hex(FirstSource));
@@ -323,8 +326,10 @@ int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
if (!Expect(*device, false)) return VulkanTestSkipped;
if (!Expect(*device, true)) return VulkanTestSkipped;
for (const bool readOnly : {false, true}) {
if (!Expect(*device, false, readOnly)) return VulkanTestSkipped;
if (!Expect(*device, true, readOnly)) return VulkanTestSkipped;
}
std::puts("linear target draw binding tests passed");
return 0;
} catch (const std::exception& error) {
@@ -0,0 +1,109 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <utility>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Width = 16;
constexpr std::uint32_t Height = 8;
constexpr std::uint32_t Groups = Width * Height / Threads;
constexpr std::uint32_t MaxPitch = 256;
constexpr std::uint32_t Type2D = 9;
constexpr std::uint32_t Format32UInt = 20;
constexpr std::uint32_t SwizzleXYZW = 0xfacu;
constexpr std::uint32_t Mark = 0xa0000000u;
alignas(256) std::array<std::uint32_t, Width * Height * 4> Output{};
alignas(256) std::array<std::uint32_t, MaxPitch * Height> Texels{};
alignas(256) constexpr std::array<std::uint32_t, 16> LoadXy{
0x7e080218u, 0x343c0885u, 0x4a3c3d00u, 0x34063c84u, 0x2c3e3c84u, 0x363c3c8fu, 0x7e140280u, 0x7e160280u,
0x7e180280u, 0x7e1a0280u, 0xf0001f08u, 0x00010a1eu, 0xbf8c3f70u, 0xe0781000u, 0x80000a03u, 0xbf810000u,
};
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
std::array<std::uint32_t, 8> TextureDescriptor(std::uint32_t depth) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(Texels.data()));
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Width - 1u) & 3u) << 30u),
((Width - 1u) >> 2u) | ((Height - 1u) << 14u),
SwizzleXYZW | (Type2D << 28u),
depth,
0u,
0u,
0u,
};
}
void Run(AgcDriver::VulkanDevice& device, std::uint32_t depth) {
Output.fill(0xdeadbeefu);
std::vector<std::uint32_t> userData(24, 0u);
const auto outputAddress = reinterpret_cast<std::uintptr_t>(Output.data());
const std::array<std::uint32_t, 4> output{static_cast<std::uint32_t>(outputAddress), static_cast<std::uint32_t>((outputAddress >> 32u) & 0xffffu), static_cast<std::uint32_t>(Output.size() * 4u), 0x31016facu};
const auto texture = TextureDescriptor(depth);
std::copy(output.begin(), output.end(), userData.begin());
std::copy(texture.begin(), texture.end(), userData.begin() + 4);
const std::span<const std::uint32_t> code = LoadXy;
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {true, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, Groups, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void Check(AgcDriver::VulkanDevice& device, std::uint32_t depth, std::uint32_t pitch) {
Run(device, depth);
for (std::uint32_t index = 0; index < Width * Height; ++index) {
const auto x = index % Width;
const auto y = index / Width;
const auto expected = Mark | (y * pitch + x);
const auto actual = Output[index * 4u];
Require(actual == expected, "linear 2D texture with DEPTH " + std::to_string(depth) + ": texel (" + std::to_string(x) + ", " + std::to_string(y) + ") is " + Hex(actual) + ", expected " + Hex(expected) + " (row pitch " + std::to_string(pitch) + ")");
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
for (std::uint32_t index = 0; index < Texels.size(); ++index) Texels[index] = Mark | index;
for (const auto& [depth, pitch] : std::array<std::pair<std::uint32_t, std::uint32_t>, 6>{{{15u, 64u}, {63u, 64u}, {64u, 128u}, {127u, 128u}, {128u, 192u}, {255u, 256u}}}) {
Check(*device, depth, pitch);
}
std::puts("linear texture pitch tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -84,7 +84,8 @@ void Check(AgcDriver::VulkanDevice& device, AgcDriver::Graphics::ShaderPath path
fragment.Initialize(1u);
const std::array<std::uint32_t, 7> pixelCode{0xc8020002u, 0xc8060102u, 0xc80a0202u, 0xc80e0302u, 0xf800180fu, 0x03020100u, 0xbf810000u};
fragment.snapshot->code.resize(64);
fragment.snapshot->code.insert(fragment.snapshot->code.end(), pixelCode.begin(), pixelCode.end());
auto& fragmentCode = fragment.snapshot->code.Mutable();
fragmentCode.insert(fragmentCode.end(), pixelCode.begin(), pixelCode.end());
ShaderRegistry registry;
for (const auto* fixture : {&front, &back, &domain, &fragment}) registry.emplace(fixture->snapshot->codeAddress, std::vector<std::shared_ptr<const ShaderSnapshot>>{fixture->snapshot});
AgcDriver::QueueState queue{};
@@ -338,7 +338,7 @@ void Registration(AgcDriver::VulkanDevice& device) {
Require(invocation.Materialize(*capture)->spirv.data() == result->spirv.data(), "prepared handle did not retain its artifact");
auto changed = makeSnapshot();
changed->headerAddress = replacement->headerAddress;
changed->code[0] ^= 1;
changed->code.Mutable()[0] ^= 1;
PublishRegisteredShader(registry, changed);
Require(registry->at(0x10000).back() == changed && registry->at(0x10000).size() == 1 && old.expired(), "changed shader code retained obsolete headers");
auto metadata = makeSnapshot();
@@ -596,6 +596,7 @@ void SharedCodeHeaders() {
};
const auto [vertexSnapshot, meshSnapshot] = snapshots();
Require(vertexSnapshot->registeredState != meshSnapshot->registeredState, "a header with other registers shared another header's registered state");
Require(vertexSnapshot->code.SharesWith(meshSnapshot->code), "two headers of one program keep separate copies of its code");
Shader copy = vertex.shader;
copy.user_data = nullptr;
AgcDriverResolveShaderAbi_nid_postfix(&copy, {}, primitive);
@@ -343,8 +343,10 @@ void CopyPendingImage(std::size_t bytes, const char* phase) {
int main() {
#ifdef _WIN32
_putenv_s("APS5_PIPELINED_DRAWS", "1");
_putenv_s("APS5_NO_IDLE_STORE", "1");
#else
setenv("APS5_PIPELINED_DRAWS", "1", 1);
setenv("APS5_NO_IDLE_STORE", "1", 1);
#endif
try {
{
@@ -45,7 +45,7 @@ void QueuedCode(bool cached) {
const auto after = ReadRawShader(address);
finished.store(true, std::memory_order_release);
DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Submission);
if (after->code.front() != 0x7e0002f2u || (before != nullptr && (before->code.front() != 0x7e000200u || before == after))) {
if (after->code[0] != 0x7e0002f2u || (before != nullptr && (before->code[0] != 0x7e000200u || before == after))) {
throw std::runtime_error(std::string(cached ? "cached" : "fresh") + " raw shader read ignored a queued write to its code");
}
}
@@ -0,0 +1,158 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <mutex>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::size_t BlockBytes = 1u << 20u;
constexpr std::uint32_t Width = 256;
constexpr std::uint32_t Height = 128;
constexpr std::size_t ImageBytes = Width * Height * 4u;
constexpr std::uint32_t Format32UInt = 20;
constexpr std::uint32_t Type2D = 9;
constexpr std::uint32_t Initial = 0x3f800000u;
constexpr std::uint32_t Loaded = 0x5a5a1234u;
constexpr std::uint32_t Stored = 0x22222222u;
constexpr std::size_t SourceOffset = BlockBytes / 2u;
constexpr std::size_t LoadedOffset = SourceOffset + 0x40u;
constexpr std::size_t QueryOffset = SourceOffset + 0x10000u;
alignas(256) constexpr std::array<std::uint32_t, 10> LoadToImageCode{0x7e000200u, 0x7e020201u, 0xdc308040u, 0x027d0000u, 0xbf8c3f70u, 0x7e000280u, 0x7e020280u, 0xf0200108u, 0x00010200u, 0xbf810000u};
alignas(256) constexpr std::array<std::uint32_t, 7> StoreGlobalCode{0x7e000208u, 0x7e020209u, 0x7e0402ffu, Stored, 0xdc708100u, 0x007d0200u, 0xbf810000u};
class GuestBlock {
public:
GuestBlock() {
#ifdef _WIN32
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
block = static_cast<std::uint8_t*>(std::aligned_alloc(65536, BlockBytes));
#endif
Require(block != nullptr, "read-only lease writer: cannot allocate the guest block");
std::fill(Words(), Words() + BlockBytes / 4u, Initial);
Words()[LoadedOffset / 4u] = Loaded;
GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(block);
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
#else
std::free(block);
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint32_t* Words() const { return reinterpret_cast<std::uint32_t*>(block); }
std::uint64_t Address() const { return static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(block)); }
private:
std::uint8_t* block = nullptr;
};
ShaderRecompiler::RecompileResult Compile(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const ShaderRecompiler::SpirvTarget& target) {
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{64u, 0, userData, compute, std::nullopt, std::nullopt, memory},
target,
{0, 0, 0, 128}
};
request.useCache = false;
return ShaderRecompiler::Recompile(request);
}
std::vector<std::uint32_t> LoadUserData(std::uint64_t image, std::uint64_t source) {
std::vector<std::uint32_t> userData(12, 0u);
userData[0] = static_cast<std::uint32_t>(source);
userData[1] = static_cast<std::uint32_t>(source >> 32u);
userData[4] = static_cast<std::uint32_t>(image >> 8u);
userData[5] = static_cast<std::uint32_t>((image >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Width - 1u) & 3u) << 30u);
userData[6] = ((Width - 1u) >> 2u) | ((Height - 1u) << 14u);
userData[7] = 0xfacu | (Type2D << 28u);
return userData;
}
std::vector<std::uint32_t> StoreUserData(std::uint64_t base) {
std::vector<std::uint32_t> userData(10, 0u);
userData[8] = static_cast<std::uint32_t>(base);
userData[9] = static_cast<std::uint32_t>(base >> 32u);
return userData;
}
struct Pending {
bool read;
std::uint64_t writer;
std::uint64_t open;
};
Pending PendingAt(std::uint64_t address) {
std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex());
const auto* recorder = AgcDriver::Graphics::Recorder::Active();
Require(recorder != nullptr, "read-only lease writer: no active recorder");
return {recorder->DescribePendingRead(address, 64u).has_value(), recorder->PendingWriterSerial(address, 64u), recorder->OpenSerial()};
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
const bool counted = std::getenv("APS5_NO_READ_ONLY_LEASE_SKIP") != nullptr;
GuestBlock guest;
const auto target = device->Target();
const auto query = guest.Address() + QueryOffset;
device->WaitIdle();
const auto reader = Compile(LoadToImageCode, LoadUserData(guest.Address(), guest.Address() + SourceOffset), target);
device->Dispatch(reader, 1u, 1u, 1u, {}, reinterpret_cast<std::uintptr_t>(LoadToImageCode.data()));
const auto afterRead = PendingAt(query);
Require(afterRead.read, "read-only lease writer: the build that only loads through BDA noted no lease over the guest block");
if (counted) Require(afterRead.writer == afterRead.open, "read-only lease writer: APS5_NO_READ_ONLY_LEASE_SKIP did not count the read-only lease as a writer");
else Require(afterRead.writer == 0, "read-only lease writer: the lease of a build that never stores through BDA counts as a writer (serial " + std::to_string(afterRead.writer) + ")");
Require(AgcDriver::Graphics::StorageTexture::FlushPending(guest.Address(), ImageBytes, nullptr, "test"), "read-only lease writer: the read-only build's image store left no results pending");
device->WaitIdle();
Require(guest.Words()[0] == Loaded, "read-only lease writer: the read-only build did not store the loaded word into its image");
const auto storer = Compile(StoreGlobalCode, StoreUserData(guest.Address() + SourceOffset), target);
device->Dispatch(storer, 1u, 1u, 1u, {}, reinterpret_cast<std::uintptr_t>(StoreGlobalCode.data()));
const auto afterStore = PendingAt(query);
Require(afterStore.writer == afterStore.open, "read-only lease writer: the lease of a build that stores through BDA does not count as a writer");
device->WaitIdle();
Require(guest.Words()[(SourceOffset + 0x100u) / 4u] == Stored, "read-only lease writer: the storing build's word did not reach guest memory");
std::puts("read-only lease writer tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -0,0 +1,120 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 64;
constexpr std::uint32_t Inputs = 4;
constexpr std::uint32_t Results = 16;
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
alignas(256) constexpr std::array<std::uint32_t, 54> Code{
0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd4a10008u, 0x000100ffu, 0x80000000u,
0xd501000au, 0x00210280u, 0xd4a40008u, 0x0001fe80u, 0xff00ff00u, 0xd501000bu, 0x00210280u, 0xd4a20008u,
0x000208ffu, 0xfffffffbu, 0xd501000cu, 0x00210280u, 0xbe8918ffu, 0x80000000u, 0x7e1a0209u, 0xd701000eu,
0x0001fe84u, 0x80000000u, 0xd7000010u, 0x0001fe84u, 0x80000000u, 0xd5770a12u, 0x03fd0481u, 0xfffffffbu,
0xd5760a14u, 0x03fd0481u, 0xfffffffbu, 0x918c84ffu, 0x80000000u, 0x7e2c020cu, 0x7e2e020du, 0xbe9003ffu,
0x00280010u, 0x950e10ffu, 0x80000000u, 0x7e30020eu, 0x7e32020fu, 0xe0781000u, 0x80010a02u, 0xe0781010u,
0x80010e02u, 0xe0781020u, 0x80011202u, 0xe0781030u, 0x80011602u, 0xbf810000u,
};
constexpr std::array<const char*, Results> Names{
"v_cmp_lt_i64 0x80000000, 0",
"v_cmp_gt_i64 0, 0xff00ff00",
"v_cmp_eq_i64 0xfffffffb, -5",
"s_flbit_i32_i64 0x80000000",
"v_ashrrev_i64 4, 0x80000000 (low)",
"v_ashrrev_i64 4, 0x80000000 (high)",
"v_lshrrev_b64 4, 0x80000000 (low)",
"v_lshrrev_b64 4, 0x80000000 (high)",
"v_mad_i64_i32 1, 2, 0xfffffffb (low)",
"v_mad_i64_i32 1, 2, 0xfffffffb (high)",
"v_mad_u64_u32 1, 2, 0xfffffffb (low)",
"v_mad_u64_u32 1, 2, 0xfffffffb (high)",
"s_ashr_i64 0x80000000, 4 (low)",
"s_ashr_i64 0x80000000, 4 (high)",
"s_bfe_i64 0x80000000, offset 16 width 40 (low)",
"s_bfe_i64 0x80000000, offset 16 width 40 (high)",
};
constexpr std::array<std::uint32_t, Results> Expected{
0x00000001u, 0x00000001u, 0x00000001u, 0x00000021u, 0xf8000000u, 0xffffffffu, 0x08000000u, 0x00000000u,
0xfffffffdu, 0xffffffffu, 0xfffffffdu, 0x00000000u, 0xf8000000u, 0xffffffffu, 0xffff8000u, 0xffffffffu,
};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x31016facu};
}
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
void Run(AgcDriver::VulkanDevice& device) {
Input.fill(0u);
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
Input[lane * Inputs] = 0xfffffffbu;
Input[lane * Inputs + 1u] = 0xffffffffu;
}
Output.fill(0xdeadbeefu);
std::vector<std::uint32_t> userData(8, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
const std::span<const std::uint32_t> code(Code);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void Check() {
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
for (std::uint32_t column = 0; column < Results; ++column) {
const auto value = Output[lane * Results + column];
Require(value == Expected[column], std::string(Names[column]) + " in thread " + std::to_string(lane) + " is " + Hex(value) + ", expected " + Hex(Expected[column]));
}
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Run(*device);
Check();
std::puts("signed 64-bit literal tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -1,4 +1,5 @@
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
@@ -13,6 +14,7 @@
#include <mutex>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
namespace {
@@ -86,8 +88,9 @@ int main(int argc, char** argv) {
}
std::vector<std::uint32_t> storage((BlockBytes + 0x10000u) / 4u);
const bool wake = argc == 2 && std::string_view(argv[1]) == "--idle-epoch-wake";
std::vector<std::uint32_t> nextStorage(wake ? (BlockBytes + 0x10000u) / 4u : 0u);
auto* nextTexels = wake ? reinterpret_cast<std::uint32_t*>((reinterpret_cast<std::uintptr_t>(nextStorage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}) : nullptr;
const bool disabled = argc == 2 && std::string_view(argv[1]) == "--disabled-idle-store";
std::vector<std::uint32_t> nextStorage(wake || disabled ? (BlockBytes + 0x10000u) / 4u : 0u);
auto* nextTexels = wake || disabled ? reinterpret_cast<std::uint32_t*>((reinterpret_cast<std::uintptr_t>(nextStorage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}) : nullptr;
struct ShutdownGuard {
~ShutdownGuard() { try { AgcDriverShutdown_nid_postfix(); } catch (...) {} }
} shutdown;
@@ -127,9 +130,30 @@ int main(int argc, char** argv) {
cpu = nextTexels;
Store(nextTexels);
}
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
while (!stored() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(stored(), "the queue-idle store did not bring the result to guest memory without a direct flush or further submission");
if (disabled) {
const auto pendingDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
while (!AgcDriver::Graphics::StorageTexture::AnyPending() && std::chrono::steady_clock::now() < pendingDeadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "disabled idle store fixture did not observe pending storage results");
const auto dormant = [] { return AgcDriver::DriverDetail::queue0Dormant.load(std::memory_order_relaxed); };
const auto dormantDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
while (!dormant() && std::chrono::steady_clock::now() < dormantDeadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(dormant(), "with APS5_NO_IDLE_STORE queue 0 kept polling over pending storage results with every queue idle instead of waiting");
std::this_thread::sleep_for(std::chrono::milliseconds(200));
Require(AgcDriver::Graphics::StorageTexture::AnyPending() && !stored(), "with APS5_NO_IDLE_STORE the idle store still brought the result to guest memory");
const std::array<std::pair<std::uint64_t, std::uint64_t>, 1> nextRange{{{AddressOf(nextTexels), AddressOf(nextTexels) + bytes}}};
Require(!AgcDriver::Graphics::StorageTexture::AnyPendingOverlaps(nextRange), "disabled idle store fixture found a result pending over the second texture before its dispatch");
Store(nextTexels);
const auto wakeDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
while (!AgcDriver::Graphics::StorageTexture::AnyPendingOverlaps(nextRange) && std::chrono::steady_clock::now() < wakeDeadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(AgcDriver::Graphics::StorageTexture::AnyPendingOverlaps(nextRange), "queue 0 did not run a submission made after it waited with APS5_NO_IDLE_STORE");
const auto parkedAgainDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
while (!dormant() && std::chrono::steady_clock::now() < parkedAgainDeadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(dormant(), "with APS5_NO_IDLE_STORE queue 0 kept polling after running the second dispatch instead of waiting again");
} else {
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10);
while (!stored() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(stored(), "the queue-idle store did not bring the result to guest memory without a direct flush or further submission");
}
AgcDriverShutdown_nid_postfix();
{
@@ -0,0 +1,156 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <mutex>
#include <span>
#include <vector>
namespace AgcDriver {
struct VulkanDeviceTestAccess {
static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); }
};
}
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 8;
constexpr std::uint32_t Side = 256;
constexpr std::uint32_t Format32UInt = 20;
constexpr std::uint32_t TileR64KBX = 0x1b;
constexpr std::uint32_t Type2D = 9;
constexpr std::size_t BlockBytes = 0x60000;
constexpr std::uint32_t Untouched = 0xdeadbeefu;
constexpr std::uint32_t Stored = 0x12345678u;
alignas(256) constexpr std::array<std::uint32_t, 7> StoreCode{
0x7e040300u, 0x7e060280u, 0x7e0202ffu, Stored, 0xf0200108u, 0x00020102u, 0xbf810000u,
};
std::array<std::uint32_t, 8> TextureDescriptor(const std::uint32_t* texels) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(texels));
return {
static_cast<std::uint32_t>(address >> 8u),
static_cast<std::uint32_t>((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Side - 1u) & 3u) << 30u),
((Side - 1u) >> 2u) | ((Side - 1u) << 14u),
0xfacu | (TileR64KBX << 20u) | (Type2D << 28u),
0u,
0u,
0u,
0u,
};
}
void Store(AgcDriver::VulkanDevice& device, const std::uint32_t* texels) {
std::vector<std::uint32_t> userData(16, 0u);
const auto texture = TextureDescriptor(texels);
std::copy(texture.begin(), texture.end(), userData.begin() + 8);
const std::span<const std::uint32_t> code(StoreCode);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
device.Target(),
{0, 0, 0, 128}
};
request.useCache = false;
const auto result = ShaderRecompiler::Recompile(request);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void FlushAll(const char* reason) {
std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex());
AgcDriver::Graphics::StorageTexture::FlushAllPending(reason);
}
void Label(AgcDriver::VulkanDevice& device, const std::uint32_t* label) {
{
std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex());
const std::uint32_t value = 1;
static_cast<void>(device.WriteLabelOnGpu(static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(label)), std::as_bytes(std::span(&value, 1)), 1, 0, false));
device.SubmitRecorded(false);
}
device.WaitIdle();
}
}
int main(int argc, char** argv) {
try {
const bool label = argc > 1 && std::strcmp(argv[1], "--label") == 0;
Require(!label || AgcDriver::Graphics::StorageTexture::StoreAtLabelRequested(std::getenv("APS5_STORE_AT_LABEL")), "two devices: --label needs APS5_STORE_AT_LABEL=1");
const auto first = OpenVulkanTestDevice();
if (!first) return VulkanTestSkipped;
std::vector<std::uint32_t> storage((BlockBytes + 0x10000u) / 4u);
auto* texels = reinterpret_cast<std::uint32_t*>((reinterpret_cast<std::uintptr_t>(storage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu});
std::fill(texels, texels + BlockBytes / 4u, Untouched);
std::array<std::uint32_t, 4> labelWords{};
{
GuestAllocations::Mutation mutation;
mutation.Add(texels, BlockBytes, true, true, true);
}
const auto bytes = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor(texels))).guestBytes;
const volatile std::uint32_t* cpu = texels;
const auto stored = [&] {
for (std::size_t index = 0; index < bytes / 4u; ++index) {
if (cpu[index] == Stored) return true;
}
return false;
};
Store(*first, texels);
Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: the first device's image store left no pending results");
{
const auto second = OpenVulkanTestDevice();
Require(second != nullptr, "two devices: the second device did not open");
Require(AgcDriver::Graphics::Recorder::Active() == AgcDriver::VulkanDeviceTestAccess::ContextOf(*second).recorder, "two devices: the second device did not activate its recorder");
second->StoreIdleResults(false);
second->WaitIdle();
Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: the second device's idle store took the first device's pending results");
Require(!stored(), "two devices: the first device's results reached guest memory through the second device's idle store");
if (label) {
Label(*second, labelWords.data());
Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: the second device's label store took the first device's pending results");
Require(!stored(), "two devices: the first device's results reached guest memory through the second device's label store");
Label(*first, labelWords.data());
Require(!AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: the first device's label store left its results pending");
Require(stored(), "two devices: the first device's label store did not bring its results to guest memory");
} else {
FlushAll("memory access");
first->WaitIdle();
Require(!AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: a flush with the second device active left the first device's results pending");
Require(stored(), "two devices: a flush with the second device active did not bring the first device's results to guest memory");
}
second->WaitIdle();
}
{
GuestAllocations::Mutation mutation;
mutation.Remove(texels);
}
std::puts("two device pending store tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}
@@ -1,27 +1,129 @@
#include "VulkanTestDevice.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Submit/include/Acb.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <array>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace AgcDriver::DriverDetail {
struct DriverTestAccess {
static void PrepareSamplingForRejectedAhead() {
auto& driver = Driver::Get();
driver.evidenceValidations.store(1u, std::memory_order_relaxed);
driver.evidenceReads.store(15u, std::memory_order_relaxed);
}
};
}
namespace {
void Require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
constexpr std::size_t BlockBytes = 65536;
struct GuestBlock {
explicit GuestBlock(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) {
#ifdef _WIN32
if (watched) {
data = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes));
GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes);
} else {
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
}
#else
if (watched) {
void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(raw != MAP_FAILED, "cannot map a guest block");
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
const auto aligned = (begin + BlockBytes - 1) & ~(static_cast<std::uintptr_t>(BlockBytes) - 1);
if (aligned != begin) munmap(raw, aligned - begin);
if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + BlockBytes - aligned);
data = reinterpret_cast<std::uint8_t*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes);
} else {
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
}
#endif
Require(data != nullptr, "cannot allocate a guest block");
GuestAllocations::Mutation mutation;
mutation.Add(data, bytes, true, true, true);
}
~GuestBlock() {
{
GuestAllocations::Mutation mutation;
mutation.Remove(data);
}
#ifdef _WIN32
if (watched) {
GuestArena::GuestArenaReset_nid_postfix(data, bytes);
GuestArena::GuestArenaRelease_nid_postfix(data, bytes);
} else {
VirtualFree(data, 0, MEM_RELEASE);
}
#else
if (watched) {
munmap(data, bytes);
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes);
} else {
std::free(data);
}
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint32_t* Dwords(std::size_t byteOffset) const { return reinterpret_cast<std::uint32_t*>(data + byteOffset); }
std::uint8_t* data = nullptr;
std::size_t bytes = 0;
bool watched = false;
};
alignas(256) std::array<std::uint32_t, 7> CounterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000281u, 0xe0c80000u, 0x80020000u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 9> WriterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000202u, 0x7e020203u, 0x7e040204u, 0xe07c0000u, 0x80020000u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 9> ImageWriterCode{0x7e000280u, 0x7e020280u, 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0xf0201f08u, 0x00000200u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 4> GdsWriterCode{0x7e000280u, 0xd8360000u, 0x00000000u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 16> StreamCopyCode{0xd7460008u, 0x04010e08u, 0x8f6a8708u, 0xd76d0009u, 0x0401806au, 0xe00c2000u, 0x80000008u, 0xe00c2000u, 0x80000409u, 0xbf8c3f71u, 0xe01c2000u, 0x80010008u, 0xbf8c3f70u, 0xe01c2000u, 0x80010409u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 9> FillKernelCode{0xd7460004u, 0x04010c08u, 0x7e000204u, 0x7e020205u, 0x7e040206u, 0x7e060207u, 0xe01c2000u, 0x80000004u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 69> DwordPatternFillCode{
0xbfa00003u, 0xd7460002u, 0x04010c0au, 0x7da80408u, 0xbf88003fu, 0x7e000c09u, 0xbf070980u, 0x858a807eu,
0x7e005700u, 0x100000ffu, 0x4f800000u, 0x7e060f00u, 0xd5766a00u, 0x02020609u, 0x7d8a0280u, 0x4c020080u,
0x02000101u, 0xd56a0001u, 0x00020700u, 0x4c000303u, 0x4a020303u, 0x02000101u, 0xd56a0000u, 0x00020500u,
0xd5690001u, 0x00020009u, 0x4c060302u, 0x7d860609u, 0x7d8c02f9u, 0x06068c02u, 0x87ea6a0cu, 0x50000080u,
0xd5286a00u, 0x003200c1u, 0xd5010000u, 0x002a00c1u, 0xd5690000u, 0x00020009u, 0x4c000102u, 0x7d0a0080u,
0xbe88246au, 0xbf880015u, 0x7d0a0081u, 0xbe8a246au, 0xbf88000cu, 0x7d0a0082u, 0xbeea246au, 0xbf880003u,
0x7e000207u, 0xe0102000u, 0x80000002u, 0x8afe7e6au, 0xbf880003u, 0x7e000206u, 0xe0102000u, 0x80000002u,
0xbefe046au, 0x8afe7e0au, 0xbf880003u, 0x7e000205u, 0xe0102000u, 0x80000002u, 0xbefe040au, 0x8afe7e08u,
0xbf880003u, 0x7e000204u, 0xe0102000u, 0x80000002u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, 4> First{};
alignas(256) std::array<std::uint32_t, 4> Stale{};
alignas(256) std::array<std::uint32_t, 4> Fresh{};
alignas(256) std::array<std::uint32_t, 4> Arguments{1, 1, 1, 0};
alignas(256) std::array<std::uint32_t, 4> FirstDescriptor{};
alignas(256) std::array<std::uint32_t, 4> SecondDescriptor{};
alignas(256) std::array<std::uint32_t, 4> CopySource{};
alignas(256) std::array<std::uint32_t, 4> ArgumentsDescriptor{};
alignas(256) volatile std::uint32_t Gate = 1;
alignas(256) volatile std::uint32_t Done = 0;
@@ -41,22 +143,113 @@ void SetShaderRegister(std::vector<std::uint32_t>& words, std::uint32_t offset,
words.insert(words.end(), {0xc0017600u, offset, value});
}
void Program(std::vector<std::uint32_t>& words, const void* code, std::initializer_list<std::uint32_t> userData) {
void Program(std::vector<std::uint32_t>& words, const void* code, std::initializer_list<std::uint32_t> userData, std::uint32_t threadsX = 1, bool groupIdX = false) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(code));
SetShaderRegister(words, 0x20c, static_cast<std::uint32_t>(address >> 8u));
SetShaderRegister(words, 0x20d, static_cast<std::uint32_t>(address >> 40u) & 0xffu);
for (const std::uint32_t offset : {0x207u, 0x208u, 0x209u}) SetShaderRegister(words, offset, 1);
SetShaderRegister(words, 0x207, threadsX);
SetShaderRegister(words, 0x208, 1);
SetShaderRegister(words, 0x209, 1);
SetShaderRegister(words, 0x212, 0);
SetShaderRegister(words, 0x213, static_cast<std::uint32_t>(userData.size()) << 1u);
SetShaderRegister(words, 0x213, (static_cast<std::uint32_t>(userData.size()) << 1u) | (groupIdX ? (1u << 7u) : 0u));
std::uint32_t offset = 0x240;
for (const auto value : userData) SetShaderRegister(words, offset++, value);
}
void Count(std::vector<std::uint32_t>& words, const void* descriptor) {
Program(words, CounterCode.data(), {Low(descriptor), High(descriptor)});
void Count(std::vector<std::uint32_t>& words, const void* descriptor, std::uint32_t threadsX = 1) {
Program(words, CounterCode.data(), {Low(descriptor), High(descriptor)}, threadsX);
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
void CountIndirect(std::vector<std::uint32_t>& words, const void* descriptor, const void* arguments) {
Program(words, CounterCode.data(), {Low(descriptor), High(descriptor)});
words.insert(words.end(), {0xc0021600u, Low(arguments), High(arguments), 0x8041u});
}
void WriteThreeDwords(std::vector<std::uint32_t>& words, const void* targetDescriptor, std::uint32_t d0, std::uint32_t d1, std::uint32_t d2) {
Program(words, WriterCode.data(), {Low(targetDescriptor), High(targetDescriptor), d0, d1, d2});
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
void ImageWriteDescriptor(std::vector<std::uint32_t>& words, const void* destination, const std::array<std::uint32_t, 4>& pattern) {
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(destination));
constexpr std::uint32_t format = 75u;
constexpr std::uint32_t width = 16u;
constexpr std::uint32_t height = 1u;
constexpr std::uint32_t type2d = 9u;
Program(words, ImageWriterCode.data(), {
static_cast<std::uint32_t>(address >> 8u),
(static_cast<std::uint32_t>(address >> 40u) & 0xffu) | (format << 20u) | (((width - 1u) & 3u) << 30u),
((width - 1u) >> 2u) | ((height - 1u) << 14u),
0xfacu | (type2d << 28u),
0u, 0u, 0u, 0u,
pattern[0], pattern[1], pattern[2], pattern[3]
});
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
void GdsWrite(std::vector<std::uint32_t>& words) {
Program(words, GdsWriterCode.data(), {});
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
void StreamCopyDescriptor(std::vector<std::uint32_t>& words, const void* source, const void* destination) {
constexpr std::uint32_t format = 0x0004bfacu;
Program(words, StreamCopyCode.data(), {
Low(source), (High(source) & 0xffffu) | (16u << 16u), 1u, format,
Low(destination), (High(destination) & 0xffffu) | (16u << 16u), 1u, format
}, 64, true);
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
void FillKernelDescriptor(std::vector<std::uint32_t>& words, const void* destination, const std::array<std::uint32_t, 4>& pattern) {
constexpr std::uint32_t format = 0x0004bfacu;
Program(words, FillKernelCode.data(), {
Low(destination), (High(destination) & 0xffffu) | (16u << 16u), 1u, format,
pattern[0], pattern[1], pattern[2], pattern[3]
}, 64, true);
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
void DwordPatternFillDescriptor(std::vector<std::uint32_t>& words, const void* destination, const std::array<std::uint32_t, 4>& pattern) {
constexpr std::uint32_t format = 0x00014facu;
Program(words, DwordPatternFillCode.data(), {
Low(destination), (High(destination) & 0xffffu) | (4u << 16u), 4u, format,
pattern[0], pattern[1], pattern[2], pattern[3], 4u, 4u
}, 64, true);
words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u});
}
template <typename EmitUpdate>
void RunUpdateAheadCase(const std::string& label, std::uint32_t* targetDescriptor, const std::array<std::uint32_t, 4>& stale, EmitUpdate&& emitUpdate, std::uint64_t expectedAdopted = 4, bool exactAdopted = false) {
First[0] = 0;
Stale[0] = 0;
Fresh[0] = 0;
Done = 0;
std::memcpy(targetDescriptor, stale.data(), sizeof(stale));
const auto adoptedBefore = AgcDriverDispatchesAdoptedAhead_nid_postfix();
std::vector<std::uint32_t> words;
words.insert(words.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u});
Count(words, FirstDescriptor.data());
WriteThreeDwords(words, ArgumentsDescriptor.data(), 1u, 1u, 1u);
emitUpdate(words);
Count(words, targetDescriptor);
CountIndirect(words, targetDescriptor, Arguments.data());
words.insert(words.end(), {0xc0033700u, 0x00100200u, Low(&Done), High(&Done), 1u});
Packet packet{words.data(), static_cast<std::uint32_t>(words.size()), 0, {}};
Require(sceAgcDriverSubmitAcb(0x20, &packet) == 0, label + ": compute submit failed");
AgcDriverWaitIdle_nid_postfix();
Require(Done == 1, label + ": submission did not finish");
Require(First[0] == 1, label + ": first dispatch did not run");
Require(Fresh[0] == 2 && Stale[0] == 0, label + ": dispatches after descriptor update used stale descriptor (stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")");
const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix() - adoptedBefore;
if (exactAdopted) {
Require(adopted == expectedAdopted, label + ": expected exactly " + std::to_string(expectedAdopted) + " dispatches adopted ahead, got " + std::to_string(adopted));
} else {
Require(adopted >= expectedAdopted, label + ": expected at least " + std::to_string(expectedAdopted) + " dispatches adopted ahead across pre-update and reopened post-update gates, got " + std::to_string(adopted));
}
}
}
int main() {
@@ -64,19 +257,72 @@ int main() {
auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
device.reset();
GuestBlock imported(BlockBytes);
std::uint32_t* importedSecondDescriptor = imported.Dwords(0);
std::uint32_t* importedCopySource = imported.Dwords(256);
std::uint32_t* importedCopySourceDescriptor = imported.Dwords(512);
std::uint32_t* importedPolluteDescriptor = imported.Dwords(768);
std::uint32_t* importedPolluteMetaDescriptor = imported.Dwords(1024);
const auto stale = Descriptor(Stale.data(), sizeof(Stale));
const auto fresh = Descriptor(Fresh.data(), sizeof(Fresh));
FirstDescriptor = Descriptor(First.data(), sizeof(First));
SecondDescriptor = Descriptor(Stale.data(), sizeof(Stale));
SecondDescriptor = stale;
std::memcpy(importedSecondDescriptor, stale.data(), sizeof(stale));
const auto copySrcDesc = Descriptor(importedCopySource, 16u);
std::memcpy(importedCopySourceDescriptor, copySrcDesc.data(), sizeof(copySrcDesc));
std::memcpy(importedPolluteDescriptor, stale.data(), sizeof(stale));
const auto polluteMetaDesc = Descriptor(importedPolluteDescriptor, 16u);
std::memcpy(importedPolluteMetaDescriptor, polluteMetaDesc.data(), sizeof(polluteMetaDesc));
ArgumentsDescriptor = Descriptor(Arguments.data(), sizeof(Arguments));
std::vector<std::uint32_t> warmUp;
Count(warmUp, FirstDescriptor.data());
Count(warmUp, SecondDescriptor.data());
CountIndirect(warmUp, SecondDescriptor.data(), Arguments.data());
Count(warmUp, importedSecondDescriptor);
CountIndirect(warmUp, importedSecondDescriptor, Arguments.data());
WriteThreeDwords(warmUp, ArgumentsDescriptor.data(), 1u, 1u, 1u);
WriteThreeDwords(warmUp, importedCopySourceDescriptor, fresh[0], fresh[1], fresh[2]);
Count(warmUp, importedPolluteDescriptor, 2);
for (int i = 0; i < 4; ++i) {
WriteThreeDwords(warmUp, importedPolluteMetaDescriptor, stale[0], stale[1], stale[2]);
Count(warmUp, importedPolluteDescriptor, 2);
}
WriteThreeDwords(warmUp, importedPolluteMetaDescriptor, fresh[0], fresh[1], fresh[2]);
ImageWriteDescriptor(warmUp, importedSecondDescriptor, stale);
GdsWrite(warmUp);
warmUp.insert(warmUp.end(), {0xc0033700u, 0x00100200u, Low(&Done), High(&Done), 2u});
Packet first{warmUp.data(), static_cast<std::uint32_t>(warmUp.size()), 0, {}};
Require(sceAgcDriverSubmitAcb(0x20, &first) == 0, "warm-up submit failed");
AgcDriverWaitIdle_nid_postfix();
Require(Done == 2 && First[0] == 1 && Stale[0] == 1, "the warm-up dispatches did not run");
Require(Done == 2 && First[0] == 1 && Stale[0] == 14, "the warm-up dispatches did not run");
First[0] = 0;
Stale[0] = 0;
const auto fresh = Descriptor(Fresh.data(), sizeof(Fresh));
Fresh[0] = 0;
{
Done = 0;
std::memcpy(importedPolluteDescriptor, stale.data(), sizeof(stale));
AgcDriver::DriverDetail::DriverTestAccess::PrepareSamplingForRejectedAhead();
const auto countersBefore = AgcDriver::DriverDetail::Driver::Get().DispatchCacheCounters();
const auto adoptedBefore = AgcDriverDispatchesAdoptedAhead_nid_postfix();
std::vector<std::uint32_t> polluteWords;
polluteWords.insert(polluteWords.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u});
Count(polluteWords, FirstDescriptor.data());
WriteThreeDwords(polluteWords, importedPolluteMetaDescriptor, fresh[0], fresh[1], fresh[2]);
Count(polluteWords, importedPolluteDescriptor, 3);
polluteWords.insert(polluteWords.end(), {0xc0033700u, 0x00100200u, Low(&Done), High(&Done), 1u});
Packet pollutePacket{polluteWords.data(), static_cast<std::uint32_t>(polluteWords.size()), 0, {}};
Require(sceAgcDriverSubmitAcb(0x20, &pollutePacket) == 0, "rejected ahead cache pollution submit failed");
AgcDriverWaitIdle_nid_postfix();
const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix() - adoptedBefore;
const auto countersAfter = AgcDriver::DriverDetail::Driver::Get().DispatchCacheCounters();
Require(Done == 1 && First[0] == 1, "rejected ahead cache pollution submission did not finish");
Require(adopted == 2, "rejected ahead capture was unexpectedly adopted (adopted " + std::to_string(adopted) + ")");
Require(Fresh[0] == 3 && Stale[0] == 0, "normal dispatch after rejected ahead capture received the rejected stale variant from global dispatchCache (stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")");
Require(countersAfter.inserts - countersBefore.inserts == 1, "rejected ahead capture polluted global dispatchCache with an extra insert (inserts delta " + std::to_string(countersAfter.inserts - countersBefore.inserts) + ")");
}
First[0] = 0;
Stale[0] = 0;
Fresh[0] = 0;
std::vector<std::uint32_t> words;
words.insert(words.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u});
Count(words, FirstDescriptor.data());
@@ -90,6 +336,62 @@ int main() {
Require(SecondDescriptor[0] == fresh[0] && SecondDescriptor[1] == fresh[1], "WRITE_DATA did not replace the descriptor");
Require(First[0] == 1, "the first dispatch did not run");
Require(Fresh[0] == 1 && Stale[0] == 0, "a dispatch whose descriptor a WRITE_DATA before it replaces used the old descriptor (stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")");
CopySource = fresh;
RunUpdateAheadCase("HLE StreamCopy (CPU-backed)", SecondDescriptor.data(), stale, [&](std::vector<std::uint32_t>& cmd) {
StreamCopyDescriptor(cmd, CopySource.data(), SecondDescriptor.data());
});
const auto gpuCopiesBefore = AgcDriver::DriverDetail::copyPathCounts[AgcDriver::DriverDetail::CopyGpu].load();
RunUpdateAheadCase("HLE StreamCopy (GPU-backed SourcePending)", importedSecondDescriptor, stale, [&](std::vector<std::uint32_t>& cmd) {
const std::array<std::uint32_t, 4> initSrc{0u, 0u, fresh[2], fresh[3]};
std::memcpy(importedCopySource, initSrc.data(), sizeof(initSrc));
WriteThreeDwords(cmd, importedCopySourceDescriptor, fresh[0], fresh[1], fresh[2]);
StreamCopyDescriptor(cmd, importedCopySource, importedSecondDescriptor);
}, 4);
Require(AgcDriver::DriverDetail::copyPathCounts[AgcDriver::DriverDetail::CopyGpu].load() > gpuCopiesBefore, "GPU-backed StreamCopy did not take CopyGpu path");
RunUpdateAheadCase("HLE FillKernel (CPU-backed)", SecondDescriptor.data(), stale, [&](std::vector<std::uint32_t>& cmd) {
FillKernelDescriptor(cmd, SecondDescriptor.data(), fresh);
});
RunUpdateAheadCase("HLE FillKernel (GPU-backed)", importedSecondDescriptor, stale, [&](std::vector<std::uint32_t>& cmd) {
FillKernelDescriptor(cmd, importedSecondDescriptor, fresh);
});
RunUpdateAheadCase("HLE DwordPatternFill (CPU-backed)", SecondDescriptor.data(), stale, [&](std::vector<std::uint32_t>& cmd) {
DwordPatternFillDescriptor(cmd, SecondDescriptor.data(), fresh);
});
RunUpdateAheadCase("HLE DwordPatternFill (GPU-backed)", importedSecondDescriptor, stale, [&](std::vector<std::uint32_t>& cmd) {
DwordPatternFillDescriptor(cmd, importedSecondDescriptor, fresh);
});
RunUpdateAheadCase("StorageImage write overlap", importedSecondDescriptor, stale, [&](std::vector<std::uint32_t>& cmd) {
ImageWriteDescriptor(cmd, importedSecondDescriptor, fresh);
}, 3, true);
{
First[0] = 0;
Stale[0] = 0;
Fresh[0] = 0;
Done = 0;
SecondDescriptor = fresh;
const auto adoptedBefore = AgcDriverDispatchesAdoptedAhead_nid_postfix();
std::vector<std::uint32_t> gdsWords;
gdsWords.insert(gdsWords.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u});
Count(gdsWords, FirstDescriptor.data());
GdsWrite(gdsWords);
Count(gdsWords, SecondDescriptor.data());
gdsWords.insert(gdsWords.end(), {0xc0033700u, 0x00100200u, Low(&Done), High(&Done), 1u});
Packet gdsPacket{gdsWords.data(), static_cast<std::uint32_t>(gdsWords.size()), 0, {}};
Require(sceAgcDriverSubmitAcb(0x20, &gdsPacket) == 0, "GDS unbounded write submit failed");
AgcDriverWaitIdle_nid_postfix();
const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix() - adoptedBefore;
Require(Done == 1 && First[0] == 1 && Fresh[0] == 1, "GDS unbounded write submission did not run");
Require(adopted == 2, "unbounded GDS write did not stop resolveGroupAhead (expected 2 adopted ahead, got " + std::to_string(adopted) + ")");
}
std::cout << "write data written ahead test passed\n";
AgcDriverShutdown_nid_postfix();
return 0;
+146 -2
View File
@@ -1,10 +1,154 @@
#include <cstddef>
#include "prx/libSceDbg/include/Api.hpp"
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
APS5_DUMMY_FUN
int APS5_VABI sceDbgUserChannelPrint(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgAmprAprGetNumberOfErrors(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgMapperVirtualQuery(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgUserChannelPrintf(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgAssertionHandler(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgGetSystemSwVersion(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgSetBreakOnWarningState(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgSetBreakOnErrorState(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgDeleteExternalTriggerEvent(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgAmprAmmGetNumberOfErrors(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgGetModuleInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgBacktraceSelf(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgAmprAmmGetErrors(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgGetDebugSuspendCount(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgUserChannelVPrintf(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgSetMinimumLogLevel(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgGetModuleList(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgClearHardwareBreakPoint(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgAddExternalTriggerEvent(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgLoggingHandler(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgEnableExtraHeapDebugInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgAmprAprGetErrors(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgVLoggingHandler(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgGetReleaseCheckMode(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgSetHardwareBreakPoint(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgGetExecutablePath(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgGetCpuGpuFrequencySetting(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgRequireSystemSwVersion(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDbgIsDebuggerAttached(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI sceDbgUserChannelPrint(void);
int APS5_VABI sceDbgAmprAprGetNumberOfErrors(void);
int APS5_VABI sceDbgMapperVirtualQuery(void);
int APS5_VABI sceDbgUserChannelPrintf(void);
int APS5_VABI sceDbgAssertionHandler(void);
int APS5_VABI sceDbgGetSystemSwVersion(void);
int APS5_VABI sceDbgSetBreakOnWarningState(void);
int APS5_VABI sceDbgSetBreakOnErrorState(void);
int APS5_VABI sceDbgDeleteExternalTriggerEvent(void);
int APS5_VABI sceDbgAmprAmmGetNumberOfErrors(void);
int APS5_VABI sceDbgGetModuleInfo(void);
int APS5_VABI sceDbgBacktraceSelf(void);
int APS5_VABI sceDbgAmprAmmGetErrors(void);
int APS5_VABI sceDbgGetDebugSuspendCount(void);
int APS5_VABI sceDbgUserChannelVPrintf(void);
int APS5_VABI sceDbgSetMinimumLogLevel(void);
int APS5_VABI sceDbgGetModuleList(void);
int APS5_VABI sceDbgClearHardwareBreakPoint(void);
int APS5_VABI sceDbgAddExternalTriggerEvent(void);
int APS5_VABI sceDbgLoggingHandler(void);
int APS5_VABI sceDbgEnableExtraHeapDebugInfo(void);
int APS5_VABI sceDbgAmprAprGetErrors(void);
int APS5_VABI sceDbgVLoggingHandler(void);
int APS5_VABI sceDbgGetReleaseCheckMode(void);
int APS5_VABI sceDbgSetHardwareBreakPoint(void);
int APS5_VABI sceDbgGetExecutablePath(void);
int APS5_VABI sceDbgGetCpuGpuFrequencySetting(void);
int APS5_VABI sceDbgRequireSystemSwVersion(void);
int APS5_VABI sceDbgIsDebuggerAttached(void);
}
+2
View File
@@ -1 +1,3 @@
#pragma once
#include "prx/libSceDbg/include/Api.hpp"
+36 -2
View File
@@ -1,10 +1,44 @@
#include <cstddef>
#include "prx/libSceDeci5/include/Api.hpp"
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
APS5_DUMMY_FUN
int APS5_VABI sceDeci5Write(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDeci5Read(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDeci5CreateHostProcessAndWait(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDeci5Close(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDeci5CreateHostProcess(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDeci5Open(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceDeci5GetStatus(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI sceDeci5Write(void);
int APS5_VABI sceDeci5Read(void);
int APS5_VABI sceDeci5CreateHostProcessAndWait(void);
int APS5_VABI sceDeci5Close(void);
int APS5_VABI sceDeci5CreateHostProcess(void);
int APS5_VABI sceDeci5Open(void);
int APS5_VABI sceDeci5GetStatus(void);
}
@@ -1 +1,3 @@
#pragma once
#include "prx/libSceDeci5/include/Api.hpp"
+10 -1
View File
@@ -4,7 +4,7 @@ add_library(libSceFreeType SHARED EXCLUDE_FROM_ALL
src/Outline.cpp
src/Charmap.cpp
src/FaceInfo.cpp
src/Stroker.cpp
src/Stroker.cpp src/OutlineStorage.cpp
src/GuestObjects.cpp
src/Names.cpp
src/Trigonometry.cpp
@@ -133,6 +133,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_freetype_stroker PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
endif()
add_test_executable(guest_freetype_outline_storage_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutlineStorage.cpp)
target_include_directories(guest_freetype_outline_storage_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_freetype_outline_storage_tests PRIVATE libSceFreeType freetype libc)
configure_windows_unwind(guest_freetype_outline_storage_tests)
add_test(NAME guest_freetype_outline_storage COMMAND guest_freetype_outline_storage_tests)
if(WIN32)
set_tests_properties(guest_freetype_outline_storage PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFreeType>")
endif()
add_test_executable(guest_freetype_charmap_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCharmap.cpp)
target_include_directories(guest_freetype_charmap_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_freetype_charmap_tests PRIVATE libSceFreeType freetype libc)
@@ -110,4 +110,8 @@ int APS5_VABI FT_Stroker_ConicTo_nid_postfix(void* stroker, const FreeType::Gues
int APS5_VABI ft_corner_is_flat_nid_postfix(std::int64_t in_x, std::int64_t in_y, std::int64_t out_x, std::int64_t out_y);
int APS5_VABI ft_corner_orientation_nid_postfix(std::int64_t in_x, std::int64_t in_y, std::int64_t out_x, std::int64_t out_y);
int APS5_VABI FT_Outline_New_nid_postfix(FreeType::GuestLibrary* library, std::uint32_t numPoints, std::int32_t numContours, FreeType::GuestOutline* outline);
int APS5_VABI FT_Outline_Done_nid_postfix(FreeType::GuestLibrary* library, FreeType::GuestOutline* outline);
int APS5_VABI sceFreeTypeOtOutlineDone_nid_no_patch();
int APS5_VABI sceFreeTypeOtOutlineNew_nid_no_patch();
}
@@ -0,0 +1,50 @@
#include "prx/libSceFreeType/include/Api.hpp"
#include "prx/libSceFreeType/include/GuestObjects.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include FT_OUTLINE_H
#include <cstdint>
#include <cstdlib>
using namespace FreeType;
extern "C" {
int APS5_VABI FT_Outline_Done_nid_postfix(GuestLibrary* library, GuestOutline* outline) {
if (!library) return FT_Err_Invalid_Library_Handle;
if (!outline) return FT_Err_Invalid_Outline;
if (!library->host) return FT_Err_Invalid_Argument;
if (outline->flags & FT_OUTLINE_OWNER) {
std::free(outline->points);
std::free(outline->tags);
std::free(outline->contours);
}
*outline = {};
return 0;
}
int APS5_VABI FT_Outline_New_nid_postfix(GuestLibrary* library, std::uint32_t numPoints, std::int32_t numContours, GuestOutline* outline) {
if (!library) return FT_Err_Invalid_Library_Handle;
if (!outline) return FT_Err_Invalid_Argument;
if (!library->host) return FT_Err_Invalid_Argument;
*outline = {};
if (numContours < 0 || static_cast<std::uint32_t>(numContours) > numPoints) return FT_Err_Invalid_Argument;
if (numPoints > FT_OUTLINE_POINTS_MAX) return FT_Err_Array_Too_Large;
if (numPoints != 0) {
outline->points = static_cast<GuestVector*>(std::calloc(numPoints, sizeof(GuestVector)));
outline->tags = static_cast<unsigned char*>(std::calloc(numPoints, sizeof(unsigned char)));
}
if (numContours != 0) outline->contours = static_cast<std::uint16_t*>(std::calloc(static_cast<std::uint32_t>(numContours), sizeof(std::uint16_t)));
const bool failed = numPoints != 0 && (!outline->points || !outline->tags) || numContours != 0 && !outline->contours;
outline->flags |= FT_OUTLINE_OWNER;
if (failed) {
FT_Outline_Done_nid_postfix(library, outline);
return FT_Err_Out_Of_Memory;
}
outline->n_points = static_cast<std::uint16_t>(numPoints);
outline->n_contours = static_cast<std::uint16_t>(numContours);
return 0;
}
}
+4 -4
View File
@@ -229,10 +229,10 @@ int APS5_VABI sceFreeTypeOtOutlineDecompose_nid_no_patch() {
}
APS5_EXPORT("++lgV9pRsO4", sceFreeTypeOtOutlineDecompose_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch() {
int APS5_VABI sceFreeTypeOtOutlineDone_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BqGJsJcFYaM", sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch);
APS5_EXPORT("BqGJsJcFYaM", sceFreeTypeOtOutlineDone_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
@@ -264,10 +264,10 @@ int APS5_VABI sceFreeTypeOtOutlineGetOrientation_nid_no_patch() {
}
APS5_EXPORT("weVsBCT+TTQ", sceFreeTypeOtOutlineGetOrientation_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch() {
int APS5_VABI sceFreeTypeOtOutlineNew_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("QH2emcgeFck", sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch);
APS5_EXPORT("QH2emcgeFck", sceFreeTypeOtOutlineNew_nid_no_patch);
int APS5_VABI sceFreeTypeOtUnknown_NG7OgYsgcF8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
+41 -2
View File
@@ -1,10 +1,49 @@
#include <cstddef>
#include "prx/libSceGpuTrace/include/Api.hpp"
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
APS5_DUMMY_FUN
int APS5_VABI sceGpuTraceParametersSetDuration(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGpuTraceParametersInit(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGpuTraceParametersSetMemorySize(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGpuTraceParametersSetGroup(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGpuTraceStart(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGpuTraceStop(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGpuTraceMonitorTrigger(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceGpuTraceCancel(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
@@ -0,0 +1,16 @@
#pragma once
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI sceGpuTraceParametersSetDuration(void);
int APS5_VABI sceGpuTraceParametersInit(void);
int APS5_VABI sceGpuTraceParametersSetMemorySize(void);
int APS5_VABI sceGpuTraceParametersSetGroup(void);
int APS5_VABI sceGpuTraceStart(void);
int APS5_VABI sceGpuTraceStop(void);
int APS5_VABI sceGpuTraceMonitorTrigger(void);
int APS5_VABI sceGpuTraceCancel(void);
}
@@ -1 +1,3 @@
#pragma once
#include "prx/libSceGpuTrace/include/Api.hpp"
+6
View File
@@ -73,6 +73,12 @@ if(BUILD_TESTING)
set_tests_properties(guest_http2 guest_http2_cookies guest_http2_options guest_http2_transport guest_http2_early_response guest_http2_dns_first guest_http2_system_roots PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttp2>")
endif()
add_test(NAME guest_http2_redirects COMMAND guest_http2_transport_tests --redirects)
set_tests_properties(guest_http2_redirects PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_http2_redirects PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttp2>")
endif()
endif()
if(BUILD_TESTING)
+6 -3
View File
@@ -116,7 +116,7 @@ static std::shared_ptr<Http2::Request> Transport(int id, bool create) {
if (!handle) return nullptr;
if (create && !handle->transport) {
if (handle->version != 3) NotImplemented_nid_no_patch("sceHttp2SendRequest: HTTP/1.x transport is not implemented");
handle->transport = Http2::CreateRequest_nid_no_patch(handle->sslContext, handle->capacity, handle->settings, handle->method, handle->url);
handle->transport = Http2::CreateRequest_nid_no_patch(id, handle->sslContext, handle->capacity, handle->settings, handle->method, handle->url);
}
if (!handle->transport) NotImplemented_nid_no_patch("sceHttp2: request has not been sent");
return handle->transport;
@@ -264,8 +264,11 @@ int APS5_VABI sceHttp2SetRecvTimeOut(int id, uint32_t usec) {
});
}
int APS5_VABI sceHttp2SetRedirectCallback(int id, void* cb_func, void* user_arg) {
return Update(id, __func__, [&](auto&) { if (cb_func || user_arg) NotImplemented_nid_no_patch("sceHttp2SetRedirectCallback"); });
int APS5_VABI sceHttp2SetRedirectCallback(int id, Http2RedirectCallback cb_func, void* user_arg) {
return Update(id, __func__, [&](auto& settings) {
settings.redirectCallback = cb_func;
settings.redirectArgument = user_arg;
});
}
int APS5_VABI sceHttp2SetRequestContentLength(int id, uint64_t content_length) {
+142 -28
View File
@@ -106,6 +106,52 @@ Url Parse(std::string_view text) {
return url;
}
std::string NormalizePath(std::string_view path) {
std::string result;
while (!path.empty()) {
if (path.starts_with("../")) path.remove_prefix(3);
else if (path.starts_with("./")) path.remove_prefix(2);
else if (path.starts_with("/./")) path.remove_prefix(2);
else if (path == "/.") path = "/";
else if (path.starts_with("/../") || path == "/..") {
path = path == "/.." ? "/" : path.substr(3);
const auto slash = result.rfind('/');
result.erase(slash == std::string::npos ? 0 : slash);
} else if (path == "." || path == "..") path = {};
else {
const auto slash = path.find('/', path.front() == '/' ? 1 : 0);
result.append(path.substr(0, slash));
path = slash == std::string_view::npos ? std::string_view{} : path.substr(slash);
}
}
return result;
}
Url RedirectTarget(const Url& base, std::string_view reference) {
reference = reference.substr(0, reference.find('#'));
const auto colon = reference.find(':');
const auto delimiter = reference.find_first_of("/?");
Url result;
if (colon != std::string_view::npos && (delimiter == std::string_view::npos || colon < delimiter)) result = Parse(reference);
else if (reference.starts_with("//")) result = Parse("https:" + std::string(reference));
else {
std::string path;
if (reference.empty()) path = base.path;
else if (reference.starts_with('?')) path = base.path.substr(0, base.path.find('?')) + std::string(reference);
else if (reference.starts_with('/')) path = reference;
else {
path = base.path.substr(0, base.path.find('?'));
path.erase(path.rfind('/') + 1);
path += reference;
}
result = Parse("https://" + base.authority + path);
}
const auto query = result.path.find('?');
result.path = NormalizePath(std::string_view(result.path).substr(0, query)) +
(query == std::string::npos ? std::string{} : result.path.substr(query));
return result;
}
struct Address {
std::array<std::uint8_t, 28> bytes{};
std::uint32_t size = 0;
@@ -245,21 +291,25 @@ int AddHeader(Settings& settings, const char* name, const char* value, std::uint
}
struct Request::State {
const int id;
int sslContext;
const std::shared_ptr<Capacity> capacity;
bool acquired = false;
const Settings settings;
const std::string method;
const Url url;
Settings settings;
std::string method;
Url url;
std::mutex mutex;
std::atomic<bool> cancelled{false};
bool started = false;
bool callbackActive = false;
bool ready = false;
bool ended = false;
int status = 0;
std::string contentEncoding;
bool inflatedGzip = false;
int blockStatus = 0;
std::string location;
bool hasLocation = false;
int networkResult = 0;
int socket = -1;
int ssl = -1;
@@ -279,8 +329,8 @@ struct Request::State {
std::exception_ptr callbackFailure;
Clock::time_point totalDeadline;
State(int context, std::shared_ptr<Capacity> capacity, Settings options, std::string verb, std::string target)
: sslContext(context), capacity(std::move(capacity)), settings(std::move(options)), method(std::move(verb)), url(Parse(target)) {
State(int id, int context, std::shared_ptr<Capacity> capacity, Settings options, std::string verb, std::string target)
: id(id), sslContext(context), capacity(std::move(capacity)), settings(std::move(options)), method(std::move(verb)), url(Parse(target)) {
Require(!method.empty() && nghttp2_check_method(reinterpret_cast<const std::uint8_t*>(method.data()), method.size()),
"sceHttp2CreateRequestWithURL: invalid method");
Require(method != "CONNECT", "sceHttp2: CONNECT is not implemented");
@@ -452,6 +502,8 @@ struct Request::State {
state.blockStatus = 0;
state.hasLength = false;
state.contentLength = 0;
state.location.clear();
state.hasLocation = false;
}
});
}
@@ -467,6 +519,11 @@ struct Request::State {
const std::string_view key(reinterpret_cast<const char*>(name), nameSize);
const std::string_view text(reinterpret_cast<const char*>(value), valueSize);
if (key == ":status") state.blockStatus = static_cast<int>(Number(text));
if (key == "location") {
Require(!state.hasLocation, "sceHttp2: duplicate redirect location; console error mapping is unknown");
state.location = text;
state.hasLocation = true;
}
if (key == "content-length") {
state.contentLength = Number(text);
state.hasLength = true;
@@ -484,8 +541,6 @@ struct Request::State {
if (frame->hd.stream_id != state.stream) return;
if (frame->hd.type == NGHTTP2_HEADERS && state.blockStatus >= 200) {
state.status = state.blockStatus;
Require(!state.settings.redirect || (state.status != 301 && state.status != 302 && state.status != 303 && state.status != 307 && state.status != 308),
"sceHttp2: automatic redirects are not implemented");
Require(!state.settings.auth || (state.status != 401 && state.status != 407), "sceHttp2: authentication is not implemented");
state.ready = true;
}
@@ -605,6 +660,7 @@ struct Request::State {
std::unique_lock<std::mutex> Lock() {
std::unique_lock lock(mutex, std::try_to_lock);
Require(lock.owns_lock(), "sceHttp2: concurrent operation on one request is not implemented");
Require(!callbackActive, "sceHttp2: operation during redirect callback is not implemented");
if (failure) std::rethrow_exception(failure);
Require(!cancelled.load(), "sceHttp2: request cancelled; console error mapping is unknown");
return lock;
@@ -617,8 +673,8 @@ struct Request::State {
}
};
Request::Request(int context, std::shared_ptr<Capacity> capacity, Settings settings, std::string method, std::string url)
: state(std::make_unique<State>(context, std::move(capacity), std::move(settings), std::move(method), std::move(url))) {}
Request::Request(int id, int context, std::shared_ptr<Capacity> capacity, Settings settings, std::string method, std::string url)
: state(std::make_unique<State>(id, context, std::move(capacity), std::move(settings), std::move(method), std::move(url))) {}
Request::~Request() = default;
int Request::Send(const void* data, std::size_t size) {
@@ -629,24 +685,82 @@ int Request::Send(const void* data, std::size_t size) {
state->started = true;
state->totalDeadline = Clock::now() + std::chrono::microseconds(state->settings.timeout);
try {
const int active = state->capacity->active.fetch_add(1);
state->acquired = true;
Require(active < state->capacity->maximum, "sceHttp2: connection slot waiting is not implemented");
state->networkResult = state->Connect();
if (state->networkResult < 0) { state->Close(); return state->networkResult; }
state->Setup(data, size);
const auto sendDeadline = state->Deadline(state->settings.sendTimeout);
auto receiveDeadline = sendDeadline;
bool uploaded = false;
for (;;) {
state->Flush(uploaded ? state->Deadline(state->settings.sendTimeout) : sendDeadline);
if (!uploaded && state->uploadOffset == size) {
uploaded = true;
receiveDeadline = state->Deadline(state->settings.recvTimeout);
const void* payload = data;
std::size_t payloadSize = size;
for (unsigned redirects = 0;; ++redirects) {
const int active = state->capacity->active.fetch_add(1);
state->acquired = true;
Require(active < state->capacity->maximum, "sceHttp2: connection slot waiting is not implemented");
state->networkResult = state->Connect();
if (state->networkResult < 0) { state->Close(); return state->networkResult; }
state->Setup(payload, payloadSize);
const auto sendDeadline = state->Deadline(state->settings.sendTimeout);
auto receiveDeadline = sendDeadline;
bool uploaded = false;
for (;;) {
state->Flush(uploaded ? state->Deadline(state->settings.sendTimeout) : sendDeadline);
if (!uploaded && state->uploadOffset == payloadSize) {
uploaded = true;
receiveDeadline = state->Deadline(state->settings.recvTimeout);
}
if (state->ready) break;
state->Receive(uploaded ? receiveDeadline : sendDeadline);
if (state->ready) break;
}
if (state->ready) break;
state->Receive(uploaded ? receiveDeadline : sendDeadline);
if (state->ready) break;
const bool redirectStatus = state->status == 301 || state->status == 302 || state->status == 303 || state->status == 307 || state->status == 308;
if (!state->settings.redirect || !redirectStatus || state->location.empty()) break;
const bool retrieve = (state->status == 303 && state->method != "HEAD") ||
((state->status == 301 || state->status == 302) && state->method == "POST");
std::string method = retrieve ? "GET" : state->method;
if (const auto callback = state->settings.redirectCallback) {
const char* selected = method.c_str();
state->callbackActive = true;
lock.unlock();
int result;
try {
result = callback(state->id, state->status, &selected, state->location.c_str(), state->settings.redirectArgument);
} catch (...) {
lock.lock();
state->callbackActive = false;
throw;
}
lock.lock();
state->callbackActive = false;
state->Check(state->totalDeadline);
if (result != 0) break;
Require(selected, "sceHttp2: redirect callback returned a null method");
method = selected;
}
Require(redirects < 6, "sceHttp2: redirect limit exceeded; console error mapping is unknown");
Require(!method.empty() && nghttp2_check_method(reinterpret_cast<const std::uint8_t*>(method.data()), method.size()) && method != "CONNECT",
"sceHttp2: redirect callback returned an unsupported method");
auto target = RedirectTarget(state->url, state->location);
const bool differentOrigin = Lower(target.host) != Lower(state->url.host) || target.port != state->url.port;
const bool dropBody = (retrieve || method != state->method) && (method == "GET" || method == "HEAD");
std::erase_if(state->settings.headers, [&](const auto& header) {
const auto& key = header.first;
return (differentOrigin && (key == "authorization" || key == "proxy-authorization" || key == "cookie" || key == "origin" || key == "referer")) ||
(dropBody && (key.starts_with("content-") || key == "digest" || key == "last-modified"));
});
if (dropBody) { payload = nullptr; payloadSize = 0; }
state->settings.contentLength = payloadSize;
state->Close();
state->method = std::move(method);
state->url = std::move(target);
state->ready = state->ended = false;
state->status = state->blockStatus = 0;
state->location.clear();
state->hasLocation = state->hasLength = false;
state->contentLength = 0;
state->headerBytes = 0;
state->body.clear();
state->bodyOffset = 0;
state->contentEncoding.clear();
state->inflatedGzip = false;
state->upload = nullptr;
state->uploadSize = state->uploadOffset = 0;
state->callbackFailure = nullptr;
state->stream = -1;
}
state->upload = nullptr;
if (state->ended && state->body.empty()) state->Close();
@@ -711,8 +825,8 @@ std::shared_ptr<Capacity> CreateCapacity_nid_no_patch(int maximum) {
return std::make_shared<Capacity>(maximum);
}
std::shared_ptr<Request> CreateRequest_nid_no_patch(int sslContext, std::shared_ptr<Capacity> capacity, Settings settings, std::string method, std::string url) {
return std::make_shared<Request>(sslContext, std::move(capacity), std::move(settings), std::move(method), std::move(url));
std::shared_ptr<Request> CreateRequest_nid_no_patch(int id, int sslContext, std::shared_ptr<Capacity> capacity, Settings settings, std::string method, std::string url) {
return std::make_shared<Request>(id, sslContext, std::move(capacity), std::move(settings), std::move(method), std::move(url));
}
}
+6 -2
View File
@@ -1,5 +1,7 @@
#pragma once
#include "prx/libSceHttp2/include/Http2.hpp"
#include <atomic>
#include <cstddef>
#include <cstdint>
@@ -18,6 +20,8 @@ inline std::atomic<LookupHook>& LookupHookSlot() {
}
struct Settings {
Http2RedirectCallback redirectCallback = nullptr;
void* redirectArgument = nullptr;
std::string userAgent;
std::vector<std::pair<std::string, std::string>> headers;
std::uint64_t contentLength = 0;
@@ -46,7 +50,7 @@ struct Capacity {
class Request {
public:
Request(int sslContext, std::shared_ptr<Capacity> capacity, Settings settings, std::string method, std::string url);
Request(int id, int sslContext, std::shared_ptr<Capacity> capacity, Settings settings, std::string method, std::string url);
~Request();
Request(const Request&) = delete;
Request& operator=(const Request&) = delete;
@@ -63,7 +67,7 @@ private:
int AddHeader(Settings& settings, const char* name, const char* value, std::uint32_t mode);
std::shared_ptr<Capacity> CreateCapacity_nid_no_patch(int maximum);
std::shared_ptr<Request> CreateRequest_nid_no_patch(int sslContext, std::shared_ptr<Capacity> capacity, Settings settings, std::string method, std::string url);
std::shared_ptr<Request> CreateRequest_nid_no_patch(int id, int sslContext, std::shared_ptr<Capacity> capacity, Settings settings, std::string method, std::string url);
}
+3 -1
View File
@@ -2,6 +2,8 @@
#include "SceTypes.hpp"
using Http2RedirectCallback = int (APS5_VABI*)(int requestId, int statusCode, const char** method, const char* location, void* userArg);
struct Http2MemoryPoolStats {
size_t pool_size;
size_t max_inuse_size;
@@ -48,7 +50,7 @@ int APS5_VABI sceHttp2SetConnectionWaitTimeOut(int id, uint32_t usec);
int APS5_VABI sceHttp2SetConnectTimeOut(int id, uint32_t usec);
int APS5_VABI sceHttp2SetInflateGZIPEnabled(int id, int enable);
int APS5_VABI sceHttp2SetRecvTimeOut(int id, uint32_t usec);
int APS5_VABI sceHttp2SetRedirectCallback(int id, void* cbFunc, void* userArg);
int APS5_VABI sceHttp2SetRedirectCallback(int id, Http2RedirectCallback cbFunc, void* userArg);
int APS5_VABI sceHttp2SetRequestContentLength(int id, uint64_t contentLength);
int APS5_VABI sceHttp2SetResolveTimeOut(int id, uint32_t usec);
int APS5_VABI sceHttp2SetSendTimeOut(int id, uint32_t usec);
+14 -1
View File
@@ -1,4 +1,4 @@
add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Export.cpp Invariant.cpp Version.cpp Unescape.cpp)
add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Export.cpp Invariant.cpp Version.cpp Unescape.cpp Tokenize.cpp)
target_include_directories(libSceIcu PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceIcu)
target_sources(libSceIcu PRIVATE ConverterCallbacks.cpp)
@@ -112,3 +112,16 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(guest_icu_tokenize_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuTokenize.cpp)
target_include_directories(guest_icu_tokenize_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_icu_tokenize_tests PRIVATE libSceIcu libkernel libc)
configure_windows_unwind(guest_icu_tokenize_tests)
add_test(NAME guest_icu_tokenize COMMAND guest_icu_tokenize_tests)
set_tests_properties(guest_icu_tokenize PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_icu_tokenize PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIcu>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()
+118
View File
@@ -0,0 +1,118 @@
#include "prx/libSceIcu/libSceIcu.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include <cstddef>
#include <cstdint>
#include <limits>
namespace {
struct Character {
std::uint32_t value;
std::size_t width;
};
bool Lead(char16_t value) { return value >= 0xd800 && value <= 0xdbff; }
bool Trail(char16_t value) { return value >= 0xdc00 && value <= 0xdfff; }
Character Next(const char16_t* text) {
if (Lead(text[0]) && Trail(text[1]))
return {0x10000u + ((text[0] - 0xd800u) << 10) + (text[1] - 0xdc00u), 2};
return {text[0], 1};
}
bool Contains(const char16_t* set, std::size_t length, std::uint32_t value) {
for (std::size_t position = 0; position < length;) {
const auto character = Next(set + position);
if (character.value == value) return true;
position += character.width;
}
return false;
}
std::size_t FindSet(const char16_t* text, const char16_t* set, bool matches) {
constexpr auto maximum = static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max());
std::size_t length = 0;
while (set[length] != 0) if (++length > maximum) return std::numeric_limits<std::size_t>::max();
std::size_t position = 0;
while (text[position] != 0) {
const auto character = Next(text + position);
if (Contains(set, length, character.value) == matches) break;
position += character.width;
if (position > maximum) return std::numeric_limits<std::size_t>::max();
}
return position;
}
} // namespace
extern "C" {
std::int32_t APS5_VABI u_strspn_nid_postfix(const char16_t* text, const char16_t* set) {
if (!text || !set) NotImplemented_nid_no_patch(__func__);
const auto position = FindSet(text, set, false);
if (position > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) NotImplemented_nid_no_patch(__func__);
return static_cast<std::int32_t>(position);
}
std::int32_t APS5_VABI u_strcspn_nid_postfix(const char16_t* text, const char16_t* set) {
if (!text || !set) NotImplemented_nid_no_patch(__func__);
const auto position = FindSet(text, set, true);
if (position > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) NotImplemented_nid_no_patch(__func__);
return static_cast<std::int32_t>(position);
}
char16_t* APS5_VABI u_strpbrk_nid_postfix(const char16_t* text, const char16_t* set) {
if (!text || !set) NotImplemented_nid_no_patch(__func__);
const auto position = FindSet(text, set, true);
if (position > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) NotImplemented_nid_no_patch(__func__);
return text[position] != 0 ? const_cast<char16_t*>(text + position) : nullptr;
}
char16_t* APS5_VABI u_strtok_r_nid_postfix(char16_t* source, const char16_t* delimiters, char16_t** state) {
if (!state) NotImplemented_nid_no_patch(__func__);
auto* text = source ? source : *state;
if (!text) return nullptr;
if (!delimiters) NotImplemented_nid_no_patch(__func__);
GuestArena::HostWrite stateAccess(state, sizeof(*state));
if (!stateAccess.Open()) NotImplemented_nid_no_patch(__func__);
if (source) *state = source;
const auto skipped = FindSet(text, delimiters, false);
if (skipped > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) NotImplemented_nid_no_patch(__func__);
text += skipped;
if (*text == 0) {
*state = nullptr;
return nullptr;
}
const auto length = FindSet(text, delimiters, true);
if (length > static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) NotImplemented_nid_no_patch(__func__);
auto* next = text + length;
if (*next != 0) {
GuestArena::HostWrite textAccess(next, sizeof(*next));
if (!textAccess.Open()) NotImplemented_nid_no_patch(__func__);
*next++ = 0;
*state = next;
} else *state = nullptr;
return text;
}
char16_t* APS5_VABI u_memrchr32_nid_postfix(const char16_t* text, std::int32_t value, std::int32_t count) {
if (count <= 0 || static_cast<std::uint32_t>(value) > 0x10ffffu || (value > 0xffff && count < 2)) return nullptr;
if (!text) {
if (value >= 0xd800 && value <= 0xdfff) return nullptr;
NotImplemented_nid_no_patch(__func__);
}
auto position = static_cast<std::size_t>(count);
while (position != 0) {
--position;
std::uint32_t character = text[position];
if (Trail(text[position]) && position != 0 && Lead(text[position - 1])) {
--position;
character = 0x10000u + ((text[position] - 0xd800u) << 10) + (text[position + 1] - 0xdc00u);
}
if (character == static_cast<std::uint32_t>(value)) return const_cast<char16_t*>(text + position);
}
return nullptr;
}
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
extern "C" {
std::int32_t APS5_VABI u_strspn_nid_postfix(const char16_t* text, const char16_t* set);
std::int32_t APS5_VABI u_strcspn_nid_postfix(const char16_t* text, const char16_t* set);
char16_t* APS5_VABI u_strpbrk_nid_postfix(const char16_t* text, const char16_t* set);
char16_t* APS5_VABI u_strtok_r_nid_postfix(char16_t* source, const char16_t* delimiters, char16_t** state);
char16_t* APS5_VABI u_memrchr32_nid_postfix(const char16_t* text, std::int32_t value, std::int32_t count);
}
+2
View File
@@ -10,3 +10,5 @@
#include "Space.hpp"
#include "Unescape.hpp"
#include "Identified.hpp"
#include "prx/libSceIcu/include/Api.hpp"
+429 -4
View File
@@ -1,8 +1,256 @@
add_library(libSceJson2 SHARED EXCLUDE_FROM_ALL Export.cpp InventoryExports.cpp)
target_link_libraries(libSceJson2 PRIVATE libkernel libc)
target_include_directories(libSceJson2 PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceJson2)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/Api.hpp")
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/Api.hpp" jsonDeclarations REGEX "_ZN")
set(jsonExports "")
set(jsonWindowsExports "")
foreach(declaration IN LISTS jsonDeclarations)
string(REGEX MATCH "(_ZN[A-Za-z0-9_]+)" match "${declaration}")
set(symbol "${CMAKE_MATCH_1}")
list(APPEND jsonExports "${symbol}")
if(declaration MATCHES "\\(")
list(APPEND jsonWindowsExports "${symbol}")
else()
list(APPEND jsonWindowsExports "${symbol} DATA")
endif()
endforeach()
if(APPLE)
set(exportList "")
foreach(symbol IN LISTS jsonExports)
string(APPEND exportList "_${symbol}\n")
endforeach()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/json2.exports" "${exportList}")
target_link_options(libSceJson2 PRIVATE "LINKER:-exported_symbols_list,${CMAKE_CURRENT_BINARY_DIR}/json2.exports")
elseif(WIN32)
string(JOIN "\n" exportList ${jsonWindowsExports})
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/json2.def" "EXPORTS\n${exportList}\n")
target_sources(libSceJson2 PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/json2.def")
else()
string(JOIN ";\n" exportList ${jsonExports})
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/json2.exports" "{ global:\n${exportList};\nlocal: *; };\n")
target_link_options(libSceJson2 PRIVATE "LINKER:--version-script=${CMAKE_CURRENT_BINARY_DIR}/json2.exports")
endif()
if(BUILD_TESTING)
add_test_executable(guest_json_legacy_self_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonLegacySelf.cpp Export.cpp)
target_include_directories(guest_json_legacy_self_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_legacy_self_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_legacy_self_tests)
add_test(NAME guest_json_legacy_self COMMAND guest_json_legacy_self_tests)
if(WIN32)
set_tests_properties(guest_json_legacy_self PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_json_termination_exceptions_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonTerminationExceptions.cpp)
target_include_directories(guest_json_termination_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_termination_exceptions_tests PRIVATE libSceJson2 libkernel libc)
configure_windows_unwind(guest_json_termination_exceptions_tests)
add_test_executable(guest_json_legacy_termination_exceptions_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonTerminationExceptions.cpp Export.cpp)
target_compile_definitions(guest_json_legacy_termination_exceptions_tests PRIVATE JSON2_TERMINATION_LEGACY_SDK)
target_include_directories(guest_json_legacy_termination_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_legacy_termination_exceptions_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_legacy_termination_exceptions_tests)
foreach(flavor modern legacy)
if(flavor STREQUAL "legacy")
set(exception_target guest_json_legacy_termination_exceptions_tests)
else()
set(exception_target guest_json_termination_exceptions_tests)
endif()
add_test(NAME guest_json_${flavor}_termination_healthy COMMAND ${exception_target} 0 before)
foreach(position RANGE 1 12)
foreach(boundary before after)
add_test(NAME guest_json_${flavor}_termination_exception_${boundary}_${position} COMMAND ${exception_target} ${position} ${boundary})
if(WIN32 AND flavor STREQUAL "legacy")
set_tests_properties(guest_json_${flavor}_termination_exception_${boundary}_${position} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endforeach()
endforeach()
foreach(position 1 5 6 8 9 11 12)
add_test(NAME guest_json_${flavor}_termination_retry_${position} COMMAND ${exception_target} ${position} before retry)
if(WIN32 AND flavor STREQUAL "legacy")
set_tests_properties(guest_json_${flavor}_termination_retry_${position} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endforeach()
if(flavor STREQUAL "legacy")
add_test(NAME guest_json_legacy_termination_retry_2 COMMAND ${exception_target} 2 before retry)
add_test(NAME guest_json_legacy_termination_retry_null_after COMMAND ${exception_target} 1 after retry)
if(WIN32)
set_tests_properties(guest_json_legacy_termination_retry_2 guest_json_legacy_termination_retry_null_after PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()
if(WIN32 AND flavor STREQUAL "legacy")
set_tests_properties(guest_json_${flavor}_termination_healthy PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endforeach()
add_test_executable(guest_json_initialization_exceptions_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationExceptions.cpp)
target_include_directories(guest_json_initialization_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_initialization_exceptions_tests PRIVATE libSceJson2 libkernel libc)
configure_windows_unwind(guest_json_initialization_exceptions_tests)
foreach(position RANGE 0 12)
foreach(callback allocate notify)
add_test(NAME guest_json_initialization_exception_${callback}_${position} COMMAND guest_json_initialization_exceptions_tests ${position} ${callback})
endforeach()
endforeach()
add_test_executable(guest_json_legacy_initialization_exceptions_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationExceptions.cpp Export.cpp)
target_compile_definitions(guest_json_legacy_initialization_exceptions_tests PRIVATE JSON2_EXCEPTION_LEGACY_SDK)
target_include_directories(guest_json_legacy_initialization_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_legacy_initialization_exceptions_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_legacy_initialization_exceptions_tests)
foreach(position RANGE 0 11)
foreach(callback allocate notify)
add_test(NAME guest_json_legacy_initialization_exception_${callback}_${position} COMMAND guest_json_legacy_initialization_exceptions_tests ${position} ${callback})
if(WIN32)
set_tests_properties(guest_json_legacy_initialization_exception_${callback}_${position} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endforeach()
endforeach()
add_test_executable(guest_json_legacy_composite_destruction_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCompositeDestruction.cpp Export.cpp)
target_compile_definitions(guest_json_legacy_composite_destruction_tests PRIVATE JSON2_DESTRUCTION_LEGACY_SDK)
target_include_directories(guest_json_legacy_composite_destruction_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_legacy_composite_destruction_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_legacy_composite_destruction_tests)
add_test(NAME guest_json_legacy_composite_destruction COMMAND guest_json_legacy_composite_destruction_tests)
if(WIN32)
set_tests_properties(guest_json_legacy_composite_destruction PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_json_composite_destruction_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCompositeDestruction.cpp)
target_include_directories(guest_json_composite_destruction_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_composite_destruction_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_composite_destruction_tests)
add_test(NAME guest_json_composite_destruction COMMAND guest_json_composite_destruction_tests)
add_test_executable(guest_json_legacy_initialization_publication_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationPublication.cpp Export.cpp)
target_compile_definitions(guest_json_legacy_initialization_publication_tests PRIVATE JSON2_PUBLICATION_LEGACY_SDK)
target_include_directories(guest_json_legacy_initialization_publication_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_legacy_initialization_publication_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_legacy_initialization_publication_tests)
add_test(NAME guest_json_legacy_initialization_publication COMMAND guest_json_legacy_initialization_publication_tests)
if(WIN32)
set_tests_properties(guest_json_legacy_initialization_publication PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_json_initialization_publication_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationPublication.cpp)
target_include_directories(guest_json_initialization_publication_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_initialization_publication_tests PRIVATE libSceJson2 libkernel libc)
configure_windows_unwind(guest_json_initialization_publication_tests)
add_test(NAME guest_json_initialization_publication COMMAND guest_json_initialization_publication_tests)
add_test_executable(guest_json_callback_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCallbackLifecycle.cpp)
target_include_directories(guest_json_callback_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_callback_lifecycle_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_callback_lifecycle_tests)
add_test(NAME guest_json_callback_lifecycle COMMAND guest_json_callback_lifecycle_tests)
add_test_executable(guest_json_callback_copies_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCallbackCopies.cpp)
target_include_directories(guest_json_callback_copies_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_callback_copies_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_callback_copies_tests)
add_test(NAME guest_json_callback_copies COMMAND guest_json_callback_copies_tests)
add_test_executable(guest_json_pair_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonPairAllocator.cpp)
target_include_directories(guest_json_pair_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_pair_allocator_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_pair_allocator_tests)
add_test(NAME guest_json_pair_allocator COMMAND guest_json_pair_allocator_tests)
add_test_executable(guest_json_pair_layout_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonPairLayout.cpp)
target_include_directories(guest_json_pair_layout_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_pair_layout_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_pair_layout_tests)
add_test(NAME guest_json_pair_layout COMMAND guest_json_pair_layout_tests)
add_test_executable(guest_json_kernel_initialization_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonKernelInitialization.cpp Export.cpp)
target_include_directories(guest_json_kernel_initialization_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_kernel_initialization_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_kernel_initialization_tests)
add_test(NAME guest_json_kernel_initialization COMMAND guest_json_kernel_initialization_tests)
if(WIN32)
set_tests_properties(guest_json_kernel_initialization PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_json_inherited_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInheritedRecovery.cpp)
target_include_directories(guest_json_inherited_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_inherited_recovery_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_inherited_recovery_tests)
add_test(NAME guest_json_inherited_recovery COMMAND guest_json_inherited_recovery_tests)
add_test_executable(guest_json_initialization_release_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationRelease.cpp)
target_include_directories(guest_json_initialization_release_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_initialization_release_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_initialization_release_tests)
add_test(NAME guest_json_initialization_release COMMAND guest_json_initialization_release_tests)
add_test_executable(guest_json_legacy_initialization_release_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationRelease.cpp Export.cpp)
target_compile_definitions(guest_json_legacy_initialization_release_tests PRIVATE JSON2_LEGACY_TEST)
target_include_directories(guest_json_legacy_initialization_release_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_legacy_initialization_release_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_legacy_initialization_release_tests)
add_test(NAME guest_json_legacy_initialization_release COMMAND guest_json_legacy_initialization_release_tests)
if(WIN32)
set_tests_properties(guest_json_initialization_release guest_json_legacy_initialization_release PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceJson2>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_json_recovery_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonRecoveryAllocator.cpp)
target_include_directories(guest_json_recovery_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_recovery_allocator_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_recovery_allocator_tests)
add_test(NAME guest_json_recovery_allocator COMMAND guest_json_recovery_allocator_tests)
add_test_executable(guest_json_parser_metadata_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonParserMetadata.cpp)
target_include_directories(guest_json_parser_metadata_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_parser_metadata_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_parser_metadata_tests)
add_test(NAME guest_json_parser_metadata COMMAND guest_json_parser_metadata_tests)
add_test_executable(guest_json_decimal_range_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonDecimalRange.cpp)
target_include_directories(guest_json_decimal_range_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_decimal_range_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_decimal_range_tests)
add_test(NAME guest_json_decimal_range COMMAND guest_json_decimal_range_tests)
add_test_executable(guest_json_serialization_callback_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonSerializationCallback.cpp)
target_include_directories(guest_json_serialization_callback_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_serialization_callback_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_serialization_callback_tests)
add_test(NAME guest_json_serialization_callback COMMAND guest_json_serialization_callback_tests)
add_test_executable(guest_json_key_lookup_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonKeyLookup.cpp)
target_include_directories(guest_json_key_lookup_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_key_lookup_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_key_lookup_tests)
add_test(NAME guest_json_key_lookup COMMAND guest_json_key_lookup_tests)
add_test_executable(guest_json_array_insertion_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonArrayInsertion.cpp)
target_include_directories(guest_json_array_insertion_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_array_insertion_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_array_insertion_tests)
add_test(NAME guest_json_array_insertion COMMAND guest_json_array_insertion_tests)
add_test_executable(guest_json_array_mutation_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonArrayMutation.cpp)
target_include_directories(guest_json_array_mutation_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_array_mutation_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_array_mutation_tests)
add_test(NAME guest_json_array_mutation COMMAND guest_json_array_mutation_tests)
add_test_executable(guest_json_internal_initializer_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInternalInitializer.cpp)
target_include_directories(guest_json_internal_initializer_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_internal_initializer_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_internal_initializer_tests)
add_test(NAME guest_json_internal_initializer COMMAND guest_json_internal_initializer_tests)
add_test_executable(guest_json_allocation_entry_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonAllocationEntry.cpp)
target_include_directories(guest_json_allocation_entry_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_allocation_entry_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_allocation_entry_tests)
add_test(NAME guest_json_allocation_entry COMMAND guest_json_allocation_entry_tests)
add_test_executable(guest_json_string_search_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonStringSearch.cpp)
target_include_directories(guest_json_string_search_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_string_search_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_string_search_tests)
add_test(NAME guest_json_string_search COMMAND guest_json_string_search_tests)
add_test_executable(guest_json_reference_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonReferences.cpp)
target_include_directories(guest_json_reference_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_reference_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_reference_tests)
add_test(NAME guest_json_references COMMAND guest_json_reference_tests)
add_test_executable(guest_json_alias_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonAliases.cpp)
target_include_directories(guest_json_alias_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_alias_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_alias_tests)
add_test(NAME guest_json_aliases COMMAND guest_json_alias_tests)
add_test_executable(guest_json_tests ${LIBS_INCLUDE_DIR}/tests/GuestJson.cpp)
target_include_directories(guest_json_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_tests PRIVATE libSceJson2 libc)
@@ -10,6 +258,76 @@ if(BUILD_TESTING)
add_test(NAME guest_json COMMAND guest_json_tests)
add_test(NAME guest_json_numeric_locale COMMAND guest_json_tests --numeric-locale)
set_tests_properties(guest_json_numeric_locale PROPERTIES SKIP_RETURN_CODE 77)
add_test_executable(guest_json_float_settings_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonFloatSettings.cpp)
target_include_directories(guest_json_float_settings_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_float_settings_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_float_settings_tests)
add_test(NAME guest_json_float_settings COMMAND guest_json_float_settings_tests)
add_test_executable(guest_json_allocator_settings_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonAllocatorSettings.cpp)
target_include_directories(guest_json_allocator_settings_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_allocator_settings_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_allocator_settings_tests)
add_test(NAME guest_json_allocator_settings COMMAND guest_json_allocator_settings_tests)
add_test_executable(guest_json_parser_state_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonParserState.cpp)
target_include_directories(guest_json_parser_state_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_parser_state_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_parser_state_tests)
add_test(NAME guest_json_parser_state COMMAND guest_json_parser_state_tests)
add_test_executable(guest_json_value_layout_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonValueLayout.cpp)
target_include_directories(guest_json_value_layout_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_value_layout_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_value_layout_tests)
add_test(NAME guest_json_value_layout COMMAND guest_json_value_layout_tests)
add_test_executable(guest_json_value_ownership_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonValueOwnership.cpp)
target_include_directories(guest_json_value_ownership_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_value_ownership_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_value_ownership_tests)
add_test(NAME guest_json_value_ownership COMMAND guest_json_value_ownership_tests)
add_test_executable(guest_json_special_float_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonSpecialFloat.cpp)
target_include_directories(guest_json_special_float_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_special_float_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_special_float_tests)
add_test(NAME guest_json_special_float COMMAND guest_json_special_float_tests)
add_test_executable(guest_json_element_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonElementRecovery.cpp)
target_include_directories(guest_json_element_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_element_recovery_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_element_recovery_tests)
add_test(NAME guest_json_element_recovery COMMAND guest_json_element_recovery_tests)
add_test_executable(guest_json_null_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonNullRecovery.cpp)
target_include_directories(guest_json_null_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_null_recovery_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_null_recovery_tests)
add_test(NAME guest_json_null_recovery COMMAND guest_json_null_recovery_tests)
add_test_executable(guest_json_global_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonGlobalRecovery.cpp)
target_include_directories(guest_json_global_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_global_recovery_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_global_recovery_tests)
add_test(NAME guest_json_global_recovery COMMAND guest_json_global_recovery_tests)
add_test_executable(guest_json_string_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonStringAllocator.cpp)
target_include_directories(guest_json_string_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_string_allocator_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_string_allocator_tests)
add_test(NAME guest_json_string_allocator COMMAND guest_json_string_allocator_tests)
add_test_executable(guest_json_array_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonArrayAllocator.cpp)
target_include_directories(guest_json_array_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_array_allocator_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_array_allocator_tests)
add_test(NAME guest_json_array_allocator COMMAND guest_json_array_allocator_tests)
add_test_executable(guest_json_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonAllocator.cpp)
target_include_directories(guest_json_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_allocator_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_allocator_tests)
add_test(NAME guest_json_allocator COMMAND guest_json_allocator_tests)
add_test_executable(guest_json_value_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonValueAllocator.cpp)
target_include_directories(guest_json_value_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_value_allocator_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_value_allocator_tests)
add_test(NAME guest_json_value_allocator COMMAND guest_json_value_allocator_tests)
add_test_executable(guest_json_object_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonObjectAllocator.cpp)
target_include_directories(guest_json_object_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_object_allocator_tests PRIVATE libSceJson2 libc)
configure_windows_unwind(guest_json_object_allocator_tests)
add_test(NAME guest_json_object_allocator COMMAND guest_json_object_allocator_tests)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(numeric_locale_dir ${CMAKE_CURRENT_BINARY_DIR}/numeric-locale)
set(numeric_locale_data ${LIBS_INCLUDE_DIR}/tests/numeric-locale)
@@ -20,23 +338,130 @@ if(BUILD_TESTING)
ENVIRONMENT "LOCPATH=${numeric_locale_dir}")
endif()
if(WIN32)
set_tests_properties(guest_json guest_json_numeric_locale PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceJson2>")
get_property(json2_module_exception_tests DIRECTORY PROPERTY TESTS)
list(FILTER json2_module_exception_tests INCLUDE REGEX "^guest_json_(modern_termination_|initialization_exception_)")
set_tests_properties(${json2_module_exception_tests} guest_json_composite_destruction PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceJson2>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(guest_json guest_json_initialization_publication guest_json_callback_lifecycle guest_json_callback_copies guest_json_pair_allocator guest_json_pair_layout guest_json_inherited_recovery guest_json_recovery_allocator guest_json_parser_metadata guest_json_decimal_range guest_json_serialization_callback guest_json_key_lookup guest_json_array_insertion guest_json_array_mutation guest_json_internal_initializer guest_json_allocation_entry guest_json_string_search guest_json_references guest_json_aliases guest_json_parser_state guest_json_numeric_locale guest_json_float_settings guest_json_allocator_settings guest_json_value_layout guest_json_value_ownership guest_json_special_float guest_json_element_recovery guest_json_null_recovery guest_json_global_recovery guest_json_string_allocator guest_json_array_allocator guest_json_object_allocator guest_json_value_allocator guest_json_allocator PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceJson2>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(guest_json_copy_failure_tests ${LIBS_INCLUDE_DIR}/tests/JsonCopyFailure.cpp Export.cpp)
target_include_directories(guest_json_copy_failure_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_copy_failure_tests PRIVATE libkernel libc)
target_compile_features(guest_json_copy_failure_tests PRIVATE cxx_std_23)
if(WIN32)
target_link_libraries(guest_json_copy_failure_tests PRIVATE libc)
configure_windows_unwind(guest_json_copy_failure_tests)
endif()
add_test(NAME guest_json_copy_failure COMMAND guest_json_copy_failure_tests)
set_tests_properties(guest_json_copy_failure PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_json_copy_failure PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(guest_json_float_failure_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonFloatFailure.cpp Export.cpp)
target_include_directories(guest_json_float_failure_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_float_failure_tests PRIVATE libkernel libc)
if(WIN32)
configure_windows_unwind(guest_json_float_failure_tests)
endif()
add_test(NAME guest_json_float_failure COMMAND guest_json_float_failure_tests)
set_tests_properties(guest_json_float_failure PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_json_float_failure PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(guest_json_file_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonFile.cpp Export.cpp)
target_include_directories(guest_json_file_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_file_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_file_tests)
add_test(NAME guest_json_file COMMAND guest_json_file_tests)
set_tests_properties(guest_json_file PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(guest_json_file PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(guest_json_legacy_handlers_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonLegacyHandlers.cpp Export.cpp)
target_include_directories(guest_json_legacy_handlers_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_legacy_handlers_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_legacy_handlers_tests)
add_test(NAME guest_json_legacy_handlers COMMAND guest_json_legacy_handlers_tests)
foreach(position RANGE 0 2)
foreach(alias RANGE 0 1)
foreach(wrapper direct wrapped)
add_test(NAME guest_json_legacy_object_heap_exception_${position}_${alias}_${wrapper} COMMAND guest_json_legacy_handlers_tests object-heap ${position} ${alias} ${wrapper})
if(WIN32)
set_tests_properties(guest_json_legacy_object_heap_exception_${position}_${alias}_${wrapper} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endforeach()
endforeach()
endforeach()
foreach(position RANGE 0 2)
foreach(alias RANGE 0 1)
add_test(NAME guest_json_legacy_array_heap_exception_${position}_${alias} COMMAND guest_json_legacy_handlers_tests array-heap ${position} ${alias})
add_test(NAME guest_json_legacy_value_array_heap_exception_${position}_${alias} COMMAND guest_json_legacy_handlers_tests value-array-heap ${position} ${alias})
if(WIN32)
set_tests_properties(guest_json_legacy_array_heap_exception_${position}_${alias} guest_json_legacy_value_array_heap_exception_${position}_${alias} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endforeach()
endforeach()
foreach(shape RANGE 0 3)
foreach(alias RANGE 0 2)
add_test(NAME guest_json_legacy_copy_heap_exception_${shape}_${alias} COMMAND guest_json_legacy_handlers_tests ${shape} ${alias})
if(WIN32)
set_tests_properties(guest_json_legacy_copy_heap_exception_${shape}_${alias} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endforeach()
endforeach()
foreach(position RANGE 0 2)
add_test(NAME guest_json_legacy_heap_exception_${position} COMMAND guest_json_legacy_handlers_tests ${position})
if(WIN32)
set_tests_properties(guest_json_legacy_heap_exception_${position} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endforeach()
if(WIN32)
set_tests_properties(guest_json_legacy_handlers PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(guest_json_legacy_serialization_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonLegacySerialization.cpp Export.cpp)
target_include_directories(guest_json_legacy_serialization_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_legacy_serialization_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_json_legacy_serialization_tests)
add_test(NAME guest_json_legacy_serialization COMMAND guest_json_legacy_serialization_tests)
if(WIN32)
set_tests_properties(guest_json_legacy_serialization PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()
if(BUILD_TESTING)
find_package(Threads REQUIRED)
add_test_executable(guest_json_callback_threads_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCallbackThreads.cpp Export.cpp)
target_include_directories(guest_json_callback_threads_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_json_callback_threads_tests PRIVATE libkernel libc Threads::Threads)
configure_windows_unwind(guest_json_callback_threads_tests)
add_test(NAME guest_json_callback_threads COMMAND guest_json_callback_threads_tests)
set_tests_properties(guest_json_callback_threads PROPERTIES TIMEOUT 10)
if(WIN32)
set_tests_properties(guest_json_callback_threads PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
endif()
File diff suppressed because it is too large. Load diff
+2 -392
View File
@@ -1,3 +1,4 @@
#include "prx/libSceJson2/include/Api.hpp"
#include <cstddef>
#include <cstdint>
#include "SceTypes.hpp"
@@ -5,400 +6,9 @@
extern "C" {
int APS5_VABI sceJson2Unknown_001_nid_no_patch() {
int APS5_VABI _ZN3sce4Json12MemAllocatorD0Ev() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_", sceJson2Unknown_001_nid_no_patch);
int APS5_VABI sceJson2Unknown_002_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json11Initializer28setGlobalSpecialFloatHandlerEPFKNS0_5ValueENS0_12FunctionTypeEdPS3_PvPbES6_", sceJson2Unknown_002_nid_no_patch);
int APS5_VABI sceJson2Unknown_003_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json11Initializer28setGlobalTypeMismatchHandlerEPFRKNS0_5ValueENS0_9ValueTypeEPS3_S6_PvES7_", sceJson2Unknown_003_nid_no_patch);
int APS5_VABI sceJson2Unknown_004_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json11Initializer36setGlobalElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKNS0_5ValueES9_PvESA_", sceJson2Unknown_004_nid_no_patch);
int APS5_VABI sceJson2Unknown_005_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json11InitializerC2Ev", sceJson2Unknown_005_nid_no_patch);
int APS5_VABI sceJson2Unknown_006_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json11InitializerD2Ev", sceJson2Unknown_006_nid_no_patch);
int APS5_VABI sceJson2Unknown_007_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json12MemAllocatorD0Ev", sceJson2Unknown_007_nid_no_patch);
int APS5_VABI sceJson2Unknown_008_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json12MemAllocatorD1Ev", sceJson2Unknown_008_nid_no_patch);
int APS5_VABI sceJson2Unknown_009_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json17InitParameterRttiC2EPNS0_14AllocParamRttiEPvm", sceJson2Unknown_009_nid_no_patch);
int APS5_VABI sceJson2Unknown_010_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json19InternalInitializer19terminate_staticlibEv", sceJson2Unknown_010_nid_no_patch);
int APS5_VABI sceJson2Unknown_011_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE", sceJson2Unknown_011_nid_no_patch);
int APS5_VABI sceJson2Unknown_012_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json19InternalInitializerC1Ev", sceJson2Unknown_012_nid_no_patch);
int APS5_VABI sceJson2Unknown_013_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json19InternalInitializerC2Ev", sceJson2Unknown_013_nid_no_patch);
int APS5_VABI sceJson2Unknown_014_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json19InternalInitializerD1Ev", sceJson2Unknown_014_nid_no_patch);
int APS5_VABI sceJson2Unknown_015_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json19InternalInitializerD2Ev", sceJson2Unknown_015_nid_no_patch);
int APS5_VABI sceJson2Unknown_016_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json4FreeEPv", sceJson2Unknown_016_nid_no_patch);
int APS5_VABI sceJson2Unknown_017_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Array10push_frontERKNS0_5ValueE", sceJson2Unknown_017_nid_no_patch);
int APS5_VABI sceJson2Unknown_018_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Array5eraseERKNS1_8iteratorE", sceJson2Unknown_018_nid_no_patch);
int APS5_VABI sceJson2Unknown_019_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE", sceJson2Unknown_019_nid_no_patch);
int APS5_VABI sceJson2Unknown_020_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Array8iteratorC2ERKS2_", sceJson2Unknown_020_nid_no_patch);
int APS5_VABI sceJson2Unknown_021_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Array8iteratorC2Ev", sceJson2Unknown_021_nid_no_patch);
int APS5_VABI sceJson2Unknown_022_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Array8iteratorD2Ev", sceJson2Unknown_022_nid_no_patch);
int APS5_VABI sceJson2Unknown_023_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Array8pop_backEv", sceJson2Unknown_023_nid_no_patch);
int APS5_VABI sceJson2Unknown_024_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Array9pop_frontEv", sceJson2Unknown_024_nid_no_patch);
int APS5_VABI sceJson2Unknown_025_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ArrayC2ERKS1_", sceJson2Unknown_025_nid_no_patch);
int APS5_VABI sceJson2Unknown_026_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ArrayC2Ev", sceJson2Unknown_026_nid_no_patch);
int APS5_VABI sceJson2Unknown_027_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ArrayD2Ev", sceJson2Unknown_027_nid_no_patch);
int APS5_VABI sceJson2Unknown_029_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Value11referStringEv", sceJson2Unknown_029_nid_no_patch);
int APS5_VABI sceJson2Unknown_030_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Value12referBooleanEv", sceJson2Unknown_030_nid_no_patch);
int APS5_VABI sceJson2Unknown_031_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Value12referIntegerEv", sceJson2Unknown_031_nid_no_patch);
int APS5_VABI sceJson2Unknown_032_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Value13referUIntegerEv", sceJson2Unknown_032_nid_no_patch);
int APS5_VABI sceJson2Unknown_033_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Value9referRealEv", sceJson2Unknown_033_nid_no_patch);
int APS5_VABI sceJson2Unknown_034_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_", sceJson2Unknown_034_nid_no_patch);
int APS5_VABI sceJson2Unknown_035_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_", sceJson2Unknown_035_nid_no_patch);
int APS5_VABI sceJson2Unknown_036_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2ENS0_9ValueTypeE", sceJson2Unknown_036_nid_no_patch);
int APS5_VABI sceJson2Unknown_037_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2EPKc", sceJson2Unknown_037_nid_no_patch);
int APS5_VABI sceJson2Unknown_038_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2ERKNS0_5ArrayE", sceJson2Unknown_038_nid_no_patch);
int APS5_VABI sceJson2Unknown_039_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2ERKNS0_6ObjectE", sceJson2Unknown_039_nid_no_patch);
int APS5_VABI sceJson2Unknown_040_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2ERKNS0_6StringE", sceJson2Unknown_040_nid_no_patch);
int APS5_VABI sceJson2Unknown_041_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2ERKS1_", sceJson2Unknown_041_nid_no_patch);
int APS5_VABI sceJson2Unknown_042_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2Eb", sceJson2Unknown_042_nid_no_patch);
int APS5_VABI sceJson2Unknown_043_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2Ed", sceJson2Unknown_043_nid_no_patch);
int APS5_VABI sceJson2Unknown_044_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2El", sceJson2Unknown_044_nid_no_patch);
int APS5_VABI sceJson2Unknown_045_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2Em", sceJson2Unknown_045_nid_no_patch);
int APS5_VABI sceJson2Unknown_046_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueC2Ev", sceJson2Unknown_046_nid_no_patch);
int APS5_VABI sceJson2Unknown_047_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json5ValueD2Ev", sceJson2Unknown_047_nid_no_patch);
int APS5_VABI sceJson2Unknown_048_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6MallocEm", sceJson2Unknown_048_nid_no_patch);
int APS5_VABI sceJson2Unknown_049_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE", sceJson2Unknown_049_nid_no_patch);
int APS5_VABI sceJson2Unknown_050_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6Object4PairC2Ev", sceJson2Unknown_050_nid_no_patch);
int APS5_VABI sceJson2Unknown_051_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6Object4PairD2Ev", sceJson2Unknown_051_nid_no_patch);
int APS5_VABI sceJson2Unknown_052_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6Object8iteratorC2ERKS2_", sceJson2Unknown_052_nid_no_patch);
int APS5_VABI sceJson2Unknown_053_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6Object8iteratorC2Ev", sceJson2Unknown_053_nid_no_patch);
int APS5_VABI sceJson2Unknown_054_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6Object8iteratorD2Ev", sceJson2Unknown_054_nid_no_patch);
int APS5_VABI sceJson2Unknown_055_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6ObjectC2ERKS1_", sceJson2Unknown_055_nid_no_patch);
int APS5_VABI sceJson2Unknown_056_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6ObjectC2Ev", sceJson2Unknown_056_nid_no_patch);
int APS5_VABI sceJson2Unknown_057_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6ObjectD2Ev", sceJson2Unknown_057_nid_no_patch);
int APS5_VABI sceJson2Unknown_058_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6Parser5parseERNS0_5ValueEPKc", sceJson2Unknown_058_nid_no_patch);
int APS5_VABI sceJson2Unknown_059_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6StringC2EPKc", sceJson2Unknown_059_nid_no_patch);
int APS5_VABI sceJson2Unknown_060_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6StringC2ERKS1_", sceJson2Unknown_060_nid_no_patch);
int APS5_VABI sceJson2Unknown_061_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6StringC2Ev", sceJson2Unknown_061_nid_no_patch);
int APS5_VABI sceJson2Unknown_062_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZN3sce4Json6StringD2Ev", sceJson2Unknown_062_nid_no_patch);
int APS5_VABI sceJson2Unknown_063_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZNK3sce4Json14InitParameter211getUserDataEv", sceJson2Unknown_063_nid_no_patch);
int APS5_VABI sceJson2Unknown_064_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZNK3sce4Json14InitParameter212getAllocatorEv", sceJson2Unknown_064_nid_no_patch);
int APS5_VABI sceJson2Unknown_065_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZNK3sce4Json14InitParameter217getFileBufferSizeEv", sceJson2Unknown_065_nid_no_patch);
int APS5_VABI sceJson2Unknown_066_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZNK3sce4Json14InitParameter225getSpecialFloatFormatTypeEv", sceJson2Unknown_066_nid_no_patch);
int APS5_VABI sceJson2Unknown_067_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
APS5_EXPORT("_ZNK3sce4Json5ValueixERKNS0_6StringE", sceJson2Unknown_067_nid_no_patch);
}
+262 -289
View File
@@ -1,23 +1,8 @@
#pragma once
#include "SceTypes.hpp"
#include <algorithm>
#include <bit>
#include <cerrno>
#include <charconv>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <list>
#include <locale>
#include <memory>
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
namespace SceJson2 {
@@ -32,75 +17,42 @@ enum ValueType : std::int32_t {
TypeObject = 7,
};
}
struct Value;
struct Text;
struct Auxiliary;
struct ArrayStorage;
struct ObjectStorage;
struct ArrayIteratorState;
struct ObjectIteratorState;
namespace SceJson2 {
struct Text { char* characters; std::uint32_t length; std::uint32_t capacity; std::uint64_t reserved; };
struct Node;
struct String { Text* text; };
}
struct Array { ArrayStorage* items; };
namespace SceJson2 {
struct Value { Node* node; };
}
namespace SceJson2 {
struct String { std::string* text; };
}
namespace SceJson2 {
struct Array { std::list<Value>* items; };
}
namespace SceJson2 {
struct Pair;
}
namespace SceJson2 {
struct Object { std::list<Pair>* items; };
}
namespace SceJson2 {
struct ArrayIteratorState {
std::list<Value>::iterator position;
std::list<Value>::iterator end;
};
}
namespace SceJson2 {
struct Object { ObjectStorage* items; };
struct ArrayIterator { ArrayIteratorState* state; };
}
namespace SceJson2 {
struct ObjectIteratorState {
std::list<Pair>* owner = nullptr;
std::list<Pair>::iterator position{};
};
}
namespace SceJson2 {
struct ObjectIterator { ObjectIteratorState* state; };
}
namespace SceJson2 {
struct Value {
Value* parent = nullptr;
Auxiliary* auxiliary = nullptr;
union {
std::uint64_t payload = 0;
bool boolean;
std::int64_t integer;
std::uint64_t uinteger;
double real;
String* string;
Array* array;
Object* object;
};
std::uint32_t reserved = 0;
ValueType type = TypeNull;
};
struct Pair {
String key;
@@ -108,79 +60,12 @@ struct Pair {
Value value;
};
}
namespace SceJson2 {
using TypeMismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*);
}
namespace SceJson2 {
using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*);
}
namespace SceJson2 {
using ElementAccessFailureHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*);
}
namespace SceJson2 {
struct Handlers {
TypeMismatchHandler typeMismatch = nullptr;
void* typeMismatchContext = nullptr;
SpecialFloatHandler specialFloat = nullptr;
void* specialFloatContext = nullptr;
ElementAccessFailureHandler elementAccessFailure = nullptr;
void* elementAccessFailureContext = nullptr;
};
}
namespace SceJson2 {
struct Node {
ValueType type = TypeNull;
bool boolean = false;
std::int64_t integer = 0;
std::uint64_t uinteger = 0;
double real = 0;
String string{};
Array array{};
Object object{};
Handlers handlers{};
};
}
namespace SceJson2 {
using NullAccessCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*);
}
namespace SceJson2 {
using ParserReadCallback = int (APS5_VABI*)(char&, void*);
}
namespace SceJson2 {
struct InitParameterRtti {
void* allocParam;
void* userData;
std::size_t fileBufferSize;
};
}
namespace SceJson2 {
struct InitParameter2 {
void* allocator;
void* userData;
@@ -189,159 +74,247 @@ struct InitParameter2 {
std::uint32_t reserved[3];
};
}
using TypeMismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*);
using SpecialFloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*);
using ElementAccessFailureHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*);
using NullAccessCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*);
using ParserReadCallback = int (APS5_VABI*)(char&, void*);
using SerializationCallback = int (APS5_VABI*)(String&, void*);
extern "C" {
extern const bool _ZN3sce4Json5Value10s_nullboolE;
extern const std::int64_t _ZN3sce4Json5Value13s_nullintegerE;
extern const std::uint64_t _ZN3sce4Json5Value14s_nulluintegerE;
extern const double _ZN3sce4Json5Value10s_nullrealE;
extern String* _ZN3sce4Json5Value12s_nullstringE;
extern Array* _ZN3sce4Json5Value11s_nullarrayE;
extern Object* _ZN3sce4Json5Value12s_nullobjectE;
int APS5_VABI _ZN3sce4Json11InitializerC1Ev(void* self);
int APS5_VABI _ZN3sce4Json11InitializerD1Ev(void* self);
int APS5_VABI _ZN3sce4Json12MemAllocatorD0Ev(void);
extern std::size_t _ZN3sce4Json6String4nposE;
void APS5_VABI _ZN3sce4Json11InitializerC1Ev(void* self);
void APS5_VABI _ZN3sce4Json11InitializerD1Ev(void* self);
int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(void* self, const void* parameter);
void APS5_VABI _ZN3sce4Json19InternalInitializerC1Ev(void* self);
void APS5_VABI _ZN3sce4Json19InternalInitializerC2Ev(void* self);
void APS5_VABI _ZN3sce4Json19InternalInitializerD1Ev(void* self);
void APS5_VABI _ZN3sce4Json19InternalInitializerD2Ev(void* self);
int APS5_VABI _ZN3sce4Json19InternalInitializer19terminate_staticlibEv(void* self);
int APS5_VABI _ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE(void* self, const void* parameter, std::int32_t option);
int APS5_VABI _ZN3sce4Json11Initializer9terminateEv(void* self);
int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, SceJson2::NullAccessCallback callback, void* context);
int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, SceJson2::NullAccessCallback callback, void* context);
int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, NullAccessCallback callback, void* context);
int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, NullAccessCallback callback, void* context);
int APS5_VABI _ZN3sce4Json11Initializer28setGlobalTypeMismatchHandlerEPFRKNS0_5ValueENS0_9ValueTypeEPS3_S6_PvES7_(void* self, TypeMismatchHandler callback, void* context);
int APS5_VABI _ZN3sce4Json11Initializer28setGlobalSpecialFloatHandlerEPFKNS0_5ValueENS0_12FunctionTypeEdPS3_PvPbES6_(void* self, SpecialFloatHandler callback, void* context);
int APS5_VABI _ZN3sce4Json11Initializer36setGlobalElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKNS0_5ValueES9_PvESA_(void* self, ElementAccessFailureHandler callback, void* context);
void APS5_VABI _ZN3sce4Json12MemAllocatorC2Ev(void* self);
void APS5_VABI _ZN3sce4Json12MemAllocatorD2Ev(void* self);
void APS5_VABI _ZN3sce4Json6StringC1Ev(SceJson2::String* self);
void APS5_VABI _ZN3sce4Json6StringC1EPKc(SceJson2::String* self, const char* text);
void APS5_VABI _ZN3sce4Json6StringC1ERKS1_(SceJson2::String* self, const SceJson2::String* other);
void APS5_VABI _ZN3sce4Json6StringD1Ev(SceJson2::String* self);
SceJson2::String* APS5_VABI _ZN3sce4Json6StringaSERKS1_(SceJson2::String* self, const SceJson2::String* other);
const char* APS5_VABI _ZNK3sce4Json6String5c_strEv(const SceJson2::String* self);
bool APS5_VABI _ZNK3sce4Json6String5emptyEv(const SceJson2::String* self);
std::size_t APS5_VABI _ZNK3sce4Json6String6lengthEv(const SceJson2::String* self);
std::size_t APS5_VABI _ZNK3sce4Json6String4sizeEv(const SceJson2::String* self);
std::size_t APS5_VABI _ZNK3sce4Json6String8capacityEv(const SceJson2::String* self);
bool APS5_VABI _ZNK3sce4Json6StringeqEPKc(const SceJson2::String* self, const char* text);
bool APS5_VABI _ZNK3sce4Json6StringeqERKS1_(const SceJson2::String* self, const SceJson2::String* other);
void APS5_VABI _ZN3sce4Json6String5clearEv(SceJson2::String* self);
void APS5_VABI _ZN3sce4Json6String6resizeEm(SceJson2::String* self, std::size_t count);
void APS5_VABI _ZN3sce4Json6String7reserveEm(SceJson2::String* self, std::size_t capacity);
SceJson2::String* APS5_VABI _ZN3sce4Json6String6appendERKS1_(SceJson2::String* self, const SceJson2::String* other);
SceJson2::String* APS5_VABI _ZN3sce4Json6String6appendEPKc(SceJson2::String* self, const char* text);
SceJson2::String* APS5_VABI _ZN3sce4Json6String6appendEPKcm(SceJson2::String* self, const char* text, std::size_t length);
SceJson2::String* APS5_VABI _ZN3sce4Json6StringpLEPKc(SceJson2::String* self, const char* text);
SceJson2::String* APS5_VABI _ZN3sce4Json6StringpLEh(SceJson2::String* self, unsigned char ch);
SceJson2::String* APS5_VABI _ZNK3sce4Json6String6substrEmm(SceJson2::String* result, const SceJson2::String* self, std::size_t position, std::size_t length);
std::size_t APS5_VABI _ZNK3sce4Json6String4findEcm(const SceJson2::String* self, char character, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEcm(const SceJson2::String* self, char character, std::size_t position);
char APS5_VABI _ZNK3sce4Json6String2atEm(const SceJson2::String* self, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcm(const SceJson2::String* self, const char* text, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcmm(const SceJson2::String* self, const char* text, std::size_t position, std::size_t length);
std::size_t APS5_VABI _ZNK3sce4Json6String4findERKS1_m(const SceJson2::String* self, const SceJson2::String* other, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcm(const SceJson2::String* self, const char* text, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcmm(const SceJson2::String* self, const char* text, std::size_t position, std::size_t length);
std::size_t APS5_VABI _ZNK3sce4Json6String5rfindERKS1_m(const SceJson2::String* self, const SceJson2::String* other, std::size_t position);
int APS5_VABI _ZNK3sce4Json6String7compareEPKc(const SceJson2::String* self, const char* text);
int APS5_VABI _ZNK3sce4Json6String7compareERKS1_(const SceJson2::String* self, const SceJson2::String* other);
void APS5_VABI _ZN3sce4Json5ArrayC1Ev(SceJson2::Array* self);
void APS5_VABI _ZN3sce4Json5ArrayC1ERKS1_(SceJson2::Array* self, const SceJson2::Array* other);
void APS5_VABI _ZN3sce4Json5ArrayD1Ev(SceJson2::Array* self);
SceJson2::Array* APS5_VABI _ZN3sce4Json5ArrayaSERKS1_(SceJson2::Array* self, const SceJson2::Array* other);
void APS5_VABI _ZN3sce4Json5Array9push_backERKNS0_5ValueE(SceJson2::Array* self, const SceJson2::Value* value);
SceJson2::ArrayIterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(SceJson2::ArrayIterator* result, const SceJson2::Array* self);
SceJson2::ArrayIterator* APS5_VABI _ZNK3sce4Json5Array3endEv(SceJson2::ArrayIterator* result, const SceJson2::Array* self);
bool APS5_VABI _ZNK3sce4Json5Array5emptyEv(const SceJson2::Array* self);
std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const SceJson2::Array* self);
void APS5_VABI _ZN3sce4Json5Array5clearEv(SceJson2::Array* self);
const SceJson2::Value* APS5_VABI _ZNK3sce4Json5Array5frontEv(const SceJson2::Array* self);
const SceJson2::Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const SceJson2::Array* self);
void APS5_VABI _ZN3sce4Json5Array9pop_frontEv(SceJson2::Array* self);
void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(SceJson2::ArrayIterator* self);
SceJson2::ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(SceJson2::ArrayIterator* self);
SceJson2::Value* APS5_VABI _ZNK3sce4Json5Array8iteratordeEv(const SceJson2::ArrayIterator* self);
SceJson2::Value* APS5_VABI _ZNK3sce4Json5Array8iteratorptEv(const SceJson2::ArrayIterator* self);
bool APS5_VABI _ZNK3sce4Json5Array8iteratorneERKS2_(const SceJson2::ArrayIterator* self, const SceJson2::ArrayIterator* other);
void APS5_VABI _ZN3sce4Json5Array8iteratorC1ERKS2_(SceJson2::ArrayIterator* self, const SceJson2::ArrayIterator* other);
SceJson2::ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iterator7advanceEm(SceJson2::ArrayIterator* self, std::size_t count);
void APS5_VABI _ZN3sce4Json6ObjectC1Ev(SceJson2::Object* self);
void APS5_VABI _ZN3sce4Json6ObjectC1ERKS1_(SceJson2::Object* self, const SceJson2::Object* other);
void APS5_VABI _ZN3sce4Json6ObjectD1Ev(SceJson2::Object* self);
SceJson2::Object* APS5_VABI _ZN3sce4Json6ObjectaSERKS1_(SceJson2::Object* self, const SceJson2::Object* other);
void APS5_VABI _ZN3sce4Json6Object5clearEv(SceJson2::Object* self);
SceJson2::Value* APS5_VABI _ZN3sce4Json6ObjectixERKNS0_6StringE(SceJson2::Object* self, const SceJson2::String* key);
SceJson2::ObjectIterator* APS5_VABI _ZNK3sce4Json6Object5beginEv(SceJson2::ObjectIterator* result, const SceJson2::Object* self);
SceJson2::ObjectIterator* APS5_VABI _ZNK3sce4Json6Object3endEv(SceJson2::ObjectIterator* result, const SceJson2::Object* self);
void APS5_VABI _ZN3sce4Json6Object8iteratorD1Ev(SceJson2::ObjectIterator* self);
SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEv(SceJson2::ObjectIterator* self);
SceJson2::Pair* APS5_VABI _ZNK3sce4Json6Object8iteratordeEv(const SceJson2::ObjectIterator* self);
SceJson2::Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const SceJson2::ObjectIterator* self);
SceJson2::ObjectIterator* APS5_VABI _ZNK3sce4Json6Object4findERKNS0_6StringE(SceJson2::ObjectIterator* result, const SceJson2::Object* self, const SceJson2::String* key);
bool APS5_VABI _ZNK3sce4Json6Object8iteratoreqERKS2_(const SceJson2::ObjectIterator* self, const SceJson2::ObjectIterator* other);
bool APS5_VABI _ZNK3sce4Json6Object8iteratorneERKS2_(const SceJson2::ObjectIterator* self, const SceJson2::ObjectIterator* other);
void APS5_VABI _ZN3sce4Json6Object8iteratorC1ERKS2_(SceJson2::ObjectIterator* self, const SceJson2::ObjectIterator* other);
SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iterator7advanceEm(SceJson2::ObjectIterator* self, std::size_t count);
bool APS5_VABI _ZNK3sce4Json6Object5emptyEv(const SceJson2::Object* self);
std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const SceJson2::Object* self);
void APS5_VABI _ZN3sce4Json6Object5eraseERKNS0_6StringE(SceJson2::Object* self, const SceJson2::String* key);
void APS5_VABI _ZN3sce4Json5ValueC1Ev(SceJson2::Value* self);
void APS5_VABI _ZN3sce4Json5ValueC1Eb(SceJson2::Value* self, bool value);
void APS5_VABI _ZN3sce4Json5ValueC1El(SceJson2::Value* self, std::int64_t value);
void APS5_VABI _ZN3sce4Json5ValueC1Em(SceJson2::Value* self, std::uint64_t value);
void APS5_VABI _ZN3sce4Json5ValueC1Ed(SceJson2::Value* self, double value);
void APS5_VABI _ZN3sce4Json5ValueC1EPKc(SceJson2::Value* self, const char* text);
void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6StringE(SceJson2::Value* self, const SceJson2::String* text);
void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(SceJson2::Value* self, const SceJson2::Array* array);
void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(SceJson2::Value* self, const SceJson2::Object* object);
void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(SceJson2::Value* self, const SceJson2::Value* other);
void APS5_VABI _ZN3sce4Json5ValueD1Ev(SceJson2::Value* self);
SceJson2::Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(SceJson2::Value* self, const SceJson2::Value* other);
void APS5_VABI _ZN3sce4Json5Value4swapERS1_(SceJson2::Value* self, SceJson2::Value* other);
int APS5_VABI _ZN3sce4Json5Value3setENS0_9ValueTypeE(SceJson2::Value* self, SceJson2::ValueType type);
int APS5_VABI _ZN3sce4Json5Value3setEb(SceJson2::Value* self, bool value);
int APS5_VABI _ZN3sce4Json5Value3setEl(SceJson2::Value* self, std::int64_t value);
int APS5_VABI _ZN3sce4Json5Value3setEm(SceJson2::Value* self, std::uint64_t value);
int APS5_VABI _ZN3sce4Json5Value3setEd(SceJson2::Value* self, double value);
int APS5_VABI _ZN3sce4Json5Value3setEPKc(SceJson2::Value* self, const char* text);
int APS5_VABI _ZN3sce4Json5Value3setERKNS0_6StringE(SceJson2::Value* self, const SceJson2::String* text);
int APS5_VABI _ZN3sce4Json5Value3setERKNS0_5ArrayE(SceJson2::Value* self, const SceJson2::Array* array);
int APS5_VABI _ZN3sce4Json5Value3setERKNS0_6ObjectE(SceJson2::Value* self, const SceJson2::Object* object);
int APS5_VABI _ZN3sce4Json5Value3setERKS1_(SceJson2::Value* self, const SceJson2::Value* other);
SceJson2::ValueType APS5_VABI _ZNK3sce4Json5Value7getTypeEv(const SceJson2::Value* self);
const bool* APS5_VABI _ZNK3sce4Json5Value10getBooleanEv(const SceJson2::Value* self);
const std::int64_t* APS5_VABI _ZNK3sce4Json5Value10getIntegerEv(const SceJson2::Value* self);
const std::uint64_t* APS5_VABI _ZNK3sce4Json5Value11getUIntegerEv(const SceJson2::Value* self);
const double* APS5_VABI _ZNK3sce4Json5Value7getRealEv(const SceJson2::Value* self);
const SceJson2::String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const SceJson2::Value* self);
const SceJson2::Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const SceJson2::Value* self);
const SceJson2::Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const SceJson2::Value* self);
std::size_t APS5_VABI _ZNK3sce4Json5Value5countEv(const SceJson2::Value* self);
const SceJson2::Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const SceJson2::Value* self, const char* key);
const SceJson2::Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const SceJson2::Value* self, std::size_t index);
void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(SceJson2::Value* self, SceJson2::ValueType type);
const SceJson2::Value* APS5_VABI _ZNK3sce4Json5Value8getValueEm(const SceJson2::Value* self, std::size_t index);
const SceJson2::Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const SceJson2::Value* self, const SceJson2::String* key);
SceJson2::Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(SceJson2::Value* self, std::size_t index);
SceJson2::Value* APS5_VABI _ZN3sce4Json5Value10referValueERKNS0_6StringE(SceJson2::Value* self, const SceJson2::String* key);
SceJson2::Array* APS5_VABI _ZN3sce4Json5Value10referArrayEv(SceJson2::Value* self);
SceJson2::Object* APS5_VABI _ZN3sce4Json5Value11referObjectEv(SceJson2::Value* self);
int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(SceJson2::Value* self, SceJson2::String* out);
int APS5_VABI _ZNK3sce4Json5Value8toStringERNS0_6StringE(const SceJson2::Value* self, SceJson2::String* out);
int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(SceJson2::Value* out, SceJson2::ParserReadCallback read, void* context);
int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(SceJson2::Value* out, const char* text, std::size_t size);
void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(SceJson2::InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize);
void APS5_VABI _ZN3sce4Json14InitParameter2C1Ev(SceJson2::InitParameter2* self);
void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(SceJson2::InitParameter2* self);
void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(SceJson2::InitParameter2* self, void* allocator, void* userData);
void APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(SceJson2::InitParameter2* self, void* allocParam, void* userData);
void APS5_VABI _ZN3sce4Json14InitParameter217setFileBufferSizeEm(SceJson2::InitParameter2* self, std::size_t size);
void APS5_VABI _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(SceJson2::InitParameter2* self, std::int32_t type);
int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(void* self, const SceJson2::InitParameter2* parameter);
void APS5_VABI _ZN3sce4Json5Value5clearEv(SceJson2::Value* self);
void APS5_VABI _ZN3sce4Json12MemAllocatorD2Ev(void*);
void APS5_VABI _ZN3sce4Json12MemAllocatorD1Ev(void*);
void* APS5_VABI _ZN3sce4Json6MallocEm(std::size_t size);
void APS5_VABI _ZN3sce4Json4FreeEPv(void* memory);
int APS5_VABI _ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_(void*, void (APS5_VABI*)(int, ValueType, void*), void*);
void APS5_VABI _ZN3sce4Json6StringC1Ev(String* self);
void APS5_VABI _ZN3sce4Json6StringC1EPKc(String* self, const char* text);
void APS5_VABI _ZN3sce4Json6StringC1ERKS1_(String* self, const String* other);
void APS5_VABI _ZN3sce4Json6StringD1Ev(String* self);
String* APS5_VABI _ZN3sce4Json6StringaSERKS1_(String* self, const String* other);
const char* APS5_VABI _ZNK3sce4Json6String5c_strEv(const String* self);
bool APS5_VABI _ZNK3sce4Json6String5emptyEv(const String* self);
std::size_t APS5_VABI _ZNK3sce4Json6String6lengthEv(const String* self);
std::size_t APS5_VABI _ZNK3sce4Json6String4sizeEv(const String* self);
std::size_t APS5_VABI _ZNK3sce4Json6String8capacityEv(const String* self);
bool APS5_VABI _ZNK3sce4Json6StringeqEPKc(const String* self, const char* text);
bool APS5_VABI _ZNK3sce4Json6StringeqERKS1_(const String* self, const String* other);
void APS5_VABI _ZN3sce4Json6String5clearEv(String* self);
void APS5_VABI _ZN3sce4Json6String6resizeEm(String* self, std::size_t count);
void APS5_VABI _ZN3sce4Json6String7reserveEm(String* self, std::size_t capacity);
String* APS5_VABI _ZN3sce4Json6String6appendERKS1_(String* self, const String* other);
String* APS5_VABI _ZN3sce4Json6String6appendEPKc(String* self, const char* text);
String* APS5_VABI _ZN3sce4Json6String6appendEPKcm(String* self, const char* text, std::size_t length);
String* APS5_VABI _ZN3sce4Json6StringpLEPKc(String* self, const char* text);
String* APS5_VABI _ZN3sce4Json6StringpLEh(String* self, unsigned char ch);
String* APS5_VABI _ZNK3sce4Json6String6substrEmm(String* result, const String* self, std::size_t position, std::size_t length);
char APS5_VABI _ZNK3sce4Json6String2atEm(const String* self, std::size_t index);
std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcm(const String* self, const char* needle, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcmm(const String* self, const char* needle, std::size_t position, std::size_t count);
std::size_t APS5_VABI _ZNK3sce4Json6String4findERKS1_m(const String* self, const String* needle, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcm(const String* self, const char* needle, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcmm(const String* self, const char* needle, std::size_t position, std::size_t count);
std::size_t APS5_VABI _ZNK3sce4Json6String5rfindERKS1_m(const String* self, const String* needle, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String4findEcm(const String* self, char character, std::size_t position);
std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEcm(const String* self, char character, std::size_t position);
int APS5_VABI _ZNK3sce4Json6String7compareEPKc(const String* self, const char* text);
int APS5_VABI _ZNK3sce4Json6String7compareERKS1_(const String* self, const String* other);
void APS5_VABI _ZN3sce4Json5ArrayC1Ev(Array* self);
void APS5_VABI _ZN3sce4Json5ArrayC1ERKS1_(Array* self, const Array* other);
void APS5_VABI _ZN3sce4Json5ArrayD1Ev(Array* self);
Array* APS5_VABI _ZN3sce4Json5ArrayaSERKS1_(Array* self, const Array* other);
void APS5_VABI _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(Array* self, const Value* value);
ArrayIterator* APS5_VABI _ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE(ArrayIterator* result, Array* self, const ArrayIterator* position, const Value* value);
void APS5_VABI _ZN3sce4Json5Array9push_backERKNS0_5ValueE(Array* self, const Value* value);
ArrayIterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(ArrayIterator* result, const Array* self);
ArrayIterator* APS5_VABI _ZNK3sce4Json5Array3endEv(ArrayIterator* result, const Array* self);
bool APS5_VABI _ZNK3sce4Json5Array5emptyEv(const Array* self);
std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const Array* self);
void APS5_VABI _ZN3sce4Json5Array5clearEv(Array* self);
const Value* APS5_VABI _ZNK3sce4Json5Array5frontEv(const Array* self);
const Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const Array* self);
void APS5_VABI _ZN3sce4Json5Array8pop_backEv(Array* self);
ArrayIterator* APS5_VABI _ZN3sce4Json5Array5eraseERKNS1_8iteratorE(ArrayIterator* result, Array* self, const ArrayIterator* position);
void APS5_VABI _ZN3sce4Json5Array9pop_frontEv(Array* self);
void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(ArrayIterator* self);
ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(ArrayIterator* self);
Value* APS5_VABI _ZNK3sce4Json5Array8iteratordeEv(const ArrayIterator* self);
Value* APS5_VABI _ZNK3sce4Json5Array8iteratorptEv(const ArrayIterator* self);
bool APS5_VABI _ZNK3sce4Json5Array8iteratorneERKS2_(const ArrayIterator* self, const ArrayIterator* other);
void APS5_VABI _ZN3sce4Json5Array8iteratorC1ERKS2_(ArrayIterator* self, const ArrayIterator* other);
void APS5_VABI _ZN3sce4Json5Array8iterator7advanceEm(ArrayIterator* self, std::size_t count);
void APS5_VABI _ZN3sce4Json6ObjectC1Ev(Object* self);
void APS5_VABI _ZN3sce4Json6ObjectC1ERKS1_(Object* self, const Object* other);
void APS5_VABI _ZN3sce4Json6ObjectD1Ev(Object* self);
Object* APS5_VABI _ZN3sce4Json6ObjectaSERKS1_(Object* self, const Object* other);
void APS5_VABI _ZN3sce4Json6Object5clearEv(Object* self);
Value* APS5_VABI _ZN3sce4Json6ObjectixERKNS0_6StringE(Object* self, const String* key);
ObjectIterator* APS5_VABI _ZNK3sce4Json6Object5beginEv(ObjectIterator* result, const Object* self);
ObjectIterator* APS5_VABI _ZNK3sce4Json6Object3endEv(ObjectIterator* result, const Object* self);
void APS5_VABI _ZN3sce4Json6Object8iteratorD1Ev(ObjectIterator* self);
ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEv(ObjectIterator* self);
Pair* APS5_VABI _ZNK3sce4Json6Object8iteratordeEv(const ObjectIterator* self);
Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const ObjectIterator* self);
ObjectIterator* APS5_VABI _ZNK3sce4Json6Object4findERKNS0_6StringE(ObjectIterator* result, const Object* self, const String* key);
bool APS5_VABI _ZNK3sce4Json6Object8iteratoreqERKS2_(const ObjectIterator* self, const ObjectIterator* other);
bool APS5_VABI _ZNK3sce4Json6Object8iteratorneERKS2_(const ObjectIterator* self, const ObjectIterator* other);
void APS5_VABI _ZN3sce4Json6Object8iteratorC1ERKS2_(ObjectIterator* self, const ObjectIterator* other);
void APS5_VABI _ZN3sce4Json6Object8iterator7advanceEm(ObjectIterator* self, std::size_t count);
bool APS5_VABI _ZNK3sce4Json6Object5emptyEv(const Object* self);
std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const Object* self);
void APS5_VABI _ZN3sce4Json6Object5eraseERKNS0_6StringE(Object* self, const String* key);
void APS5_VABI _ZN3sce4Json5ValueC1Ev(Value* self);
void APS5_VABI _ZN3sce4Json5ValueC1Eb(Value* self, bool value);
void APS5_VABI _ZN3sce4Json5ValueC1El(Value* self, std::int64_t value);
void APS5_VABI _ZN3sce4Json5ValueC1Em(Value* self, std::uint64_t value);
void APS5_VABI _ZN3sce4Json5ValueC1Ed(Value* self, double value);
void APS5_VABI _ZN3sce4Json5ValueC1EPKc(Value* self, const char* text);
void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6StringE(Value* self, const String* text);
void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(Value* self, const Array* array);
void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(Value* self, const Object* object);
void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(Value* self, const Value* other);
void APS5_VABI _ZN3sce4Json5ValueD1Ev(Value* self);
Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(Value* self, const Value* other);
void APS5_VABI _ZN3sce4Json5Value4swapERS1_(Value* self, Value* other);
void APS5_VABI _ZN3sce4Json5Value3setENS0_9ValueTypeE(Value* self, ValueType type);
void APS5_VABI _ZN3sce4Json5Value3setEb(Value* self, bool value);
void APS5_VABI _ZN3sce4Json5Value3setEl(Value* self, std::int64_t value);
void APS5_VABI _ZN3sce4Json5Value3setEm(Value* self, std::uint64_t value);
void APS5_VABI _ZN3sce4Json5Value3setEd(Value* self, double value);
void APS5_VABI _ZN3sce4Json5Value3setEPKc(Value* self, const char* text);
void APS5_VABI _ZN3sce4Json5Value3setERKNS0_6StringE(Value* self, const String* text);
void APS5_VABI _ZN3sce4Json5Value3setERKNS0_5ArrayE(Value* self, const Array* array);
void APS5_VABI _ZN3sce4Json5Value3setERKNS0_6ObjectE(Value* self, const Object* object);
void APS5_VABI _ZN3sce4Json5Value3setERKS1_(Value* self, const Value* other);
ValueType APS5_VABI _ZNK3sce4Json5Value7getTypeEv(const Value* self);
const bool* APS5_VABI _ZNK3sce4Json5Value10getBooleanEv(const Value* self);
const std::int64_t* APS5_VABI _ZNK3sce4Json5Value10getIntegerEv(const Value* self);
const std::uint64_t* APS5_VABI _ZNK3sce4Json5Value11getUIntegerEv(const Value* self);
const double* APS5_VABI _ZNK3sce4Json5Value7getRealEv(const Value* self);
const String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const Value* self);
const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value* self);
const Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const Value* self);
std::size_t APS5_VABI _ZNK3sce4Json5Value5countEv(const Value* self);
const Value* APS5_VABI _ZNK3sce4Json5ValueixERKNS0_6StringE(const Value* self, const String* key);
const Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const Value* self, const char* key);
const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value* self, std::size_t index);
void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(Value* self, ValueType type);
const Value* APS5_VABI _ZNK3sce4Json5Value8getValueEm(const Value* self, std::size_t index);
const Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const Value* self, const String* key);
Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(Value* self, std::size_t index);
Value* APS5_VABI _ZN3sce4Json5Value10referValueERKNS0_6StringE(Value* self, const String* key);
Array* APS5_VABI _ZN3sce4Json5Value10referArrayEv(Value* self);
Object* APS5_VABI _ZN3sce4Json5Value11referObjectEv(Value* self);
int APS5_VABI _ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(Value* self, SerializationCallback callback, void* context);
int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_(Value* self, String* output, SerializationCallback callback, void* context);
int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(Value* self, String* out);
void APS5_VABI _ZNK3sce4Json5Value8toStringERNS0_6StringE(const Value* self, String* out);
int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(Value* out, ParserReadCallback read, void* context);
int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value* out, const char* text, std::size_t size);
int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKc(Value* out, const char* path);
void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize);
void APS5_VABI _ZN3sce4Json14InitParameter2C1Ev(InitParameter2* self);
void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(InitParameter2* self);
void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(InitParameter2* self, void* allocator, void* userData);
void APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(InitParameter2* self, void* allocParam, void* userData);
void APS5_VABI _ZN3sce4Json14InitParameter217setFileBufferSizeEm(InitParameter2* self, std::size_t size);
void APS5_VABI _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(InitParameter2* self, std::int32_t type);
int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(void* self, const InitParameter2* parameter);
void APS5_VABI _ZN3sce4Json5Value5clearEv(Value* self);
void APS5_VABI _ZN3sce4Json12MemAllocator11notifyErrorEimPv(void* self, int error, std::size_t size, void* userData);
void APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(SceJson2::NullAccessCallback callback, void* context);
int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(SceJson2::Value* self, SceJson2::ElementAccessFailureHandler handler, void* context);
int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(SceJson2::Value* self, SceJson2::SpecialFloatHandler handler, void* context);
int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(SceJson2::Value* self, SceJson2::TypeMismatchHandler handler, void* context);
bool APS5_VABI _ZNK3sce4Json5ValuecvbEv(const SceJson2::Value* self);
void APS5_VABI _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(SceJson2::Pair* self, const SceJson2::String* key, const SceJson2::Value* value);
void APS5_VABI _ZN3sce4Json6Object4PairD1Ev(SceJson2::Pair* self);
SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(SceJson2::ObjectIterator* result, SceJson2::Object* self, const SceJson2::Pair* pair);
SceJson2::ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEi(SceJson2::ArrayIterator* result, SceJson2::ArrayIterator* self, int);
SceJson2::ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratoraSERKS2_(SceJson2::ArrayIterator* self, const SceJson2::ArrayIterator* other);
void APS5_VABI _ZN3sce4Json5Array8iteratorC1Ev(SceJson2::ArrayIterator* self);
SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEi(SceJson2::ObjectIterator* result, SceJson2::ObjectIterator* self, int);
SceJson2::ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratoraSERKS2_(SceJson2::ObjectIterator* self, const SceJson2::ObjectIterator* other);
void APS5_VABI _ZN3sce4Json6Object8iteratorC1Ev(SceJson2::ObjectIterator* self);
void APS5_VABI _ZN3sce4Json6Object4PairC1Ev(SceJson2::Pair* self);
int APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(Value* self, NullAccessCallback callback, void* context);
int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(Value* self, ElementAccessFailureHandler handler, void* context);
int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(Value* self, SpecialFloatHandler handler, void* context);
int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(Value* self, TypeMismatchHandler handler, void* context);
bool APS5_VABI _ZNK3sce4Json5ValuecvbEv(const Value* self);
void APS5_VABI _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(Pair* self, const String* key, const Value* value);
void APS5_VABI _ZN3sce4Json6Object4PairD1Ev(Pair* self);
ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(ObjectIterator* result, Object* self, const Pair* pair);
ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEi(ArrayIterator* self, int);
ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratoraSERKS2_(ArrayIterator* self, const ArrayIterator* other);
void APS5_VABI _ZN3sce4Json5Array8iteratorC1Ev(ArrayIterator* self);
ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEi(ObjectIterator* self, int);
ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratoraSERKS2_(ObjectIterator* self, const ObjectIterator* other);
void APS5_VABI _ZN3sce4Json6Object8iteratorC1Ev(ObjectIterator* self);
void APS5_VABI _ZN3sce4Json6Object4PairC1Ev(Pair* self);
void APS5_VABI _ZN3sce4Json11InitializerC2Ev(void* self);
void APS5_VABI _ZN3sce4Json11InitializerD2Ev(void* self);
void APS5_VABI _ZN3sce4Json17InitParameterRttiC2EPNS0_14AllocParamRttiEPvm(InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize);
void APS5_VABI _ZN3sce4Json5Array8iteratorC2ERKS2_(ArrayIterator* self, const ArrayIterator* other);
void APS5_VABI _ZN3sce4Json5Array8iteratorC2Ev(ArrayIterator* self);
void APS5_VABI _ZN3sce4Json5Array8iteratorD2Ev(ArrayIterator* self);
void APS5_VABI _ZN3sce4Json5ArrayC2ERKS1_(Array* self, const Array* other);
void APS5_VABI _ZN3sce4Json5ArrayC2Ev(Array* self);
void APS5_VABI _ZN3sce4Json5ArrayD2Ev(Array* self);
void APS5_VABI _ZN3sce4Json5ValueC2ENS0_9ValueTypeE(Value* self, ValueType type);
void APS5_VABI _ZN3sce4Json5ValueC2EPKc(Value* self, const char* text);
void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_5ArrayE(Value* self, const Array* array);
void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_6ObjectE(Value* self, const Object* object);
void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_6StringE(Value* self, const String* text);
void APS5_VABI _ZN3sce4Json5ValueC2ERKS1_(Value* self, const Value* other);
void APS5_VABI _ZN3sce4Json5ValueC2Eb(Value* self, bool value);
void APS5_VABI _ZN3sce4Json5ValueC2Ed(Value* self, double value);
void APS5_VABI _ZN3sce4Json5ValueC2El(Value* self, std::int64_t value);
void APS5_VABI _ZN3sce4Json5ValueC2Em(Value* self, std::uint64_t value);
void APS5_VABI _ZN3sce4Json5ValueC2Ev(Value* self);
void APS5_VABI _ZN3sce4Json5ValueD2Ev(Value* self);
void APS5_VABI _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(Pair* self, const String* key, const Value* value);
void APS5_VABI _ZN3sce4Json6Object4PairC2Ev(Pair* self);
void APS5_VABI _ZN3sce4Json6Object4PairD2Ev(Pair* self);
void APS5_VABI _ZN3sce4Json6Object8iteratorC2ERKS2_(ObjectIterator* self, const ObjectIterator* other);
void APS5_VABI _ZN3sce4Json6Object8iteratorC2Ev(ObjectIterator* self);
void APS5_VABI _ZN3sce4Json6Object8iteratorD2Ev(ObjectIterator* self);
void APS5_VABI _ZN3sce4Json6ObjectC2ERKS1_(Object* self, const Object* other);
void APS5_VABI _ZN3sce4Json6ObjectC2Ev(Object* self);
void APS5_VABI _ZN3sce4Json6ObjectD2Ev(Object* self);
void APS5_VABI _ZN3sce4Json6StringC2EPKc(String* self, const char* text);
void APS5_VABI _ZN3sce4Json6StringC2ERKS1_(String* self, const String* other);
void APS5_VABI _ZN3sce4Json6StringC2Ev(String* self);
void APS5_VABI _ZN3sce4Json6StringD2Ev(String* self);
String* APS5_VABI _ZN3sce4Json5Value11referStringEv(Value* self);
bool* APS5_VABI _ZN3sce4Json5Value12referBooleanEv(Value* self);
std::int64_t* APS5_VABI _ZN3sce4Json5Value12referIntegerEv(Value* self);
std::uint64_t* APS5_VABI _ZN3sce4Json5Value13referUIntegerEv(Value* self);
double* APS5_VABI _ZN3sce4Json5Value9referRealEv(Value* self);
void* APS5_VABI _ZNK3sce4Json14InitParameter211getUserDataEv(const InitParameter2* self);
void* APS5_VABI _ZNK3sce4Json14InitParameter212getAllocatorEv(const InitParameter2* self);
std::size_t APS5_VABI _ZNK3sce4Json14InitParameter217getFileBufferSizeEv(const InitParameter2* self);
std::int32_t APS5_VABI _ZNK3sce4Json14InitParameter225getSpecialFloatFormatTypeEv(const InitParameter2* self);
}
}
@@ -222,3 +222,40 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceLibcInternal>")
endif()
endif()
target_sources(libSceLibcInternal PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Sigvec.cpp)
if(BUILD_TESTING AND (WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux"))
add_test_executable(guest_sigvec_modules_tests
${LIBS_INCLUDE_DIR}/tests/GuestSigvecModules.cpp
${LIBS_INCLUDE_DIR}/nid/src/NidCompute.cpp
${LIBS_INCLUDE_DIR}/nid/src/Sha1.cpp)
target_include_directories(guest_sigvec_modules_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include)
target_link_libraries(guest_sigvec_modules_tests PRIVATE ${CMAKE_DL_LIBS})
set_source_files_properties(${LIBS_INCLUDE_DIR}/tests/GuestSigvecModules.cpp PROPERTIES COMPILE_OPTIONS "-fno-exceptions;-fno-asynchronous-unwind-tables")
if(MINGW)
target_compile_options(guest_sigvec_modules_tests PRIVATE -fasynchronous-unwind-tables)
target_link_options(guest_sigvec_modules_tests PRIVATE -static -static-libgcc -static-libstdc++)
endif()
add_library(sigvec_modules_exceptions SHARED ${LIBS_INCLUDE_DIR}/tests/SigvecModulesExceptions.cpp)
target_include_directories(sigvec_modules_exceptions PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(sigvec_modules_exceptions PRIVATE libc)
set_target_properties(sigvec_modules_exceptions PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
configure_windows_unwind(sigvec_modules_exceptions)
add_dependencies(guest_sigvec_modules_tests libc libkernel libSceLibcInternal nid_patcher sigvec_modules_exceptions)
set(sigvecAllocatorArgument)
if(TARGET mimalloc)
set(sigvecAllocatorArgument -DALLOCATOR_DIRECTORY=$<TARGET_FILE_DIR:mimalloc>)
endif()
add_test(NAME guest_sigvec_modules COMMAND ${CMAKE_COMMAND}
-DEXCEPTION_PROBE=$<TARGET_FILE:sigvec_modules_exceptions>
-DPATCHER=$<TARGET_FILE:nid_patcher>
-DLIBC=$<TARGET_FILE:libc>
-DKERNEL=$<TARGET_FILE:libkernel>
-DINTERNAL=$<TARGET_FILE:libSceLibcInternal>
${sigvecAllocatorArgument}
-DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/sigvec-modules
-P ${LIBS_INCLUDE_DIR}/tests/SigvecModules.cmake
-- "$<TARGET_FILE:guest_sigvec_modules_tests>")
set_tests_properties(guest_sigvec_modules PROPERTIES TIMEOUT 20)
endif()
+16 -118
View File
@@ -1,99 +1,20 @@
#include "prx/libkernel/include/Api.hpp"
#include "prx/libc/include/Api.hpp"
#include "prx/libc/include/GuestDirectory.hpp"
#include "prx/libc/include/GuestFilesystemPrimitives.hpp"
#include "prx/libc/include/General.hpp"
#include "SceTypes.hpp"
#include <cstddef>
#include <cstdint>
#include <cstring>
namespace {
constexpr std::size_t NameCapacity = 260;
constexpr std::size_t CurrentCapacity = 256;
constexpr int NoSuchFile = 2;
constexpr int Exists = 17;
constexpr int NotDirectory = 20;
constexpr int Invalid = 22;
constexpr int NameTooLong = 63;
constexpr std::uint16_t DefaultDirectoryMode = 0777;
constexpr std::uint16_t PermissionBits = 07777;
enum FileType : int {
TypeNotFound = -1,
TypeNone = 0,
TypeRegular = 1,
TypeDirectory = 2,
TypeSymlink = 3,
TypeBlock = 4,
TypeCharacter = 5,
TypeFifo = 6,
TypeSocket = 7,
TypeUnknown = 8,
};
int& GuestErrno() {
return *__error_nid_postfix();
}
int TypeOfMode(std::uint16_t mode) {
switch (mode & 0170000) {
case 0010000: return TypeFifo;
case 0020000: return TypeCharacter;
case 0040000: return TypeDirectory;
case 0060000: return TypeBlock;
case 0100000: return TypeRegular;
case 0120000: return TypeSymlink;
case 0140000: return TypeSocket;
default: return TypeUnknown;
}
}
int StatType(const char* path, int* perms, int* error) {
GuestErrno() = 0;
FileStat status{};
int type;
if (stat_nid_postfix(path, &status) == 0) {
GuestErrno() = 0;
if (perms) *perms = status.st_mode & PermissionBits;
type = TypeOfMode(status.st_mode);
} else {
const int code = GuestErrno();
type = code == NoSuchFile || code == NotDirectory || code == Invalid || code == NameTooLong ? TypeNotFound : TypeNone;
}
if (error) *error = GuestErrno();
return type;
}
void CopyName(char* target, const char* source) {
std::size_t length = 0;
while (length < NameCapacity - 1 && source[length] != '\0') {
target[length] = source[length];
++length;
}
target[length] = '\0';
}
bool IsDotEntry(const char* name) {
return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
}
}
extern "C" {
int APS5_VABI _ZNSt10filesystem5_StatEPKcPNS_5permsEPi_nid_postfix(const char* path, int* perms, int* error) {
return StatType(path, perms, error);
return GuestFilesystem::StatType(path, perms, error);
}
int APS5_VABI _ZNSt10filesystem6_LstatEPKcPNS_5permsEPi_nid_postfix(const char* path, int* perms, int* error) {
return StatType(path, perms, error);
return GuestFilesystem::StatType(path, perms, error);
}
std::uint64_t APS5_VABI _ZNSt10filesystem10_File_sizeEPKcPi_nid_postfix(const char* path, int* error) {
FileStat status{};
if (stat_nid_postfix(path, &status) != 0) {
if (error) *error = GuestErrno();
if (error) *error = GuestFilesystem::GuestErrno();
return UINT64_MAX;
}
return static_cast<std::uint64_t>(status.st_size);
@@ -101,23 +22,23 @@ std::uint64_t APS5_VABI _ZNSt10filesystem10_File_sizeEPKcPi_nid_postfix(const ch
int APS5_VABI _ZNSt10filesystem16_Last_write_timeEPKcPlS2__nid_postfix(const char* path, std::int64_t* seconds, std::int64_t* nanoseconds) {
FileStat status{};
if (stat_nid_postfix(path, &status) != 0) return GuestErrno();
if (stat_nid_postfix(path, &status) != 0) return GuestFilesystem::GuestErrno();
*seconds = status.st_mtim.tv_sec;
*nanoseconds = status.st_mtim.tv_nsec;
return 0;
}
int APS5_VABI _ZNSt10filesystem9_Make_dirEPKcS1__nid_postfix(const char* path, const char* existing) {
GuestErrno() = 0;
std::uint16_t mode = DefaultDirectoryMode;
GuestFilesystem::GuestErrno() = 0;
std::uint16_t mode = GuestFilesystem::DefaultDirectoryMode;
if (existing) {
FileStat status{};
if (stat_nid_postfix(existing, &status) != 0) return -GuestErrno();
if (stat_nid_postfix(existing, &status) != 0) return -GuestFilesystem::GuestErrno();
mode = status.st_mode;
}
if (mkdir_nid_postfix(path, mode) == 0) return 1;
if (GuestErrno() == Exists) return 0;
return -GuestErrno();
if (GuestFilesystem::GuestErrno() == GuestFilesystem::Exists) return 0;
return -GuestFilesystem::GuestErrno();
}
bool APS5_VABI _ZNSt10filesystem11_Remove_dirEPKc_nid_postfix(const char* path) {
@@ -125,8 +46,8 @@ bool APS5_VABI _ZNSt10filesystem11_Remove_dirEPKc_nid_postfix(const char* path)
}
int APS5_VABI _ZNSt10filesystem7_UnlinkEPKc_nid_postfix(const char* path) {
GuestErrno() = 0;
return unlink_nid_postfix(path) == 0 ? 0 : GuestErrno();
GuestFilesystem::GuestErrno() = 0;
return unlink_nid_postfix(path) == 0 ? 0 : GuestFilesystem::GuestErrno();
}
int APS5_VABI _ZNSt10filesystem6_ChmodEPKcNS_5permsE_nid_postfix(const char* path, int perms) {
@@ -134,42 +55,19 @@ int APS5_VABI _ZNSt10filesystem6_ChmodEPKcNS_5permsE_nid_postfix(const char* pat
}
bool APS5_VABI _ZNSt10filesystem12_Current_getERA260_c_nid_postfix(char* target) {
char current[CurrentCapacity];
const bool found = getcwd_nid_postfix(current, sizeof(current)) != nullptr;
if (found) CopyName(target, current);
else target[0] = '\0';
return found;
return GuestFilesystem::CurrentGet(target);
}
char* APS5_VABI _ZNSt10filesystem9_Read_dirERA260_cPvRNS_9file_typeE_nid_postfix(char* target, void* handle, int* type) {
*type = TypeUnknown;
GuestDirectoryEntry entry{};
GuestDirectoryEntry* result = nullptr;
while (readdir_r_nid_postfix(handle, &entry, &result) == 0 && result) {
if (IsDotEntry(entry.name)) continue;
CopyName(target, entry.name);
return target;
}
target[0] = '\0';
return target;
return GuestFilesystem::ReadDir(target, handle, type);
}
void* APS5_VABI _ZNSt10filesystem9_Open_dirERA260_cPKcRiRNS_9file_typeE_nid_postfix(char* target, const char* path, int* error, int* type) {
GuestErrno() = 0;
void* handle = opendir_nid_postfix(path);
if (!handle) {
*error = GuestErrno();
return nullptr;
}
*error = 0;
_ZNSt10filesystem9_Read_dirERA260_cPvRNS_9file_typeE_nid_postfix(target, handle, type);
if (target[0] != '\0') return handle;
closedir_nid_postfix(handle);
return nullptr;
return GuestFilesystem::OpenDir(target, path, error, type);
}
int APS5_VABI _ZNSt10filesystem10_Close_dirEPv_nid_postfix(void* handle) {
return closedir_nid_postfix(handle);
return GuestFilesystem::CloseDir(handle);
}
}
Loaded 100 of 360 files, more files were not shown because too many files have changed in this diff. Show more