mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-07 13:39:55 +00:00
Merge commit '3c3b3cda739f9eb00a07fae50d94a3af5f6fb8f2' into HEAD
# Conflicts: # core/libs/prx/libc/src/Math.cpp
This commit is contained in:
commit
6ea7d0ee1c
360 files changed
+29382
-2979
No files matched your search
@@ -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
@@ -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.
|
||||
|
||||
@@ -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(®s.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(®s.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()
|
||||
+8
@@ -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()
|
||||
+8
@@ -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()
|
||||
+9
@@ -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(©, {}, 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include "prx/libSceDbg/include/Api.hpp"
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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__);
|
||||
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -10,3 +10,5 @@
|
||||
#include "Space.hpp"
|
||||
#include "Unescape.hpp"
|
||||
#include "Identified.hpp"
|
||||
|
||||
#include "prx/libSceIcu/include/Api.hpp"
|
||||
@@ -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()
|
||||
+2782
-581
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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
Reference in new issue
Block a user