Merge main into eqaa-color-targets

This commit is contained in:
Dean Galvin committed 2026-11-01 12:50:11 +13:00
commit 8efaff36c0
1366 files changed
+97924 -13376

No files matched your search

+15 -2
View File
@@ -26,8 +26,21 @@ jobs:
state=$RUNNER_TEMP/area-state.json
[ -s "$state" ] || echo '{}' > "$state"
config=$(gh api "repos/$GITHUB_REPOSITORY/contents/.github/labeler.yml" --jq .sha)
gh pr list -R "$GITHUB_REPOSITORY" --base "${{ github.event.repository.default_branch }}" -L 1000 --json number,headRefOid \
--jq "map({key: (.number | tostring), value: (.headRefOid + \"-$config\")}) | from_entries" > "$RUNNER_TEMP/area-open.json"
gh api graphql --paginate --slurp \
-f owner="${GITHUB_REPOSITORY%/*}" -f name="${GITHUB_REPOSITORY#*/}" \
-f base="${{ github.event.repository.default_branch }}" -f query='
query($owner: String!, $name: String!, $base: String!, $endCursor: String) {
repository(owner: $owner, name: $name) {
pullRequests(states: OPEN, baseRefName: $base, first: 100, after: $endCursor,
orderBy: {field: CREATED_AT, direction: DESC}) {
pageInfo { hasNextPage endCursor }
nodes { number headRefOid }
}
}
}' | jq -c --arg config "$config" \
'[.[].data.repository.pullRequests.nodes[]
| {key: (.number | tostring), value: (.headRefOid + "-" + $config)}] | from_entries' \
> "$RUNNER_TEMP/area-open.json"
jq -r --slurpfile old "$state" 'to_entries[] | select($old[0][.key] != .value) | .key' "$RUNNER_TEMP/area-open.json" | head -n 40 > "$RUNNER_TEMP/area-todo"
jq -c --slurpfile open "$RUNNER_TEMP/area-open.json" --rawfile todo "$RUNNER_TEMP/area-todo" \
'with_entries(select(.key | in($open[0]))) + ($open[0] | with_entries(select(.key | IN($todo | split("\n")[]))))' "$state" > "$RUNNER_TEMP/area-next.json"
+3 -3
View File
@@ -24,15 +24,15 @@ jobs:
build:
name: Build and test (${{ matrix.platform }})
strategy:
fail-fast: true
fail-fast: false
matrix:
include:
- platform: linux
runner: ubuntu-latest
timeout: 30
timeout: 60
- platform: windows
runner: windows-latest
timeout: 45
timeout: 60
runs-on: ${{ matrix.runner }}
timeout-minutes: ${{ matrix.timeout }}
defaults:
+32
View File
@@ -0,0 +1,32 @@
name: Run report schema
on:
pull_request:
paths:
- .github/workflows/run-report-schema.yml
- docs/run-report.schema.json
- docs/run-report-fixtures/**
- tools/requirements-run-report.txt
- tools/tests/test_run_report_schema.py
permissions:
contents: read
concurrency:
group: run-report-schema-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
schema:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Install the pinned validator
run: |
python3 -m venv "$RUNNER_TEMP/venv"
"$RUNNER_TEMP/venv/bin/python" -m pip install --require-virtualenv -r tools/requirements-run-report.txt
- name: Validate the fixtures against the schema
run: '"$RUNNER_TEMP/venv/bin/python" tools/tests/test_run_report_schema.py'
+1
View File
@@ -94,6 +94,7 @@ jobs:
env:
ASAN_OPTIONS: detect_leaks=0
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
APS5_SKIP_TIMING_TESTS: 1
run: ctest --output-on-failure --timeout 300 --parallel 4 --output-log ctest.log --output-junit ctest.xml || true
- name: Summarize the sanitizer findings
if: ${{ !cancelled() && steps.configure.outcome == 'success' }}
+2
View File
@@ -101,6 +101,8 @@ downloads/
*.json
!demo/web/i18n/*.json
!/docs/run-report.schema.json
!/docs/run-report-fixtures/*.json
!/assets/*.jpg
!/assets/*.png
+3
View File
@@ -67,3 +67,6 @@
[submodule "3rdparty/nghttp2"]
path = 3rdparty/nghttp2
url = https://github.com/nghttp2/nghttp2.git
[submodule "3rdparty/quickjs"]
path = 3rdparty/quickjs
url = https://github.com/quickjs-ng/quickjs.git
Vendored Submodule
+1
Submodule 3rdparty/quickjs added at 6d46d07d04.
+22 -1
View File
@@ -190,6 +190,9 @@ if(BUILD_TESTING)
if(Python3_Interpreter_FOUND)
add_test(NAME mingw_host_only_test_targets COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_mingw_host_only_test_targets.py)
add_test(NAME patch_lib_atrac9 COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_patch_lib_atrac9.py ${CMAKE_COMMAND})
set_tests_properties(patch_lib_atrac9 PROPERTIES TIMEOUT 60)
add_test(NAME build_changed_guest_tests COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_build_changed_guest_tests.py)
add_test(NAME shader_cache_native_paths COMMAND ${Python3_EXECUTABLE}
@@ -207,6 +210,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler ${CMAKE_COMMAND} --shard ${shard}/7)
set_tests_properties(${shader_cache_sources_test_name} PROPERTIES TIMEOUT 180)
endforeach()
add_test(NAME tools_nid_name_audit COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_audit_nid_names.py)
set_tests_properties(tools_nid_name_audit PROPERTIES TIMEOUT 20)
add_test(NAME pr_overlap_encoding COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_encoding.py)
add_test(NAME pr_overlap_dependencies COMMAND ${Python3_EXECUTABLE}
@@ -222,6 +227,8 @@ if(BUILD_TESTING)
set_tests_properties(progress_history_output PROPERTIES TIMEOUT 20)
add_test(NAME progress_report COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress.py)
add_test(NAME tools_compdb_inventory COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_compile_database_inventory.py)
set_tests_properties(tools_compdb_inventory PROPERTIES TIMEOUT 20)
add_test(NAME progress_encoding COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_encoding.py)
add_test(NAME progress_render_sliver COMMAND ${Python3_EXECUTABLE}
@@ -267,6 +274,8 @@ if(BUILD_TESTING)
set_tests_properties(preview_pad_timeline PROPERTIES TIMEOUT 20)
add_test(NAME conventions_git_errors COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_git.py)
add_test(NAME tools_configure_diff COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_compare_configure_checks.py)
set_tests_properties(tools_configure_diff PROPERTIES TIMEOUT 20)
add_test(NAME conventions_doc_links COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_doc_links.py)
add_test(NAME conventions_unknown_nids COMMAND ${Python3_EXECUTABLE}
@@ -282,12 +291,16 @@ if(BUILD_TESTING)
add_test(NAME check_skips_ctest COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips_ctest.py)
set_tests_properties(check_skips_ctest PROPERTIES TIMEOUT 30)
add_test(NAME tools_pe_inventory COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_pe_symbol_inventory.py)
set_tests_properties(tools_pe_inventory PROPERTIES TIMEOUT 20)
add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips.py)
add_test(NAME sanitizer_report COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_sanitizer_report.py)
add_test(NAME select_tests COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_select_tests.py)
add_test(NAME tools_self_inspect COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_inspect_self.py)
set_tests_properties(tools_self_inspect PROPERTIES TIMEOUT 20)
add_test(NAME check_main_sync COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_main_sync.py CheckMainSyncTests)
set_tests_properties(check_main_sync PROPERTIES TIMEOUT 30)
@@ -331,6 +344,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_stubgen.py)
add_test(NAME debt_split COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_debt_split.py)
add_test(NAME agc_test_split COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_agc_test_split.py)
add_test(NAME stubgen_stdout_newlines COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_stubgen_stdout_newlines.py)
add_test(NAME hardware_oracle COMMAND ${Python3_EXECUTABLE}
@@ -357,6 +372,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_pages.py)
add_test(NAME pr_overlap_export_cache COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_export_cache.py)
add_test(NAME pr_overlap_fail_closed COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_fail_closed.py)
add_test(NAME nid_database_cache COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_nid_names.py)
add_test(NAME release_package COMMAND ${Python3_EXECUTABLE}
@@ -399,7 +416,7 @@ if(BUILD_TESTING)
string(REGEX REPLACE "^ def (test_[A-Za-z0-9_]+).*" "\\1" test_case "${test_case}")
add_test(NAME check_compile_modes_${test_case} COMMAND ${Python3_EXECUTABLE}
"${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_compile_modes.py" CheckCompileModeTests.${test_case})
set_tests_properties(check_compile_modes_${test_case} PROPERTIES TIMEOUT 60)
set_tests_properties(check_compile_modes_${test_case} PROPERTIES TIMEOUT 30)
endforeach()
add_test(NAME trace_digest COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_trace_digest.py)
@@ -454,6 +471,8 @@ if(BUILD_TESTING)
endif()
endif()
endforeach()
add_test(NAME tools_headers_manifest COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_public_headers_manifest.py)
set_tests_properties(tools_headers_manifest PROPERTIES TIMEOUT 20)
add_test(NAME conventions_pr_template COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_pr_template.py)
add_test(NAME check_compile_preprocessor COMMAND ${Python3_EXECUTABLE}
@@ -472,6 +491,8 @@ if(BUILD_TESTING)
set_tests_properties(check_compile_${test_case} PROPERTIES TIMEOUT 30)
endif()
endforeach()
add_test(NAME tools_seed_check COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_check_configure_seed.py)
set_tests_properties(tools_seed_check PROPERTIES TIMEOUT 20)
add_test(NAME performance_compare COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_performance_compare.py)
set_tests_properties(performance_compare PROPERTIES TIMEOUT 20)
+1
View File
@@ -60,6 +60,7 @@ The [pull request template](.github/pull_request_template.md) is the checklist f
- Open pull requests per author are limited by how many of theirs are merged into `main`: 1 with none merged, 20 with at least 1, no limit from 20. A pull request opened or reopened over the limit is closed automatically; reopen it once another one is merged or closed.
- Before starting, check that no open pull request already implements the same functions.
- Keep the branch up to date with `main` and resolve conflicts yourself; rebasing and force-pushing is fine.
- An AGC test executable and its tests are registered in `core/libs/prx/libSceAgcDriver/tests/cmake/<executable>.cmake`; the tests of an execution test in `tests/execution/` go in the fragment of its `agc_driver_<name>_tests` executable. `CMakeLists.txt` includes the fragments in sorted order, so a fragment uses nothing another fragment defines. A pull request whose change to `libSceAgcDriver/CMakeLists.txt` conflicts after the split rebases and runs `python3 tools/agc_test_split.py resolve` during the stopped rebase, which moves its registrations to their fragments and leaves conflict markers where `main` changed the same lines.
- A pull request that conflicts with `main` and has no activity for 7 days is labeled `stale` with a reminder. It is not closed.
- If a pull request needs another one first, say so on the checklist line (`Depends on: #N`; `none` otherwise).
- If a pull request fixes a reported issue, write `Fixes #N` on its checklist line (`none` otherwise). GitHub closes the issue on merge, and the `fix error` label is added within the hour.
+1 -1
View File
@@ -24,7 +24,7 @@ Includes a [relinker](core/relinker) that converts executable to the target syst
<sub>* System libraries: percentage of the functions known to the project so far (declared in [core/libs/prx](core/libs/prx)), not of every PS5 system function. The total grows as more functions are declared.</sub>
Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates.
Unsupported or unexpected states throw a C++ exception, usually `std::runtime_error`. An exception that nothing catches prints its `what()` to stderr and ends the process.
The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V (validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`).
+52
View File
@@ -452,10 +452,21 @@ if(BUILD_TESTING)
target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libSceAmpr libkernel libc)
configure_windows_unwind(guest_kernel_sys_tests)
add_test(NAME guest_kernel_sys COMMAND guest_kernel_sys_tests)
set_tests_properties(guest_kernel_sys PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_kernel_sys PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAmpr>")
endif()
add_test_executable(guest_kernel_sys_vm_map_timestamp_tests tests/GuestKernelSysVmMapTimestamp.cpp)
target_include_directories(guest_kernel_sys_vm_map_timestamp_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_kernel_sys_vm_map_timestamp_tests PRIVATE libkernel_sys libkernel libc)
configure_windows_unwind(guest_kernel_sys_vm_map_timestamp_tests)
add_test(NAME guest_kernel_sys_vm_map_timestamp COMMAND guest_kernel_sys_vm_map_timestamp_tests)
set_tests_properties(guest_kernel_sys_vm_map_timestamp PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_kernel_sys_vm_map_timestamp PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
endif()
add_test_executable(guest_kernel_sys_thread_defaults_tests tests/GuestKernelSysThreadDefaults.cpp)
target_include_directories(guest_kernel_sys_thread_defaults_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_kernel_sys_thread_defaults_tests PRIVATE libkernel_sys libkernel libc)
@@ -489,6 +500,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_firmware_constant_returns PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAvSetting>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceComposite>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFsInternalForVsh>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttpCache>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePlayReady>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSdma>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSsl>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceVisionManager>;PATH=path_list_prepend:$<TARGET_FILE_DIR:libmdbg_syscore>")
endif()
add_test_executable(guest_playready_tests tests/GuestPlayReady.cpp)
target_include_directories(guest_playready_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_playready_tests PRIVATE libScePlayReady libkernel libc)
configure_windows_unwind(guest_playready_tests)
add_test(NAME guest_playready COMMAND guest_playready_tests)
set_tests_properties(guest_playready PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_playready PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePlayReady>")
endif()
if(Python3_Interpreter_FOUND)
add_test(NAME nid_verneed COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_verneed.py $<TARGET_FILE:nid_patcher>)
@@ -668,6 +689,16 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceGnmDriverForNeoMode>")
endif()
add_test_executable(guest_gnm_neo_context_state_internal_tests tests/GuestGnmNeoContextStateInternal.cpp)
target_include_directories(guest_gnm_neo_context_state_internal_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_gnm_neo_context_state_internal_tests PRIVATE libSceGnmDriverForNeoMode libc)
configure_windows_unwind(guest_gnm_neo_context_state_internal_tests)
add_test(NAME guest_gnm_neo_context_state_internal COMMAND guest_gnm_neo_context_state_internal_tests)
if(WIN32)
set_tests_properties(guest_gnm_neo_context_state_internal PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceGnmDriverForNeoMode>")
endif()
add_test_executable(guest_vr_tracker_devices_tests tests/GuestVrTrackerDevices.cpp)
target_include_directories(guest_vr_tracker_devices_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_vr_tracker_devices_tests PRIVATE libSceVrTracker libc)
@@ -743,6 +774,17 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNpCommon>")
endif()
add_test_executable(guest_xml_value_types_tests tests/GuestXmlValueTypes.cpp)
target_include_directories(guest_xml_value_types_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_xml_value_types_tests PRIVATE libSceXml libkernel libc)
configure_windows_unwind(guest_xml_value_types_tests)
add_test(NAME guest_xml_value_types COMMAND guest_xml_value_types_tests)
set_tests_properties(guest_xml_value_types PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_xml_value_types PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceXml>")
endif()
add_test_executable(guest_offline_imports_tests tests/GuestOfflineImports.cpp)
target_include_directories(guest_offline_imports_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_offline_imports_tests PRIVATE libSceHttp libSceNpEntitlementAccess libSceRudp libSceVideoRecordingP libc)
@@ -1037,6 +1079,16 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
endif()
add_test_executable(guest_src_utility_tests tests/GuestSrcUtility.cpp)
target_include_directories(guest_src_utility_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_src_utility_tests PRIVATE libSceSrcUtl libc)
configure_windows_unwind(guest_src_utility_tests)
add_test(NAME guest_src_utility COMMAND guest_src_utility_tests)
if(WIN32)
set_tests_properties(guest_src_utility PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSrcUtl>")
endif()
add_test_executable(guest_crt_tests tests/GuestCrt.cpp)
target_include_directories(guest_crt_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_crt_tests PRIVATE libSceAmpr libc)
+182
View File
@@ -0,0 +1,182 @@
#include "prx/libCprm/include/InventoryExports.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/general/ExportMacros.hpp"
extern "C" {
int APS5_VABI libCprmUnknown__plus_Eh9ZNYi6zo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("+Eh9ZNYi6zo", libCprmUnknown__plus_Eh9ZNYi6zo_nid_no_patch);
int APS5_VABI libCprmUnknown__plus_X9TC_plus_1sw8c_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("+X9TC+1sw8c", libCprmUnknown__plus_X9TC_plus_1sw8c_nid_no_patch);
int APS5_VABI libCprmUnknown_1M23A00Z5YY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("1M23A00Z5YY", libCprmUnknown_1M23A00Z5YY_nid_no_patch);
int APS5_VABI libCprmUnknown_9Blq0Y1cfC4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("9Blq0Y1cfC4", libCprmUnknown_9Blq0Y1cfC4_nid_no_patch);
int APS5_VABI libCprmUnknown_C_plus_UDpkvBfq0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("C+UDpkvBfq0", libCprmUnknown_C_plus_UDpkvBfq0_nid_no_patch);
int APS5_VABI libCprmUnknown_Dxqbh2fid_minus_0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Dxqbh2fid-0", libCprmUnknown_Dxqbh2fid_minus_0_nid_no_patch);
int APS5_VABI libCprmUnknown_GYIfXUO2DQM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("GYIfXUO2DQM", libCprmUnknown_GYIfXUO2DQM_nid_no_patch);
int APS5_VABI libCprmUnknown_GzdjmaMs2ec_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("GzdjmaMs2ec", libCprmUnknown_GzdjmaMs2ec_nid_no_patch);
int APS5_VABI libCprmUnknown_Ja2HmL89pQk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Ja2HmL89pQk", libCprmUnknown_Ja2HmL89pQk_nid_no_patch);
int APS5_VABI libCprmUnknown_N1QA8ydpT5s_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("N1QA8ydpT5s", libCprmUnknown_N1QA8ydpT5s_nid_no_patch);
int APS5_VABI libCprmUnknown_Qqt9wsrnORY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Qqt9wsrnORY", libCprmUnknown_Qqt9wsrnORY_nid_no_patch);
int APS5_VABI libCprmUnknown_SebG2x8vugA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("SebG2x8vugA", libCprmUnknown_SebG2x8vugA_nid_no_patch);
int APS5_VABI libCprmUnknown_Sz5wfo9uoQo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Sz5wfo9uoQo", libCprmUnknown_Sz5wfo9uoQo_nid_no_patch);
int APS5_VABI libCprmUnknown_UXwPU6S3K3M_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("UXwPU6S3K3M", libCprmUnknown_UXwPU6S3K3M_nid_no_patch);
int APS5_VABI libCprmUnknown_Ut6wGp0l_minus_EU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Ut6wGp0l-EU", libCprmUnknown_Ut6wGp0l_minus_EU_nid_no_patch);
int APS5_VABI libCprmUnknown_Uv_minus_1Y7ctRPg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Uv-1Y7ctRPg", libCprmUnknown_Uv_minus_1Y7ctRPg_nid_no_patch);
int APS5_VABI libCprmUnknown_WWfjXT2d7SM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("WWfjXT2d7SM", libCprmUnknown_WWfjXT2d7SM_nid_no_patch);
int APS5_VABI libCprmUnknown_YhPUdyQoZ2w_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("YhPUdyQoZ2w", libCprmUnknown_YhPUdyQoZ2w_nid_no_patch);
int APS5_VABI libCprmUnknown_YvBd1vant8Y_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("YvBd1vant8Y", libCprmUnknown_YvBd1vant8Y_nid_no_patch);
int APS5_VABI libCprmUnknown_aoE6klVaQmo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("aoE6klVaQmo", libCprmUnknown_aoE6klVaQmo_nid_no_patch);
int APS5_VABI libCprmUnknown_fb1srL_plus__plus_42M_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("fb1srL++42M", libCprmUnknown_fb1srL_plus__plus_42M_nid_no_patch);
int APS5_VABI libCprmUnknown_gnClz0AOgtw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("gnClz0AOgtw", libCprmUnknown_gnClz0AOgtw_nid_no_patch);
int APS5_VABI libCprmUnknown_h9jXWgBLon0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("h9jXWgBLon0", libCprmUnknown_h9jXWgBLon0_nid_no_patch);
int APS5_VABI libCprmUnknown_hs4OD9CTzsU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("hs4OD9CTzsU", libCprmUnknown_hs4OD9CTzsU_nid_no_patch);
int APS5_VABI libCprmUnknown_hvZH8f_plus_HBVg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("hvZH8f+HBVg", libCprmUnknown_hvZH8f_plus_HBVg_nid_no_patch);
int APS5_VABI libCprmUnknown_i2aJI0V3U3A_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("i2aJI0V3U3A", libCprmUnknown_i2aJI0V3U3A_nid_no_patch);
int APS5_VABI libCprmUnknown_iDCilRZjGRM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("iDCilRZjGRM", libCprmUnknown_iDCilRZjGRM_nid_no_patch);
int APS5_VABI libCprmUnknown_lCmvZpz_minus_8Sg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("lCmvZpz-8Sg", libCprmUnknown_lCmvZpz_minus_8Sg_nid_no_patch);
int APS5_VABI libCprmUnknown_llRRCXKDB8g_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("llRRCXKDB8g", libCprmUnknown_llRRCXKDB8g_nid_no_patch);
int APS5_VABI libCprmUnknown_mq_plus_QSu3eTDM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mq+QSu3eTDM", libCprmUnknown_mq_plus_QSu3eTDM_nid_no_patch);
int APS5_VABI libCprmUnknown_pa1PYEalrtU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("pa1PYEalrtU", libCprmUnknown_pa1PYEalrtU_nid_no_patch);
int APS5_VABI libCprmUnknown_tsJszAZ7k_plus_A_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("tsJszAZ7k+A", libCprmUnknown_tsJszAZ7k_plus_A_nid_no_patch);
int APS5_VABI libCprmUnknown_vR2Jy582NrI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("vR2Jy582NrI", libCprmUnknown_vR2Jy582NrI_nid_no_patch);
int APS5_VABI libCprmUnknown_x5bhPjRC_plus_WQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("x5bhPjRC+WQ", libCprmUnknown_x5bhPjRC_plus_WQ_nid_no_patch);
int APS5_VABI libCprmUnknown_y2mH4WVl028_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("y2mH4WVl028", libCprmUnknown_y2mH4WVl028_nid_no_patch);
}
@@ -0,0 +1,43 @@
#pragma once
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI libCprmUnknown__plus_Eh9ZNYi6zo_nid_no_patch();
int APS5_VABI libCprmUnknown__plus_X9TC_plus_1sw8c_nid_no_patch();
int APS5_VABI libCprmUnknown_1M23A00Z5YY_nid_no_patch();
int APS5_VABI libCprmUnknown_9Blq0Y1cfC4_nid_no_patch();
int APS5_VABI libCprmUnknown_C_plus_UDpkvBfq0_nid_no_patch();
int APS5_VABI libCprmUnknown_Dxqbh2fid_minus_0_nid_no_patch();
int APS5_VABI libCprmUnknown_GYIfXUO2DQM_nid_no_patch();
int APS5_VABI libCprmUnknown_GzdjmaMs2ec_nid_no_patch();
int APS5_VABI libCprmUnknown_Ja2HmL89pQk_nid_no_patch();
int APS5_VABI libCprmUnknown_N1QA8ydpT5s_nid_no_patch();
int APS5_VABI libCprmUnknown_Qqt9wsrnORY_nid_no_patch();
int APS5_VABI libCprmUnknown_SebG2x8vugA_nid_no_patch();
int APS5_VABI libCprmUnknown_Sz5wfo9uoQo_nid_no_patch();
int APS5_VABI libCprmUnknown_UXwPU6S3K3M_nid_no_patch();
int APS5_VABI libCprmUnknown_Ut6wGp0l_minus_EU_nid_no_patch();
int APS5_VABI libCprmUnknown_Uv_minus_1Y7ctRPg_nid_no_patch();
int APS5_VABI libCprmUnknown_WWfjXT2d7SM_nid_no_patch();
int APS5_VABI libCprmUnknown_YhPUdyQoZ2w_nid_no_patch();
int APS5_VABI libCprmUnknown_YvBd1vant8Y_nid_no_patch();
int APS5_VABI libCprmUnknown_aoE6klVaQmo_nid_no_patch();
int APS5_VABI libCprmUnknown_fb1srL_plus__plus_42M_nid_no_patch();
int APS5_VABI libCprmUnknown_gnClz0AOgtw_nid_no_patch();
int APS5_VABI libCprmUnknown_h9jXWgBLon0_nid_no_patch();
int APS5_VABI libCprmUnknown_hs4OD9CTzsU_nid_no_patch();
int APS5_VABI libCprmUnknown_hvZH8f_plus_HBVg_nid_no_patch();
int APS5_VABI libCprmUnknown_i2aJI0V3U3A_nid_no_patch();
int APS5_VABI libCprmUnknown_iDCilRZjGRM_nid_no_patch();
int APS5_VABI libCprmUnknown_lCmvZpz_minus_8Sg_nid_no_patch();
int APS5_VABI libCprmUnknown_llRRCXKDB8g_nid_no_patch();
int APS5_VABI libCprmUnknown_mq_plus_QSu3eTDM_nid_no_patch();
int APS5_VABI libCprmUnknown_pa1PYEalrtU_nid_no_patch();
int APS5_VABI libCprmUnknown_tsJszAZ7k_plus_A_nid_no_patch();
int APS5_VABI libCprmUnknown_vR2Jy582NrI_nid_no_patch();
int APS5_VABI libCprmUnknown_x5bhPjRC_plus_WQ_nid_no_patch();
int APS5_VABI libCprmUnknown_y2mH4WVl028_nid_no_patch();
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include "prx/libCprm/include/InventoryExports.hpp"
@@ -1,3 +1,6 @@
#include "prx/libSceAbstractStorage/include/LegacyExports.hpp"
#include "prx/libc/include/general/ExportMacros.hpp"
#include "prx/libSceAbstractStorage/include/InventoryExports.hpp"
#include <cstddef>
#include <cstdint>
#include "SceTypes.hpp"
@@ -557,3 +560,116 @@ int APS5_VABI sceAbstractStorageUnknown_1uryzAWFUuA_nid_no_patch() {
APS5_EXPORT("1uryzAWFUuA", sceAbstractStorageUnknown_1uryzAWFUuA_nid_no_patch);
}
extern "C" {
int APS5_VABI libSceAbstractStorageUnknown__plus_bCU7y60g28_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("+bCU7y60g28", libSceAbstractStorageUnknown__plus_bCU7y60g28_nid_no_patch);
int APS5_VABI libSceAbstractStorageUnknown_JwplLmzUrs0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("JwplLmzUrs0", libSceAbstractStorageUnknown_JwplLmzUrs0_nid_no_patch);
int APS5_VABI libSceAbstractStorageUnknown_Nrv7BRYovYk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Nrv7BRYovYk", libSceAbstractStorageUnknown_Nrv7BRYovYk_nid_no_patch);
int APS5_VABI libSceAbstractStorageUnknown_YBmeXvldPyg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("YBmeXvldPyg", libSceAbstractStorageUnknown_YBmeXvldPyg_nid_no_patch);
int APS5_VABI libSceAbstractStorageUnknown_jsjQnoZHra8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("jsjQnoZHra8", libSceAbstractStorageUnknown_jsjQnoZHra8_nid_no_patch);
int APS5_VABI libSceAbstractStorageUnknown_lMU9P5mJtNM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("lMU9P5mJtNM", libSceAbstractStorageUnknown_lMU9P5mJtNM_nid_no_patch);
int APS5_VABI libSceAbstractStorageUnknown_scAYh9JGyYI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("scAYh9JGyYI", libSceAbstractStorageUnknown_scAYh9JGyYI_nid_no_patch);
}
extern "C" {
int APS5_VABI abstractStorageLegacyUnknown__minus_lJCD9v3ShE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("-lJCD9v3ShE", abstractStorageLegacyUnknown__minus_lJCD9v3ShE_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_58hoiRX9eq0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("58hoiRX9eq0", abstractStorageLegacyUnknown_58hoiRX9eq0_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_5E95udm3i4A_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("5E95udm3i4A", abstractStorageLegacyUnknown_5E95udm3i4A_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_5sF6O0X8XOg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("5sF6O0X8XOg", abstractStorageLegacyUnknown_5sF6O0X8XOg_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_7re96ARYiKU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("7re96ARYiKU", abstractStorageLegacyUnknown_7re96ARYiKU_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_8urQQ98mwcg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8urQQ98mwcg", abstractStorageLegacyUnknown_8urQQ98mwcg_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_BcT4KRrLn2U_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BcT4KRrLn2U", abstractStorageLegacyUnknown_BcT4KRrLn2U_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_EawgMNYrP2A_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("EawgMNYrP2A", abstractStorageLegacyUnknown_EawgMNYrP2A_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_Gz6_plus_aMUIR44_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Gz6+aMUIR44", abstractStorageLegacyUnknown_Gz6_plus_aMUIR44_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_QieeCoOG9bQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("QieeCoOG9bQ", abstractStorageLegacyUnknown_QieeCoOG9bQ_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_YyubErxn_plus_aM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("YyubErxn+aM", abstractStorageLegacyUnknown_YyubErxn_plus_aM_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_njl_plus_S_plus_GCNOQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("njl+S+GCNOQ", abstractStorageLegacyUnknown_njl_plus_S_plus_GCNOQ_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_nkDADDAUxc4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("nkDADDAUxc4", abstractStorageLegacyUnknown_nkDADDAUxc4_nid_no_patch);
int APS5_VABI abstractStorageLegacyUnknown_pfToDbCVd_minus_Y_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("pfToDbCVd-Y", abstractStorageLegacyUnknown_pfToDbCVd_minus_Y_nid_no_patch);
}
@@ -0,0 +1,15 @@
#pragma once
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI libSceAbstractStorageUnknown__plus_bCU7y60g28_nid_no_patch();
int APS5_VABI libSceAbstractStorageUnknown_JwplLmzUrs0_nid_no_patch();
int APS5_VABI libSceAbstractStorageUnknown_Nrv7BRYovYk_nid_no_patch();
int APS5_VABI libSceAbstractStorageUnknown_YBmeXvldPyg_nid_no_patch();
int APS5_VABI libSceAbstractStorageUnknown_jsjQnoZHra8_nid_no_patch();
int APS5_VABI libSceAbstractStorageUnknown_lMU9P5mJtNM_nid_no_patch();
int APS5_VABI libSceAbstractStorageUnknown_scAYh9JGyYI_nid_no_patch();
}
@@ -0,0 +1,22 @@
#pragma once
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI abstractStorageLegacyUnknown__minus_lJCD9v3ShE_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_58hoiRX9eq0_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_5E95udm3i4A_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_5sF6O0X8XOg_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_7re96ARYiKU_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_8urQQ98mwcg_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_BcT4KRrLn2U_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_EawgMNYrP2A_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_Gz6_plus_aMUIR44_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_QieeCoOG9bQ_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_YyubErxn_plus_aM_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_njl_plus_S_plus_GCNOQ_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_nkDADDAUxc4_nid_no_patch();
int APS5_VABI abstractStorageLegacyUnknown_pfToDbCVd_minus_Y_nid_no_patch();
}
@@ -1 +1,3 @@
#pragma once
#include "prx/libSceAbstractStorage/include/InventoryExports.hpp"
#include "prx/libSceAbstractStorage/include/LegacyExports.hpp"
+206
View File
@@ -1,3 +1,5 @@
#include "prx/libSceAbstractTcs/include/LegacyExports.hpp"
#include "prx/libc/include/general/ExportMacros.hpp"
#include <cstddef>
#include <cstdint>
#include "SceTypes.hpp"
@@ -8,3 +10,207 @@ extern "C" {
APS5_DUMMY_FUN
}
extern "C" {
int APS5_VABI abstractTcsLegacyUnknown__plus_uyvmTPkPT0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("+uyvmTPkPT0", abstractTcsLegacyUnknown__plus_uyvmTPkPT0_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown__plus_yHj1U_minus_k1zU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("+yHj1U-k1zU", abstractTcsLegacyUnknown__plus_yHj1U_minus_k1zU_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_065iwsKaKA4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("065iwsKaKA4", abstractTcsLegacyUnknown_065iwsKaKA4_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_0MDv01EA_minus_Wo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("0MDv01EA-Wo", abstractTcsLegacyUnknown_0MDv01EA_minus_Wo_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_1vgiZdMEU8k_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("1vgiZdMEU8k", abstractTcsLegacyUnknown_1vgiZdMEU8k_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_29VsAacFFxw_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("29VsAacFFxw", abstractTcsLegacyUnknown_29VsAacFFxw_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_4_minus_Soqq0Hrqg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("4-Soqq0Hrqg", abstractTcsLegacyUnknown_4_minus_Soqq0Hrqg_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_5NEeXee1_minus_kQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("5NEeXee1-kQ", abstractTcsLegacyUnknown_5NEeXee1_minus_kQ_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_5b5Tx09Z8uE_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("5b5Tx09Z8uE", abstractTcsLegacyUnknown_5b5Tx09Z8uE_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_6zjNlHxuPhY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6zjNlHxuPhY", abstractTcsLegacyUnknown_6zjNlHxuPhY_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_8uRxMgcUE6c_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8uRxMgcUE6c", abstractTcsLegacyUnknown_8uRxMgcUE6c_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_9KwAuQYhInc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("9KwAuQYhInc", abstractTcsLegacyUnknown_9KwAuQYhInc_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_9RskWGK_plus_Kto_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("9RskWGK+Kto", abstractTcsLegacyUnknown_9RskWGK_plus_Kto_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_BnQJZLesghs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("BnQJZLesghs", abstractTcsLegacyUnknown_BnQJZLesghs_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_CKQqO0otsv0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("CKQqO0otsv0", abstractTcsLegacyUnknown_CKQqO0otsv0_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_IarxytjvEuI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("IarxytjvEuI", abstractTcsLegacyUnknown_IarxytjvEuI_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_J5koYi_plus_uPnk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("J5koYi+uPnk", abstractTcsLegacyUnknown_J5koYi_plus_uPnk_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_JwWSY1YarEs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("JwWSY1YarEs", abstractTcsLegacyUnknown_JwWSY1YarEs_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_La1inpscek8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("La1inpscek8", abstractTcsLegacyUnknown_La1inpscek8_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_RZ_plus_sajO2y8o_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("RZ+sajO2y8o", abstractTcsLegacyUnknown_RZ_plus_sajO2y8o_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_SoP4nFTiu2I_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("SoP4nFTiu2I", abstractTcsLegacyUnknown_SoP4nFTiu2I_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_V4T61w1Cqe8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("V4T61w1Cqe8", abstractTcsLegacyUnknown_V4T61w1Cqe8_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_ZWtONs1OIvg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ZWtONs1OIvg", abstractTcsLegacyUnknown_ZWtONs1OIvg_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_ahZrMN_minus_Jt6Y_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ahZrMN-Jt6Y", abstractTcsLegacyUnknown_ahZrMN_minus_Jt6Y_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_bVvlpS2KicM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("bVvlpS2KicM", abstractTcsLegacyUnknown_bVvlpS2KicM_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_bg0nEmwlF7w_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("bg0nEmwlF7w", abstractTcsLegacyUnknown_bg0nEmwlF7w_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_cAWoVxe45V0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("cAWoVxe45V0", abstractTcsLegacyUnknown_cAWoVxe45V0_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_cCGY_plus_evCvVc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("cCGY+evCvVc", abstractTcsLegacyUnknown_cCGY_plus_evCvVc_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_clQhSM8NcKU_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("clQhSM8NcKU", abstractTcsLegacyUnknown_clQhSM8NcKU_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_eY11gxVQj3w_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("eY11gxVQj3w", abstractTcsLegacyUnknown_eY11gxVQj3w_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_fe6dqznfu0M_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("fe6dqznfu0M", abstractTcsLegacyUnknown_fe6dqznfu0M_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_foVwhntBh5s_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("foVwhntBh5s", abstractTcsLegacyUnknown_foVwhntBh5s_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_l7InnXfshQg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("l7InnXfshQg", abstractTcsLegacyUnknown_l7InnXfshQg_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_mFGc4QJTRHA_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mFGc4QJTRHA", abstractTcsLegacyUnknown_mFGc4QJTRHA_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_nCiacsEZ4bs_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("nCiacsEZ4bs", abstractTcsLegacyUnknown_nCiacsEZ4bs_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_ru8kyGpFGLM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ru8kyGpFGLM", abstractTcsLegacyUnknown_ru8kyGpFGLM_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_tp06H_minus_EvwAg_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("tp06H-EvwAg", abstractTcsLegacyUnknown_tp06H_minus_EvwAg_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_uTt_minus_bJVheZI_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("uTt-bJVheZI", abstractTcsLegacyUnknown_uTt_minus_bJVheZI_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_yCjJib_minus_VZrc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("yCjJib-VZrc", abstractTcsLegacyUnknown_yCjJib_minus_VZrc_nid_no_patch);
int APS5_VABI abstractTcsLegacyUnknown_zz6g6ReL0Ec_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("zz6g6ReL0Ec", abstractTcsLegacyUnknown_zz6g6ReL0Ec_nid_no_patch);
}
@@ -0,0 +1,48 @@
#pragma once
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI abstractTcsLegacyUnknown__plus_uyvmTPkPT0_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown__plus_yHj1U_minus_k1zU_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_065iwsKaKA4_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_0MDv01EA_minus_Wo_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_1vgiZdMEU8k_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_29VsAacFFxw_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_4_minus_Soqq0Hrqg_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_5NEeXee1_minus_kQ_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_5b5Tx09Z8uE_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_6zjNlHxuPhY_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_8uRxMgcUE6c_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_9KwAuQYhInc_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_9RskWGK_plus_Kto_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_BnQJZLesghs_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_CKQqO0otsv0_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_IarxytjvEuI_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_J5koYi_plus_uPnk_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_JwWSY1YarEs_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_La1inpscek8_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_RZ_plus_sajO2y8o_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_SoP4nFTiu2I_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_V4T61w1Cqe8_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_ZWtONs1OIvg_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_ahZrMN_minus_Jt6Y_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_bVvlpS2KicM_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_bg0nEmwlF7w_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_cAWoVxe45V0_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_cCGY_plus_evCvVc_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_clQhSM8NcKU_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_eY11gxVQj3w_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_fe6dqznfu0M_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_foVwhntBh5s_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_l7InnXfshQg_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_mFGc4QJTRHA_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_nCiacsEZ4bs_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_ru8kyGpFGLM_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_tp06H_minus_EvwAg_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_uTt_minus_bJVheZI_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_yCjJib_minus_VZrc_nid_no_patch();
int APS5_VABI abstractTcsLegacyUnknown_zz6g6ReL0Ec_nid_no_patch();
}
@@ -1 +1,2 @@
#pragma once
#include "prx/libSceAbstractTcs/include/LegacyExports.hpp"
+37 -1
View File
@@ -1,3 +1,5 @@
#include "prx/libc/include/general/ExportMacros.hpp"
#include "prx/libSceAcm/include/InventoryExports.hpp"
#include "prx/libSceAcm/include/Api.hpp"
#include <cstdint>
#include <cstddef>
@@ -398,7 +400,12 @@ int APS5_VABI sceAcm_Panner(AcmBatchInfo* batch, std::uint32_t sourceCount, cons
}
int APS5_VABI sceAcmBatchJobPriority(AcmBatchInfo* batch, std::uint32_t priority) {
NotImplemented_nid_no_patch(__func__);
if (!batch || priority >= 4) return SCE_ACM_ERROR_BAD_POINTER;
const auto offset = batch->offset;
if (offset > batch->buffer_size || batch->buffer_size - offset < 8) return static_cast<int>(0x81940009);
const std::uint64_t job = 0x10 | (static_cast<std::uint64_t>(priority) << 8);
std::memcpy(static_cast<std::byte*>(batch->buffer) + offset, &job, sizeof(job));
batch->offset = offset + sizeof(job);
return 0;
}
@@ -417,3 +424,32 @@ int APS5_VABI sceAcmBatchJobNotification() {
}
}
extern "C" {
int APS5_VABI libSceAcmUnknown_9_plus_f3tPdeyP0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("9+f3tPdeyP0", libSceAcmUnknown_9_plus_f3tPdeyP0_nid_no_patch);
int APS5_VABI libSceAcmUnknown_DvsnlQr4yyk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("DvsnlQr4yyk", libSceAcmUnknown_DvsnlQr4yyk_nid_no_patch);
int APS5_VABI libSceAcmUnknown_PLNywpi1zR4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("PLNywpi1zR4", libSceAcmUnknown_PLNywpi1zR4_nid_no_patch);
int APS5_VABI libSceAcmUnknown_YpYvibqaqz0_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("YpYvibqaqz0", libSceAcmUnknown_YpYvibqaqz0_nid_no_patch);
int APS5_VABI libSceAcmUnknown_ngDRON91_plus__minus_Y_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("ngDRON91+-Y", libSceAcmUnknown_ngDRON91_plus__minus_Y_nid_no_patch);
}
@@ -0,0 +1,13 @@
#pragma once
#include "SceTypes.hpp"
extern "C" {
int APS5_VABI libSceAcmUnknown_9_plus_f3tPdeyP0_nid_no_patch();
int APS5_VABI libSceAcmUnknown_DvsnlQr4yyk_nid_no_patch();
int APS5_VABI libSceAcmUnknown_PLNywpi1zR4_nid_no_patch();
int APS5_VABI libSceAcmUnknown_YpYvibqaqz0_nid_no_patch();
int APS5_VABI libSceAcmUnknown_ngDRON91_plus__minus_Y_nid_no_patch();
}
+1
View File
@@ -1,3 +1,4 @@
#pragma once
#include "prx/libSceAcm/include/Api.hpp"
#include "prx/libSceAcm/include/InventoryExports.hpp"
@@ -36,6 +36,25 @@ void testPacketSize() {
}
}
void testSystemSoftwareVersion() {
std::array<std::uint32_t, 3> packet{0xc0020000u, 0xbeefcafeu, 0};
std::uint32_t version = 0;
check(sceAgcDcbGetSystemSoftwareVersion(&version, packet.data()) == 0, "assigned system software version");
check(version == 0xbeefcafeu, "system software version uses the second packet word");
check(packet[0] == 0xc0020000u && packet[1] == 0xbeefcafeu, "query does not modify the packet");
std::array<std::uint32_t, 2> tooShort{0xc0000000u, 0xbeefcafeu};
expectFailure([&] { sceAgcDcbGetSystemSoftwareVersion(&version, tooShort.data()); });
expectFailure([&] { sceAgcDcbGetSystemSoftwareVersion(nullptr, packet.data()); });
expectFailure([&] { sceAgcDcbGetSystemSoftwareVersion(&version, nullptr); });
for (const auto header : {0x80000000u, 0x3fff1000u, 0x40001000u, 0x00027600u}) {
std::array<std::uint32_t, 3> invalidHeader{header, 0xbeefcafeu, 0};
expectFailure([&] { sceAgcDcbGetSystemSoftwareVersion(&version, invalidHeader.data()); });
}
}
void testRejections() {
for (const auto header : {0x80000000u, 0x3fff1000u, 0x40001000u, 0x00047600u}) {
std::uint32_t word = header;
@@ -53,6 +72,7 @@ int main() {
try {
testPacketSize();
testRejections();
testSystemSoftwareVersion();
LibcRunShutdown_nid_postfix();
std::puts("AGC packet info tests passed");
return 0;
File diff suppressed because it is too large. Load diff
@@ -44,6 +44,11 @@ struct DispatchVariant {
std::uint32_t pushOffset = 0;
std::vector<std::int8_t> runBases;
std::uint64_t relocatableWords = 0;
std::uint64_t valueWords = 0;
std::vector<std::array<std::uint32_t, 3>> valueSlots;
std::vector<std::array<std::uint32_t, 3>> descriptorPatches;
std::uint64_t calleeWords = 0;
std::vector<std::pair<std::uint32_t, std::uint32_t>> calleeReads;
std::vector<std::pair<std::uint32_t, std::uint32_t>> shaderDataPatches;
std::uint64_t userDataMask = ~std::uint64_t{0};
std::shared_ptr<const ShaderRecompiler::ShaderVertexStageInfo> vertexInfo;
@@ -135,6 +135,8 @@ struct StageCapture {
struct RelocatableUserWord {
std::uint32_t reg;
std::vector<std::pair<std::int64_t, std::uint32_t>> reads;
bool value = false;
std::uint32_t high = 0;
};
struct DrawRelocation {
@@ -13,6 +13,7 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Dispatch/IndirectDispatch.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/WriteEvidence.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Packets/PacketTiming.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/LabelTakes.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/WaitMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
@@ -102,6 +103,9 @@ public:
static void FoldDrawOffsets(const ShaderRecompiler::RecompileResult& result, const DrawProgram& program, Pm4::DrawParameters& parameters);
static std::optional<Graphics::IndirectDrawPath> ClassifyIndirectDraw(const ShaderRecompiler::RecompileResult& result, const Graphics::State& graphics, const DrawProgram& frontProgram, const std::shared_ptr<VulkanDevice>& localDevice, Pm4::DrawParameters& drawParameters, bool traceIndirect);
static void tagVariantRuns(DispatchVariant& variant, std::span<const ShaderRecompiler::MemoryRegion> regions, const ShaderRecompiler::SrtUserDataUse& use, bool eligible, std::uint32_t hiddenWords, std::span<const std::shared_ptr<const ShaderRecompiler::SrtGuestWords>> supplied = {});
static void tagUserValueWords(DispatchVariant& variant, const DrawProgram& program);
static std::uint64_t relocatedContentKey(const RelocatableUserWord& word, std::uint32_t value, bool& readable);
static void applyShaderDataPatches(DispatchVariant& variant);
static std::string dumpRequest(std::uint64_t address, const ShaderRecompiler::RecompileRequest& request, std::string_view suffix = {});
static void reportSkip(const char* kind, const std::string& what);
DrawEntryCounters DrawCacheCounters();
@@ -194,8 +198,6 @@ private:
void traceDrawKeyLookup(const std::array<std::uint64_t, 5>& components, bool cacheMiss);
static std::uint64_t drawIdentityKey(const QueueState& queue, const ShaderRegistry& registry);
static bool drawRelocationEnabled();
static std::uint64_t relocatedContentKey(const RelocatableUserWord& word, std::uint32_t value, bool& readable);
static void applyShaderDataPatches(DispatchVariant& variant);
void learnPendingRelocation();
void relocateDrawEntry(const QueueState& queue, std::uint64_t alias, std::shared_ptr<DrawEntry>& entry, std::shared_ptr<const DrawDecode>& decode, std::vector<DrawUserPatch>& patches);
void reportDrawRelocation();
@@ -220,6 +222,7 @@ private:
void noteLabelStore(std::uint64_t address, std::span<const std::byte> bytes, std::uint64_t stamp, std::uint32_t queue);
void noteLabelPass(std::span<const std::uint32_t> packet, std::uint32_t queue);
void noteLabelIssue(std::span<const std::uint32_t> packet, std::uint32_t queue);
void retireLabelTakes(std::uint32_t queue, std::uint64_t received);
bool storedSince(std::span<const std::uint32_t> packet, std::uint64_t address, std::size_t bytes, std::uint64_t received, std::uint32_t queue, std::uint32_t* writer = nullptr);
static void traceLabel(std::span<const std::uint32_t> packet, std::uint32_t queue);
static std::array<WriteRecord, 16384>& writeHistory();
@@ -461,8 +464,7 @@ private:
AgcDriver::Mutex labelStoresMutex;
std::unordered_map<std::uint64_t, std::array<LabelStore, LabelStoreHistory>> labelStores;
std::unordered_map<std::uint64_t, std::uint64_t> labelPasses;
std::unordered_map<std::uint64_t, std::uint64_t> labelTaken;
LabelTakes labelTakes;
std::vector<std::shared_ptr<VulkanDevice>> replacedDevices;
std::stop_token shutdownToken = LibcShutdownToken_nid_postfix();
@@ -223,7 +223,9 @@ void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const st
void SubmitRegistrationPreparation(std::function<void()> task, const PreparedShaders* prepared = nullptr);
void PrioritizeRegistrationPreparation(PreparedShaders& prepared);
void AwaitRegistrationPreparation();
void SubmitLibraryPrebuild(std::function<void()> task);
void SubmitLibraryPrebuild(std::function<void()> task, std::uint64_t program = 0);
bool LibraryPrebuildsWaiting();
void PrioritizeLibraryPrebuilds(std::span<const std::uint64_t> programs);
void SubmitComputePrebuild(std::function<void()> task);
void AwaitLibraryPrebuilds();
void CloseRegistrationPreparation();
@@ -0,0 +1,88 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_SYNCHRONIZATION_LABELTAKES_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_SYNCHRONIZATION_LABELTAKES_HPP
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <unordered_map>
#include <utility>
namespace AgcDriver::DriverDetail {
class LabelTakes {
public:
void Pass(std::uint32_t queue, std::uint64_t dword, std::uint64_t serial) {
const auto key = queueDword(queue, dword);
auto& entry = entries[key];
if (entry.passed == 0) passCounts[slot(key)].fetch_add(1, std::memory_order_relaxed);
entry.passed = serial;
passes[queue].emplace_back(serial, key);
}
bool MayHavePass(std::uint32_t queue, std::uint64_t dword) const {
return passCounts[slot(queueDword(queue, dword))].load(std::memory_order_relaxed) != 0;
}
void Take(std::uint32_t queue, std::uint64_t dword) {
const auto key = queueDword(queue, dword);
const auto found = entries.find(key);
if (found == entries.end() || found->second.passed == 0) return;
found->second.taken = found->second.passed;
found->second.passed = 0;
passCounts[slot(key)].fetch_sub(1, std::memory_order_relaxed);
}
std::uint64_t Taken(std::uint32_t queue, std::uint64_t dword) const {
const auto found = entries.find(queueDword(queue, dword));
return found == entries.end() ? 0 : found->second.taken;
}
void Retire(std::uint32_t queue, std::uint64_t received) {
const auto queued = passes.find(queue);
if (queued == passes.end()) return;
auto& order = queued->second;
while (!order.empty() && order.front().first <= received) {
const auto [serial, key] = order.front();
order.pop_front();
const auto found = entries.find(key);
if (found == entries.end()) continue;
if (found->second.passed == serial) {
found->second.passed = 0;
passCounts[slot(key)].fetch_sub(1, std::memory_order_relaxed);
}
if (found->second.taken == serial) found->second.taken = 0;
if (found->second.passed == 0 && found->second.taken == 0) entries.erase(found);
}
if (order.empty()) passes.erase(queued);
}
bool Empty() const {
return entries.empty() && passes.empty();
}
private:
struct Entry {
std::uint64_t passed = 0;
std::uint64_t taken = 0;
};
static constexpr std::size_t PassSlotBits = 12;
static std::uint64_t queueDword(std::uint32_t queue, std::uint64_t dword) {
return dword | (static_cast<std::uint64_t>(queue) << 56u);
}
static std::size_t slot(std::uint64_t key) {
return static_cast<std::size_t>(((key >> 2u) * 0x9e3779b97f4a7c15ull) >> (64u - PassSlotBits));
}
std::unordered_map<std::uint64_t, Entry> entries;
std::unordered_map<std::uint32_t, std::deque<std::pair<std::uint64_t, std::uint64_t>>> passes;
std::array<std::atomic<std::uint32_t>, std::size_t{1} << PassSlotBits> passCounts{};
};
}
#endif
@@ -87,6 +87,7 @@ public:
};
static VouchCounts Vouches();
static bool Vouched(PendingWriteQuery pending, std::uint64_t address, std::uint64_t bytes, const ShaderRecompiler::SrtWriteStamp& stamp);
static std::uint64_t descriptorScope(void* context, std::size_t bytes);
private:
std::shared_ptr<const ShaderRecompiler::ResourceCapture> capture(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle, const ShaderRecompiler::PreparedShaderInvocation* invocation);
@@ -143,7 +144,6 @@ private:
static ShaderRecompiler::SrtWriteStamp stampWrites(void* context, std::uint64_t address, std::uint64_t bytes);
static bool unchangedSince(void* context, std::uint64_t address, std::uint64_t bytes, const ShaderRecompiler::SrtWriteStamp& stamp);
static bool supplyWords(void* context, const std::shared_ptr<const ShaderRecompiler::SrtGuestWords>& words);
static std::uint64_t descriptorScope(void* context, std::size_t bytes);
bool invalidateRawPages();
Page& page(std::uint64_t base);
[[nodiscard]] std::size_t pageAtOrAfter(std::uint64_t base) const;
@@ -25,6 +25,7 @@ namespace AgcDriver {
namespace Graphics {
class StorageTexture;
struct HtileMaskClear;
}
// Stage A of a dispatch's resource build (see VulkanDevice::PrepareDispatch); opaque to callers.
@@ -107,7 +108,7 @@ public:
// mutex through the flush hook, so a thread submitting on another queue's behalf (a WAIT_REG_MEM
// poller, a compute worker's between-packet flush) passes false: it must only submit, never wait.
void SubmitRecorded(bool reapFirst = true);
void StoreIdleResults(bool reapFirst = true);
void StoreIdleResults(bool reapFirst = true, const std::function<bool()>& stop = [] { return false; });
// Unlocked drain: SubmitAndEpoch (under the mutex) returns the serial covering every recorded
// batch (0: nothing in flight); WaitRecorded(serial) waits for it WITHOUT the mutex (the caller
// holds a shared_ptr to this device); ReapRecorded(serial) (under the mutex) runs the completions
@@ -145,6 +146,7 @@ public:
// every recorded store over the range.
bool FillBuffer(std::uint64_t address, std::size_t bytes, std::span<const std::uint32_t, 4> pattern);
bool DumpSamplesOnGpu(std::uint64_t address);
void NoteHtileMask(const Graphics::HtileMaskClear& clear) const;
// Copies `bytes` of guest memory from `source` to `destination` (disjoint ranges) in place of the
// engine's memcpy kernel (Driver.cpp copyBuffer). `path` 0: copied on the CPU at once, when
// every test of the rule holds (each a pure query, nothing flushed or recorded before the
@@ -296,7 +298,7 @@ public:
// records. Null when nothing is prepared: the resource cache may serve the dispatch (its
// Revalidate stays under the mutex), or APS5_LOCKED_BUILD=1 keeps the whole build under it as
// before. `shader` and `snapshots` must outlive the dispatch.
std::shared_ptr<PreparedDispatch> PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t dispatchThreads = 0, std::uint64_t programAddress = 0);
std::shared_ptr<PreparedDispatch> PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t dispatchThreads = 0, std::uint64_t programAddress = 0, std::optional<std::pair<std::uint64_t, std::size_t>> htileClear = std::nullopt);
// APS5_PROFILE_DRAW: the parts of a PrepareDispatch in milliseconds, in the order key, find,
// precollect, presync, stage A (the driver's 'prepare:' rows).
static std::span<const double, 5> PreparePhaseMs(const PreparedDispatch& prepared);
@@ -136,6 +136,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
const bool pipelineCandidate = !resolving && !fromAhead && DispatchItemsEnabled() && DrawPipeline::Active() && indirectArguments == 0 && deferredLabels().labels.empty() && !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);
const bool partialLastGroup = partialGroups != std::array<std::uint32_t, 3>{};
std::optional<std::pair<std::uint64_t, std::size_t>> htileClearCompletion;
Graphics::HtileMaskClear htileMask;
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;
@@ -158,6 +159,10 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
const auto nativeBytes = Graphics::HtileDepthClearBytes_nid_no_patch(std::span(snapshot.code).subspan(codeOffset), userData, compute, packet, indirectArguments);
const auto bytes = static_cast<std::size_t>(threads * 16u);
if (Graphics::NoteHtileDepthClear(htile, bytes, nativeBytes)) htileClearCompletion = std::pair{htile, bytes};
if (nativeBytes != 0u) htileMask = {htile, nativeBytes, userData[5], userData[4]};
} else if (const auto buffered = Graphics::MatchHtileBufferMaskClear(std::span(snapshot.code).subspan(codeOffset), userData, compute, packet, indirectArguments)) {
if (Graphics::NoteHtileDepthClear(buffered->address, buffered->bytes, buffered->bytes)) htileClearCompletion = std::pair{buffered->address, buffered->bytes};
htileMask = *buffered;
}
if (!partialLastGroup && indirectArguments == 0 && copyBuffer(queue, submission.queue, packet, std::span(snapshot.code).subspan(codeOffset), userData, compute, localDevice, address)) {
pendingDispatchPhases().outcome = DispatchOutcome::CopyHle;
@@ -493,7 +498,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
const auto epoch = DrawPipeline::EpochToken().load(std::memory_order_relaxed);
const auto queueId = submission.queue;
itemCommits.fetch_add(1, std::memory_order_relaxed);
DrawPipeline::Queue0().Enqueue([this, localDevice = std::move(localDevice), result = compiledResult, snapshots = std::move(snapshots), liveWords = std::move(liveWords), groups, address, queueId, recipe, keepVariant, dataHit, epoch, htileClearCompletion] {
DrawPipeline::Queue0().Enqueue([this, localDevice = std::move(localDevice), result = compiledResult, snapshots = std::move(snapshots), liveWords = std::move(liveWords), groups, address, queueId, recipe, keepVariant, dataHit, epoch, htileClearCompletion, htileMask] {
DrawPipeline::FollowEpoch(epoch);
GuestMemory::SetCurrentPacket(0x15, queueId);
const auto& compiledCommit = *result;
@@ -504,7 +509,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
bool writersNoted = false;
for (;;) {
std::shared_ptr<PreparedDispatch> prepared;
if (recipeHit == nullptr || VulkanDevice::VerifyRecipes() || htileClearCompletion.has_value()) prepared = localDevice->PrepareDispatch(compiledCommit, snapshots);
if (recipeHit == nullptr || VulkanDevice::VerifyRecipes() || htileClearCompletion.has_value()) prepared = localDevice->PrepareDispatch(compiledCommit, snapshots, 0, address, htileClearCompletion);
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Dispatch);
std::lock_guard gpuLock(GuestMemory::GpuMutex());
recordLabelsForPacket(localDevice.get(), queueId);
@@ -524,6 +529,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
} else {
localDevice->Dispatch(compiledCommit, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), htileClearCompletion.has_value() ? nullptr : &builtRecipe, htileClearCompletion);
}
if (htileMask.bytes != 0u) localDevice->NoteHtileMask(htileMask);
break;
}
if (noteWrites && !writerKeyedEvidence()) noteWrittenBuffers(address, queueId, compiledCommit);
@@ -577,7 +583,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
std::shared_ptr<PreparedDispatch> prepared;
if (recipeHit == nullptr || VulkanDevice::VerifyRecipes()) {
try {
prepared = localDevice->PrepareDispatch(compiled, snapshots, dispatchThreads, address);
prepared = localDevice->PrepareDispatch(compiled, snapshots, dispatchThreads, address, htileClearCompletion);
} catch (const std::exception& error) {
if (rethrow(error)) return;
}
@@ -635,6 +641,7 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
const auto note = keysRecorder->LastWriteNote(keysFill->base, keysFill->bytes);
if (note != 0 && note != keysNoteBefore) Graphics::NoteKeysFillOnGpu(keysFill->base, keysFill->bytes, *filledKeys);
}
if (htileMask.bytes != 0u) localDevice->NoteHtileMask(htileMask);
if (builtRecipe != nullptr) {
if (dataHit) {
@@ -657,7 +657,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
throw;
}
for (std::size_t i = 0; i < matched.size(); ++i) {
if (matched[i] == nullptr || matched[i]->shaderDataPatches.empty()) continue;
if (matched[i] == nullptr || (matched[i]->shaderDataPatches.empty() && matched[i]->descriptorPatches.empty())) continue;
applyShaderDataPatches(*matched[i]);
if (stageData[i].compiled != nullptr) stageData[i].compiled = patchDataWords(*matched[i], stageData[i].words);
}
@@ -237,7 +237,9 @@ void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawP
std::vector<ShaderRecompiler::MemoryRegion> regions(stageCapture.regions.begin(), stageCapture.regions.end());
for (const auto& read : decodeReads[i]) regions.push_back({read.address, read.Bytes()});
std::stable_sort(regions.begin(), regions.end(), [](const ShaderRecompiler::MemoryRegion& a, const ShaderRecompiler::MemoryRegion& b) { return a.guestAddress < b.guestAddress; });
tagVariantRuns(*variant, regions, stageCapture.userDataUse, decodeReads[i].empty() && (!vertexInfos[i] || vertexInfos[i]->resourcesNum == 0), programs[i].hiddenUserWords, stageCapture.supplied);
const bool relocatable = decodeReads[i].empty() && (!vertexInfos[i] || vertexInfos[i]->resourcesNum == 0);
tagVariantRuns(*variant, regions, stageCapture.userDataUse, relocatable, programs[i].hiddenUserWords, stageCapture.supplied);
if (relocatable) tagUserValueWords(*variant, programs[i]);
if (drawStageDataHits() && stageCapture.capture != nullptr && !stageCapture.capture->readTrace.leaves.empty()) recordStageDataPositions(*variant, *stageCapture.capture, decodeReads[i]);
if (traceDrawCache() && entry != nullptr && matched[i] == nullptr && i < entry->stages.size()) {
const auto& variants = entry->stages[i];
@@ -1,4 +1,6 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "ControlFlow/GraphBuilder.hpp"
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
@@ -9,6 +11,8 @@
#include <cstdlib>
#include <cstring>
#include <limits>
#include <optional>
#include <span>
namespace AgcDriver::DriverDetail {
@@ -34,6 +38,7 @@ std::uint64_t Driver::relocatedContentKey(const RelocatableUserWord& word, std::
hash ^= mixed;
hash *= 0x100000001b3ull;
};
if (word.value) return hash;
mix(value & 3u);
if (value == 0) {
readable = false;
@@ -45,7 +50,7 @@ std::uint64_t Driver::relocatedContentKey(const RelocatableUserWord& word, std::
mix(words);
for (std::size_t done = 0; done < words; done += MaxContentWords) {
const auto count = std::min<std::size_t>(words - done, MaxContentWords);
const auto address = AlignedWord(value) + static_cast<std::uint64_t>(offset) + done * sizeof(std::uint32_t);
const auto address = (static_cast<std::uint64_t>(word.high) << 32u) + AlignedWord(value) + static_cast<std::uint64_t>(offset) + done * sizeof(std::uint32_t);
if (GuestMemory::CopyMapped(address, std::as_writable_bytes(std::span(content).first(count))) != GuestMemory::Compare::Equal) {
readable = false;
return hash;
@@ -56,6 +61,80 @@ std::uint64_t Driver::relocatedContentKey(const RelocatableUserWord& word, std::
return hash;
}
void Driver::tagUserValueWords(DispatchVariant& variant, const DrawProgram& program) {
if (!drawRelocationEnabled() || variant.compiled == nullptr) return;
const auto& compiled = *variant.compiled;
const auto& forwarded = compiled.shaderDataUserWords;
const auto users = std::min<std::size_t>(program.userData.size(), 64);
const auto bound = [&](std::uint32_t value) {
std::size_t count = 0;
for (const auto& binding : compiled.bindings) count += static_cast<std::size_t>(std::count(binding.guestDescriptor.begin(), binding.guestDescriptor.end(), value));
return count;
};
const auto elsewhere = [&](std::uint32_t index, std::uint32_t value) {
for (std::size_t slot = 0; (slot + 1) * sizeof(std::uint32_t) <= compiled.pushConstants.size(); ++slot) {
std::uint32_t word = 0;
std::memcpy(&word, compiled.pushConstants.data() + slot * sizeof(std::uint32_t), sizeof(word));
if (word == value && (slot >= forwarded.size() || forwarded[slot] != index)) return true;
}
if (std::any_of(compiled.specialization.begin(), compiled.specialization.end(), [&](const auto& constant) { return constant.value == value; })) return true;
return std::find(variant.words.begin(), variant.words.end(), value) != variant.words.end();
};
const auto readThrough = [&](const std::uint32_t* descriptor) {
const auto base = static_cast<std::uint64_t>(descriptor[0]) | static_cast<std::uint64_t>(descriptor[1] & 0xffffu) << 32u;
const auto stride = (descriptor[1] >> 16u) & 0x3fffu;
const auto bytes = std::max<std::uint64_t>(stride != 0 ? static_cast<std::uint64_t>(stride) * descriptor[2] : descriptor[2], 16u);
return std::any_of(variant.runs.begin(), variant.runs.end(), [&](const auto& run) { return run.first < base + bytes && run.second > base; });
};
const auto taken = [&](std::uint32_t index) { return ((variant.relocatableWords | variant.valueWords | variant.calleeWords) >> index & 1u) != 0; };
if (ShaderRecompiler::GcnGuestShaders() && program.binary.stage == ShaderRecompiler::ShaderStage::Vertex) {
for (const auto instruction : program.binary.code) {
if ((instruction & 0xff80ff00u) != 0xbe802100u) continue;
const auto target = instruction & 0xffu;
if (target < program.firstUserSgpr) continue;
const auto index = program.hiddenUserWords + target - program.firstUserSgpr;
if (index + 1 >= users || taken(index) || taken(index + 1)) continue;
const auto low = program.userData[index];
const auto address = static_cast<std::uint64_t>(low) | static_cast<std::uint64_t>(program.userData[index + 1]) << 32u;
if (address == 0 || address % 4u != 0 || bound(low) != 0 || elsewhere(index, low)) continue;
std::array<std::uint32_t, 1024> code;
if (GuestMemory::CopyMapped(address, std::as_writable_bytes(std::span(code))) != GuestMemory::Compare::Equal) continue;
std::optional<std::uint32_t> length;
try {
length = ShaderRecompiler::UserDataCalleeLength(code, (instruction >> 16u) & 0x7fu);
} catch (const std::exception&) {
continue;
}
if (!length || *length == 0) continue;
variant.calleeWords |= std::uint64_t {1} << index;
variant.calleeReads.emplace_back(index, *length);
}
}
for (std::uint32_t index = program.hiddenUserWords; index + 2 < users; ++index) {
const auto base = program.userData[index];
if (base == 0 || taken(index) || bound(base) != 1 || elsewhere(index, base)) continue;
std::uint32_t binding = 0, word = 0;
for (std::size_t b = 0; b < compiled.bindings.size(); ++b) {
const auto& descriptor = compiled.bindings[b].guestDescriptor;
const auto found = std::find(descriptor.begin(), descriptor.end(), base);
if (found == descriptor.end()) continue;
binding = static_cast<std::uint32_t>(b);
word = static_cast<std::uint32_t>(found - descriptor.begin());
}
const auto& target = compiled.bindings[binding];
const auto element = word / 4u;
const bool readOnly = target.role == ShaderRecompiler::DescriptorRole::GuestBuffers && target.guestDescriptor.size() == static_cast<std::size_t>(target.count) * 4u && word % 4u == 0 && element < target.bufferWritten.size() && !target.bufferWritten[element] && (element >= target.bufferAtomic.size() || !target.bufferAtomic[element]);
if (!readOnly || target.guestDescriptor[word + 1] != program.userData[index + 1] || target.guestDescriptor[word + 2] != program.userData[index + 2] || readThrough(target.guestDescriptor.data() + word)) continue;
variant.valueWords |= std::uint64_t {1} << index;
variant.valueSlots.push_back({index, binding, word});
const auto records = program.userData[index + 2];
if (records != 0 && !taken(index + 2) && bound(records) == 1 && !elsewhere(index + 2, records)) {
variant.valueWords |= std::uint64_t {1} << (index + 2);
variant.valueSlots.push_back({index + 2, binding, word + 2});
}
}
}
void Driver::tagVariantRuns(DispatchVariant& variant, std::span<const ShaderRecompiler::MemoryRegion> regions, const ShaderRecompiler::SrtUserDataUse& use, bool eligible, std::uint32_t hiddenWords, std::span<const std::shared_ptr<const ShaderRecompiler::SrtGuestWords>> supplied) {
std::uint64_t relocatable = eligible && drawRelocationEnabled() ? use.asBase & ~use.asValue : 0;
if (hiddenWords != 0) relocatable &= hiddenWords >= 64 ? 0 : ~((std::uint64_t {1} << hiddenWords) - 1);
@@ -149,7 +228,9 @@ void Driver::learnPendingRelocation() {
for (std::uint32_t index = program.hiddenUserWords; index < program.userData.size() && index < 64; ++index) {
const auto reg = program.userDataBase + index - program.hiddenUserWords;
const auto held = std::find_if(shape->words.begin(), shape->words.end(), [&](const RelocatableUserWord& word) { return word.reg == reg; });
if ((variant.relocatableWords >> index & 1u) == 0) {
const bool relocatable = (variant.relocatableWords >> index & 1u) != 0;
const bool value = !relocatable && (variant.valueWords >> index & 1u) != 0;
if (!relocatable && !value && (variant.calleeWords >> index & 1u) == 0) {
if (held == shape->words.end()) continue;
shape->words.erase(held);
shape->banned.push_back(reg);
@@ -157,7 +238,12 @@ void Driver::learnPendingRelocation() {
continue;
}
if (held != shape->words.end() || std::find(shape->banned.begin(), shape->banned.end(), reg) != shape->banned.end()) continue;
RelocatableUserWord word{reg, {}};
RelocatableUserWord word{reg, {}, value};
for (const auto& [callee, length] : variant.calleeReads) {
if (callee != index) continue;
word.reads.emplace_back(0, length);
word.high = program.userData[index + 1];
}
const auto base = AlignedWord(program.userData[index]);
for (std::size_t run = 0; run < variant.runs.size() && run < variant.runBases.size(); ++run) {
if (variant.runBases[run] != static_cast<std::int8_t>(index)) continue;
@@ -215,7 +301,7 @@ void Driver::relocateDrawEntry(const QueueState& queue, std::uint64_t alias, std
}
for (const auto& variant : donor->stages[i]) {
if (variant == nullptr || variant->compiled == nullptr) continue;
const bool allowed = std::all_of(moves.begin(), moves.end(), [&](const Move& move) { return move.program != i || (move.index < 64 && (variant->relocatableWords >> move.index & 1u) != 0 && move.from != 0 && move.to != 0 && ((move.from ^ move.to) & 3u) == 0); });
const bool allowed = std::all_of(moves.begin(), moves.end(), [&](const Move& move) { return move.program != i || (move.index < 64 && move.from != 0 && move.to != 0 && ((((variant->relocatableWords | variant->calleeWords) >> move.index & 1u) != 0 && ((move.from ^ move.to) & 3u) == 0) || (variant->valueWords >> move.index & 1u) != 0)); });
if (!allowed) {
++counters.refused;
continue;
@@ -224,6 +310,10 @@ void Driver::relocateDrawEntry(const QueueState& queue, std::uint64_t alias, std
copy->runs = variant->runs;
copy->runBases = variant->runBases;
copy->relocatableWords = variant->relocatableWords;
copy->valueWords = variant->valueWords;
copy->valueSlots = variant->valueSlots;
copy->calleeWords = variant->calleeWords;
copy->calleeReads = variant->calleeReads;
copy->words = variant->words;
copy->forgetSerial = variant->forgetSerial;
copy->compiled = variant->compiled;
@@ -246,6 +336,9 @@ void Driver::relocateDrawEntry(const QueueState& queue, std::uint64_t alias, std
for (std::uint32_t slot = 0; slot < forwarded.size(); ++slot) {
if (forwarded[slot] == move.index) copy->shaderDataPatches.emplace_back(slot, move.to);
}
for (const auto& [index, binding, word] : variant->valueSlots) {
if (index == move.index) copy->descriptorPatches.push_back({binding, word, move.to});
}
}
relocated->stages[i].push_back(std::move(copy));
}
@@ -257,7 +350,7 @@ void Driver::relocateDrawEntry(const QueueState& queue, std::uint64_t alias, std
}
void Driver::applyShaderDataPatches(DispatchVariant& variant) {
if (variant.shaderDataPatches.empty()) return;
if (variant.shaderDataPatches.empty() && variant.descriptorPatches.empty()) return;
auto rebased = std::make_shared<ShaderRecompiler::RecompileResult>(*variant.compiled);
for (const auto& [slot, value] : variant.shaderDataPatches) {
const auto at = static_cast<std::size_t>(slot) * sizeof(std::uint32_t);
@@ -266,8 +359,12 @@ void Driver::applyShaderDataPatches(DispatchVariant& variant) {
if (binding.role == ShaderRecompiler::DescriptorRole::ShaderData && slot < binding.guestDescriptor.size()) binding.guestDescriptor[slot] = value;
}
}
for (const auto& [binding, word, value] : variant.descriptorPatches) {
if (binding < rebased->bindings.size() && word < rebased->bindings[binding].guestDescriptor.size()) rebased->bindings[binding].guestDescriptor[word] = value;
}
variant.compiled = std::move(rebased);
variant.shaderDataPatches.clear();
variant.descriptorPatches.clear();
}
void Driver::reportDrawRelocation() {
@@ -216,7 +216,7 @@ void Driver::run(std::uint32_t id) noexcept {
}
lock.unlock();
if (storeIdleResults) {
if (const auto localDevice = device.Load()) localDevice->StoreIdleResults();
if (const auto localDevice = device.Load()) localDevice->StoreIdleResults(true, [&] { return worker.queued.load(std::memory_order_acquire) != 0 || runningWorkers.load(std::memory_order_acquire) != 0; });
}
lock.lock();
if (ready()) break;
@@ -278,6 +278,7 @@ void Driver::run(std::uint32_t id) noexcept {
running.Start();
if (profile && submission.enqueuedAt != std::chrono::steady_clock::time_point{}) costs.dequeueNs += static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - submission.enqueuedAt).count());
}
retireLabelTakes(id, submission.received);
execute(submission);
if (submission.progress && submission.rewindTail == nullptr && !completionDeferred()) submitOpenWork(submission.progress);
if (traceGpu) std::fprintf(stderr, "[gpu] %.1f done serial=%llu queue=0x%x\n", TraceMs(), static_cast<unsigned long long>(submission.serial), id);
@@ -13,11 +13,14 @@
#include "prx/libSceAgcDriver/Eq/include/Event.hpp"
#include "ThreadOwned.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include <algorithm>
#include <atomic>
#include <bit>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <optional>
namespace AgcDriver::DriverDetail {
@@ -320,6 +323,53 @@ void Driver::reserveOutputs(Submission& submission) {
for (const auto token : eopTokens) eopFlips.erase(token);
}
namespace {
std::vector<std::uint64_t> BoundGraphicsPrograms(const Submission& submission) {
constexpr std::array<std::uint32_t, 6> programRegisters{0x008u, 0x048u, 0x088u, 0x0c8u, 0x108u, 0x148u};
static std::array<std::atomic<std::uint32_t>, programRegisters.size()> lastHigh{};
std::array<std::optional<std::uint32_t>, programRegisters.size()> low{};
std::array<bool, programRegisters.size()> written{};
std::vector<std::uint64_t> programs;
const auto note = [&](std::uint32_t offset, std::uint32_t value) {
for (std::size_t program = 0; program < programRegisters.size(); ++program) {
if (offset == programRegisters[program] + 1u) lastHigh[program].store(value & 0xffu, std::memory_order_relaxed);
else if (offset == programRegisters[program]) low[program] = value;
else continue;
written[program] = true;
}
};
const auto flush = [&] {
for (std::size_t program = 0; program < programRegisters.size(); ++program) {
if (!written[program] || !low[program]) continue;
written[program] = false;
programs.push_back((static_cast<std::uint64_t>(*low[program]) << 8u) | (static_cast<std::uint64_t>(lastHigh[program].load(std::memory_order_relaxed)) << 40u));
}
};
const auto& commands = submission.commands;
for (std::size_t cursor = 0; cursor < commands.size();) {
const auto header = commands[cursor];
const auto count = Pm4::PacketWords(header);
if (cursor + count > commands.size()) break;
const auto packet = std::span(commands).subspan(cursor, count);
const auto opcode = (header >> 8u) & 0xffu;
if (!Pm4::FillerPacket(header) && opcode == 0x76 && count > 2 && !Pm4::IsTagMarker(packet)) {
for (std::size_t index = 2; index < count; ++index) note((packet[1] & ~0x70000000u) + static_cast<std::uint32_t>(index - 2), packet[index]);
} else if (!Pm4::FillerPacket(header) && opcode == 0x63) {
if (const auto found = submission.registerLists.find(cursor); found != submission.registerLists.end()) {
for (std::size_t index = 0; index + 1 < found->second.size(); index += 2) note(found->second[index] & ~0x70000000u, found->second[index + 1]);
}
}
flush();
cursor += count;
}
std::ranges::sort(programs);
programs.erase(std::ranges::unique(programs).begin(), programs.end());
return programs;
}
}
void Driver::executeRewindTail(const Submission& stalled) {
PerformanceTimer timing("Driver.Rewind");
static const bool ring = ReadSubmissionRingSettings();
@@ -357,11 +407,13 @@ void Driver::executeRewindTail(const Submission& stalled) {
if (APS5_ENABLE_TIMING_LOG) tail.copiedAt = std::chrono::steady_clock::now();
validate(tail, stalled.rewindTail);
readRegisterLists(tail);
if (LibraryPrebuildsWaiting()) PrioritizeLibraryPrebuilds(BoundGraphicsPrograms(tail));
if (APS5_ENABLE_TIMING_LOG) tail.validatedAt = std::chrono::steady_clock::now();
waitForFlipRoom(tail);
if (APS5_ENABLE_TIMING_LOG) tail.roomReadyAt = std::chrono::steady_clock::now();
auto* const capture = SubmissionCapture::Active();
auto captured = capture != nullptr ? SubmissionCapture::Prepare(tail.queue, SubmissionKind::Rewind, tail.commands, tail.conditionalEnds) : CapturedSubmission{};
std::uint64_t retiredTakes = 0;
{
std::lock_guard lock(mutex);
rethrowFailure();
@@ -370,12 +422,15 @@ void Driver::executeRewindTail(const Submission& stalled) {
tail.shaders = shaders;
tail.serial = stalled.serial;
tail.received = ++eventSerial;
const auto& waiting = workers[tail.queue].pending;
retiredTakes = waiting.empty() ? tail.received : std::min(tail.received, waiting.front().received);
if (capture != nullptr) capture->Take(captured, tail.serial, captureTime(), tail.heldAtSubmit);
if (ring) RecordSubmission(tail.queue, tail.serial, tail.commands, stalled.rewindTail, true);
}
if (capture != nullptr) writeCaptured(*this, *capture, captured);
if (APS5_ENABLE_TIMING_LOG) tail.enqueuedAt = tail.dequeuedAt = tail.orderedAt = std::chrono::steady_clock::now();
timing.Finish();
retireLabelTakes(tail.queue, retiredTakes);
execute(tail);
}
@@ -417,6 +472,7 @@ void Driver::Submit(const Packet* packet, std::uint32_t queue, bool cpuFlip) {
waitForFlipRoom(submission);
holdFlipBehindWorker(submission);
AwaitRegistrationPreparation();
if (LibraryPrebuildsWaiting()) PrioritizeLibraryPrebuilds(BoundGraphicsPrograms(submission));
if (APS5_ENABLE_TIMING_LOG) submission.roomReadyAt = std::chrono::steady_clock::now();
static const bool trace = std::getenv("APS5_TRACE_GPU") != nullptr;
if (trace) std::fprintf(stderr, "[gpu] %.1f submit queue=0x%x dwords=%zu at %p\n", TraceMs(), queue, submission.commands.size(), static_cast<const void*>(descriptor.addr));
@@ -1221,6 +1221,7 @@ struct RegisteredPreparation {
std::optional<ShaderRecompiler::RecompileRequest> request;
std::vector<std::uint64_t> abiKey;
std::shared_ptr<VulkanDevice> prebuildDevice;
std::uint64_t program = 0;
};
bool RegistrationPrepareWithinMemoryBudget();
@@ -1267,6 +1268,7 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
const auto code = std::span(snapshot.code).subspan(codeOffset);
auto plan = std::make_unique<RegisteredPreparation>();
plan->codeOffset = codeOffset;
plan->program = address;
ShaderRecompiler::RdnaProgram decoded;
plan->registration = registration;
try {
@@ -1351,7 +1353,7 @@ bool LibraryPrebuildDisabled() {
return disabled;
}
void PrebuildStageLibrary(const std::shared_ptr<VulkanDevice>& device, const Graphics::ShaderStages& stages, ShaderRecompiler::ShaderStage stage, std::shared_ptr<const ShaderRecompiler::RecompileResult> result, std::uint32_t pushOffset) {
void PrebuildStageLibrary(const std::shared_ptr<VulkanDevice>& device, const Graphics::ShaderStages& stages, ShaderRecompiler::ShaderStage stage, std::shared_ptr<const ShaderRecompiler::RecompileResult> result, std::uint32_t pushOffset, std::uint64_t program) {
if (LibraryPrebuildDisabled() || result == nullptr || (stage != ShaderRecompiler::ShaderStage::Vertex && stage != ShaderRecompiler::ShaderStage::Fragment)) return;
SubmitLibraryPrebuild([device, stages, stage, result = std::move(result), pushOffset] {
const std::array<Graphics::CompiledShader, 1> shaders{{{stage, result.get(), pushOffset}}};
@@ -1360,7 +1362,7 @@ void PrebuildStageLibrary(const std::shared_ptr<VulkanDevice>& device, const Gra
} catch (const std::exception& error) {
APS5_LOG_OUT("shader library prebuild skipped: %s", error.what());
}
});
}, program);
}
std::vector<PreparedShaders::Entry> PrepareRegistered(RegisteredPreparation& plan) {
@@ -1374,7 +1376,7 @@ std::vector<PreparedShaders::Entry> PrepareRegistered(RegisteredPreparation& pla
append();
if (plan.prebuildDevice != nullptr && request.target.registrationModules && request.shader.stage == ShaderRecompiler::ShaderStage::Fragment && !LibraryPrebuildDisabled()) {
try {
PrebuildStageLibrary(plan.prebuildDevice, {Graphics::ShaderPath::Vertex, 0u, 32u, request.context.waveSize, {}, {}}, request.shader.stage, ShaderRecompiler::RegistrationResult(request, *entries.front().handle), request.layout.pushConstantOffsetBytes);
PrebuildStageLibrary(plan.prebuildDevice, {Graphics::ShaderPath::Vertex, 0u, 32u, request.context.waveSize, {}, {}}, request.shader.stage, ShaderRecompiler::RegistrationResult(request, *entries.front().handle), request.layout.pushConstantOffsetBytes, plan.program);
} catch (const std::exception& error) {
APS5_LOG_OUT("shader library prebuild skipped: %s", error.what());
}
@@ -1566,13 +1568,14 @@ private:
public:
void Submit(std::function<void()> task, const PreparedShaders* prepared = nullptr, bool budgeted = false) {
void Submit(std::function<void()> task, const PreparedShaders* prepared = nullptr, bool budgeted = false, std::uint64_t program = 0) {
{
std::unique_lock lock(mutex);
if (threads != 0 && !closed) {
const auto sequence = ++submitted;
if (prepared != nullptr && prepared->demanded.load()) tasks.push_front({std::move(task), prepared, sequence, budgeted});
else tasks.push_back({std::move(task), prepared, sequence, budgeted});
if (prepared != nullptr && prepared->demanded.load()) tasks.push_front({std::move(task), prepared, sequence, budgeted, program});
else tasks.push_back({std::move(task), prepared, sequence, budgeted, program});
if (program != 0) waitingPrograms.fetch_add(1, std::memory_order_relaxed);
unfinished.insert(sequence);
lock.unlock();
ready.notify_one();
@@ -1590,6 +1593,16 @@ public:
std::rotate(tasks.begin(), found, std::next(found));
}
void Prioritize(std::span<const std::uint64_t> programs) {
const auto bound = [&](const Task& task) { return task.program != 0 && std::ranges::binary_search(programs, task.program); };
std::lock_guard lock(mutex);
if (std::ranges::any_of(tasks, bound)) std::stable_partition(tasks.begin(), tasks.end(), bound);
}
bool ProgramsWaiting() const {
return waitingPrograms.load(std::memory_order_relaxed) != 0;
}
void AwaitSubmitted() {
std::unique_lock lock(mutex);
const auto ticket = submitted;
@@ -1604,6 +1617,7 @@ public:
closed = true;
if (dropQueued) {
dropped.swap(tasks);
waitingPrograms.store(0, std::memory_order_relaxed);
for (const auto& task : dropped) unfinished.erase(task.sequence);
idle.notify_all();
}
@@ -1688,6 +1702,7 @@ private:
}
task = std::move(*next);
pool.tasks.erase(next);
if (task.program != 0) pool.waitingPrograms.fetch_sub(1, std::memory_order_relaxed);
++pool.running;
if (task.budgeted) ++pool.budgetedRunning;
}
@@ -1717,12 +1732,14 @@ private:
const PreparedShaders* prepared = nullptr;
std::uint64_t sequence = 0;
bool budgeted = false;
std::uint64_t program = 0;
};
std::mutex mutex;
std::condition_variable ready;
std::condition_variable idle;
std::deque<Task> tasks;
std::atomic<std::size_t> waitingPrograms{0};
std::vector<Worker> workers;
unsigned threads = 0;
inline static std::atomic<unsigned> liveWorkers{0};
@@ -1807,8 +1824,17 @@ void AwaitRegistrationPreparation() {
if (auto* pool = RegistrationPreparePool::Existing()) pool->AwaitSubmitted();
}
void SubmitLibraryPrebuild(std::function<void()> task) {
RegistrationPreparePool::Prebuild().Submit(std::move(task));
void SubmitLibraryPrebuild(std::function<void()> task, std::uint64_t program) {
RegistrationPreparePool::Prebuild().Submit(std::move(task), nullptr, false, program);
}
bool LibraryPrebuildsWaiting() {
auto* pool = RegistrationPreparePool::ExistingPrebuild();
return pool != nullptr && pool->ProgramsWaiting();
}
void PrioritizeLibraryPrebuilds(std::span<const std::uint64_t> programs) {
if (auto* pool = RegistrationPreparePool::ExistingPrebuild(); pool != nullptr && !programs.empty()) pool->Prioritize(programs);
}
void SubmitComputePrebuild(std::function<void()> task) {
@@ -2013,6 +2039,11 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
return;
}
auto prepared = PrepareGraphicsStages(decoded, localDevice->Target(), true);
if (decoded.state.stages.path == Graphics::ShaderPath::Vertex) {
for (const auto& stage : prepared) {
if (stage.registration != nullptr) PrebuildStageLibrary(localDevice, decoded.state.stages, stage.stage, stage.registration, stage.pushOffset, stage.snapshot->codeAddress + stage.entry.codeOffset * sizeof(std::uint32_t));
}
}
for (auto& stage : prepared) {
if (stage.entry.handle == nullptr) continue;
const auto& entries = transaction.Read(*stage.snapshot).entries;
@@ -2023,11 +2054,6 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
if (stage.snapshot->type == 1) continue;
ResolvePreparedGraphics(*stage.snapshot, {}, primitiveType == state.userConfig.end() ? 0u : primitiveType->second, localDevice->Target(), true);
}
if (decoded.state.stages.path == Graphics::ShaderPath::Vertex) {
for (const auto& stage : prepared) {
if (stage.registration != nullptr) PrebuildStageLibrary(localDevice, decoded.state.stages, stage.stage, stage.registration, stage.pushOffset);
}
}
PreparedShaderState::GraphicsAbi abi{key, registry, {}};
for (const auto& stage : prepared) abi.stages.push_back(stage.snapshot);
auto& graphicsAbis = transaction.Edit(*owner).graphicsAbis;
@@ -20,14 +20,6 @@ void Driver::noteLabelStore(std::uint64_t address, std::span<const std::byte> by
}
}
namespace {
std::uint64_t queueDword(std::uint32_t queue, std::uint64_t dword) {
return dword | (static_cast<std::uint64_t>(queue) << 56u);
}
}
void Driver::noteLabelPass(std::span<const std::uint32_t> packet, std::uint32_t queue) {
const auto opcode = (packet[0] >> 8u) & 0xffu;
if ((opcode != 0x3c && opcode != 0x93) || packet.size() < 7 || ((packet[1] >> 4u) & 3u) != 1u) return;
@@ -35,20 +27,28 @@ void Driver::noteLabelPass(std::span<const std::uint32_t> packet, std::uint32_t
const std::uint64_t address = (packet[2] & (bytes == 8 ? ~7u : ~3u)) | (static_cast<std::uint64_t>(packet[3]) << 32u);
const auto passed = eventSerial.load(std::memory_order_acquire);
std::lock_guard lock(labelStoresMutex);
for (std::size_t offset = 0; offset < bytes; offset += 4) labelPasses[queueDword(queue, address + offset)] = passed;
for (std::size_t offset = 0; offset < bytes; offset += 4) labelTakes.Pass(queue, address + offset, passed);
}
void Driver::retireLabelTakes(std::uint32_t queue, std::uint64_t received) {
std::lock_guard lock(labelStoresMutex);
labelTakes.Retire(queue, received);
}
void Driver::noteLabelIssue(std::span<const std::uint32_t> packet, std::uint32_t queue) {
const auto take = [&](std::uint64_t address, std::uint64_t bytes) {
const auto mayTake = [&](std::uint64_t address, std::uint64_t bytes) {
for (std::uint64_t offset = 0; offset < bytes; offset += 4) {
const auto key = queueDword(queue, address + offset);
if (const auto pass = labelPasses.find(key); pass != labelPasses.end()) labelTaken[key] = pass->second;
if (labelTakes.MayHavePass(queue, address + offset)) return true;
}
return false;
};
const auto take = [&](std::uint64_t address, std::uint64_t bytes) {
for (std::uint64_t offset = 0; offset < bytes; offset += 4) labelTakes.Take(queue, address + offset);
};
switch ((packet[0] >> 8u) & 0xffu) {
case 0x37: case 0x49: {
const auto label = Pm4::DecodeLabelWrite(packet);
if (!label.has_value() || !KeepsLabelStore(label->address, label->Bytes().size())) return;
if (!label.has_value() || !KeepsLabelStore(label->address, label->Bytes().size()) || !mayTake(label->address, label->Bytes().size())) return;
std::lock_guard lock(labelStoresMutex);
take(label->address, label->Bytes().size());
return;
@@ -57,7 +57,7 @@ void Driver::noteLabelIssue(std::span<const std::uint32_t> packet, std::uint32_t
default: return;
}
auto stores = Pm4::DecodeStores(packet);
std::erase_if(stores, [](const Pm4::PacketStore& store) { return store.conditional || !KeepsLabelStore(store.address, static_cast<std::size_t>(store.bytes)); });
std::erase_if(stores, [&](const Pm4::PacketStore& store) { return store.conditional || !KeepsLabelStore(store.address, static_cast<std::size_t>(store.bytes)) || !mayTake(store.address, store.bytes); });
if (stores.empty()) return;
std::lock_guard lock(labelStoresMutex);
for (const auto& store : stores) take(store.address, store.bytes);
@@ -70,9 +70,7 @@ bool Driver::storedSince(std::span<const std::uint32_t> packet, std::uint64_t ad
const auto high = bytes == 8 ? labelStores.find(address + 4) : labelStores.end();
if (bytes == 8 && high == labelStores.end()) return false;
std::uint64_t taken = 0;
for (std::size_t offset = 0; offset < bytes; offset += 4) {
if (const auto found = labelTaken.find(queueDword(queue, address + offset)); found != labelTaken.end()) taken = std::max(taken, found->second);
}
for (std::size_t offset = 0; offset < bytes; offset += 4) taken = std::max(taken, labelTakes.Taken(queue, address + offset));
for (const auto& store : low->second) {
if (store.stamp <= received || (store.queue != queue && store.stamp <= taken)) continue;
std::uint64_t value = store.value;
@@ -494,7 +494,8 @@ void FrameRecorder::readGuest(std::uint64_t address, std::span<std::byte> bytes,
bool FrameRecorder::abandonIfDiskLow() {
std::error_code error;
const auto space = std::filesystem::space(directory, error);
if (error || space.available >= MinimumFreeBytes) return false;
static const auto minimumFree = readNumber(std::getenv("APS5_FRAME_CAPTURE_MIN_FREE_BYTES"), MinimumFreeBytes);
if (error || space.available >= minimumFree) return false;
stop("stopped: less than 4 GiB free on the capture volume");
return true;
}
@@ -506,7 +506,7 @@ bool ShaderMemory::readWords(void* context, std::uint64_t address, std::uint32_t
std::uint64_t ShaderMemory::descriptorScope(void* context, std::size_t bytes) {
auto& self = *static_cast<ShaderMemory*>(context);
if (bytes == ShaderRecompiler::SrtDescriptorForceSync) {
++self.forceSync;
if (self.forceSync++ == 0u && self.invalidateRawPages()) ++self.rawInvalidations;
self.descriptorSpanBytes = 0;
return self.rawInvalidations;
}
@@ -839,6 +839,7 @@ struct VulkanDevice::State {
}
const double gapMs = stamped ? (lastNs - firstNs) / 1e6 - busyMs : 0;
if (auto* frame = PerformanceContext::Current()) frame->NoteRetired(0, busyMs, gapMs);
if (stamped && missing == 0) Graphics::Recorder::NoteFrameGpuBusy(busyMs);
presentCounters.gpuBusyMs += busyMs;
presentCounters.gpuGapMs += gapMs;
presentCounters.gpuUnread += missing;
@@ -1400,10 +1401,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
state->fragmentShaderBarycentric = barycentricFeatures.fragmentShaderBarycentric == VK_TRUE;
state->fragmentBarycentricBuiltins = state->fragmentShaderBarycentric;
if (state->fragmentShaderBarycentric && hasExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) {
VkPhysicalDeviceDriverProperties driver{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver};
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &driverProperties);
state->fragmentShaderBarycentric = FragmentShaderBarycentricUsable(driver.driverID, state->fragmentShaderBarycentric);
state->fragmentShaderBarycentric = FragmentShaderBarycentricUsable(driverId, state->fragmentShaderBarycentric);
}
}
VkPhysicalDeviceShaderTerminateInvocationFeaturesKHR terminateFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_TERMINATE_INVOCATION_FEATURES_KHR};
@@ -1427,11 +1425,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
state->shaderClock = clockFeatures.shaderSubgroupClock == VK_TRUE && clockFeatures.shaderDeviceClock == VK_TRUE;
}
if (state->shaderClock && hasExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) {
VkPhysicalDeviceDriverProperties driver{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver};
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &driverProperties);
state->narrowSubgroupClock = NarrowSubgroupClock(driver.driverID, state->properties.deviceID, state->properties.deviceName);
APS5_LOG_OUT("Shader clock driver=%d narrowSubgroupClock=%d", static_cast<int>(driver.driverID), state->narrowSubgroupClock ? 1 : 0);
state->narrowSubgroupClock = NarrowSubgroupClock(driverId, state->properties.deviceID, state->properties.deviceName);
APS5_LOG_OUT("Shader clock driver=%d narrowSubgroupClock=%d", static_cast<int>(driverId), state->narrowSubgroupClock ? 1 : 0);
}
VkPhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT demoteFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DEMOTE_TO_HELPER_INVOCATION_FEATURES_EXT};
if (hasExtension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME)) {
@@ -1740,10 +1735,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) :
if (libraryDevice) deviceExtensions.insert(deviceExtensions.end(), libraryExtensions.begin(), libraryExtensions.end());
state->graphicsPipelineLibrary = libraryDevice && std::getenv("APS5_NO_GPL") == nullptr;
if (state->graphicsPipelineLibrary && hasExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME)) {
VkPhysicalDeviceDriverProperties driver{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
VkPhysicalDeviceProperties2 driverProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &driver};
state->InstanceFunction<PFN_vkGetPhysicalDeviceProperties2>("vkGetPhysicalDeviceProperties2")(selected, &driverProperties);
state->pipelineLinkTimeOptimization = PipelineLinkTimeOptimizationUsable(driver.driverID);
state->pipelineLinkTimeOptimization = PipelineLinkTimeOptimizationUsable(driverId);
}
}
VkPhysicalDeviceMaintenance5FeaturesKHR maintenance5Features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES_KHR};
@@ -2389,16 +2381,33 @@ bool VulkanDevice::ReplayComputePipeline(const ShaderRecompiler::RecompileResult
if (state->computePipelines.contains(key) || state->computePipelinesBuilding.contains(key)) return false;
}
const CompileBudget::Hold hold(BackgroundCompileBudget(), shader.spirv.size() * sizeof(std::uint32_t));
std::promise<std::shared_ptr<ComputePipelineObjects>> building;
{
std::lock_guard pipelines(state->computePipelinesMutex);
if (state->computePipelines.contains(key) || state->computePipelinesBuilding.contains(key)) return false;
state->computePipelinesBuilding.emplace(key, building.get_future().share());
}
const auto context = graphicsContext();
const std::array<Graphics::CompiledShader, 1> shaders{{{ShaderRecompiler::ShaderStage::Compute, &shader, 0}}};
const auto layouts = Graphics::DescribeSetLayouts(context, shaders);
auto objects = createComputePipeline(context, shader, layouts.layouts.main, layouts.mainKey, pushConstants ? VK_SHADER_STAGE_COMPUTE_BIT : 0u, 0);
std::lock_guard pipelines(state->computePipelinesMutex);
return state->computePipelines.try_emplace(key, std::move(objects)).second;
std::shared_ptr<ComputePipelineObjects> objects;
std::exception_ptr error;
try {
const auto context = graphicsContext();
const std::array<Graphics::CompiledShader, 1> shaders{{{ShaderRecompiler::ShaderStage::Compute, &shader, 0}}};
const auto layouts = Graphics::DescribeSetLayouts(context, shaders);
objects = createComputePipeline(context, shader, layouts.layouts.main, layouts.mainKey, pushConstants ? VK_SHADER_STAGE_COMPUTE_BIT : 0u, 0);
} catch (...) {
error = std::current_exception();
}
{
std::lock_guard pipelines(state->computePipelinesMutex);
if (objects != nullptr) state->computePipelines.try_emplace(key, objects);
state->computePipelinesBuilding.erase(key);
}
if (error != nullptr) {
building.set_exception(error);
std::rethrow_exception(error);
}
building.set_value(std::move(objects));
return true;
}
bool VulkanDevice::HasComputePipeline(const ShaderRecompiler::RecompileResult& shader) const {
@@ -2475,12 +2484,12 @@ bool OpportunisticReap() {
}
void VulkanDevice::StoreIdleResults(bool reapFirst) {
void VulkanDevice::StoreIdleResults(bool reapFirst, const std::function<bool()>& stop) {
if (!Graphics::StorageTexture::AnyPending()) return;
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush);
std::lock_guard gpuLock(GuestMemory::GpuMutex());
const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix();
Graphics::StorageTexture::FlushAllPending("idle");
static_cast<void>(Graphics::StorageTexture::FlushAllPendingUntil("idle", stop));
SubmitRecorded(reapFirst);
}
@@ -2654,6 +2663,10 @@ bool VulkanDevice::AfterRecordedWork(std::function<void()> action, bool reapFirs
return state->recorder->AfterRecordedWork(std::move(action));
}
void VulkanDevice::NoteHtileMask(const Graphics::HtileMaskClear& clear) const {
Graphics::NoteHtileMaskOnGpu(graphicsContext(), clear);
}
bool VulkanDevice::DumpSamplesOnGpu(std::uint64_t address) {
static const bool syncDumps = std::getenv("APS5_SYNC_SAMPLE_DUMPS") != nullptr || std::getenv("APS5_NO_GPU_SAMPLE_DUMPS") != nullptr;
if (syncDumps) return false;
@@ -5001,7 +5014,7 @@ VulkanDevice::PresyncStatistics VulkanDevice::PresyncCounts() {
return {counters.checked.load(std::memory_order_relaxed), counters.presyncs.load(std::memory_order_relaxed), counters.keyChecks.load(std::memory_order_relaxed), counters.keyWaits.load(std::memory_order_relaxed)};
}
std::shared_ptr<PreparedDispatch> VulkanDevice::PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t dispatchThreads, std::uint64_t programAddress) {
std::shared_ptr<PreparedDispatch> VulkanDevice::PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span<const Graphics::GuestMemorySnapshot> snapshots, std::uint64_t dispatchThreads, std::uint64_t programAddress, std::optional<std::pair<std::uint64_t, std::size_t>> htileClear) {
ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion);
// APS5_LOCKED_BUILD=1: the whole build under the mutex, as before the split.
static const bool lockedBuild = std::getenv("APS5_LOCKED_BUILD") != nullptr;
@@ -5023,7 +5036,7 @@ std::shared_ptr<PreparedDispatch> VulkanDevice::PrepareDispatch(const ShaderReco
prepared->phaseMs[which] += std::chrono::duration<double, std::milli>(now - phaseStart).count();
phaseStart = now;
};
if (ResourceCacheEnabled() && shader.PipelineVariantId() != 0) {
if (!htileClear.has_value() && ResourceCacheEnabled() && shader.PipelineVariantId() != 0) {
prepared->key = DispatchContentKey(compute, context.device);
phase(PreparedDispatch::PrepareKey);
cached = state->resourceCache.Find(prepared->key);
@@ -5040,7 +5053,7 @@ std::shared_ptr<PreparedDispatch> VulkanDevice::PrepareDispatch(const ShaderReco
phase(PreparedDispatch::PrepareFind);
}
if (cached == nullptr) {
prepared->resources = std::make_shared<Graphics::ShaderResources>(context, compute, snapshots, true, dispatchThreads);
prepared->resources = std::make_shared<Graphics::ShaderResources>(context, compute, snapshots, true, dispatchThreads, htileClear);
phase(PreparedDispatch::PrepareStageA);
}
if (cached != nullptr && CachedPrecollect()) {
@@ -5594,7 +5607,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
const auto lookupsBefore = Graphics::DeviceProcLookups();
std::shared_ptr<Graphics::ShaderResources> resources;
ResourceCache::Key contentKey;
const bool cacheable = ResourceCacheEnabled() && shader.PipelineVariantId() != 0;
const bool cacheable = !htileClear.has_value() && ResourceCacheEnabled() && shader.PipelineVariantId() != 0;
// The object came from the resource cache: a compute template whose data buffers take this
// dispatch's words at the record (ShaderResources::RefreshData).
bool fromCache = false;
@@ -5751,7 +5764,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec
}
if (resources == nullptr) {
const auto buildStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
resources = std::make_shared<Graphics::ShaderResources>(context, shaders[0], snapshots, false, prepared != nullptr ? prepared->dispatchThreads : 0);
resources = std::make_shared<Graphics::ShaderResources>(context, shaders[0], snapshots, false, prepared != nullptr ? prepared->dispatchThreads : 0, htileClear);
if (profile) timer.add(PhaseResourcesFullBuild, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - buildStart).count());
if (cacheable) {
++d.cacheMisses;
+85 -35
View File
@@ -1,3 +1,4 @@
#include "prx/libc/include/general/ExportMacros.hpp"
#include "prx/libSceAgcDriver/include/AgcDriver.hpp"
#include <cstdint>
#include <cstddef>
@@ -6,11 +7,6 @@
extern "C" {
int APS5_VABI sceAgcDriverAcquireRazorACQ(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverCreateQueue(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -36,11 +32,6 @@ int APS5_VABI sceAgcDriverDestroyQueue(void) {
return 0;
}
int APS5_VABI sceAgcDriverGetPaDebugInterfaceVersion(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverGetRegShadowInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -66,11 +57,6 @@ int APS5_VABI sceAgcDriverIDHSSubmit(void) {
return 0;
}
int APS5_VABI sceAgcDriverModuleRegistration(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverNotifyDefaultStates(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -91,11 +77,6 @@ int APS5_VABI sceAgcDriverSetFlip(void) {
return 0;
}
int APS5_VABI sceAgcDriverSetHsOffchipParamDirect(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverSetWorkloadComplete(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -116,31 +97,16 @@ int APS5_VABI sceAgcDriverSetupRegisterShadow(void) {
return 0;
}
int APS5_VABI sceAgcDriverSubmitMultiCommandBuffersDirect(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverSubmitToHDRScopesACQ(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverSubmitToRazorACQ(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverSuspendPointSubmit(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverSuspendPointSubmitDirect(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDriverSysEnableSubmitDone45Exception(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -182,3 +148,87 @@ int APS5_VABI sceAgcDriverUserDataWriteSetMarker(void) {
}
}
extern "C" {
int APS5_VABI libSceAgcDriverUnknown_6WO4CscVnfM_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6WO4CscVnfM", libSceAgcDriverUnknown_6WO4CscVnfM_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_6v_minus_D6CQHA4Y_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("6v-D6CQHA4Y", libSceAgcDriverUnknown_6v_minus_D6CQHA4Y_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_8CXPPlNmvIc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("8CXPPlNmvIc", libSceAgcDriverUnknown_8CXPPlNmvIc_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_Cia3HgY0WiY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("Cia3HgY0WiY", libSceAgcDriverUnknown_Cia3HgY0WiY_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_DGN_plus_IaHNR8o_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("DGN+IaHNR8o", libSceAgcDriverUnknown_DGN_plus_IaHNR8o_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_EqwPDQyh_plus_Ec_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("EqwPDQyh+Ec", libSceAgcDriverUnknown_EqwPDQyh_plus_Ec_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_IGYqP_plus_WKCgc_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("IGYqP+WKCgc", libSceAgcDriverUnknown_IGYqP_plus_WKCgc_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_LgctzJSGy7s_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("LgctzJSGy7s", libSceAgcDriverUnknown_LgctzJSGy7s_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_NmE3PfE211o_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("NmE3PfE211o", libSceAgcDriverUnknown_NmE3PfE211o_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_O144kUYDSBo_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("O144kUYDSBo", libSceAgcDriverUnknown_O144kUYDSBo_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_gpF0LZ3_plus_19c_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("gpF0LZ3+19c", libSceAgcDriverUnknown_gpF0LZ3_plus_19c_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_mxyUaQfPgb8_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("mxyUaQfPgb8", libSceAgcDriverUnknown_mxyUaQfPgb8_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_oFb2hMcoJa4_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("oFb2hMcoJa4", libSceAgcDriverUnknown_oFb2hMcoJa4_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_sW7yp7NVlrk_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("sW7yp7NVlrk", libSceAgcDriverUnknown_sW7yp7NVlrk_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_tD2ECNSuTQQ_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("tD2ECNSuTQQ", libSceAgcDriverUnknown_tD2ECNSuTQQ_nid_no_patch);
int APS5_VABI libSceAgcDriverUnknown_wxdcyVqXJEY_nid_no_patch() {
NotImplemented_nid_no_patch(__func__);
}
APS5_EXPORT("wxdcyVqXJEY", libSceAgcDriverUnknown_wxdcyVqXJEY_nid_no_patch);
}
@@ -0,0 +1,173 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_ADAPTIVESTAGING_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_ADAPTIVESTAGING_HPP
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <map>
#include <string>
#include <utility>
#include <vector>
namespace AgcDriver::Graphics {
class AdaptiveStaging {
public:
static constexpr std::uint64_t SettleFrames = 4;
static constexpr std::uint64_t MeasuredFrames = 8;
static constexpr std::uint64_t EarlyFrames = 3;
static constexpr std::int64_t ReprobeMs = 20000;
static constexpr std::int64_t MaxReprobeMs = 160000;
static constexpr std::int64_t ShareWindowMs = 10000;
static constexpr double SceneShift = 0.25;
std::function<void(const std::string&)> report;
bool InPlace(std::uint64_t begin, std::uint64_t end, bool use, std::uint64_t frames, double busyMs, std::int64_t nowMs) {
const Key key{begin, end};
auto& element = elements[key];
if (use) element.bytes += end - begin;
tick(frames, busyMs, nowMs);
if (probe.active && probe.key == key) return probe.phase == 1 ? !element.inPlace : element.inPlace;
return element.inPlace;
}
private:
using Key = std::pair<std::uint64_t, std::uint64_t>;
struct Element {
std::uint64_t bytes = 0;
bool inPlace = false;
std::int64_t nextProbeMs = 0;
std::int64_t intervalMs = ReprobeMs;
};
struct Probe {
bool active = false;
Key key{};
int phase = 0;
std::uint64_t phaseFrames = 0;
std::vector<double> samples[3];
};
void say(const char* format, auto... args) {
if (!report) return;
char text[256];
std::snprintf(text, sizeof(text), format, args...);
report(text);
}
void tick(std::uint64_t frames, double busyMs, std::int64_t now) {
if (frames == lastFrames) return;
const auto newFrames = frames - lastFrames;
const double perFrame = (busyMs - lastBusyMs) / static_cast<double>(newFrames);
lastFrames = frames;
lastBusyMs = busyMs;
if (now - windowStartMs >= ShareWindowMs) {
for (auto& [key, element] : elements) element.bytes /= 2;
windowStartMs = now;
}
if (!probe.active) {
watchScene(perFrame, newFrames, now);
start(now);
return;
}
auto& element = elements[probe.key];
probe.phaseFrames += newFrames;
if (probe.phaseFrames <= SettleFrames) return;
if (perFrame > 0) probe.samples[probe.phase].push_back(perFrame);
if (probe.phase == 1 && clearlySlower()) {
decide(element, now);
return;
}
if (probe.phaseFrames < SettleFrames + MeasuredFrames) return;
if (++probe.phase < 3) {
probe.phaseFrames = 0;
return;
}
decide(element, now);
}
bool clearlySlower() const {
const auto& current = probe.samples[0];
const auto& other = probe.samples[1];
if (current.size() < MeasuredFrames || other.size() < EarlyFrames) return false;
return *std::min_element(other.begin(), other.end()) > *std::max_element(current.begin(), current.end());
}
void watchScene(double perFrame, std::uint64_t newFrames, std::int64_t now) {
if (perFrame <= 0 || decidedMs <= 0) return;
if (settling > 0) {
settling -= std::min(settling, newFrames);
return;
}
sceneMs = sceneMs * 0.9 + perFrame * 0.1;
if (std::abs(sceneMs - decidedMs) <= SceneShift * decidedMs) return;
say("[staging] adaptive: frame GPU %.1f -> %.1f ms, probing again", decidedMs, sceneMs);
for (auto& [key, element] : elements) {
element.nextProbeMs = std::min(element.nextProbeMs, now);
element.intervalMs = ReprobeMs;
}
decidedMs = 0;
}
void start(std::int64_t now) {
std::uint64_t total = 0;
for (const auto& [key, element] : elements) total += element.bytes;
const Element* best = nullptr;
Key bestKey{};
for (const auto& [key, element] : elements) {
if (element.nextProbeMs > now || element.bytes * 10 < total) continue;
if (best == nullptr || element.bytes > best->bytes) {
best = &element;
bestKey = key;
}
}
if (best == nullptr) return;
probe = Probe{};
probe.active = true;
probe.key = bestKey;
}
void decide(Element& element, std::int64_t now) {
std::vector<double> current = probe.samples[0];
current.insert(current.end(), probe.samples[2].begin(), probe.samples[2].end());
const auto& other = probe.samples[1];
const auto stats = [](const std::vector<double>& values) {
double sum = 0, squares = 0;
for (const auto value : values) sum += value;
const double mean = values.empty() ? 0 : sum / static_cast<double>(values.size());
for (const auto value : values) squares += (value - mean) * (value - mean);
const double variance = values.size() > 1 ? squares / static_cast<double>(values.size() - 1) : 0;
return std::pair{mean, values.empty() ? 0.0 : variance / static_cast<double>(values.size())};
};
const auto [currentMean, currentError] = stats(current);
const auto [otherMean, otherError] = stats(other);
const bool measured = current.size() >= MeasuredFrames && other.size() >= EarlyFrames;
const bool flip = measured && otherMean < currentMean - 2.0 * std::sqrt(currentError + otherError);
const double stagedMs = element.inPlace ? otherMean : currentMean;
const double inPlaceMs = element.inPlace ? currentMean : otherMean;
if (flip) element.inPlace = !element.inPlace;
element.intervalMs = flip || !measured ? ReprobeMs : std::min(element.intervalMs * 2, MaxReprobeMs);
element.nextProbeMs = now + element.intervalMs;
decidedMs = flip ? otherMean : currentMean;
sceneMs = decidedMs;
settling = SettleFrames;
say("[staging] adaptive: 0x%llx+0x%llx GPU %.1f ms/frame staged, %.1f in place (%zu/%zu frames) -> %s%s, next probe in %lld s", static_cast<unsigned long long>(probe.key.first), static_cast<unsigned long long>(probe.key.second - probe.key.first), stagedMs, inPlaceMs, current.size(), other.size(), element.inPlace ? "in place" : "staged", measured ? "" : " (not measured)", static_cast<long long>(element.intervalMs / 1000));
probe = Probe{};
}
std::map<Key, Element> elements;
Probe probe;
std::uint64_t lastFrames = 0;
double lastBusyMs = 0;
double decidedMs = 0;
double sceneMs = 0;
std::uint64_t settling = 0;
std::int64_t windowStartMs = 0;
};
}
#endif
@@ -38,7 +38,7 @@ bool DirectDepthCopy(const Context& context, VkFormat depthFormat, VkImageAspect
void SetDirectDepthCopy(bool enabled);
std::uint64_t DirectDepthCopyCount();
std::uint64_t MultisampledDepthBytes_nid_no_patch(VkExtent2D extent, std::uint32_t bytesPerTexel, std::uint32_t samples);
void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes);
void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes, std::optional<std::pair<std::uint64_t, std::size_t>> htileClear = std::nullopt);
void AppendHostWrittenDepthRanges(std::uint64_t address, std::size_t bytes, std::vector<std::pair<std::uint64_t, std::uint64_t>>& ranges);
bool HasResidentDepthGuards_nid_no_patch();
void RetireUnmappedDepth_nid_no_patch(std::uintptr_t address, std::size_t bytes);
@@ -63,6 +63,15 @@ bool NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::siz
void ArmHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes);
void QueueHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes);
void CompleteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes);
struct HtileMaskClear {
std::uint64_t address = 0;
std::size_t bytes = 0;
std::uint32_t mask = 0;
std::uint32_t value = 0;
};
std::optional<HtileMaskClear> MatchHtileBufferMaskClear(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, std::span<const std::uint32_t> packet, std::uint64_t indirectArguments);
void NoteHtileMaskOnGpu(const Context& context, const HtileMaskClear& clear);
void ForgetHtileMasks_nid_no_patch(std::uint64_t recorderId);
struct MemoryValueFill {
std::uint64_t address;
std::size_t bytes;
@@ -38,6 +38,7 @@ void BindPixelFunctionLds(const Context& context, VkCommandBuffer commands, VkPi
void CheckPixelFunctionLds(const Context& context);
void ReleasePixelFunctionLds(VkDevice device);
bool UsesMeshWindow(std::span<const CompiledShader> shaders);
bool ValidationKey(const Context& context, std::span<const CompiledShader> shaders, const State& state, std::vector<std::uint64_t>& key);
VkDescriptorSetLayout MeshWindowSetLayout(const Context& context);
void PrepareMeshWindow(const Context& context);
void BindMeshWindow(const Context& context, VkCommandBuffer commands, VkPipelineLayout layout);
@@ -254,6 +254,7 @@ struct MirrorStats {
std::uint64_t sweeps = 0;
std::uint64_t heapChecks = 0;
std::uint64_t sparseBinds = 0;
std::uint64_t evictions = 0;
};
MirrorStats MirrorCounters();
std::uint64_t SnapshotMismatches();
@@ -321,11 +322,16 @@ public:
void AllowDeviceStaging() { stagingAllowed = true; }
void DisallowAddressStaging() { addressStaging = false; }
void AllowAdjustedRegions() { adjustedRegions = true; }
bool HoldsMirror(const std::shared_ptr<ImageMirror>& mirror) const;
bool EvictMirror(const std::shared_ptr<ImageMirror>& mirror);
bool WasEvicted(std::uint64_t address) const;
void RecordEvicted(std::uint64_t address);
// Records the copy-in of every staged region anew for another use of this upload (a resource
// cache hit, from ShaderResources::Revalidate, under GuestMemory::GpuMutex, once the imports
// were confirmed unchanged): the previous use's copy-back left the shadow behind, and the next
// RecordCopyBacks copies it back again. Nothing to do without staged regions.
void RecordStagingCopies(Recorder& recorder);
bool StagingDecisionsCurrent() const;
void AddSnapshot(const GuestMemorySnapshot& snapshot);
// Upload is the two stages below back to back. UploadPrepare needs no device lock: it merges the
// regions, binds the image mirrors and host imports that already serve them (an import pointer is
@@ -336,7 +342,7 @@ public:
// and the recorder's pending-write note stays closed.
void Upload(bool addressable);
void UploadPrepare(bool addressable);
void UploadFinish(bool addressable);
void UploadFinish(bool addressable, std::optional<std::pair<std::uint64_t, std::size_t>> htileClear = std::nullopt);
VkDescriptorBufferInfo Descriptor(std::uint64_t address, std::size_t bytes, std::uint32_t& adjustment) const;
static std::uint64_t ViewBytes(std::uint64_t bytes, std::uint32_t adjustment);
std::vector<ShaderRecompiler::BdaAbi::Range> AddressRanges() const;
@@ -457,6 +463,7 @@ private:
// path it would take without staging, in both upload stages.
bool unstaged = false;
bool overlay = false;
bool adaptiveInPlace = false;
};
// How [begin, end) lies against the space's base regions.
@@ -502,6 +509,8 @@ private:
// written element inside, and an atomic element or a size within the written-shadow window.
// Independent of the import, so UploadPrepare and UploadFinish decide alike.
bool stagingEligible(const Region& region, bool addressable) const;
bool stagingWanted(const Region& region, bool addressable) const;
bool adaptiveInPlace(const Region& region, bool use) const;
bool overlayCandidate(const Region& inside, bool readOnly) const;
void resolveOverlays();
bool bindableInPlace(std::uint64_t offset, bool addressable) const;
@@ -533,6 +542,7 @@ private:
std::vector<std::pair<std::uint64_t, std::uint64_t>> servedInsideBase;
std::vector<std::pair<std::uint64_t, std::vector<std::byte>>> heapReferences;
std::vector<std::pair<std::uint64_t, std::shared_ptr<Buffer>>> heapReadbacks;
std::vector<std::uint64_t> evictedRanges;
// UploadPrepare ran (regions are frozen); `uploaded` once UploadFinish ran.
bool prepared = false;
bool uploaded = false;
@@ -425,6 +425,7 @@ public:
// and for helpers that must not recycle resources the recorded work still uses.
static Recorder* Active();
VkDevice Device() const { return context.device; }
std::uint64_t Id() const { return id; }
// Whether the calling thread runs inside a batch's completion action (finish()): a store made
// there must not have a publish recorded for it (UnitShadow), see SyncThrough.
static bool InCompletion();
@@ -529,6 +530,9 @@ public:
// Presentations so far (never reset): a frame clock for policies that count per frame, such as
// StorageTexture's write-back hysteresis.
static std::uint64_t Presents();
static void NoteFrameGpuBusy(double milliseconds);
static std::pair<std::uint64_t, double> FrameGpuBusy();
static void RequestBatchStamps();
// What the presenter learns about finished batches (Driver::Present's [present] line and the
// frame record): finish() writes an entry per submitted batch into a ring of the last 128,
@@ -20,6 +20,8 @@ using GuestRuns = std::vector<std::pair<std::uint64_t, std::uint64_t>>;
GuestRuns GuestNonzeroBlocks(const GuestRuns& candidates);
bool GuestBytesZero(std::uint64_t begin, std::uint64_t end);
void SetResidentAddressSpaceLimit(std::uint64_t bytes);
void SetHeapMirrorBudget(std::uint64_t bytes);
std::shared_ptr<void> HoldHeapMirror(std::uint64_t address);
class ResidentBuffer : public std::enable_shared_from_this<ResidentBuffer> {
public:
@@ -155,6 +155,7 @@ struct ImageTableReuseCounts {
std::uint64_t writesMade = 0;
};
ImageTableReuseCounts ImageTableReuses();
std::uint64_t SampledTexturesCleared();
std::uint64_t SampledLookupKeyScans();
struct PresyncSurface {
@@ -173,7 +174,7 @@ public:
// then no other member may be used. `compute` and `snapshots` must outlive Complete(). An
// address-based shader (BDA tables) builds entirely in Complete(): its lease acquisition
// reconciles imports and refreshes mirrors, which needs the lock.
ShaderResources(const Context& context, const CompiledShader& compute, std::span<const GuestMemorySnapshot> snapshots, bool deferred, std::uint64_t dispatchThreads = 0);
ShaderResources(const Context& context, const CompiledShader& compute, std::span<const GuestMemorySnapshot> snapshots, bool deferred, std::uint64_t dispatchThreads = 0, std::optional<std::pair<std::uint64_t, std::size_t>> htileClear = std::nullopt);
void Complete();
bool Completed() const { return completed; }
~ShaderResources();
@@ -622,6 +623,7 @@ private:
std::vector<std::pair<std::uint64_t, std::uint64_t>> writeOnlyRanges;
bool drawBuild = false;
std::uint64_t dispatchThreads = 0;
std::optional<std::pair<std::uint64_t, std::size_t>> htileClear;
std::vector<std::shared_ptr<Texture>> textures;
std::vector<bool> textureFirstLayer;
std::vector<bool> textureBaseLevel;
@@ -59,7 +59,7 @@ struct OwnedImage {
class Texture {
public:
Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span<const std::byte> snapshot, bool depthCompare = false);
Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span<const std::byte> snapshot, bool depthCompare = false, const VkClearColorValue* clear = nullptr);
Texture(const Context& context, const std::shared_ptr<ResidentColor>& source, const GuestTextureResource& descriptor, VkComponentMapping components);
// A view of a storage image's own VkImage: the sampled texture follows the image's content, so a
// compute pass writing it and the next pass sampling it share one image and copy nothing.
@@ -218,6 +218,7 @@ public:
// 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 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 constexpr std::uint64_t SmallStoreBytes = 64 * 1024;
@@ -683,9 +683,14 @@ bool FillDccClear(VkFormat format, DccKeys keys, bool alphaOnMsb, std::span<std:
if (((value >> i) & 1u) != 0) element[bit / 8u] |= static_cast<std::byte>(1u << (bit % 8u));
}
}
std::size_t offset = 0;
for (; offset + element.size() <= bytes.size(); offset += element.size()) std::memcpy(bytes.data() + offset, element.data(), element.size());
std::fill(bytes.begin() + offset, bytes.end(), std::byte{0});
const std::size_t whole = bytes.size() / element.size() * element.size();
if (whole != 0) std::memcpy(bytes.data(), element.data(), element.size());
for (std::size_t filled = element.size(); filled < whole;) {
const auto chunk = std::min(filled, whole - filled);
std::memcpy(bytes.data() + filled, bytes.data(), chunk);
filled += chunk;
}
std::fill(bytes.begin() + static_cast<std::ptrdiff_t>(whole), bytes.end(), std::byte{0});
return true;
}
@@ -1861,6 +1861,42 @@ std::optional<std::uint32_t> htileAfterPendingFills(const Recorder& recorder, st
return landed;
}
struct HtileMaskWrite {
HtileMaskClear clear;
std::uint64_t note;
std::uint64_t recorderId;
VkDevice device;
};
std::mutex& htileMasksMutex() {
static std::mutex mutex;
return mutex;
}
std::vector<HtileMaskWrite>& htileMasks() {
static auto* masks = new std::vector<HtileMaskWrite>();
return *masks;
}
std::optional<std::pair<VkImageAspectFlags, std::uint32_t>> htileAfterMaskClear(const Recorder& recorder, const DepthTarget& target, std::size_t bytes) {
const auto newest = recorder.NewestWriteNote(target.htileAddress, bytes);
if (newest == 0) return std::nullopt;
std::optional<HtileMaskClear> clear;
{
std::lock_guard lock(htileMasksMutex());
for (const auto& write : htileMasks()) {
if (write.recorderId == recorder.Id() && write.note == newest && write.clear.address <= target.htileAddress && write.clear.address + write.clear.bytes >= target.htileAddress + bytes) clear = write.clear;
}
}
if (!clear.has_value() || (clear->mask & 0xfu) != 0 || (clear->value & 0xfu) != 0) return std::nullopt;
if (target.firstSlice != 0 && clear->mask != 0) return std::nullopt;
if (!target.htileStencil) return std::pair<VkImageAspectFlags, std::uint32_t>{VK_IMAGE_ASPECT_DEPTH_BIT, 0xfffc000fu};
if ((clear->mask & 0x300u) != 0) return std::nullopt;
if (((clear->value >> 8u) & 0x3u) == 0) return std::pair<VkImageAspectFlags, std::uint32_t>{VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0xfffff3ffu};
if ((clear->mask & 0x3f0u) != 0) return std::nullopt;
return std::pair<VkImageAspectFlags, std::uint32_t>{VK_IMAGE_ASPECT_DEPTH_BIT, (clear->value & 0x3f0u) | 0xfffff00fu};
}
bool storeHtileOnGpu(const Context& context, Recorder& recorder, std::uint64_t address, std::size_t bytes, std::uint32_t word) {
if (address % 4u != 0 || bytes % 4u != 0) return false;
const auto* import = HostImportFor(context, address, bytes);
@@ -1910,6 +1946,11 @@ VkImageAspectFlags storeBackClearedHtile(const Context& context, const DepthTarg
htileReads()[target.htileAddress] = generation;
return cleared;
}
} else if (const auto known = htileAfterMaskClear(*recorder, target, bytes); known.has_value() && storeHtileOnGpu(context, *recorder, target.htileAddress, bytes, known->second)) {
const auto generation = GuestMemory::CollectWrites(target.htileAddress, bytes);
std::lock_guard lock(htileReadsMutex());
htileReads()[target.htileAddress] = generation;
return known->first;
}
}
std::vector<std::uint32_t> words(bytes / 4u);
@@ -2095,10 +2136,22 @@ std::uint64_t MultisampledDepthBytes_nid_no_patch(VkExtent2D extent, std::uint32
return width * height * bytesPerTexel * samples;
}
void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes) {
void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes, std::optional<std::pair<std::uint64_t, std::size_t>> htileClear) {
if (bytes == 0u || !anyDepthGuard().load(std::memory_order_acquire)) return;
Require(address <= std::numeric_limits<std::uint64_t>::max() - bytes, "native depth read range overflows");
const auto overlaps = [&](const DepthGuard& range) { return address < range.address + range.bytes && range.address < address + bytes; };
if (htileClear.has_value() && htileClear->second <= std::numeric_limits<std::uint64_t>::max() - htileClear->first && htileClear->first <= address && bytes <= htileClear->second && address - htileClear->first <= htileClear->second - bytes) {
std::lock_guard lock(surfacesMutex());
std::shared_lock guardLock(depthGuardMutex());
const bool accepted = std::ranges::all_of(depthGuards(), [&](const auto& range) {
if (!overlaps(range)) return true;
return std::ranges::any_of(surfaces(), [&](const auto& surface) {
const auto& metadata = surface->sampling.initial[1];
return surface->active == range.active && surface->active->load(std::memory_order_acquire) && surface->samplingActive && surface->sampling.refusal.empty() && surface->clearPending && metadata.address == range.address && metadata.bytes == range.bytes && metadata.address == htileClear->first && metadata.bytes <= htileClear->second;
});
});
if (accepted) return;
}
{
std::shared_lock lock(depthGuardMutex());
if (std::ranges::none_of(depthGuards(), overlaps)) return;
@@ -2439,6 +2492,30 @@ std::uint64_t HtileDepthClearAddress(std::span<const std::uint32_t> code, std::s
return userData[2] | (static_cast<std::uint64_t>(userData[3]) << 32u);
}
std::optional<HtileMaskClear> MatchHtileBufferMaskClear(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, std::span<const std::uint32_t> packet, std::uint64_t indirectArguments) {
static constexpr std::array<std::uint32_t, 16> kernel{0xd7460004u, 0x04010c0cu, 0xe00c2000u, 0x80000004u, 0xbf8c3f70u, 0xd7710000u, 0x00120008u, 0xd7710001u, 0x00160209u, 0xd7710002u, 0x001a040au, 0xd7710003u, 0x001e060bu, 0xe01c2000u, 0x80000004u, 0xbf810000u};
if (userData.size() != 12u || packet.size() < 5u || indirectArguments != 0u) return std::nullopt;
if (code.size() < kernel.size() || !std::equal(kernel.begin(), kernel.end(), code.begin())) return std::nullopt;
if (compute.numThreads != std::array<std::uint32_t, 3>{64u, 1u, 1u} || !compute.groupIdEnable[0] || compute.threadIdComponentCount == 0u || compute.threadIdComponentCount > 3u) return std::nullopt;
if (packet[1] == 0u || packet[2] != 1u || packet[3] != 1u || (packet[4] & 0x3u) != 0x1u) return std::nullopt;
for (std::size_t word = 1; word < 4; ++word) {
if (userData[4 + word] != userData[4] || userData[8 + word] != userData[8]) return std::nullopt;
}
if ((userData[8] & 0xfu) != 0u || (userData[4] & 0xfu) != 0u) return std::nullopt;
const auto stride = (userData[1] >> 16u) & 0x3fffu;
const bool swizzled = ((userData[1] >> 31u) & 1u) != 0u;
const bool addTid = ((userData[3] >> 23u) & 1u) != 0u;
const auto type = userData[3] >> 30u;
const auto format = (userData[3] >> 12u) & 0x7fu;
const auto selects = userData[3] & 0xfffu;
if (stride != 16u || swizzled || addTid || type != 0u || format != 75u || selects != 0xfacu) return std::nullopt;
const auto address = userData[0] | (static_cast<std::uint64_t>(userData[1] & 0xffffu) << 32u);
const std::uint64_t threads = (packet[4] & 0x20u) != 0u ? packet[1] : static_cast<std::uint64_t>(packet[1]) * 64u;
const auto records = std::min<std::uint64_t>(threads, userData[2]);
if (address == 0u || (address & 15u) != 0u || records == 0u || records > 0x400000u) return std::nullopt;
return HtileMaskClear{address, static_cast<std::size_t>(records * 16u), userData[8], userData[4]};
}
std::optional<MemoryValueFill> MatchMemoryValueFill(std::span<const std::uint32_t> code, std::span<const std::uint32_t> userData, const std::array<std::uint32_t, 3>& numThreads, const std::array<std::uint32_t, 3>& groups) {
static constexpr std::array<std::uint32_t, 9> kernel{0xd7460000u, 0x04010c08u, 0xf4201a82u, 0xfa000000u, 0xbf8cc07fu, 0x7e02026au, 0xe0102000u, 0x80000100u, 0xbf810000u};
if (userData.size() < 8 || numThreads != std::array<std::uint32_t, 3>{64u, 1u, 1u} || groups[1] != 1u || groups[2] != 1u) return std::nullopt;
@@ -2531,6 +2608,31 @@ std::size_t HtileDepthClearBytes_nid_no_patch(std::span<const std::uint32_t> cod
return address <= std::numeric_limits<std::uint64_t>::max() - bytes ? static_cast<std::size_t>(bytes) : 0u;
}
void NoteHtileMaskOnGpu(const Context& context, const HtileMaskClear& clear) {
const auto* recorder = RecorderFor(context);
if (recorder == nullptr || clear.bytes == 0) return;
const auto end = clear.address + clear.bytes;
std::uint64_t note = 0;
bool covers = false;
for (const auto& write : recorder->PendingWritesOver(clear.address, clear.bytes)) {
if (write.note <= note) continue;
note = write.note;
covers = write.begin <= clear.address && write.end >= end;
}
std::lock_guard lock(htileMasksMutex());
auto& masks = htileMasks();
std::erase_if(masks, [&](const HtileMaskWrite& write) { return clear.address < write.clear.address + write.clear.bytes && write.clear.address < end; });
if (!covers) return;
constexpr std::size_t KeptMasks = 64;
if (masks.size() >= KeptMasks) masks.erase(masks.begin());
masks.push_back({clear, note, recorder->Id(), context.device});
}
void ForgetHtileMasks_nid_no_patch(std::uint64_t recorderId) {
std::lock_guard lock(htileMasksMutex());
std::erase_if(htileMasks(), [&](const HtileMaskWrite& write) { return write.recorderId == recorderId; });
}
bool NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::size_t nativeBytes) {
std::lock_guard lock(surfacesMutex());
forgetHtileReads(htileAddress, bytes);
@@ -2593,6 +2695,10 @@ void CompleteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes) {
void ClearDepthSurfaces(VkDevice device) {
std::lock_guard gpu(GuestMemory::GpuMutex());
{
std::lock_guard lock(htileMasksMutex());
std::erase_if(htileMasks(), [&](const HtileMaskWrite& write) { return write.device == device; });
}
std::lock_guard lock(surfacesMutex());
drawnDepth = nullptr;
surfacesSerial.fetch_add(1, std::memory_order_release);
@@ -950,6 +950,8 @@ void countCache(std::uint64_t DrawProfile::*counter) {
++(stats.*counter);
}
}
// ValidateShaders decodes every SPIR-V instruction of every stage on every draw. Its outcome depends
// only on the stages' compiled variants (a variant id names identical SPIR-V and binding layout),
// their push constant placement and vertex attribute shapes, and the state fields it checks, so the
@@ -967,10 +969,12 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
if (keyed) {
key.reserve(40 + shaders.size() * 12);
add(shaders.size());
const bool patches = BarycentricPatches(state, shaders);
add(patches ? static_cast<std::uint64_t>(state.provokingVertexMode) + 1u : 0u);
for (const auto& shader : shaders) {
Require(shader.program != nullptr, "missing compiled shader");
const auto& program = *shader.program;
const bool generated = (state.rectList && (shader.stage == Stage::TessellationControl || shader.stage == Stage::TessellationEvaluation)) || shader.stage == Stage::Geometry;
const bool generated = ((state.rectList || patches) && (shader.stage == Stage::TessellationControl || shader.stage == Stage::TessellationEvaluation)) || shader.stage == Stage::Geometry;
if (!generated && program.PipelineVariantId() == 0) {
keyed = false;
break;
@@ -1037,6 +1041,8 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
return keyed;
}
namespace {
AgcDriver::Mutex& validationMutex() {
static AgcDriver::Mutex mutex;
return mutex;
@@ -1,5 +1,6 @@
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/AdaptiveStaging.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp"
#include "ThreadOwned.hpp"
@@ -41,6 +42,7 @@
#include <mutex>
#include <optional>
#include <set>
#include <unordered_set>
#include <thread>
#include <stop_token>
#include <cerrno>
@@ -86,6 +88,7 @@ struct ImageMirror {
// apart from host import serials.
std::uint64_t serial = 0;
std::atomic<std::uint64_t> watchedSerial{0};
std::uint64_t lastDispatch = 0;
};
// The address space. An address-based build maps every readable registered range; building that
@@ -135,7 +138,16 @@ struct GuestBufferMemory::AddressSpace {
mutable std::list<const std::vector<std::uint64_t>*> tableRecency;
};
std::atomic<std::uint64_t> testHeapMirrorBudget{0};
void SetHeapMirrorBudget(std::uint64_t bytes) {
testHeapMirrorBudget.store(bytes, std::memory_order_release);
}
namespace {
struct RefreshBlock;
static thread_local GuestBufferMemory* buildingBufferMemory = nullptr;
static thread_local std::vector<RefreshBlock>* buildingBlocks = nullptr;
struct RetiredImport;
@@ -2462,6 +2474,8 @@ struct ImageMirrors {
std::uint64_t pinnedVersion = 0;
std::uint64_t sweeps = 0;
std::uint64_t heapChecks = 0;
std::uint64_t evictions = 0;
std::uint64_t dispatchCounter = 0;
std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now();
};
@@ -2545,6 +2559,7 @@ bool mirrorsEnabled() {
}
std::uint64_t heapMirrorBudget() {
if (const auto test = testHeapMirrorBudget.load(std::memory_order_acquire); test != 0) return test;
static const std::uint64_t bytes = [] {
const char* value = std::getenv("APS5_HEAP_MIRROR_MIB");
return (value ? std::strtoull(value, nullptr, 10) : 24576ull) << 20u;
@@ -2562,7 +2577,7 @@ std::string heapMirrorError(std::uint64_t address, std::uint64_t bytes, std::uin
return std::string(prefix) + where + std::string(reason) + holding;
}
void sweepMirrors();
void sweepMirrors(std::uint64_t neededBytes = 0, std::uint64_t protectAddress = 0);
void unlistMirror(ImageMirrors& state, ImageMirror& mirror) {
if (!mirror.listed) return;
@@ -2632,7 +2647,7 @@ void reserveHeapBytes(std::uint64_t address, std::uint64_t bytes, std::uint64_t
}
if (attempt != 0) throw std::runtime_error(heapMirrorError(address, bytes, held, "past the budget"));
}
sweepMirrors();
sweepMirrors(need, address);
}
}
@@ -2654,7 +2669,7 @@ void growResident(ImageMirror& mirror, const GuestRuns& runs) {
}
if (attempt != 0) throw std::runtime_error(heapMirrorError(mirror.base, mirror.reserved, state.heapBytes, "past the budget"));
}
sweepMirrors();
sweepMirrors(need, mirror.base);
}
try {
mirror.resident->Bind(runs);
@@ -3295,14 +3310,20 @@ std::shared_ptr<ImageMirror> findMirror(const Context& context, std::uint64_t be
return mirror;
}
// Drops mirrors whose registered range is gone (a later build made a new one for the replacement).
void sweepMirrors() {
void sweepMirrors(std::uint64_t neededBytes, std::uint64_t protectAddress) {
const bool released = releaseExpiredJoinedHeaps();
const auto space = Spaces().current.load();
auto space = Spaces().current.load();
auto& state = Mirrors();
std::vector<std::shared_ptr<ImageMirror>> dropped;
std::lock_guard lock(state.mutex);
if (released) ++state.version;
if (space != nullptr && state.pinnedSpace == space->serial && state.pinnedVersion == state.version) return;
if (neededBytes == 0 && space != nullptr && state.pinnedSpace == space->serial && state.pinnedVersion == state.version) return;
if (neededBytes != 0 && state.heapBytes + neededBytes > heapMirrorBudget()) {
if (auto cached = Spaces().current.exchange(nullptr); cached != nullptr) {
space.reset();
cached.reset();
}
}
++state.sweeps;
bool pinned = space != nullptr;
std::size_t leased = 0;
@@ -3316,9 +3337,47 @@ void sweepMirrors() {
continue;
}
if (it->second->heap) unlistMirror(state, *it->second);
dropped.push_back(std::move(it->second));
it = state.entries.erase(it);
++state.version;
}
if (neededBytes != 0 && state.heapBytes + neededBytes > heapMirrorBudget()) {
std::unordered_set<const ImageMirror*> queued;
if (buildingBlocks != nullptr) {
for (const auto& block : *buildingBlocks) queued.insert(block.mirror);
}
while (state.heapBytes + neededBytes > heapMirrorBudget()) {
auto victim = state.entries.end();
for (auto it = state.entries.begin(); it != state.entries.end(); ++it) {
if (!it->second->heap || it->first == protectAddress || queued.count(it->second.get()) != 0) continue;
const bool buildingHolds = buildingBufferMemory != nullptr && buildingBufferMemory->HoldsMirror(it->second);
const auto allowedCount = (buildingHolds && !it->second->writable) ? 2 : 1;
if (it->second.use_count() == allowedCount) {
if (victim == state.entries.end()) {
victim = it;
} else if (it->second->lastDispatch != victim->second->lastDispatch) {
if (it->second->lastDispatch < victim->second->lastDispatch) victim = it;
} else if (it->second->writable != victim->second->writable) {
if (!it->second->writable && victim->second->writable) victim = it;
} else if (it->first < victim->first) {
victim = it;
}
}
}
if (victim == state.entries.end()) break;
if (buildingBufferMemory != nullptr) {
buildingBufferMemory->RecordEvicted(victim->first);
if (buildingBufferMemory->HoldsMirror(victim->second)) {
buildingBufferMemory->EvictMirror(victim->second);
}
}
unlistMirror(state, *victim->second);
dropped.push_back(std::move(victim->second));
state.entries.erase(victim);
++state.version;
++state.evictions;
}
}
state.pinnedSpace = pinned ? space->serial : 0;
state.pinnedVersion = state.version;
}
@@ -3385,7 +3444,7 @@ void reportMirrors() {
}
heapBytes = state.heapBytes;
}
AgcDriver::ProfilePrint_nid_no_patch("[buffers] image mirrors: %zu ranges (%.1f MiB, %zu writable, %zu heap %.1f MiB), %llu address-based builds served, %llu descriptor sub-ranges bound, %llu sparse heaps bound, %llu rebuilds, %llu refreshes: %llu blocks compared, %llu copied, %llu refresh syncs; heap refills %llu, unwatched heaps %llu, %llu heap mirrors released for imports (%.1f MiB)\n", count, bytes / 1048576.0, writable, heaps, heapBytes / 1048576.0, static_cast<unsigned long long>(state.builds), static_cast<unsigned long long>(state.subranges), static_cast<unsigned long long>(state.sparseBinds), static_cast<unsigned long long>(state.rebuilds), static_cast<unsigned long long>(state.refreshes), static_cast<unsigned long long>(state.blocksCompared), static_cast<unsigned long long>(state.blocksCopied), static_cast<unsigned long long>(state.syncs), static_cast<unsigned long long>(state.heapRefills), static_cast<unsigned long long>(state.heapUnwatched), static_cast<unsigned long long>(state.heapImported), state.heapImportedBytes / 1048576.0);
AgcDriver::ProfilePrint_nid_no_patch("[buffers] image mirrors: %zu ranges (%.1f MiB, %zu writable, %zu heap %.1f MiB), %llu address-based builds served, %llu descriptor sub-ranges bound, %llu sparse heaps bound, %llu rebuilds, %llu refreshes: %llu blocks compared, %llu copied, %llu refresh syncs; heap refills %llu, unwatched heaps %llu, %llu heap mirrors evicted for budget, %llu heap mirrors released for imports (%.1f MiB)\n", count, bytes / 1048576.0, writable, heaps, heapBytes / 1048576.0, static_cast<unsigned long long>(state.builds), static_cast<unsigned long long>(state.subranges), static_cast<unsigned long long>(state.sparseBinds), static_cast<unsigned long long>(state.rebuilds), static_cast<unsigned long long>(state.refreshes), static_cast<unsigned long long>(state.blocksCompared), static_cast<unsigned long long>(state.blocksCopied), static_cast<unsigned long long>(state.syncs), static_cast<unsigned long long>(state.heapRefills), static_cast<unsigned long long>(state.heapUnwatched), static_cast<unsigned long long>(state.evictions), static_cast<unsigned long long>(state.heapImported), state.heapImportedBytes / 1048576.0);
#ifdef _WIN32
auto& imports = Imports();
std::lock_guard importsLock(imports.mutex);
@@ -3672,7 +3731,7 @@ std::string AddressCopyOverflow(std::vector<AddressCopy> copies, std::uint64_t l
MirrorStats MirrorCounters() {
auto& state = Mirrors();
std::lock_guard lock(state.mutex);
MirrorStats stats{0, state.heapBytes, state.rebuilds, state.blocksCopied, state.heapRefills, state.sweeps, state.heapChecks, state.sparseBinds};
MirrorStats stats{0, state.heapBytes, state.rebuilds, state.blocksCopied, state.heapRefills, state.sweeps, state.heapChecks, state.sparseBinds, state.evictions};
for (const auto& [base, mirror] : state.entries) stats.heapMirrors += mirror->heap ? 1 : 0;
return stats;
}
@@ -3681,6 +3740,14 @@ std::uint64_t SnapshotMismatches() {
return Snapshots().mismatched.load(std::memory_order_relaxed);
}
std::shared_ptr<void> HoldHeapMirror(std::uint64_t address) {
auto& state = Mirrors();
std::lock_guard lock(state.mutex);
auto it = state.entries.find(address);
if (it != state.entries.end()) return it->second;
return nullptr;
}
HeapMirrorHold HeapMirrorsWithin(std::uint64_t begin, std::uint64_t end) {
auto& state = Mirrors();
std::lock_guard lock(state.mutex);
@@ -4358,7 +4425,50 @@ void GuestBufferMemory::CountAddressBuild(double snapshotsUs) {
totals.snapshotsUs = 0;
}
bool GuestBufferMemory::HoldsMirror(const std::shared_ptr<ImageMirror>& mirror) const {
for (const auto& region : regions) {
if (region.mirror == mirror) return true;
}
return false;
}
bool GuestBufferMemory::EvictMirror(const std::shared_ptr<ImageMirror>& mirror) {
bool found = false;
for (auto& region : regions) {
if (region.mirror == mirror) {
region.mirror.reset();
region.hostBacked = !region.writable;
auto committed = GuestMemory::DescribeCommitted(region.begin, region.end - region.begin);
if (!committed.whole) {
region.sparse = true;
region.backed = std::move(committed.ranges);
} else {
region.sparse = false;
region.backed.clear();
}
found = true;
}
}
return found;
}
bool GuestBufferMemory::WasEvicted(std::uint64_t address) const {
return std::find(evictedRanges.begin(), evictedRanges.end(), address) != evictedRanges.end();
}
void GuestBufferMemory::RecordEvicted(std::uint64_t address) {
if (!WasEvicted(address)) evictedRanges.push_back(address);
}
void GuestBufferMemory::AcquireRegistered() {
struct BuildingScope {
explicit BuildingScope(GuestBufferMemory* self) { buildingBufferMemory = self; }
~BuildingScope() {
buildingBufferMemory = nullptr;
buildingBlocks = nullptr;
}
};
const BuildingScope buildingScope(this);
Require(!uploaded && regions.empty() && lease.empty() && space == nullptr, "guest allocation lease must precede resource registration");
ensurePinWaiter();
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
@@ -4374,6 +4484,7 @@ void GuestBufferMemory::AcquireRegistered() {
// APS5_NO_SORTED_SNAPSHOT_LOOKUP=1: AddSnapshot scans the regions as before instead of searching.
static const bool sortedLookup = std::getenv("APS5_NO_SORTED_SNAPSHOT_LOOKUP") == nullptr;
std::vector<RefreshBlock> blocks;
buildingBlocks = &blocks;
std::vector<ImageMirror*> heaps;
bool mirrored = false;
bool served = false;
@@ -4528,7 +4639,7 @@ void GuestBufferMemory::AcquireRegistered() {
if (part.mirror->sparse) {
part.sparse = true;
part.backed = part.mirror->committed;
} else heaps.push_back(part.mirror.get());
}
}
regions.push_back(std::move(part));
}
@@ -4542,7 +4653,7 @@ void GuestBufferMemory::AcquireRegistered() {
imported.emplace_back(range->address, range->address + range->bytes);
continue;
}
if (mirrorsEnabled()) {
if (mirrorsEnabled() && !WasEvicted(range->address)) {
region.mirror = acquireMirror(context, range, blocks, range->releasable);
lap(timing.mirrorsUs, at);
if (region.mirror != nullptr) {
@@ -4550,8 +4661,6 @@ void GuestBufferMemory::AcquireRegistered() {
if (region.mirror->sparse) {
region.sparse = true;
region.backed = region.mirror->committed;
} else if (region.mirror->heap) {
heaps.push_back(region.mirror.get());
}
regions.push_back(std::move(region));
continue;
@@ -4595,6 +4704,9 @@ void GuestBufferMemory::AcquireRegistered() {
copies.push_back({copied.begin, copied.end, committed, reason, reason == nullptr, overBudget});
}
releaseImportedMirrors(context, imported);
for (const auto& region : regions) {
if (region.mirror != nullptr && region.mirror->heap && !region.mirror->sparse) heaps.push_back(region.mirror.get());
}
regionsSorted = sortedLookup;
if (!copies.empty()) {
std::uint64_t copied = 0;
@@ -4669,7 +4781,7 @@ void GuestBufferMemory::AcquireRegistered() {
}
space = std::move(built);
spaces.droppedByWaiter.store(false, std::memory_order_relaxed);
spaces.current.store(space);
if (evictedRanges.empty()) spaces.current.store(space);
}
}
}
@@ -5164,6 +5276,37 @@ void traceStaged(std::uint64_t begin, std::uint64_t end, bool atomic) {
std::fprintf(stderr, "[staging] 0x%llx+0x%llx (%.1f KiB)%s\n", static_cast<unsigned long long>(begin), static_cast<unsigned long long>(end - begin), (end - begin) / 1024.0, atomic ? " atomic" : "");
}
class AdaptiveStagingGate {
public:
static AdaptiveStagingGate& Get() {
static AdaptiveStagingGate instance;
return instance;
}
static bool Enabled() {
static const bool disabled = std::getenv("APS5_NO_ADAPTIVE_STAGING") != nullptr;
return !disabled;
}
bool InPlace(std::uint64_t begin, std::uint64_t end, bool use) {
if (!Enabled()) return false;
std::lock_guard lock(mutex);
if (!stampsRequested) {
Recorder::RequestBatchStamps();
policy.report = [](const std::string& line) { std::fprintf(stderr, "%s\n", line.c_str()); };
stampsRequested = true;
}
const auto [frames, busyMs] = Recorder::FrameGpuBusy();
const auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
return policy.InPlace(begin, end, use, frames, busyMs, nowMs);
}
private:
std::mutex mutex;
AdaptiveStaging policy;
bool stampsRequested = false;
};
void reportStaging() {
auto& stats = Copies();
const auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
@@ -5207,6 +5350,24 @@ bool GuestBufferMemory::bindableInPlace(std::uint64_t offset, bool addressable)
}
bool GuestBufferMemory::stagingEligible(const Region& region, bool addressable) const {
return !region.adaptiveInPlace && stagingWanted(region, addressable);
}
bool GuestBufferMemory::adaptiveInPlace(const Region& region, bool use) const {
if (!region.swept || region.atomic || region.overlay) return false;
return AdaptiveStagingGate::Get().InPlace(region.begin, region.end, use);
}
bool GuestBufferMemory::StagingDecisionsCurrent() const {
if (!AdaptiveStagingGate::Enabled()) return true;
for (const auto& region : regions) {
if (!(region.gpuCopy && region.deviceLocal) && !region.adaptiveInPlace) continue;
if (adaptiveInPlace(region, true) != region.adaptiveInPlace) return false;
}
return true;
}
bool GuestBufferMemory::stagingWanted(const Region& region, bool addressable) const {
if (!stagingAllowed || (addressable && !region.overlay) || region.unstaged || !gpuCopiesEnabled() || region.sparse || region.mirror != nullptr) return false;
const auto bytes = region.end - region.begin;
if (!WritesOverlap(region.begin, static_cast<std::size_t>(bytes))) return false;
@@ -5378,6 +5539,7 @@ void GuestBufferMemory::UploadPrepare(bool addressable) {
// staged region (see stagingEligible) takes the same path, aligned or not, with a
// device-local buffer that the CPU fallback replaces (none without a recorder to
// record the copies: UploadFinish then binds the region in place).
region.adaptiveInPlace = Recorder::Active() != nullptr && stagingWanted(region, addressable) && adaptiveInPlace(region, true);
bool staged = Recorder::Active() != nullptr && stagingEligible(region, addressable);
const bool misaligned = !bindableInPlace(HostImportOffset(*entry, region.begin), addressable);
if (staged) {
@@ -5442,15 +5604,28 @@ void GuestBufferMemory::completeSpaceWrites() const {
}
}
void GuestBufferMemory::UploadFinish(bool addressable) {
void GuestBufferMemory::UploadFinish(bool addressable, std::optional<std::pair<std::uint64_t, std::size_t>> htileClear) {
Require(prepared && !uploaded, "guest memory upload was not prepared");
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
const auto started = std::chrono::steady_clock::now();
for (const auto& region : regions) CompletePendingCpuWrites(context, region.begin, static_cast<std::size_t>(region.end - region.begin));
if (space != nullptr) completeSpaceWrites();
uploaded = true;
{
auto& state = Mirrors();
std::lock_guard lock(state.mutex);
const auto stamp = ++state.dispatchCounter;
for (const auto& region : regions) {
if (region.mirror != nullptr) region.mirror->lastDispatch = stamp;
}
if (space != nullptr) {
for (const auto& region : space->base) {
if (region.mirror != nullptr) region.mirror->lastDispatch = stamp;
}
}
}
for (const auto& region : regions) static_cast<void>(FlushDepthPlanes(region.begin, static_cast<std::size_t>(region.end - region.begin)));
for (const auto& [begin, end] : writes) RequireNoResidentDepthRead_nid_no_patch(begin, static_cast<std::size_t>(end - begin));
for (const auto& [begin, end] : writes) RequireNoResidentDepthRead_nid_no_patch(begin, static_cast<std::size_t>(end - begin), htileClear);
for (const auto& [begin, end] : writes) StorageTexture::FlushPending(begin, static_cast<std::size_t>(end - begin), nullptr, "written buffer range");
if (context.hostImportAlignment != 0) {
auto& state = Imports();
@@ -4,6 +4,7 @@
#include "prx/libSceAgcDriver/Execution/include/Mutex.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libkernel/Time/include/SleepWake.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp"
#include "prx/libSceAgcDriver/Graphics/include/RuntimeSwitches.hpp"
@@ -1190,6 +1191,7 @@ Recorder::~Recorder() {
} catch (const std::exception& error) {
std::fprintf(stderr, "[gpu] recorder teardown: %s\n", error.what());
}
ForgetHtileMasks_nid_no_patch(id);
progressWatcher.reset();
holdKept.clear();
if (activeRecorder == this) {
@@ -1854,6 +1856,9 @@ std::atomic<std::int64_t> traceBarriersLeft{[] {
}()};
std::atomic<std::uint64_t> timingPresents{0};
std::atomic<std::uint64_t> presentSerial{0};
std::atomic<std::uint64_t> settledFrames{0};
std::atomic<std::uint64_t> settledFrameBusyNs{0};
std::atomic<bool> batchStampsRequested{false};
std::atomic<std::uint64_t> timingDropped{0};
struct TimingTotals { std::uint64_t count = 0; double ms = 0; std::uint64_t bytes = 0; };
AgcDriver::Mutex timingMutex;
@@ -1919,7 +1924,7 @@ bool Recorder::GpuTimingEnabled() {
}
bool Recorder::BatchStampsEnabled() {
return GpuTimingEnabled() || DrawProfiled() || PerformanceTrace::Configured();
return GpuTimingEnabled() || DrawProfiled() || PerformanceTrace::Configured() || batchStampsRequested.load(std::memory_order_relaxed);
}
void Recorder::CountBarriers(CommandClass which, std::uint32_t count) {
@@ -2004,6 +2009,20 @@ std::uint64_t Recorder::Presents() {
return presentSerial.load(std::memory_order_relaxed);
}
void Recorder::NoteFrameGpuBusy(double milliseconds) {
settledFrameBusyNs.fetch_add(static_cast<std::uint64_t>(milliseconds * 1e6), std::memory_order_relaxed);
settledFrames.fetch_add(1, std::memory_order_release);
}
std::pair<std::uint64_t, double> Recorder::FrameGpuBusy() {
const auto frames = settledFrames.load(std::memory_order_acquire);
return {frames, static_cast<double>(settledFrameBusyNs.load(std::memory_order_relaxed)) / 1e6};
}
void Recorder::RequestBatchStamps() {
batchStampsRequested.store(true, std::memory_order_relaxed);
}
void Recorder::AddGpuTiming(CommandClass which, double nanoseconds, std::uint64_t bytes) {
std::lock_guard lock(timingMutex);
auto& totals = timingByKey[ClassKey(which)];
@@ -398,6 +398,7 @@ std::list<CachedTexture>::iterator findSampledEntry(TextureCache& cache, VkDevic
struct TextureCounters {
std::atomic<std::uint64_t> fromStorage{0};
std::atomic<std::uint64_t> snapshots{0};
std::atomic<std::uint64_t> cleared{0};
std::atomic<std::uint64_t> pendingReads{0};
std::atomic<std::uint64_t> storageFallbacks{0};
std::atomic<std::uint64_t> records{0};
@@ -435,7 +436,7 @@ void reportTextureCounters() {
auto last = counters.lastReport.load();
if (nowMs - last < 10000 || !counters.lastReport.compare_exchange_strong(last, nowMs)) return;
const auto count = [](const std::atomic<std::uint64_t>& value) { return static_cast<unsigned long long>(value.load(std::memory_order_relaxed)); };
AgcDriver::ProfilePrint_nid_no_patch("[textures] sampled created: %llu from storage images, %llu snapshots (%llu snapshot reads over recorded writes inside cachedTexture, %llu storage-path fallbacks); stage-A records %llu: %llu fast hits, %llu full lookups; storage images %llu hits, %llu created, %llu own-object refreshes; image tables %llu reused, %llu resolved; untracked snapshots %llu kept by the write-watch hint, %llu compared\n", count(counters.fromStorage), count(counters.snapshots), count(counters.pendingReads), count(counters.storageFallbacks), count(counters.records), count(counters.fastHits), count(counters.fastMisses), count(counters.storageHits), count(counters.storageCreated), count(counters.ownRefreshes), count(counters.tablesReused), count(counters.tablesResolved), count(counters.untrackedKept), count(counters.untrackedCompared));
AgcDriver::ProfilePrint_nid_no_patch("[textures] sampled created: %llu from storage images, %llu cleared on the GPU, %llu snapshots (%llu snapshot reads over recorded writes inside cachedTexture, %llu storage-path fallbacks); stage-A records %llu: %llu fast hits, %llu full lookups; storage images %llu hits, %llu created, %llu own-object refreshes; image tables %llu reused, %llu resolved; untracked snapshots %llu kept by the write-watch hint, %llu compared\n", count(counters.fromStorage), count(counters.cleared), count(counters.snapshots), count(counters.pendingReads), count(counters.storageFallbacks), count(counters.records), count(counters.fastHits), count(counters.fastMisses), count(counters.storageHits), count(counters.storageCreated), count(counters.ownRefreshes), count(counters.tablesReused), count(counters.tablesResolved), count(counters.untrackedKept), count(counters.untrackedCompared));
}
// What the sampled-texture lookups on this thread proved their returned objects current against,
@@ -532,6 +533,18 @@ bool ClearedViewEnabled() {
return !disabled;
}
std::optional<VkClearColorValue> SampledClearColor(const Context& context, const GuestTextureResource& resource, DccKeys keys, bool depthCompare) {
if (depthCompare || !IsDccClear(keys) || keys == DccKeys::ClearRegister) return std::nullopt;
if (IsBlockCompressed(resource.format) || IsRepackedTextureFormat(resource.format) || SrgbExpandsToRgba8(context, resource.format)) return std::nullopt;
const auto format = SampledTextureFormat(context, resource.format);
if (format != DccClearTextureFormat(resource.format)) return std::nullopt;
std::array<std::uint32_t, 4> pattern{};
if (!FillDccClear(format, keys, resource.dccAlphaOnMsb, std::as_writable_bytes(std::span(pattern)))) return std::nullopt;
VkClearColorValue clear{};
if (!ClearColorForTexel(format, BytesPerElement(resource.format), pattern, clear)) return std::nullopt;
return clear;
}
std::uint64_t DepthCopyMinBytes() {
static const std::uint64_t bytes = [] {
const char* text = std::getenv("APS5_DEPTH_COPY_MIN_KIB");
@@ -919,6 +932,7 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
return texture;
}
if (source != nullptr) return std::make_shared<Texture>(context, source, resource, components);
if (const auto clear = SampledClearColor(context, resource, *keys, depthCompare)) return std::make_shared<Texture>(context, *context.detiler, resource, components, std::span<const std::byte>{}, false, &*clear);
std::vector<std::byte> snapshot(bytes);
ReadTextureSurface(resource, *keys, snapshot);
return std::make_shared<Texture>(context, *context.detiler, resource, components, snapshot, depthCompare);
@@ -990,9 +1004,11 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
eraseTexture(cache, it);
++cache.counts.stale;
}
if (source != nullptr) parkSnapshot(cache, recycled);
const auto clear = source == nullptr ? SampledClearColor(context, resource, *keys, depthCompare) : std::nullopt;
const bool noSnapshot = source != nullptr || clear.has_value();
if (noSnapshot) parkSnapshot(cache, recycled);
else if (recycled.empty()) recycled = spareSnapshot(cache, bytes);
CachedTexture entry{key, address, source != nullptr ? std::vector<std::byte>{} : (recycled.empty() ? std::vector<std::byte>(bytes) : std::move(recycled)), nullptr, *keys, snapshotGeneration};
CachedTexture entry{key, address, noSnapshot ? std::vector<std::byte>{} : (recycled.empty() ? std::vector<std::byte>(bytes) : std::move(recycled)), nullptr, *keys, snapshotGeneration};
entry.committed = std::move(committed);
entry.surfaceBytes = bytes;
const auto createTexture = [&](auto&&... args) {
@@ -1019,6 +1035,10 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
}
entry.accounted = source->Cached() ? 0u : heldBytes(*source);
counters.fromStorage.fetch_add(1, std::memory_order_relaxed);
} else if (clear) {
entry.texture = createTexture(*context.detiler, resource, components, std::span<const std::byte>{}, false, &*clear);
entry.accounted = entry.texture->AllocationBytes();
counters.cleared.fetch_add(1, std::memory_order_relaxed);
} else {
// Snapshot before the upload so a write racing with it is caught by the next comparison.
const bool pending = *keys == DccKeys::Uncompressed && Recorder::SnapshotWriteOverlaps(address, bytes);
@@ -1670,7 +1690,7 @@ bool UsesAddressTables(const CompiledShader& compute) {
}
ShaderResources::ShaderResources(const Context& context, const CompiledShader& compute, std::span<const GuestMemorySnapshot> snapshots, bool deferred, std::uint64_t dispatchThreads) : context(context), guestMemory(context), dispatchThreads(dispatchThreads), deferredCompute(compute), deferredSnapshots(snapshots) {
ShaderResources::ShaderResources(const Context& context, const CompiledShader& compute, std::span<const GuestMemorySnapshot> snapshots, bool deferred, std::uint64_t dispatchThreads, std::optional<std::pair<std::uint64_t, std::size_t>> htileClear) : context(context), guestMemory(context), dispatchThreads(dispatchThreads), htileClear(htileClear), deferredCompute(compute), deferredSnapshots(snapshots) {
Require(compute.stage == ShaderRecompiler::ShaderStage::Compute, "compute resources require a compute shader");
// Every use of a compute build is a recorded dispatch that calls MarkGpuWrites, which staged
// buffers need (a synchronous draw's use would not).
@@ -2448,7 +2468,7 @@ void ShaderResources::buildComplete() {
resolveImageTables();
timing.bindingsMs += phase(BuildPhase::Images);
requireGuestBuffers();
guestMemory.UploadFinish(usesBda);
guestMemory.UploadFinish(usesBda, htileClear);
timing.uploadMs += phase(BuildPhase::Upload);
if (usesBda) bda = std::make_unique<BdaResources>(context, guestMemory);
else if (usesFaultBuffer) bda = std::make_unique<BdaResources>(context);
@@ -2652,7 +2672,7 @@ void ShaderResources::noteReusable() {
captureValidation();
reusable = false;
directRegions.clear();
if (guestMemory.HasCopiedWrites() || HoldsLease() || usesBda || !multisampleSources.empty() || (bda != nullptr && LoopGuardEnabled())) return;
if (htileClear.has_value() || guestMemory.HasCopiedWrites() || HoldsLease() || usesBda || !multisampleSources.empty() || (bda != nullptr && LoopGuardEnabled())) return;
if (std::any_of(textures.begin(), textures.end(), [](const std::shared_ptr<Texture>& texture) { return texture != nullptr && texture->RefreshedPerUse(); })) return;
if (TemplateDataRefresh() && std::any_of(allocations.begin(), allocations.end(), [](const Allocation& allocation) { return allocation.buffer != nullptr && !allocation.guest && allocation.role != ShaderRecompiler::DescriptorRole::ImageTableMap && allocation.size > MaxRefreshBytes; })) return;
const auto regions = guestMemory.DirectRegions();
@@ -3819,6 +3839,7 @@ bool ShaderResources::revalidateFull(std::span<const CompiledShader> shaders, Pr
// (3) Buffers staged in device memory (GuestBufferMemory::AllowDeviceStaging) are copied in
// from their imports anew for this use, after the flushes above and before the work is
// recorded; a failure to record leaves the object unusable for this dispatch, not the batch.
if (!guestMemory.StagingDecisionsCurrent()) return finish(fast, false, ProofFailure::Changed);
if (auto* recorder = Recorder::Active(); recorder != nullptr) {
try {
guestMemory.RecordStagingCopies(*recorder);
@@ -5436,6 +5457,10 @@ std::uint64_t SampledLookupKeyScans() {
return sampledLookupKeyScans;
}
std::uint64_t SampledTexturesCleared() {
return TextureCounts().cleared.load(std::memory_order_relaxed);
}
ImageTableReuseCounts ImageTableReuses() {
const auto& counters = TextureCounts();
return {counters.tablesReused.load(std::memory_order_relaxed) + counters.tablesReplayed.load(std::memory_order_relaxed), counters.tablesResolved.load(std::memory_order_relaxed), counters.tablesReplayed.load(std::memory_order_relaxed), counters.tableWritesKept.load(std::memory_order_relaxed), counters.tableWritesMade.load(std::memory_order_relaxed)};
@@ -5910,7 +5935,8 @@ void ShaderResources::requireGuestBuffers(std::span<const MovedBuffer> moved) co
const auto override = std::find_if(moved.begin(), moved.end(), [&](const MovedBuffer& entry) { return entry.allocation == index; });
const auto address = override != moved.end() ? override->address : item.address;
const auto bytes = override != moved.end() ? override->size : item.size;
RequireNoMultisampleTarget(address, bytes, "a shader buffer access");
RequireNoResidentDepthRead_nid_no_patch(address, bytes, htileClear);
RequireNoMultisampleColorTarget(address, bytes, "a shader buffer access");
}
}
@@ -121,7 +121,7 @@ struct Module {
return;
}
// One gl_ClipDistance array holds the guest clip distances and the recompiler's all-zero position distance.
if (!input && ((value == spv::BuiltInClipDistance && (vertex || stage == Stage::TessellationControl || stage == Stage::TessellationEvaluation || stage == Stage::Mesh)) || (value == spv::BuiltInCullDistance && (vertex || stage == Stage::Mesh)))) {
if (!input && ((value == spv::BuiltInClipDistance && (vertex || stage == Stage::TessellationControl || stage == Stage::TessellationEvaluation || stage == Stage::Mesh)) || (value == spv::BuiltInCullDistance && (vertex || stage == Stage::TessellationEvaluation || stage == Stage::Mesh)))) {
const auto signature = Signature(type);
auto& count = value == spv::BuiltInClipDistance ? clipDistances : cullDistances;
Require(count == 0 && signature.size() == 6 && signature.starts_with("f32[") && signature[4] >= '1' && signature[4] <= '8' && signature[5] == ']', "invalid or duplicate clip or cull distance output");
@@ -273,7 +273,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
if ((vertex || control || evaluation || mesh) && (capability == spv::CapabilityClipDistance || capability == spv::CapabilityCullDistance)) {
char word[16];
std::snprintf(word, sizeof(word), "0x%08x", state.paClVsOutCntl);
Require(capability == spv::CapabilityClipDistance ? shaderClipDistance : (vertex || mesh) && shaderCullDistance, std::string("vertex clip or cull distance needs shaderClipDistance or shaderCullDistance, which the device lacks (PA_CL_VS_OUT_CNTL=") + word + ")");
Require(capability == spv::CapabilityClipDistance ? shaderClipDistance : (vertex || evaluation || mesh) && shaderCullDistance, std::string("vertex clip or cull distance needs shaderClipDistance or shaderCullDistance, which the device lacks (PA_CL_VS_OUT_CNTL=") + word + ")");
}
// Enabled unconditionally or by the device setup in VulkanDevice.
@@ -289,7 +289,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
(fragment && sampleRateShading && capability == spv::CapabilityInterpolationFunction) ||
capability == spv::CapabilityImageMSArray ||
capability == spv::CapabilityStorageImageMultisample ||
((vertex || mesh) && shaderCullDistance && capability == spv::CapabilityCullDistance) ||
((vertex || evaluation || mesh) && shaderCullDistance && capability == spv::CapabilityCullDistance) ||
capability == spv::CapabilityStorageImageWriteWithoutFormat ||
capability == spv::CapabilityStorageImageReadWithoutFormat ||
capability == spv::CapabilityInt64 ||
@@ -337,7 +337,7 @@ std::uint64_t SampledTextureMemory() {
return SampledMemoryCounter().load(std::memory_order_relaxed);
}
Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span<const std::byte> snapshot, bool depthCompare) : context(context) {
Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span<const std::byte> snapshot, bool depthCompare, const VkClearColorValue* clear) : context(context) {
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
PhaseTimer timer;
@@ -363,7 +363,8 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
#endif
const auto guestBytes = geometry.guestBytes;
Require(snapshot.size() == guestBytes, "texture snapshot size mismatch");
Require(clear != nullptr ? snapshot.empty() : snapshot.size() == guestBytes, "texture snapshot size mismatch");
Require(clear == nullptr || (!depthCompare && !srgbExpand && !IsRepackedTextureFormat(descriptor.format) && !IsBlockCompressed(descriptor.format)), "a cleared texture needs a plain color format");
const auto sliceLinearBytes = geometry.sliceLinearBytes;
Require(arrayLayers == 0 || sliceLinearBytes <= UINT64_MAX / arrayLayers, "detiled texture buffer size overflows");
@@ -405,7 +406,39 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex
allocationBytes = expanded.bytes;
}
{
if (clear != nullptr) {
static const bool syncUploads = std::getenv("APS5_SYNC_TEXTURE_UPLOAD") != nullptr;
auto* recorder = syncUploads ? nullptr : Recorder::Active();
std::unique_ptr<CommandBatch> batch;
VkCommandBuffer commands = VK_NULL_HANDLE;
if (recorder != nullptr) {
commands = recorder->Commands();
recorder->Keep(owned);
} else {
batch = std::make_unique<CommandBatch>(context);
commands = batch->Handle();
}
const VkImageSubresourceRange range{aspect, 0, descriptor.mipCount, 0, geometry.imageLayers};
VkImageMemoryBarrier toTransferDst{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toTransferDst.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
toTransferDst.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
toTransferDst.image = image;
toTransferDst.subresourceRange = range;
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toTransferDst);
context.Function<PFN_vkCmdClearColorImage>("vkCmdClearColorImage")(commands, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, clear, 1, &range);
VkImageMemoryBarrier toShaderRead = toTransferDst;
toShaderRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toShaderRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
toShaderRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
toShaderRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &toShaderRead);
if (batch) batch->SubmitAndWait();
else ++Profile().recordedUploads;
if (profile) Profile().gpu += timer.lap();
} else {
// Debug aid: APS5_DUMP_TEXTURE=<hex addresses, comma separated> saves the detiled first mip
// of those sampled textures on their first 8 uploads as texture_<address>_<n>.raw (u32
// width, height, VkFormat, then tightly packed rows).
@@ -3511,8 +3544,9 @@ std::uint64_t StorageTexture::writeBackWindows(std::span<const ImportSegment> se
const auto parts = std::span<const AddedPiece>(added).subspan(first, last - first);
const auto sliceBegin = window.tiledBegin - window.window.rangeBegin;
const auto intoSlabs = std::all_of(parts.begin(), parts.end(), [&](const AddedPiece& part) {
if (!part.slab || part.seeded) return false;
const auto& copy = slabPieces[part.pieces].copies[part.copy];
return part.slab && !part.seeded && copy.dstOffset % 4 == 0 && copy.size % 4 == 0 && (part.begin - sliceBegin) % blockBytes == 0 && (part.end - sliceBegin) % blockBytes == 0;
return copy.dstOffset % 4 == 0 && copy.size % 4 == 0 && (part.begin - sliceBegin) % blockBytes == 0 && (part.end - sliceBegin) % blockBytes == 0;
});
if (intoSlabs) slabRetiled.push_back({i, first, last});
}
@@ -4643,6 +4677,25 @@ void StorageTexture::FlushAllPending(const char* reason) {
static_cast<void>(FlushPending(0, std::numeric_limits<std::size_t>::max(), nullptr, reason));
}
bool StorageTexture::FlushAllPendingUntil(const char* reason, const std::function<bool()>& stop) {
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));
}
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));
}
}
return true;
}
std::size_t StorageTexture::StoreSmallPending(const char* reason) {
std::vector<std::shared_ptr<StorageTexture>> small;
{
@@ -11,6 +11,7 @@ constexpr int DEBUG_UNAVAILABLE = static_cast<int>(0x8A6C1000);
constexpr int DRIVER_FEATURE_UNAVAILABLE = static_cast<int>(0x8A6D0001);
constexpr int DRIVER_NULL_OUTPUT = static_cast<int>(0x8A6D0003);
constexpr int GPU_RESET_UNAVAILABLE = static_cast<int>(0x8A6C0045);
constexpr int MODULE_REGISTRATION_FAILED = static_cast<int>(0x8A6D0003);
constexpr std::size_t TOOL_MEMORY_RANGE_SIZE = 12;
}
@@ -193,6 +194,34 @@ int APS5_VABI sceAgcDriverSetTargetRingForDiag() {
return DRIVER_FEATURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverAcquireRazorACQ() {
return DRIVER_FEATURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverGetPaDebugInterfaceVersion() {
return DRIVER_FEATURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverSetHsOffchipParamDirect() {
return DRIVER_FEATURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverSubmitMultiCommandBuffersDirect() {
return DRIVER_FEATURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverSubmitToRazorACQ() {
return DRIVER_FEATURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverSuspendPointSubmitDirect() {
return DRIVER_FEATURE_UNAVAILABLE;
}
int APS5_VABI sceAgcDriverModuleRegistration() {
return MODULE_REGISTRATION_FAILED;
}
int APS5_VABI sceAgcDriverSetValueToSharedGds() {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -0,0 +1,133 @@
#include "prx/libSceAgcDriver/Graphics/include/AdaptiveStaging.hpp"
#include <cstdint>
#include <functional>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::AdaptiveStaging;
void Require(bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
constexpr std::uint64_t Begin = 0x200000000;
constexpr std::uint64_t End = Begin + 0xca80000;
constexpr std::int64_t FrameMs = 100;
struct Title {
AdaptiveStaging policy;
std::vector<std::string> lines;
std::uint64_t frames = 0;
double busyMs = 0;
std::int64_t nowMs = 1;
Title() {
policy.report = [this](const std::string& line) { lines.push_back(line); };
}
std::uint64_t Run(std::uint64_t count, const std::function<double(bool, std::int64_t)>& cost) {
std::uint64_t inPlace = 0;
for (std::uint64_t frame = 0; frame < count; ++frame) {
const bool kept = policy.InPlace(Begin, End, true, frames, busyMs, nowMs);
inPlace += kept ? 1 : 0;
busyMs += cost(kept, nowMs);
++frames;
nowMs += FrameMs;
}
return inPlace;
}
bool InPlaceNow() {
return policy.InPlace(Begin, End, false, frames, busyMs, nowMs);
}
std::uint64_t Count(const std::string& text) const {
std::uint64_t count = 0;
for (const auto& line : lines) count += line.find(text) != std::string::npos ? 1 : 0;
return count;
}
};
void CheckFasterInPlaceIsKept() {
Title title;
title.Run(200, [](bool inPlace, std::int64_t) { return inPlace ? 73.0 : 91.0; });
Require(title.InPlaceNow(), "an element 18 ms faster per frame in place is still staged");
Require(title.Count("-> in place") >= 1, "no decision line chose in place");
}
void CheckFasterStagedStaysStaged() {
Title title;
title.Run(200, [](bool inPlace, std::int64_t) { return inPlace ? 114.0 : 84.0; });
Require(!title.InPlaceNow(), "an element 30 ms faster per frame staged was moved in place");
Require(title.Count("-> staged") >= 1, "no decision line kept it staged");
}
void CheckNoiseKeepsTheMode() {
Title title;
std::uint64_t frame = 0;
const auto inPlace = title.Run(3000, [&](bool, std::int64_t) { return 80.0 + static_cast<double>((frame++ * 7) % 11) - 5.0; });
Require(title.Count("-> in place") == 0, "frame noise alone moved the element in place");
Require(inPlace < 3000 / 4, "frame noise kept the element in place for " + std::to_string(inPlace) + " of 3000 frames");
}
void CheckBackOffGrowsToTheCap() {
Title title;
title.Run(6000, [](bool inPlace, std::int64_t) { return inPlace ? 114.0 : 84.0; });
Require(title.Count("next probe in 40 s") >= 1, "the second repeated answer did not wait 40 s");
Require(title.Count("next probe in 160 s") >= 1, "the back-off never reached 160 s");
Require(title.Count("next probe in 320 s") == 0, "the back-off grew past 160 s");
}
void CheckSceneChangeProbesAgain() {
Title title;
const std::int64_t changeMs = 60000;
const auto cost = [&](bool inPlace, std::int64_t nowMs) {
if (nowMs < changeMs) return inPlace ? 73.0 : 91.0;
return inPlace ? 300.0 : 200.0;
};
title.Run(600, cost);
Require(title.InPlaceNow(), "the first scene did not keep the element in place");
title.Run(400, cost);
Require(title.Count("probing again") >= 1, "a scene change from 73 to about 300 ms per frame did not probe again");
Require(!title.InPlaceNow(), "after the scene change the element is still in place although staged is faster");
}
void CheckSmallElementIsNotProbed() {
AdaptiveStaging policy;
std::uint64_t frames = 0;
double busyMs = 0;
std::int64_t nowMs = 1;
constexpr std::uint64_t SmallBegin = 0x100000000;
constexpr std::uint64_t SmallEnd = SmallBegin + 0x10000;
bool smallInPlace = false;
for (int frame = 0; frame < 2000; ++frame) {
const bool bigInPlace = policy.InPlace(Begin, End, true, frames, busyMs, nowMs);
smallInPlace = smallInPlace || policy.InPlace(SmallBegin, SmallEnd, true, frames, busyMs, nowMs);
busyMs += bigInPlace ? 73.0 : 91.0;
++frames;
nowMs += FrameMs;
}
Require(!smallInPlace, "a 64 KiB element next to a 202 MiB one was probed in place");
}
}
int main() {
try {
CheckFasterInPlaceIsKept();
CheckFasterStagedStaysStaged();
CheckNoiseKeepsTheMode();
CheckBackOffGrowsToTheCap();
CheckSceneChangeProbesAgain();
CheckSmallElementIsNotProbed();
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
std::cout << "adaptive staging tests passed\n";
return 0;
}
@@ -1,4 +1,5 @@
#include "prx/libSceAgcDriver/Execution/include/AsyncLog.hpp"
#include "tests/TemporaryFile.hpp"
#include <atomic>
#include <chrono>
#include <csignal>
@@ -122,7 +123,7 @@ std::string LargeLine(std::size_t index, std::size_t bytes) {
}
void OrderPerThread() {
std::FILE* file = std::tmpfile();
std::FILE* file = TemporaryFile::Open();
Check(file != nullptr, "temporary file");
auto& log = AgcDriver::AsyncLog::Get();
std::string expected;
@@ -139,7 +140,7 @@ void OrderPerThread() {
}
void LongLine() {
std::FILE* file = std::tmpfile();
std::FILE* file = TemporaryFile::Open();
Check(file != nullptr, "temporary file");
const std::string longText(3000, 'x');
AgcDriver::AsyncLog::Get().Defer(file, "%s|%d\n", AgcDriver::LogLiteral{longText.c_str()}, 7);
@@ -158,7 +159,7 @@ void Coalescing() {
}
void FlushWaitsForHeldStream() {
std::FILE* file = std::tmpfile();
std::FILE* file = TemporaryFile::Open();
Check(file != nullptr, "temporary file");
auto& log = AgcDriver::AsyncLog::Get();
{
@@ -178,7 +179,7 @@ void FlushWaitsForHeldStream() {
}
void BoundedFlushGivesUp() {
std::FILE* file = std::tmpfile();
std::FILE* file = TemporaryFile::Open();
Check(file != nullptr, "temporary file");
auto& log = AgcDriver::AsyncLog::Get();
Check(log.FlushFor(0ms), "FlushFor waited with nothing queued");
@@ -194,7 +195,7 @@ void BoundedFlushGivesUp() {
}
void FullQueueBlocksCaller() {
std::FILE* file = std::tmpfile();
std::FILE* file = TemporaryFile::Open();
Check(file != nullptr, "temporary file");
auto& log = AgcDriver::AsyncLog::Get();
constexpr std::size_t lineBytes = 1024 * 1024;
@@ -1,3 +1,4 @@
#include "ControlFlow/ControlFlowHelpers.hpp"
#include "ControlFlow/GraphBuilder.hpp"
#include "ControlFlow/RequestSerializer.hpp"
#include "ControlFlow/Structurizer.hpp"
@@ -174,6 +175,17 @@ ControlFlowGraph makeGraph(const std::vector<std::vector<std::uint32_t>>& succes
return graph;
}
void verifyPredecessorRebuild() {
auto graph = makeGraph({{2, 1, 2}, {2, 2}, {2}, {}});
for (auto& block : graph.blocks) block.predecessors = {3u, 3u};
const std::vector<std::vector<std::uint32_t>> expected{{}, {0u}, {0u, 1u, 2u}, {}};
for (unsigned repeat = 0; repeat < 2u; ++repeat) {
rebuildPredecessors(graph);
require(graph.blocks[0].successors == std::vector<std::uint32_t>{1u, 2u} && graph.blocks[1].successors == std::vector<std::uint32_t>{2u}, "predecessor rebuild lost successor normalization");
for (std::size_t index = 0; index < expected.size(); ++index) require(graph.blocks[index].predecessors == expected[index], "predecessor rebuild changed the incoming edges");
}
}
void verifyLoopMergeDoesNotEnterNestedSelection() {
auto graph = makeGraph({{1, 5}, {2, 3}, {5}, {5, 4}, {1}, {}});
Structurizer{}.Structurize(graph);
@@ -1445,6 +1457,7 @@ done:
}
}
try {
verifyPredecessorRebuild();
verifyNullSwappc();
verifyScheduledJumpTable();
VerifyUserDataTail();
@@ -1,4 +1,5 @@
#include "prx/libSceAgcDriver/Execution/include/FrameDumpIo.hpp"
#include "tests/TemporaryFile.hpp"
#include <array>
#include <chrono>
#include <csignal>
@@ -54,7 +55,7 @@ std::FILE* Pipe(int& reader) {
}
void SuccessfulBytes() {
auto* file = std::tmpfile();
auto* file = TemporaryFile::Open();
Check(file != nullptr, "cannot create BMP test file");
const std::array<std::byte, 16> pixels{std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}};
Check(!AgcDriver::WriteBmpStream_nid_no_patch(file, 2, 2, pixels), "valid BMP write failed");
@@ -2785,6 +2785,48 @@ void multisampledSingleClearTests() {
Require(!match(std::span(kernel).first(21), userData, group), "a truncated kernel was taken for RADV's comp-to-single clear");
}
void htileBufferMaskClearTests() {
static constexpr std::array<std::uint32_t, 16> kernel{0xd7460004u, 0x04010c0cu, 0xe00c2000u, 0x80000004u, 0xbf8c3f70u, 0xd7710000u, 0x00120008u, 0xd7710001u, 0x00160209u, 0xd7710002u, 0x001a040au, 0xd7710003u, 0x001e060bu, 0xe01c2000u, 0x80000004u, 0xbf810000u};
ShaderRecompiler::ShaderComputeStageInfo compute{};
compute.numThreads = {64u, 1u, 1u};
compute.groupIdEnable = {true, false, false};
compute.threadIdComponentCount = 1u;
const std::array<std::uint32_t, 12> userData{0x8e350000u, 0x00100050u, 0x00004000u, 0x0004bfacu, 0u, 0u, 0u, 0u, 0x3f0u, 0x3f0u, 0x3f0u, 0x3f0u};
const std::array<std::uint32_t, 5> packet{0u, 0x4000u, 1u, 1u, 0x61u};
const auto match = [&](std::span<const std::uint32_t> code, std::span<const std::uint32_t> words, const ShaderRecompiler::ShaderComputeStageInfo& info, std::span<const std::uint32_t> dispatch, std::uint64_t indirect) {
return AgcDriver::Graphics::MatchHtileBufferMaskClear(code, words, info, dispatch, indirect);
};
const auto found = match(kernel, userData, compute, packet, 0);
Require(found.has_value() && found->address == 0x508e350000ull && found->bytes == 0x40000u && found->mask == 0x3f0u && found->value == 0u, "the buffer variant of the HTILE mask kernel was not recognized with its range, mask and value");
auto groups = packet;
groups[4] = 0x41u;
groups[1] = 0x100u;
const auto byGroups = match(kernel, userData, compute, groups, 0);
Require(byGroups.has_value() && byGroups->bytes == 0x40000u, "a group-count dispatch of the buffer HTILE kernel did not cover 0x100 groups of 64 records");
groups[1] = 0x80u;
Require(match(kernel, userData, compute, groups, 0)->bytes == 0x20000u, "fewer threads than records did not limit the buffer HTILE kernel's range");
auto other = kernel;
other[6] ^= 1u;
Require(!match(other, userData, compute, packet, 0).has_value(), "another kernel was taken for the buffer HTILE mask kernel");
Require(!match(std::span(kernel).first(15), userData, compute, packet, 0).has_value(), "a truncated kernel was taken for the buffer HTILE mask kernel");
auto mixed = userData;
mixed[10] = 0x3ffu;
Require(!match(kernel, mixed, compute, packet, 0).has_value(), "a buffer HTILE kernel with per-word masks was recognized");
auto format = userData;
format[3] = 0x0004dfacu;
Require(!match(kernel, format, compute, packet, 0).has_value(), "a buffer HTILE kernel through a float descriptor was recognized");
auto stride = userData;
stride[1] = 0x00080050u;
Require(!match(kernel, stride, compute, packet, 0).has_value(), "a buffer HTILE kernel with an 8-byte stride was recognized");
auto wave32 = compute;
wave32.numThreads = {32u, 1u, 1u};
Require(!match(kernel, userData, wave32, packet, 0).has_value(), "the buffer HTILE kernel matched with another group size");
Require(!match(kernel, userData, compute, packet, 0x1000u).has_value(), "an indirect buffer HTILE kernel dispatch was recognized");
auto partial = packet;
partial[4] = 0x63u;
Require(!match(kernel, userData, compute, partial, 0).has_value(), "a partial-group buffer HTILE kernel dispatch was recognized");
}
void htileDepthClearTests() {
static constexpr std::array<std::uint32_t, 17> kernel{0xd7460000u, 0x04010c06u, 0x34000084u, 0xdc388000u, 0x04020000u, 0xbf8c3f70u, 0xd7710008u, 0x00120805u, 0xd7710009u, 0x00120a05u, 0xd771000au, 0x00120c05u, 0xd771000bu, 0x00120e05u, 0xdc788000u, 0x00020800u, 0xbf810000u};
const std::array<std::uint32_t, 3> wave{64, 1, 1};
@@ -6362,7 +6404,15 @@ void descriptorPoolResetFailureTest() {
const auto previous = _ZSt13set_terminatePFvvE_nid_postfix([] {
std::_Exit(mock.descriptorResetResult == VK_SUCCESS ? 0 : 1);
});
#ifdef __APPLE__
const auto previousHost = std::set_terminate([] {
std::_Exit(mock.descriptorResetResult == VK_SUCCESS ? 0 : 1);
});
#endif
cache.Free(allocation);
#ifdef __APPLE__
std::set_terminate(previousHost);
#endif
_ZSt13set_terminatePFvvE_nid_postfix(previous);
}
@@ -7631,6 +7681,7 @@ struct ModuleShape {
std::uint32_t floatControlsWidth = 0;
std::uint32_t clipDistanceLength = 0;
std::uint32_t cullDistanceLength = 0;
std::optional<spv::ExecutionModel> tessellationModel;
bool layerOutput = false;
bool terminateInvocation = false;
bool omitTerminateExtension = false;
@@ -7836,6 +7887,7 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
if (shape.layer) emit(words, spv::OpCapability, {spv::CapabilityGeometry});
if (shape.clipDistanceLength != 0) emit(words, spv::OpCapability, {spv::CapabilityClipDistance});
if (shape.cullDistanceLength != 0) emit(words, spv::OpCapability, {spv::CapabilityCullDistance});
if (shape.tessellationModel) emit(words, spv::OpCapability, {spv::CapabilityTessellation});
if (shape.layerOutput) {
emit(words, spv::OpCapability, {spv::CapabilityShaderViewportIndexLayerEXT});
const std::string extension = "SPV_EXT_shader_viewport_index_layer";
@@ -7865,10 +7917,14 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
emit(words, spv::OpMemoryModel, {spv::AddressingModelLogical, spv::MemoryModelGLSL450});
const auto entryPointOffset = words.size();
if (shape.vertexInput) emit(words, spv::OpEntryPoint, {spv::ExecutionModelVertex, main, 0x6e69616du, 0, output, input});
else emit(words, spv::OpEntryPoint, {shape.fragment ? spv::ExecutionModelFragment : spv::ExecutionModelVertex, main, 0x6e69616du, 0, output});
else emit(words, spv::OpEntryPoint, {shape.tessellationModel ? *shape.tessellationModel : shape.fragment ? spv::ExecutionModelFragment : spv::ExecutionModelVertex, main, 0x6e69616du, 0, output});
words[entryPointOffset] += static_cast<std::uint32_t>(extraInterface.size()) << 16u;
words.insert(words.end(), extraInterface.begin(), extraInterface.end());
if (shape.fragmentMode) emit(words, spv::OpExecutionMode, {main, *shape.fragmentMode});
if (shape.tessellationModel == spv::ExecutionModelTessellationControl) emit(words, spv::OpExecutionMode, {main, spv::ExecutionModeOutputVertices, 3});
if (shape.tessellationModel == spv::ExecutionModelTessellationEvaluation) {
for (const auto mode : {spv::ExecutionModeQuads, spv::ExecutionModeSpacingFractionalOdd, spv::ExecutionModeVertexOrderCw}) emit(words, spv::OpExecutionMode, {main, mode});
}
if (shape.fragment) emit(words, spv::OpExecutionMode, {main, spv::ExecutionModeOriginUpperLeft});
if (shape.fragDepth) emit(words, spv::OpExecutionMode, {main, spv::ExecutionModeDepthReplacing});
if (shape.floatControlsWidth != 0) {
@@ -8806,6 +8862,23 @@ void barycentricEmulationTests() {
const std::array<CompiledShader, 4> flatPatchShaders{{{ShaderStage::Vertex, &vertex, 0}, {ShaderStage::TessellationControl, &patches.control, 0}, {ShaderStage::TessellationEvaluation, &patches.evaluation, 0}, {ShaderStage::Fragment, &flatVertices, 0}}};
expectFailure([&] { ValidateProvokingVertex(context, lastState, flatPatchShaders); }, "explicit per-vertex flat interpolation");
}
{
const auto context = mockContext();
auto namedVertex = vertex;
namedVertex.variantId = 11u;
auto namedFragment = fragment;
namedFragment.variantId = 12u;
const std::array<CompiledShader, 4> namedPatchShaders{{{ShaderStage::Vertex, &namedVertex, 0}, {ShaderStage::TessellationControl, &patches.control, 0}, {ShaderStage::TessellationEvaluation, &patches.evaluation, 0}, {ShaderStage::Fragment, &namedFragment, 0}}};
auto lastState = state;
lastState.provokingVertexMode = VK_PROVOKING_VERTEX_MODE_LAST_VERTEX_EXT;
std::vector<std::uint64_t> firstKey;
std::vector<std::uint64_t> lastKey;
Require(ValidationKey(context, namedPatchShaders, state, firstKey) && ValidationKey(context, namedPatchShaders, lastState, lastKey), "a draw through generated barycentric tessellation stages skipped the shader validation memo");
Require(firstKey != lastKey, "the shader validation memo did not tell barycentric patches built for different provoking vertices apart");
const std::array<CompiledShader, 4> unnamedPatchShaders{{{ShaderStage::Vertex, &vertex, 0}, {ShaderStage::TessellationControl, &patches.control, 0}, {ShaderStage::TessellationEvaluation, &patches.evaluation, 0}, {ShaderStage::Fragment, &namedFragment, 0}}};
std::vector<std::uint64_t> unnamedKey;
Require(!ValidationKey(context, unnamedPatchShaders, state, unnamedKey), "a vertex stage without a variant id entered the shader validation memo");
}
RecompileResult clipped;
clipped.spirv = makeModule({.parameterOutput = true, .clipDistanceLength = 2});
@@ -11244,6 +11317,16 @@ void validationTests() {
vertex.spirv = makeModule({.parameterOutput = true, .cullDistanceLength = 4});
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, true, false); }, "PA_CL_VS_OUT_CNTL=0x0040000f");
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, false, true);
ShaderRecompiler::RecompileResult tessVertex, tessControl, tessEvaluation;
tessVertex.spirv = makeModule({});
tessControl.spirv = makeModule({.push = true, .tessellationModel = spv::ExecutionModelTessellationControl});
tessEvaluation.spirv = makeModule({.cullDistanceLength = 4, .tessellationModel = spv::ExecutionModelTessellationEvaluation});
const std::array<AgcDriver::Graphics::CompiledShader, 4> tessShaders{{{ShaderRecompiler::ShaderStage::Local, &tessVertex, 0}, {ShaderRecompiler::ShaderStage::TessellationControl, &tessControl, 0}, {ShaderRecompiler::ShaderStage::TessellationEvaluation, &tessEvaluation, 0}, {ShaderRecompiler::ShaderStage::Fragment, &pixel, 0}}};
auto tessState = state;
tessState.stages.path = AgcDriver::Graphics::ShaderPath::Tessellation;
tessState.stages.tessellation = ShaderRecompiler::TessellationConfiguration{3, 3, 2, 1, 4, 128, 16, false};
AgcDriver::Graphics::ValidateShaders(tessShaders, tessState, subgroup, false, false, false, false, false, false, false, true);
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(tessShaders, tessState, subgroup, false, false, false, false, false, false, true, false); }, "PA_CL_VS_OUT_CNTL=0x0040000f");
state.paClVsOutCntl = 0;
vertex.spirv = makeModule({.parameterOutput = true, .clipDistanceLength = 9});
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, true, true); }, "clip or cull distance");
@@ -12948,6 +13031,7 @@ int main(int argc, char** argv) {
OneDimensionalColorTests();
htileDepthClearTests();
multisampledSingleClearTests();
htileBufferMaskClearTests();
memoryValueFillTests();
ZExportTests();
unorm10_11_11TargetTests();
@@ -4,6 +4,7 @@
#include "IntermediateRepresentation/IrProgram.hpp"
#include "Optimization/ShaderStageInputInfo.hpp"
#include "SpirvBackend/SpirvEmitter.hpp"
#include "SpirvBackend/SpirvAnalysis.hpp"
#include "PipelineSpecialization.hpp"
#include <array>
#include <cstdint>
@@ -30,12 +31,15 @@ struct LoadCase {
IrType type;
std::uint32_t bits = 32;
std::uint32_t components = 1;
IrShaderStage stage = IrShaderStage::Compute;
bool coherent = true;
bool written = false;
};
std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint32_t offset, std::uint32_t records, bool robust, bool exact = false, bool valid = true) {
IrProgram program;
program.SetWaveSize(32);
program.Resources().stage = IrShaderStage::Compute;
program.Resources().stage = test.stage;
program.Resources().srtPlanComplete = true;
program.Resources().resourceTrackingComplete = true;
program.Metadata().shaderInfoComplete = true;
@@ -70,8 +74,9 @@ std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint
auto& handle = emit(IrOpcode::GetScratchResource, IrType::AddressResource, {});
emit(test.opcode, test.type, {&handle, &address, &zero, &enabled});
} else if (test.kind == ResourceKind::Buffer) {
program.Resources().memoryInfo.front().coherent = true;
program.Resources().memoryInfo.front().coherent = test.coherent;
program.Info().buffers.emplace_back().read = true;
program.Info().buffers.front().written = test.written;
program.Metadata().bindings.descriptors.push_back({DescriptorBindingKind::Buffers, {0}});
program.Metadata().bindings.pushDataStartDword = 0;
program.Metadata().bindings.memoryOffsetCount = 1;
@@ -92,7 +97,9 @@ std::vector<std::uint32_t> emitLoad(const LoadCase& test, bool active, std::uint
compute.hostSubgroupSize = 32;
compute.waveSize = 32;
ShaderStageInputInfo inputs;
inputs.compute = &compute;
ShaderPixelInputInfo pixel;
if (test.stage == IrShaderStage::Pixel) inputs.pixel = &pixel;
else inputs.compute = &compute;
CompiledBindingLayout bindings;
bindings.layout = program.Metadata().bindings;
SpirvTargetOptions target{};
@@ -386,6 +393,71 @@ int main() {
const auto plain = emitLoad(test, true, 8, 64, false, true);
require(AccessTrace(plain, 0).Run() == std::vector<std::uint32_t>({2}), "exact request without robustBufferAccess2 changed the plain variant");
});
run("read-only pixel robust buffer result", [] {
const auto selections = [](const std::vector<std::uint32_t>& module) {
unsigned count = 0;
for (std::size_t offset = 5; offset < module.size(); offset += module[offset] >> 16u) count += (module[offset] & 0xffffu) == spv::OpSelect;
return count;
};
LoadCase test{ResourceKind::Buffer, IrOpcode::LoadBufferU32, IrType::U32, 32, 1, IrShaderStage::Pixel, false};
for (const bool robust : {false, true}) {
for (const bool exact : {false, true}) {
const auto readOnly = selections(emitLoad(test, true, 8, 64, robust, exact));
test.written = true;
const auto writing = selections(emitLoad(test, true, 8, 64, robust, exact));
require(writing == readOnly + (robust && !exact ? 1u : 0u), "only read-only robust pixel loads may omit the result selection");
test.written = false;
test.coherent = true;
require(selections(emitLoad(test, true, 8, 64, robust, exact)) == readOnly, "coherent loads must retain the upstream volatile result folding");
test.coherent = false;
}
}
});
run("storage writers keep guarded results", [] {
IrProgram program;
program.Resources().stage = IrShaderStage::Pixel;
require(AnalyzeProgramRequirements(program).readOnlyStorage, "read-only pixel program lost its storage proof");
program.Resources().stage = IrShaderStage::Compute;
require(!AnalyzeProgramRequirements(program).readOnlyStorage, "compute entry bookkeeping must keep result guards");
program.Resources().stage = IrShaderStage::Pixel;
program.Info().usesFaultBuffer = true;
require(!AnalyzeProgramRequirements(program).readOnlyStorage, "fault-buffer writes must keep result guards");
program.Info().usesFaultBuffer = false;
program.Info().runtimeImageModes.emplace_back().emplace_back().targetMerge = true;
require(!AnalyzeProgramRequirements(program).readOnlyStorage, "runtime target merge writes must keep result guards");
program.Info().runtimeImageModes.clear();
auto& block = program.CreateBlock();
program.BlockOrder().push_back(&block);
program.Resources().memoryInfo.emplace_back().kind = ResourceKind::Lds;
auto& address = program.CreateValue(IrOpcode::Void, IrType::U32);
address.SetImmediateU32(0u);
auto& active = program.CreateValue(IrOpcode::Void, IrType::Bool);
active.SetImmediateBool(true);
auto& load = program.CreateValue(IrOpcode::LoadSharedU32, IrType::U32);
load.AddArgument(&address);
load.AddArgument(&active);
block.AppendInstruction(&load);
require(!AnalyzeProgramRequirements(program).readOnlyStorage, "read-only LDS may allocate storage and must keep result guards");
});
run("volatile robust buffer omits repeated zero selection", [] {
const LoadCase test{ResourceKind::Buffer, IrOpcode::LoadBufferU32, IrType::U32};
const auto selections = [](const std::vector<std::uint32_t>& module) {
unsigned count = 0;
for (std::size_t offset = 5; offset < module.size(); offset += module[offset] >> 16u) {
count += (module[offset] & 0xffffu) == spv::OpSelect;
}
return count;
};
for (const bool robust : {false, true}) {
for (const bool exact : {false, true}) {
auto plainTest = test;
plainTest.coherent = false;
const auto plain = emitLoad(plainTest, true, 8, 64, robust, exact);
const auto coherent = emitLoad(test, true, 8, 64, robust, exact);
require(selections(plain) == selections(coherent) + (robust && !exact ? 1u : 0u), "only volatile guarded robust loads may omit the repeated zero selection");
}
}
});
run("private scratch selection", [] {
const LoadCase test{ResourceKind::Scratch, IrOpcode::LoadAddressU32, IrType::U32};
for (const bool robust : {false, true}) {
@@ -13,6 +13,7 @@
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "SceTypes.hpp"
#include "tests/TemporaryFile.hpp"
#include <algorithm>
#include <array>
#include <atomic>
@@ -130,7 +131,7 @@ std::string verdict(const WaitReport& report) {
}
std::pair<int, std::string> report(const std::filesystem::path& directory, bool full = false) {
std::FILE* output = std::tmpfile();
std::FILE* output = TemporaryFile::Open();
check(output != nullptr, "cannot create a temporary file for the report");
const auto code = AgcDriver::Pm4Analysis::Report(directory, full, output);
std::string text;
@@ -31,6 +31,7 @@
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "tests/TemporaryFile.hpp"
#include "ResidentPresent.hpp"
#include "ImageTableAbi.hpp"
#include "IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/BufferFormat.hpp"
@@ -1420,7 +1421,7 @@ class StderrCapture {
public:
StderrCapture() {
std::fflush(stderr);
file = std::tmpfile();
file = TemporaryFile::Open();
Require(file != nullptr, "cannot open a temporary file for stderr");
#ifdef _WIN32
saved = _dup(_fileno(stderr));
@@ -9345,6 +9346,7 @@ void dataRefreshTests(const Device& device, Recorder& recorder) {
Require(!resources.DataWordsDiffer(original) && resources.DataWordsDiffer(live), "the per-word compare disagrees with the words");
Require(resources.RefreshData(recorder.Commands(), live, &recorder), "a refresh with different words recorded nothing");
Require(resources.DataWordsHash() == ShaderResources::DataWordsHash(live) && !resources.DataWordsDiffer(live), "the refreshed template's hash is not the patched words'");
RecordMemoryBarrier(context, recorder.Commands(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
Require(resources.RefreshData(recorder.Commands(), original, &recorder), "the refresh back recorded nothing");
Require(resources.DataWordsHash() == ShaderResources::DataWordsHash(original), "the refreshed template's hash is not the original words'");
Require(!resources.RefreshData(recorder.Commands(), original, &recorder), "a refresh with equal words recorded");
@@ -402,6 +402,28 @@ int main() {
return b.eliminate() == 1u && !readOtherwise.HasUses() && readHead.UseCount() == 2u && AllUsed({&readHead, &readThenAfterStore, &readMerge});
});
passed &= run("a loop without a clobber reuses its dominating ReadConst", [] {
Builder b;
IrBlock& header = b.newBlock();
IrBlock& body = b.newBlock();
IrBlock& exit = b.newBlock();
b.block->AddBranch(&header);
header.AddBranch(&body);
header.AddBranch(&exit);
body.AddBranch(&header);
auto& srt = b.emit(IrOpcode::GetSrtResource, IrType::SrtResource, {});
auto& beforeLoop = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)});
b.keep(beforeLoop);
std::vector<IrValue*> reads;
for (auto* block : {&header, &body, &exit}) {
b.block = block;
auto& read = b.emit(IrOpcode::ReadConst, IrType::U32, {&srt, &b.constant(0u)});
b.keep(read);
reads.push_back(&read);
}
return b.eliminate() == 3u && beforeLoop.UseCount() == 4u && std::ranges::none_of(reads, [](const auto* read) { return read->HasUses(); });
});
passed &= run("a store on a loop backedge prevents reusing a pre-loop ReadConst in the loop header", [] {
Builder b;
IrBlock& header = b.newBlock();
@@ -1424,25 +1424,33 @@ void verifyTypeZeroImageMaterialization() {
static std::uint64_t lastPendingAddr = 0;
static bool gateSecondWaitOnSrtRoot = false;
static bool settledRecord0 = false;
static bool knownRecord0 = false;
static std::array<std::uint32_t, 4> pendingV0{};
AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t address, std::size_t bytes) {
if (phase == 1u && !flushed && address < syncEnd && syncBegin < address + bytes) {
flushed = true;
++waits;
std::memcpy(reinterpret_cast<void*>(record0Addr), pendingT0.data(), sizeof(pendingT0));
if (record1Addr != 0u) std::memcpy(reinterpret_cast<void*>(record1Addr), pendingT1.data(), sizeof(pendingT1));
} else if (phase == 2u || phase == 3u) {
} else if (phase == 2u || phase == 3u || phase == 6u) {
if (waits == 0u && address < record1Addr + 32u && record1Addr + 12u < address + bytes) {
++waits;
std::memcpy(reinterpret_cast<void*>(record1Addr), pendingT1.data(), sizeof(pendingT1));
} else if ((waits == 1u || (phase == 3u && waits >= 1u)) && address < record2Addr + 32u && record2Addr + 12u < address + bytes) {
++waits;
std::memcpy(reinterpret_cast<void*>(record0Addr), pendingT0.data(), sizeof(pendingT0));
if (phase != 6u) std::memcpy(reinterpret_cast<void*>(record0Addr), pendingT0.data(), sizeof(pendingT0));
std::memcpy(reinterpret_cast<void*>(record2Addr), pendingT2.data(), sizeof(pendingT2));
} else if (phase == 2u && waits == 2u && address < record0Addr + 32u && record0Addr < address + bytes) {
++forcedHookCalls;
}
} else if (phase == 5u) {
++waits;
} else if (phase == 8u && !flushed && address < record0Addr + 16u && record0Addr < address + bytes) {
flushed = true;
++waits;
std::memcpy(reinterpret_cast<void*>(record0Addr), pendingV0.data(), sizeof(pendingV0));
}
});
@@ -1457,7 +1465,19 @@ void verifyTypeZeroImageMaterialization() {
++srtRootReads;
}
}
if (phase == 0u || phase == 5u) return Pending::Raw;
if (phase == 0u || phase == 5u || phase == 9u) return Pending::Raw;
if (phase == 7u) {
if (settledRecord0) return Pending::None;
if (address == record0Addr + 28u && bytes == sizeof(std::uint32_t) && !known.empty()) {
settledRecord0 = true;
std::memcpy(reinterpret_cast<void*>(record0Addr), pendingT0.data(), sizeof(pendingT0));
}
return Pending::Raw;
}
if (phase == 8u) {
if (flushed) return Pending::None;
return address < record0Addr + 16u && record0Addr < address + bytes ? Pending::Raw : Pending::None;
}
if (phase == 4u) {
if (address >= record0Addr && address + bytes <= record0Addr + 32u) {
if (known.size() == bytes) {
@@ -1485,6 +1505,24 @@ void verifyTypeZeroImageMaterialization() {
if ((!gateSecondWaitOnSrtRoot || srtRootReads >= 2u) && address >= record2Addr + 12u && address < record2Addr + 32u && bytes == sizeof(std::uint32_t)) return Pending::Sync;
return Pending::Raw;
}
if (phase == 6u && waits == 2u) {
if (settledRecord0) return Pending::None;
if (address >= record1Addr && address < record1Addr + 32u) {
settledRecord0 = true;
std::memcpy(reinterpret_cast<void*>(record0Addr), pendingT0.data(), sizeof(pendingT0));
return Pending::None;
}
if (address >= record0Addr && address + bytes <= record0Addr + 32u) {
if (knownRecord0) {
if (known.size() == bytes) {
std::memcpy(known.data(), reinterpret_cast<const std::byte*>(record0Addr) + (address - record0Addr), bytes);
}
return Pending::KnownValue;
}
return Pending::Raw;
}
return Pending::None;
}
if (phase == 2u && waits == 2u) {
if (gateSecondWaitOnSrtRoot &&
((srtRootReads == 3u && address == srtAddress + 512u * sizeof(std::uint32_t) + 56u) ||
@@ -1703,7 +1741,342 @@ void verifyTypeZeroImageMaterialization() {
return std::equal(oldT.begin(), oldT.end(), value.dwords.begin());
}), "forced pass exception scope: a normal pass over Raw words after a throwing forced attempt waited on the hook or bumped rawInvalidations");
}
for (const bool useKnown : {false, true}) {
gateSecondWaitOnSrtRoot = true;
knownRecord0 = useKnown;
settledRecord0 = false;
const std::uint32_t stride = 48u;
const std::size_t stepWords = stride / sizeof(std::uint32_t);
std::fill(srt.begin(), srt.end(), 0u);
std::copy(oldT.begin(), oldT.end(), srt.begin());
std::copy(oldT.begin(), oldT.end(), srt.begin() + stepWords);
std::copy(oldT.begin(), oldT.end(), srt.begin() + 2u * stepWords);
fillWholeSrt(srt.data() + 512u, srt.data(), stride, 3u);
RecompileRequest settleReq = wideTableReq;
settleReq.context.userData = inPageUserData;
phase = 6u;
waits = 0u;
srtRootReads = 0u;
lastPendingAddr = 0u;
flushed = false;
record0Addr = srtAddress;
record1Addr = srtAddress + stride;
record2Addr = srtAddress + 2u * stride;
pendingT0 = newT;
pendingT1 = thirdT;
pendingT2 = newT;
AgcDriver::ShaderMemory settleMemory({}, multiPending, nullptr, hookWaits);
const auto settleCapture = settleMemory.Capture(settleReq);
const auto& col = settleCapture->snapshot.tables.tables.at(0);
require(settledRecord0 && waits == 2u && col.keys == 3u, "settled force-sync test setup did not reach attempt 2 with settled record0");
require(col.words.at(col.codes.at(0)).dwords == newT && col.words.at(col.codes.at(1)).dwords == thirdT && col.words.at(col.codes.at(2)).dwords == newT,
useKnown ? "settled force-sync KnownValue: entering SrtDescriptorForceSync did not invalidate cached KnownValue words whose write settled before attempt 2"
: "settled force-sync Raw: entering SrtDescriptorForceSync did not invalidate cached Raw words whose write settled before attempt 2");
}
gateSecondWaitOnSrtRoot = false;
knownRecord0 = false;
{
srt.fill(0u);
std::copy(oldT.begin(), oldT.end(), srt.begin());
std::copy(outputV.begin(), outputV.end(), srt.begin() + 16u);
srt[20] = 1u;
phase = 0u;
waits = 0u;
flushed = false;
AgcDriver::ShaderMemory nestedScopeMemory({}, multiPending, nullptr, hookWaits);
static_cast<void>(nestedScopeMemory.Capture(request));
const auto before = AgcDriver::ShaderMemory::descriptorScope(&nestedScopeMemory, 0u);
const auto firstEnter = AgcDriver::ShaderMemory::descriptorScope(&nestedScopeMemory, SrtDescriptorForceSync);
require(firstEnter == before + 1u, "nested SrtDescriptorForceSync: 0->1 transition with cached Raw words did not increment rawInvalidations once");
static_cast<void>(nestedScopeMemory.Capture(request));
const auto afterForcedRead = AgcDriver::ShaderMemory::descriptorScope(&nestedScopeMemory, 0u);
const auto secondEnter = AgcDriver::ShaderMemory::descriptorScope(&nestedScopeMemory, SrtDescriptorForceSync);
const auto firstExit = AgcDriver::ShaderMemory::descriptorScope(&nestedScopeMemory, SrtDescriptorEndForceSync);
const auto secondExit = AgcDriver::ShaderMemory::descriptorScope(&nestedScopeMemory, SrtDescriptorEndForceSync);
require(afterForcedRead == firstEnter && secondEnter == firstEnter && firstExit == firstEnter && secondExit == firstEnter,
"nested SrtDescriptorForceSync: nested 1->2 transition or scope exit altered rawInvalidations");
}
{
srt.fill(0u);
std::copy(oldT.begin(), oldT.end(), srt.begin());
std::copy(outputV.begin(), outputV.end(), srt.begin() + 16u);
srt[20] = 1u;
phase = 1u;
waits = 0u;
flushed = true;
AgcDriver::ShaderMemory settledScopeMemory({}, multiPending, nullptr, hookWaits);
const auto settledCapture = settledScopeMemory.Capture(request);
const auto before = AgcDriver::ShaderMemory::descriptorScope(&settledScopeMemory, 0u);
const auto entered = AgcDriver::ShaderMemory::descriptorScope(&settledScopeMemory, SrtDescriptorForceSync);
const auto exited = AgcDriver::ShaderMemory::descriptorScope(&settledScopeMemory, SrtDescriptorEndForceSync);
require(!settledCapture->snapshot.buffers.empty() && entered == before && exited == before,
"settled SrtDescriptorForceSync: a 0->1 transition over cached words read with no pending write incremented rawInvalidations");
}
{
auto tornT = oldT;
tornT[1] &= ~(0x1ffu << 20u);
srt.fill(0u);
std::copy(tornT.begin(), tornT.end(), srt.begin());
std::copy(outputV.begin(), outputV.end(), srt.begin() + 16u);
srt[20] = 1u;
phase = 7u;
waits = 0u;
flushed = false;
settledRecord0 = false;
record0Addr = srtAddress;
pendingT0 = newT;
AgcDriver::ShaderMemory retryTMemory({}, multiPending, nullptr, hookWaits);
const auto retryTCapture = retryTMemory.Capture(request);
require(settledRecord0 && std::ranges::any_of(retryTCapture->snapshot.images, [&](const DescriptorValue& value) {
return std::equal(newT.begin(), newT.end(), value.dwords.begin());
}), "speculative invalid T# retry: Materialize did not retry under force-sync and invalidate settled Raw T# words");
}
{
auto validV = outputV;
validV[3] = 0x00014facu;
auto tornV = outputV;
tornV[3] = 0x50000facu;
srt.fill(0u);
std::copy(oldT.begin(), oldT.end(), srt.begin());
std::copy(tornV.begin(), tornV.end(), srt.begin() + 16u);
srt[20] = 1u;
phase = 8u;
waits = 0u;
flushed = false;
record0Addr = srtAddress + 64u;
pendingV0 = validV;
AgcDriver::ShaderMemory retryVMemory({}, multiPending, nullptr, hookWaits);
const auto retryVCapture = retryVMemory.Capture(request);
require(flushed && waits == 1u && !retryVCapture->snapshot.buffers.empty() &&
std::equal(validV.begin(), validV.end(), retryVCapture->snapshot.buffers[0].dwords.begin()),
"speculative invalid V# retry: materializeTables validation failure on attempt 0 did not retry under force-sync");
}
{
auto tornT = oldT;
tornT[1] &= ~(0x1ffu << 20u);
srt.fill(0u);
std::copy(tornT.begin(), tornT.end(), srt.begin());
std::copy(outputV.begin(), outputV.end(), srt.begin() + 16u);
srt[20] = 1u;
phase = 9u;
waits = 0u;
flushed = false;
AgcDriver::ShaderMemory badTMemory({}, multiPending, nullptr, hookWaits);
expectFailure([&] { static_cast<void>(badTMemory.Capture(request)); }, "runtime image descriptor is invalid",
"genuinely invalid T# did not throw after force-sync retry");
auto tornV = outputV;
tornV[3] = 0x50000facu;
srt.fill(0u);
std::copy(oldT.begin(), oldT.end(), srt.begin());
std::copy(tornV.begin(), tornV.end(), srt.begin() + 16u);
srt[20] = 1u;
AgcDriver::ShaderMemory badVMemory({}, multiPending, nullptr, hookWaits);
expectFailure([&] { static_cast<void>(badVMemory.Capture(request)); }, "buffer descriptor uses an unsupported type",
"genuinely invalid V# did not throw after force-sync retry");
}
{
IrResourcePlan convPlan;
convPlan.srtPlanComplete = true;
convPlan.resourceTrackingComplete = true;
convPlan.userDataCount = 0u;
convPlan.materializationSources = {0u};
const std::array<std::uint32_t, 8> convWords{
0x12345678u,
(static_cast<std::uint32_t>(IrBufferFormat::Format11_11_10UNorm) << 20u) | 0x100u,
0x0000c000u,
0x90000facu,
0u, 0u, 0u, 0u,
};
std::vector<std::unique_ptr<IrValue>> convValues;
DescriptorSource convSource;
convSource.dwordCount = 8u;
for (std::uint32_t i = 0u; i < 8u; ++i) {
auto val = std::make_unique<IrValue>(IrOpcode::Void, IrType::U32, i);
val->SetImmediateU32(convWords[i]);
convSource.dwords[i] = val.get();
convValues.push_back(std::move(val));
}
convPlan.descriptorSources.push_back(convSource);
ImageResource convImg;
convImg.source = 0u;
convImg.resourceClass = ImageResourceClass::Sampled;
convImg.dimension = RdnaImageDimension::Dim2D;
convImg.read = true;
convImg.srgbDecodeCompatible = false;
convImg.depthBitsCompatible = true;
convPlan.info.images.push_back(convImg);
convPlan.info.runtimeImageModes.push_back(ResourceMaterializer::RuntimeImageModes(convImg));
std::uint32_t forceSyncCalls = 0u;
SrtRuntime convRuntime{};
convRuntime.userContext = &forceSyncCalls;
convRuntime.descriptorScope = +[](void* ctx, std::size_t bytes) -> std::uint64_t {
if (bytes == SrtDescriptorForceSync) ++*static_cast<std::uint32_t*>(ctx);
return 0u;
};
ResourceSnapshot convSnapshot;
bool caughtConverted = false;
try {
ResourceMaterializer{}.Materialize(convPlan, convRuntime, convSnapshot);
} catch (const ConvertedSampleRequired&) {
caughtConverted = true;
}
require(caughtConverted && forceSyncCalls == 0u, "ConvertedSampleRequired was swallowed or retried under SrtDescriptorForceSync");
}
{
std::vector<std::unique_ptr<IrValue>> retryValues;
std::uint32_t retryIds = 0u;
const auto make = [&](IrOpcode opcode, IrType type) -> IrValue& {
retryValues.push_back(std::make_unique<IrValue>(opcode, type, retryIds++));
return *retryValues.back();
};
const auto userWord = [&](std::uint32_t index) -> IrValue& {
auto& reg = make(IrOpcode::Void, IrType::ScalarReg);
reg.SetRegister({RegisterBank::Scalar, index});
auto& read = make(IrOpcode::GetUserData, IrType::U32);
read.AddArgument(&reg);
return read;
};
const auto load = [&](IrValue& address, std::uint32_t offset) -> IrValue& {
auto& immediate = make(IrOpcode::Void, IrType::U32);
immediate.SetImmediateU32(offset);
auto& read = make(IrOpcode::LoadAddressU32, IrType::U32);
read.AddArgument(&address);
read.AddArgument(&immediate);
read.SetFlags(MemoryFlags{0, 0});
return read;
};
auto& base = make(IrOpcode::CompositeConstructU64, IrType::U64);
base.AddArgument(&userWord(0u));
base.AddArgument(&userWord(1u));
DescriptorSource descriptor;
descriptor.dwordCount = 4u;
for (std::uint32_t dword = 0; dword < 4u; ++dword) descriptor.dwords[dword] = &load(base, dword * 4u);
const auto describe = [&](IrProgram& program) {
auto& resources = program.Resources();
resources.srtPlanComplete = true;
resources.resourceTrackingComplete = true;
resources.userDataCount = 2u;
MemoryInfo memory;
memory.kind = ResourceKind::ScalarAddress;
resources.memoryInfo.push_back(memory);
resources.descriptorSources.push_back(descriptor);
resources.info.buffers.push_back(BufferResource{});
};
constexpr std::uint64_t RetryBase = 0x10000u;
const std::array<std::uint32_t, 2> retryUserData{static_cast<std::uint32_t>(RetryBase), 0u};
IrProgram retryProgram;
describe(retryProgram);
const auto retryPlan = ResourceMaterializer{}.ExtractPlan(retryProgram);
struct RetryMemory {
std::array<std::uint32_t, 4> words{};
std::array<std::uint32_t, 4> settled{};
std::uint64_t epoch = 0u;
std::uint32_t depth = 0u;
std::uint32_t forcedEntries = 0u;
std::uint32_t speculativeReads = 0u;
std::uint32_t forcedReads = 0u;
bool waitOnLastWord = false;
};
auto tornV = outputV;
tornV[3] = 0x50000facu;
std::uint32_t passReads = 0u;
for (const bool waitOnLastWord : {false, true}) {
RetryMemory state;
state.words = tornV;
state.settled = outputV;
state.waitOnLastWord = waitOnLastWord;
SrtRuntime retryRuntime{retryUserData};
retryRuntime.userContext = &state;
retryRuntime.readMemory = +[](void* ctx, std::uint64_t address, std::uint32_t* value) {
auto& self = *static_cast<RetryMemory*>(ctx);
if (address < RetryBase || address >= RetryBase + sizeof(self.words)) return false;
const auto index = static_cast<std::size_t>((address - RetryBase) / sizeof(std::uint32_t));
++(self.depth == 0u ? self.speculativeReads : self.forcedReads);
if (self.waitOnLastWord && self.depth == 0u && index + 1u == self.words.size() && self.words != self.settled) {
self.words = self.settled;
++self.epoch;
throw std::runtime_error("descriptor read waited on a GPU write and then faulted");
}
*value = self.words[index];
return true;
};
retryRuntime.readSpecializationMemory = retryRuntime.readMemory;
retryRuntime.descriptorScope = +[](void* ctx, std::size_t bytes) -> std::uint64_t {
auto& self = *static_cast<RetryMemory*>(ctx);
if (bytes == SrtDescriptorForceSync) {
if (self.depth++ == 0u) {
++self.forcedEntries;
self.words = self.settled;
}
} else if (bytes == SrtDescriptorEndForceSync) {
--self.depth;
}
return self.epoch;
};
ResourceSnapshot retrySnapshot;
ResourceMaterializer{}.Materialize(retryPlan, retryRuntime, retrySnapshot);
require(!retrySnapshot.buffers.empty() && std::equal(outputV.begin(), outputV.end(), retrySnapshot.buffers[0].dwords.begin()),
"descriptor retry policy: Materialize did not settle on the valid V#");
if (!waitOnLastWord) {
passReads = state.forcedReads;
require(passReads != 0u && state.forcedEntries == 1u && state.speculativeReads == passReads,
"descriptor retry policy: a failed speculative pass that changed no epoch was repeated speculatively instead of going straight to SrtDescriptorForceSync");
} else {
require(state.forcedEntries == 0u && state.speculativeReads == 2u * passReads,
"descriptor retry policy: a speculative pass that faulted after its read moved the epoch forced SrtDescriptorForceSync instead of retrying speculatively over the refreshed words");
}
}
IrProgram fillProgram;
describe(fillProgram);
auto& uniform = fillProgram.Resources().uniformFill;
uniform.fill.kind = UniformFillKind::Buffer;
uniform.fill.words = 1u;
uniform.values[0] = &load(base, 16u);
const auto fillPlan = ResourceMaterializer{}.ExtractPlan(fillProgram);
struct FillMemory {
std::array<std::uint32_t, 4> words{};
std::uint32_t fill = 0u;
std::uint32_t settledFill = 0u;
std::uint64_t epoch = 0u;
std::uint32_t fillReads = 0u;
};
FillMemory fillState;
fillState.words = outputV;
fillState.fill = 0x11111111u;
fillState.settledFill = 0x3f800000u;
SrtRuntime fillRuntime{retryUserData};
fillRuntime.userContext = &fillState;
fillRuntime.readMemory = +[](void* ctx, std::uint64_t address, std::uint32_t* value) {
auto& self = *static_cast<FillMemory*>(ctx);
if (address == RetryBase + sizeof(self.words)) {
++self.fillReads;
*value = self.fill;
if (self.fill != self.settledFill) {
self.fill = self.settledFill;
++self.epoch;
}
return true;
}
if (address < RetryBase || address >= RetryBase + sizeof(self.words)) return false;
*value = self.words[(address - RetryBase) / sizeof(std::uint32_t)];
return true;
};
fillRuntime.readSpecializationMemory = fillRuntime.readMemory;
fillRuntime.descriptorScope = +[](void* ctx, std::size_t) -> std::uint64_t { return static_cast<FillMemory*>(ctx)->epoch; };
ResourceSnapshot fillSnapshot;
ResourceMaterializer{}.Materialize(fillPlan, fillRuntime, fillSnapshot);
require(fillSnapshot.uniformFill.value == fillState.settledFill && fillState.fillReads == 2u,
"uniform fill epoch: a pass whose uniform fill read moved the epoch was accepted with the value read before the move");
}
}
void verifyOutOfRangeConstantReads() {
@@ -8215,11 +8588,18 @@ void verifyWaveUniformValues() {
auto& fromGlobalLoad = emit(IrOpcode::IAdd32, IrType::U32, {&globalLoad, &uniformSum});
auto& compare = emit(IrOpcode::ULessThan32, IrType::U1, {&laneSum, &userData});
auto& ballot = emit(IrOpcode::Ballot, IrType::U32x4, {&compare});
auto& uniformPhi = emit(IrOpcode::Phi, IrType::U32, {&userData});
auto& uniformNext = emit(IrOpcode::IAdd32, IrType::U32, {&uniformPhi, &userData});
uniformPhi.AddArgument(&uniformNext);
auto& varyingPhi = emit(IrOpcode::Phi, IrType::U32, {&userData});
auto& varyingNext = emit(IrOpcode::IAdd32, IrType::U32, {&varyingPhi, &lane});
varyingPhi.AddArgument(&varyingNext);
auto& fromVaryingPhi = emit(IrOpcode::IAdd32, IrType::U32, {&varyingPhi, &userData});
const auto uniform = WaveUniformValues(program);
for (const auto* value : {&userData, &uniformSum, &address, &scalarLoad, &fromScalarLoad, &ballot}) {
for (const auto* value : {&userData, &uniformSum, &address, &scalarLoad, &fromScalarLoad, &ballot, &uniformPhi, &uniformNext}) {
require(uniform.contains(value), "wave-uniform values: a value every lane of the wave computes alike was not found uniform");
}
for (const auto* value : {&lane, &laneSum, &laneOffsetLoad, &globalLoad, &fromGlobalLoad, &compare}) {
for (const auto* value : {&lane, &laneSum, &laneOffsetLoad, &globalLoad, &fromGlobalLoad, &compare, &varyingPhi, &varyingNext, &fromVaryingPhi}) {
require(!uniform.contains(value), "wave-uniform values: a value that may differ between lanes was found uniform");
}
}
@@ -39,6 +39,18 @@ static_assert(offsetof(Packet, addr) == 0);
static_assert(offsetof(Packet, dw_num) == 8);
static_assert(offsetof(Packet, flags) == 12);
namespace AgcDriver::DriverDetail {
struct DriverTestAccess {
static std::uint64_t LabelTaken(std::uint32_t queue, const volatile std::uint32_t* address) {
auto& driver = Driver::Get();
std::lock_guard lock(driver.labelStoresMutex);
return driver.labelTakes.Taken(queue, reinterpret_cast<std::uintptr_t>(address));
}
};
}
namespace {
void check(bool condition, const char* message) {
@@ -451,6 +463,56 @@ void testLabelTakenAndResetByEarlierSubmission() {
AgcDriverWaitIdle_nid_postfix();
}
void testLabelTakesRetire() {
using AgcDriver::DriverDetail::LabelTakes;
auto takes = std::make_unique<LabelTakes>();
constexpr std::uint64_t label = 0x1000, other = 0x2000;
takes->Pass(0x20, label, 10);
check(takes->MayHavePass(0x20, label) && takes->Taken(0x20, label) == 0, "a passed wait was not kept for the store that takes it");
takes->Take(0x20, label);
check(takes->Taken(0x20, label) == 10 && !takes->MayHavePass(0x20, label), "the queue's own store did not turn its pass into a take");
takes->Retire(0x20, 9);
check(takes->Taken(0x20, label) == 10, "a take was dropped while a submission of the queue entered before it could still wait");
takes->Retire(0x28, 20);
check(takes->Taken(0x20, label) == 10, "another queue's submission retired a take");
takes->Retire(0x20, 10);
check(takes->Taken(0x20, label) == 0 && takes->Empty(), "a take was kept after every submission of the queue that could wait entered after it");
takes->Pass(0x20, label, 30);
takes->Pass(0x20, label, 40);
takes->Retire(0x20, 30);
check(takes->MayHavePass(0x20, label), "an older pass on the dword retired its newer pass");
takes->Take(0x20, label);
takes->Retire(0x20, 39);
check(takes->Taken(0x20, label) == 40, "a take was retired by a submission entered before it");
takes->Pass(0x20, other, 50);
takes->Pass(0x28, label, 50);
takes->Retire(0x20, 50);
check(takes->Taken(0x20, label) == 0 && !takes->MayHavePass(0x20, other), "a submission of the queue entered after its passes and take did not retire them");
check(takes->MayHavePass(0x28, label) && !takes->Empty(), "a submission of one queue retired another queue's pass");
takes->Retire(0x28, 50);
check(takes->Empty(), "a pass was kept after every submission of its queue entered after it");
}
void testLabelTakeRetiredByLaterSubmission() {
using AgcDriver::DriverDetail::DriverTestAccess;
alignas(64) static volatile std::uint32_t gate = 0, label = 0, first = 0, second = 0, third = 0;
submit(0x20, commands(waitEqual(&gate, 1), waitEqual(&label, 1), writeData(&label, 0), writeData(&first, 1)));
submit(0x20, commands(waitEqual(&label, 1), writeData(&second, 1)));
submit(0, commands(writeData(&label, 1)));
waitFor(&label, 1, "producer label never landed");
gate = 1;
waitFor(&first, 1, "consumer never passed its first label wait");
std::this_thread::sleep_for(std::chrono::milliseconds(1200));
check(second == 0, "a label store an earlier submission of the queue had taken and reset satisfied a later submission's wait, or the wait expired");
check(DriverTestAccess::LabelTaken(0x20, &label) != 0, "a take was dropped while a submission of the queue entered before it was waiting");
submit(0, commands(writeData(&label, 1)));
waitFor(&second, 1, "the later submission did not resume after a new producer label");
submit(0x20, commands(writeData(&third, 1)));
waitFor(&third, 1, "a submission entered after the take never ran");
check(DriverTestAccess::LabelTaken(0x20, &label) == 0, "a take was kept after a submission of the queue entered after it started");
AgcDriverWaitIdle_nid_postfix();
}
void testWaitFreeSubmissionAfterEarlierQueue0Work() {
alignas(64) static volatile std::uint32_t first = 0, second = 0;
constexpr std::uint32_t writes = 20000;
@@ -1103,6 +1165,8 @@ int main(int argc, char** argv) {
testLabelHeldAtSubmission();
testLabelTakenAndResetInOneSubmission();
testLabelTakenAndResetByEarlierSubmission();
testLabelTakesRetire();
testLabelTakeRetiredByLaterSubmission();
testWideLabelStoredSinceSubmission();
testWaitFreeSubmissionAfterEarlierQueue0Work();
testWaitFreeSubmissionQueue0WaitsOn();
@@ -0,0 +1,20 @@
#include "execution/TargetAddressWindow.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <stdexcept>
int main() {
constexpr std::size_t BlockBytes = 65536u;
if (AgcDriver::Tests::TargetAddressWindowPair(0x1fffe0000ull, BlockBytes) != std::array<std::size_t, 2>{0u, 1u}) return 1;
if (AgcDriver::Tests::TargetAddressWindowPair(0x1ffff0000ull, BlockBytes) != std::array<std::size_t, 2>{1u, 2u}) return 1;
if (AgcDriver::Tests::TargetAddressWindowPair(0x300000000ull, BlockBytes) != std::array<std::size_t, 2>{0u, 1u}) return 1;
try {
AgcDriver::Tests::TargetAddressWindowPair(0x1ffff0000ull, static_cast<std::size_t>(0x100000000ull));
} catch (const std::runtime_error&) {
std::cout << "target address window test passed\n";
return 0;
}
return 1;
}
@@ -250,6 +250,10 @@ void fillContractTests() {
Require(FillDccClear(VK_FORMAT_B10G11R11_UFLOAT_PACK32, DccKeys::Clear1111, false, partial), "packed float fast clear was not encoded");
const std::array<std::byte, 4> white{std::byte{0xc0}, std::byte{0x03}, std::byte{0x1e}, std::byte{0x78}};
for (std::size_t index = 0; index < partial.size(); ++index) Require(partial[index] == (index < 8u ? white[index % 4u] : std::byte{0}), "a clear fill over a partial trailing element did not zero the tail");
std::vector<std::byte> large(1003, std::byte{0x5a});
Require(FillDccClear(VK_FORMAT_R16G16B16A16_SFLOAT, DccKeys::Clear1111, false, large), "half float fast clear was not encoded");
const std::array<std::byte, 8> halfOne{std::byte{0x00}, std::byte{0x3c}, std::byte{0x00}, std::byte{0x3c}, std::byte{0x00}, std::byte{0x3c}, std::byte{0x00}, std::byte{0x3c}};
for (std::size_t index = 0; index < large.size(); ++index) Require(large[index] == (index < 1000u ? halfOne[index % 8u] : std::byte{0}), "a clear fill over many elements did not repeat the element up to the last whole one");
}
void guestFormatLookupTests() {
@@ -6,5 +6,12 @@ int main() {
if (sceAgcDriverSetTargetRingForDiag() != static_cast<int>(0x8A6D0001)) return 2;
auto* volatile linked = &sceAgcDriverSetFlip;
if (linked == nullptr) return 3;
if (sceAgcDriverAcquireRazorACQ() != static_cast<int>(0x8A6D0001)) return 4;
if (sceAgcDriverGetPaDebugInterfaceVersion() != static_cast<int>(0x8A6D0001)) return 5;
if (sceAgcDriverSetHsOffchipParamDirect() != static_cast<int>(0x8A6D0001)) return 6;
if (sceAgcDriverSubmitMultiCommandBuffersDirect() != static_cast<int>(0x8A6D0001)) return 7;
if (sceAgcDriverSubmitToRazorACQ() != static_cast<int>(0x8A6D0001)) return 8;
if (sceAgcDriverSuspendPointSubmitDirect() != static_cast<int>(0x8A6D0001)) return 9;
if (sceAgcDriverModuleRegistration() != static_cast<int>(0x8A6D0003)) return 10;
return 0;
}
@@ -0,0 +1,11 @@
if(BUILD_TESTING)
add_test_executable(agc_adaptive_staging_tests tests/AdaptiveStaging.cpp)
target_include_directories(agc_adaptive_staging_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_adaptive_staging_tests PRIVATE libc)
add_test(NAME agc_adaptive_staging COMMAND agc_adaptive_staging_tests)
set_tests_properties(agc_adaptive_staging PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_adaptive_staging PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,13 @@
if(BUILD_TESTING)
add_test_executable(agc_async_log_tests tests/AsyncLog.cpp)
target_include_directories(agc_async_log_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_async_log_tests PRIVATE libc)
configure_windows_unwind(agc_async_log_tests)
add_test(NAME agc_async_log COMMAND agc_async_log_tests)
add_test(NAME agc_async_log_fatal COMMAND agc_async_log_tests fatal)
set_tests_properties(agc_async_log agc_async_log_fatal PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(agc_async_log agc_async_log_fatal PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,9 @@
add_test_executable(agc_barrier_batch_tests EXCLUDE_FROM_ALL tests/BarrierBatch.cpp Graphics/src/BarrierBatch.cpp)
target_include_directories(agc_barrier_batch_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_barrier_batch_tests PRIVATE libc)
set_target_properties(agc_barrier_batch_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_barrier_batch_tests)
if(BUILD_TESTING)
agc_add_default_test(agc_barrier_batch_tests)
endif()
@@ -0,0 +1,21 @@
add_test_executable(agc_buffer_pool_tests EXCLUDE_FROM_ALL tests/BufferPool.cpp)
target_sources(agc_buffer_pool_tests PRIVATE Graphics/src/BufferPool.cpp tests/AllocationFailure.cpp)
target_include_directories(agc_buffer_pool_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_buffer_pool_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_buffer_pool_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_buffer_pool_tests)
if(BUILD_TESTING)
set_target_properties(agc_buffer_pool_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_buffer_pool COMMAND agc_buffer_pool_tests)
add_test(NAME agc_buffer_pool_small_device_shared COMMAND agc_buffer_pool_tests --small-device-retention)
set_tests_properties(agc_buffer_pool_small_device_shared PROPERTIES ENVIRONMENT
"APS5_STAGING_POOL_MIB=256;APS5_BUFFER_POOL_SHARED=1")
add_test(NAME agc_buffer_pool_small_device_no_staging COMMAND agc_buffer_pool_tests --small-device-retention)
set_tests_properties(agc_buffer_pool_small_device_no_staging PROPERTIES ENVIRONMENT
"APS5_STAGING_POOL_MIB=0")
if(WIN32)
set_tests_properties(agc_buffer_pool agc_buffer_pool_small_device_shared agc_buffer_pool_small_device_no_staging PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,14 @@
add_test_executable(agc_buffer_resource_vcc_tests EXCLUDE_FROM_ALL tests/BufferResourceVcc.cpp)
target_link_libraries(agc_buffer_resource_vcc_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_buffer_resource_vcc_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_buffer_resource_vcc_tests)
if(BUILD_TESTING)
set_target_properties(agc_buffer_resource_vcc_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_buffer_resource_vcc COMMAND agc_buffer_resource_vcc_tests)
set_tests_properties(agc_buffer_resource_vcc PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_buffer_resource_vcc PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,37 @@
if(BUILD_TESTING)
add_test_executable(agc_capture_trace_tests tests/CaptureTrace.cpp)
target_include_directories(agc_capture_trace_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_capture_trace_tests PRIVATE libc)
configure_windows_unwind(agc_capture_trace_tests)
set(agcCaptureTraceDirectory ${CMAKE_CURRENT_BINARY_DIR}/capture_trace_tests)
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/default")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/explicit")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/failure")
file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/best-effort")
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")
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=")
add_test(NAME agc_capture_trace_path_explicit COMMAND agc_capture_trace_tests path-explicit)
set_tests_properties(agc_capture_trace_path_explicit PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceExplicitPath}")
add_test(NAME agc_capture_trace_explicit_writes COMMAND agc_capture_trace_tests explicit-writes)
set_tests_properties(agc_capture_trace_explicit_writes PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceExplicitPath}")
add_test(NAME agc_capture_trace_default_writes COMMAND agc_capture_trace_tests default-writes)
set_tests_properties(agc_capture_trace_default_writes PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/default")
add_test(NAME agc_capture_trace_open_failure COMMAND agc_capture_trace_tests open-failure)
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_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_disabled PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,12 @@
if(BUILD_TESTING AND NOT WIN32)
add_test_executable(agc_collect_memo_generation_tests
tests/CollectMemoGeneration.cpp
Execution/src/GuestMemory.cpp
Execution/src/PerformanceTraceThread.cpp
)
target_include_directories(agc_collect_memo_generation_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_collect_memo_generation_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_collect_memo_generation_tests PROPERTIES CXX_EXTENSIONS OFF)
add_test(NAME agc_collect_memo_generation COMMAND agc_collect_memo_generation_tests)
set_tests_properties(agc_collect_memo_generation PROPERTIES TIMEOUT 30)
endif()
@@ -0,0 +1,17 @@
add_test_executable(agc_command_batch_failure_tests EXCLUDE_FROM_ALL
tests/CommandBatch.cpp tests/CommandBatchFailure.cpp Graphics/src/Resources.cpp tests/AllocationFailure.cpp)
target_include_directories(agc_command_batch_failure_tests PRIVATE
${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_command_batch_failure_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_command_batch_failure_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_command_batch_failure_tests)
if(BUILD_TESTING)
set_target_properties(agc_command_batch_failure_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_command_batch_failure COMMAND agc_command_batch_failure_tests)
set_tests_properties(agc_command_batch_failure PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_command_batch_failure PROPERTIES
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,12 @@
if(BUILD_TESTING)
add_test_executable(agc_compile_budget_tests tests/CompileBudget.cpp)
target_include_directories(agc_compile_budget_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_compile_budget_tests PRIVATE libc)
configure_windows_unwind(agc_compile_budget_tests)
add_test(NAME agc_compile_budget COMMAND agc_compile_budget_tests)
set_tests_properties(agc_compile_budget PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_compile_budget PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,29 @@
if(BUILD_TESTING)
add_test_executable(agc_compute_pipeline_capture_tests tests/ComputePipelineCapture.cpp Execution/src/ComputePipelineCapture.cpp)
target_include_directories(agc_compute_pipeline_capture_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_compute_pipeline_capture_tests PRIVATE libc)
set_target_properties(agc_compute_pipeline_capture_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_compute_pipeline_capture_tests)
add_test(NAME agc_compute_pipeline_capture COMMAND agc_compute_pipeline_capture_tests)
set_tests_properties(agc_compute_pipeline_capture PROPERTIES TIMEOUT 30)
foreach(mode IN ITEMS entry-cap byte-cap io invalid)
string(REPLACE "-" "_" name "${mode}")
add_test(NAME agc_compute_pipeline_capture_optional_${name} COMMAND agc_compute_pipeline_capture_tests --optional-${mode})
set_tests_properties(agc_compute_pipeline_capture_optional_${name} PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_compute_pipeline_capture_optional_${name} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
if(WIN32)
set_tests_properties(agc_compute_pipeline_capture PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS)
add_test(NAME agc_compute_pipeline_capture_run COMMAND ${CMAKE_COMMAND}
-DFIXTURE=$<TARGET_FILE:agc_driver_branch_past_endpgm_tests>
-DREPLAY=$<TARGET_FILE:agc_compute_pipeline_replay>
-DOUTPUT_ROOT=${CMAKE_CURRENT_BINARY_DIR}/capture-tests
-P ${CMAKE_CURRENT_SOURCE_DIR}/tests/ComputePipelineCaptureRun.cmake)
set_tests_properties(agc_compute_pipeline_capture_run PROPERTIES TIMEOUT 30 SKIP_REGULAR_EXPRESSION "compute pipeline capture run: skipped, no usable Vulkan device")
endif()
@@ -0,0 +1,15 @@
if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS)
add_test_executable(agc_compute_pipeline_replay_tests tests/ComputePipelineReplay.cpp)
target_include_directories(agc_compute_pipeline_replay_tests PRIVATE
${LIBS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/core/shader/recompiler
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR}
)
target_link_libraries(agc_compute_pipeline_replay_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_compute_pipeline_replay_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_compute_pipeline_replay_tests)
add_test(NAME agc_compute_pipeline_replay COMMAND agc_compute_pipeline_replay_tests)
set_tests_properties(agc_compute_pipeline_replay PROPERTIES TIMEOUT 30 SKIP_RETURN_CODE 77)
add_dependencies(agc_compute_pipeline_replay_tests agc_compute_pipeline_replay agc_driver_branch_past_endpgm_tests)
endif()
@@ -0,0 +1,8 @@
add_test_executable(agc_constant_bit_test_tests EXCLUDE_FROM_ALL tests/ConstantBitTest.cpp)
target_link_libraries(agc_constant_bit_test_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_constant_bit_test_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_constant_bit_test_tests)
if(BUILD_TESTING)
agc_add_default_test(agc_constant_bit_test_tests)
endif()
@@ -0,0 +1,13 @@
add_test_executable(agc_constant_phi_tests EXCLUDE_FROM_ALL tests/ConstantPhi.cpp)
target_link_libraries(agc_constant_phi_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_constant_phi_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_constant_phi_tests)
if(BUILD_TESTING)
set_target_properties(agc_constant_phi_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_constant_phi COMMAND agc_constant_phi_tests)
if(WIN32)
set_tests_properties(agc_constant_phi PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,13 @@
add_test_executable(agc_constant_select_tests EXCLUDE_FROM_ALL tests/ConstantSelect.cpp)
target_link_libraries(agc_constant_select_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_constant_select_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_constant_select_tests)
if(BUILD_TESTING)
set_target_properties(agc_constant_select_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_constant_select COMMAND agc_constant_select_tests)
if(WIN32)
set_tests_properties(agc_constant_select PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,14 @@
add_test_executable(agc_copy_guest_bytes_tests EXCLUDE_FROM_ALL tests/CopyGuestBytes.cpp)
target_include_directories(agc_copy_guest_bytes_tests PRIVATE
${LIBS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/core/shader/recompiler
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR}
)
target_link_libraries(agc_copy_guest_bytes_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_copy_guest_bytes_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_copy_guest_bytes_tests)
if(BUILD_TESTING)
agc_add_default_test(agc_copy_guest_bytes_tests)
endif()
@@ -0,0 +1,14 @@
add_test_executable(agc_dcc_key_sharing_tests EXCLUDE_FROM_ALL tests/DccKeySharing.cpp)
target_include_directories(agc_dcc_key_sharing_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_dcc_key_sharing_tests PRIVATE libc)
set_target_properties(agc_dcc_key_sharing_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_dcc_key_sharing_tests)
if(BUILD_TESTING)
set_target_properties(agc_dcc_key_sharing_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_dcc_key_sharing COMMAND agc_dcc_key_sharing_tests)
if(WIN32)
set_tests_properties(agc_dcc_key_sharing PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,14 @@
add_test_executable(agc_deferred_destruction_tests EXCLUDE_FROM_ALL tests/DeferredDestruction.cpp)
target_include_directories(agc_deferred_destruction_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_deferred_destruction_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_deferred_destruction_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_deferred_destruction_tests)
if(BUILD_TESTING)
set_target_properties(agc_deferred_destruction_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_deferred_destruction COMMAND agc_deferred_destruction_tests)
if(WIN32)
set_tests_properties(agc_deferred_destruction PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,13 @@
add_test_executable(agc_denormal_flush_elimination_tests EXCLUDE_FROM_ALL tests/DenormalFlushElimination.cpp)
target_link_libraries(agc_denormal_flush_elimination_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_denormal_flush_elimination_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_denormal_flush_elimination_tests)
if(BUILD_TESTING)
set_target_properties(agc_denormal_flush_elimination_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_denormal_flush_elimination COMMAND agc_denormal_flush_elimination_tests)
if(WIN32)
set_tests_properties(agc_denormal_flush_elimination PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,14 @@
add_test_executable(agc_depth_range_remap_tests EXCLUDE_FROM_ALL tests/DepthRangeRemap.cpp)
target_link_libraries(agc_depth_range_remap_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_depth_range_remap_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_depth_range_remap_tests)
if(BUILD_TESTING)
set_target_properties(agc_depth_range_remap_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_depth_range_remap COMMAND agc_depth_range_remap_tests)
set_tests_properties(agc_depth_range_remap PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1")
if(WIN32)
set_tests_properties(agc_depth_range_remap PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,15 @@
if(BUILD_TESTING)
add_test_executable(agc_descriptor_cache_failure_tests
tests/DescriptorCacheFailure.cpp
Graphics/src/ShaderResources.cpp)
target_include_directories(agc_descriptor_cache_failure_tests PRIVATE
$<TARGET_PROPERTY:libSceAgcDriver,INCLUDE_DIRECTORIES>)
target_link_libraries(agc_descriptor_cache_failure_tests PRIVATE libSceAgcDriver shader_recompiler_agc_driver libc libkernel)
configure_windows_unwind(agc_descriptor_cache_failure_tests)
add_test(NAME agc_descriptor_cache_failure COMMAND agc_descriptor_cache_failure_tests)
set_tests_properties(agc_descriptor_cache_failure PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(agc_descriptor_cache_failure PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,14 @@
add_test_executable(agc_device_fault_tests EXCLUDE_FROM_ALL tests/DeviceFault.cpp)
target_include_directories(agc_device_fault_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
set_target_properties(agc_device_fault_tests PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(agc_device_fault_tests PRIVATE libc libkernel)
configure_windows_unwind(agc_device_fault_tests)
if(BUILD_TESTING)
set_target_properties(agc_device_fault_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_device_fault COMMAND agc_device_fault_tests)
if(WIN32)
set_tests_properties(agc_device_fault PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,14 @@
add_test_executable(agc_device_memory_fallback_tests EXCLUDE_FROM_ALL tests/DeviceMemoryFallback.cpp)
target_include_directories(agc_device_memory_fallback_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_device_memory_fallback_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_device_memory_fallback_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_device_memory_fallback_tests)
if(BUILD_TESTING)
set_target_properties(agc_device_memory_fallback_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_device_memory_fallback COMMAND agc_device_memory_fallback_tests)
if(WIN32)
set_tests_properties(agc_device_memory_fallback PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,14 @@
add_test_executable(agc_display_buffer_dcc_tests EXCLUDE_FROM_ALL tests/DisplayBufferDcc.cpp)
target_include_directories(agc_display_buffer_dcc_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_display_buffer_dcc_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_display_buffer_dcc_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_display_buffer_dcc_tests)
if(BUILD_TESTING)
set_target_properties(agc_display_buffer_dcc_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_display_buffer_dcc COMMAND agc_display_buffer_dcc_tests)
if(WIN32)
set_tests_properties(agc_display_buffer_dcc PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@@ -0,0 +1,8 @@
add_test_executable(agc_dpp_write_tests EXCLUDE_FROM_ALL tests/DppWrite.cpp)
target_link_libraries(agc_dpp_write_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_dpp_write_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_dpp_write_tests)
if(BUILD_TESTING)
agc_add_default_test(agc_dpp_write_tests)
endif()
Loaded 100 of 1366 files, more files were not shown because too many files have changed in this diff. Show more