Compare commits

..

No commits in common. "main" and "feat/show-guest-prx-processing" have entirely different histories.

1254 changed files with 7639 additions and 96745 deletions

View File

@ -1,81 +0,0 @@
title: "Game name (PPSA00000)"
body:
- type: markdown
attributes:
value: |
One thread per title, for every platform: search this category for the title ID first. If a thread exists, reply there, also when your OS or GPU differs from the one it reports.
Name the discussion `Game name (title ID)`, for example `DOOM Shareware (PPSA99666)`, without the platform.
Bug reports and implementation work belong in issues and pull requests; link them here as the blocker.
Per-game progress is also tracked on the [blocker map](https://alxcbtw.github.io/anyps5-roadmap/), built from public GitHub data. Only share material you have the right to distribute.
- type: checkboxes
id: searched
attributes:
label: Existing thread
options:
- label: I searched this category for the title ID and no thread exists for this title on any platform.
required: true
- type: input
id: title
attributes:
label: Title
description: Name and title ID from `sce_sys/param.json`.
placeholder: DOOM Shareware (PPSA99666)
validations:
required: true
- type: markdown
attributes:
value: |
### First report
The fields below describe one run. Other systems and content versions are added to the thread as replies.
- type: input
id: version
attributes:
label: Content version tested
description: The content version from `sce_sys/param.json`.
placeholder: 01.001.001
validations:
required: true
- type: input
id: commit
attributes:
label: AnyPS5 commit
description: The `main` commit that was built, or the release that was used, and any pull requests merged on top.
validations:
required: true
- type: input
id: system
attributes:
label: System tested
description: OS, GPU and driver version, and the compiler if you built it yourself.
placeholder: Arch Linux, GCC 16.2.1, RTX 5080 (NVIDIA 615.78.08)
validations:
required: true
- type: dropdown
id: reached
attributes:
label: Furthest point
options:
- Does not convert
- Stops before the first frame
- Logos or loading screen
- Menu
- In game
validations:
required: true
- type: textarea
id: seen
attributes:
label: What is shown
description: The last screen that was seen, the frame rate there, and what works or is missing (picture, audio, input).
validations:
required: true
- type: textarea
id: blocker
attributes:
label: Current blocker
description: The line where it stops, the issue that tracks it, and the open pull requests and local changes the title still needs.
- type: textarea
id: steps
attributes:
label: How it was run
description: Commands and environment variables, and what the title needed beyond the usage guide.

View File

@ -1,65 +0,0 @@
name: Performance report
description: A slow scene or a change in speed, measured with a performance trace so that reports can be compared.
title: "perf: "
body:
- type: markdown
attributes:
value: |
Capture the scene with `APS5_TRACE_FRAMES=<start>:<count>` (presents; `APS5_TRACE_SECONDS=<start>:<length>` also works), as described under [Performance trace](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/BUILD.md#performance-trace). It writes `aps5-trace-<commit>-<pid>.json.gz` and appends one line to `aps5-summary.jsonl`.
- Run the scene once and restart the title, so the shader and pipeline caches are warm.
- Use the same scene, window, resolution and settings for every capture you compare.
- Set no other `APS5_TRACE_*` or `APS5_PROFILE_*` switch and do not redirect the log; they cost time.
- Capture the base and the change on the same machine in the same session.
A trace holds timings, scope and thread names and counts, no game data. The summary line names the GPU, driver and OS.
- type: input
id: scene
attributes:
label: Title and scene
description: Title ID and what the window covers (menu, level, position, cutscene).
placeholder: PPSA01325, main menu after the intro
validations:
required: true
- type: textarea
id: observed
attributes:
label: What is slow or what changed
description: What you see (frame time, stutter, a phase that grew) and, for a comparison, the two commits.
validations:
required: true
- type: textarea
id: summary
attributes:
label: Summary lines
description: The line of aps5-summary.jsonl for each capture. It holds the commit, the APS5_* switches, GPU, driver, OS, resolution and frame times.
render: json
validations:
required: true
- type: textarea
id: trace
attributes:
label: Traces
description: Attach each aps5-trace-<commit>-<pid>.json.gz (drag it into this field). They open in ui.perfetto.dev.
validations:
required: true
- type: textarea
id: digest
attributes:
label: Digest
description: The output of `python3 tools/trace_digest.py <trace>` (with `--stacks <file>.collapsed.txt` if you sampled the worker).
- type: textarea
id: stacks
attributes:
label: Worker stacks
description: Optional. The collapsed-stack file of APS5_SAMPLE_WORKER (<file stem>.collapsed.txt), attached. It opens in speedscope.app.
- type: checkboxes
id: protocol
attributes:
label: Capture
options:
- label: The caches were warm (the scene ran once and the title was restarted)
required: true
- label: No other APS5_TRACE_* or APS5_PROFILE_* switch was set and the log was not redirected
required: true
- label: Captures compared here come from the same machine, scene and settings

View File

@ -1,18 +1,16 @@
### What
### Tested
<!-- OS, title or homebrew, what was checked. "Not tested" is a valid answer. A performance change: the summary lines of main and of this PR from the same machine and scene, with the traces attached, captured as the Performance report issue form describes. -->
<!-- OS, title or homebrew, what was checked. "Not tested" is a valid answer. -->
### Checklist
<!-- Rules behind each item: https://github.com/boykopovar/AnyPS5/blob/main/CONTRIBUTING.md -->
- [ ] Based on current `main`; no other open PR implements the same functions
- [ ] One topic per PR; follow-ups go in a new PR
- [ ] No comments in code except [technical debt](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/CONVENTIONS.md)
- [ ] Unimplemented paths throw (`NotImplemented_nid_no_patch`); silent stubs are listed under Silent stubs in their library's [technical debt](https://github.com/boykopovar/AnyPS5/tree/main/docs/dev/debt) file
- [ ] Unimplemented paths throw (`NotImplemented_nid_no_patch`); silent stubs are listed in [TechnicalDebt](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/TechnicalDebt.md#silent-stubs)
- [ ] No notes, investigation `.md` files or images added to the repository (attach them to this PR)
- [ ] New third-party code is a submodule built from source
- [ ] Generic behaviour, not specific to one title
- [ ] Depends on: <!-- #PR, or none -->
- [ ] Unblocks: <!-- title ID and Game bring-up discussion, e.g. PPSA01325 #3232, or none -->
- [ ] GPUs tested: <!-- for AGC, shader recompiler or VideoOut changes: vendor, model, driver and OS of each GPU the GPU tests ran on; otherwise n/a -->
- [ ] AI-assisted: <!-- yes / no -->

View File

@ -14,34 +14,18 @@ jobs:
contents: read
pull-requests: write
steps:
- uses: actions/cache/restore@v6
with:
path: ${{ runner.temp }}/area-state.json
key: area-labels-${{ github.run_id }}
restore-keys: area-labels-
- id: prs
env:
GH_TOKEN: ${{ github.token }}
run: |
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 200 --json number,headRefOid \
--jq "map({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"
{
echo 'numbers<<EOF'
cat "$RUNNER_TEMP/area-todo"
gh pr list -R "$GITHUB_REPOSITORY" --base "${{ github.event.repository.default_branch }}" -L 200 --json number --jq '.[].number'
echo EOF
} >> "$GITHUB_OUTPUT"
- if: steps.prs.outputs.numbers != ''
uses: actions/checkout@v7
- if: steps.prs.outputs.numbers != ''
uses: actions/labeler@v7
with:
pr-number: ${{ steps.prs.outputs.numbers }}
- run: mv "$RUNNER_TEMP/area-next.json" "$RUNNER_TEMP/area-state.json"
- uses: actions/cache/save@v6
with:
path: ${{ runner.temp }}/area-state.json
key: area-labels-${{ github.run_id }}

View File

@ -12,11 +12,6 @@ on:
description: Pull request number to build
required: true
type: string
linux:
description: Also build and test on Linux
required: false
default: true
type: boolean
permissions:
contents: read
@ -44,8 +39,6 @@ jobs:
pr: ${{ steps.gate.outputs.pr }}
head: ${{ steps.gate.outputs.head }}
relinker: ${{ steps.gate.outputs.relinker }}
register-context: ${{ steps.gate.outputs.register-context }}
write-watch: ${{ steps.gate.outputs.write-watch }}
full: ${{ steps.gate.outputs.full }}
steps:
- uses: actions/github-script@v8
@ -62,8 +55,6 @@ jobs:
core.setOutput('pr', '');
core.setOutput('head', '');
core.setOutput('relinker', true);
core.setOutput('register-context', true);
core.setOutput('write-watch', true);
core.setOutput('full', true);
await core.summary.addRaw(`Building main at ${context.sha}: all builds and tests enabled`).write();
return;
@ -106,32 +97,16 @@ jobs:
});
relinker = commits.length > 0;
}
const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number, per_page: 100 });
const paths = files.flatMap((file) => [file.filename, file.previous_filename].filter(Boolean));
const touches = (...prefixes) => files.length >= 3000 ||
paths.some((path) => prefixes.some((prefix) => prefix.endsWith('/') ? path.startsWith(prefix) : path === prefix));
const workflow = '.github/workflows/build.yml';
const libc = 'core/libs/prx/libc/';
relinker = relinker && touches(
'core/relinker/', libc, 'core/libs/prx/libkernel/AppMetadata/', 'core/libs/tests/ParamJsonParser.cpp',
'core/shader/recompiler/tests/CacheFileReadTests.cpp', 'core/shader/recompiler/ShaderCacheDirectory.hpp',
'tools/', 'cmake/', 'CMakeLists.txt', workflow
);
const registerContext = touches('core/libs/tests/register_context/', 'core/libs/tests/RegisterContext.cpp', libc, workflow);
const writeWatch = touches('core/libs/tests/write_watch/', 'core/libs/tests/WriteWatch.cpp', libc, workflow);
const { data: main } = await github.rest.repos.getBranch({ owner, repo, branch: 'main' });
core.setOutput('sha', main.commit.sha);
core.setOutput('pr', String(pull_number));
core.setOutput('head', pr.head.sha);
core.setOutput('relinker', relinker);
core.setOutput('register-context', registerContext);
core.setOutput('write-watch', writeWatch);
core.setOutput('full', manual);
core.info(`PR #${pull_number} at ${pr.head.sha} merged into main at ${main.commit.sha}`);
await core.summary.addRaw(
`PR #${pull_number} at ${pr.head.sha} merged into main at ${main.commit.sha}: full builds ${manual ? 'enabled' : 'disabled'}, ` +
`relinker ${relinker ? 'enabled' : 'skipped: no relinker file changed or the author has no commit in main'}, ` +
`register context tests ${registerContext ? 'enabled' : 'skipped'}, write watch tests ${writeWatch ? 'enabled' : 'skipped'}`
`relinker ${relinker ? 'enabled' : 'skipped because the author has no commit in main'}`
).write();
relinker:
@ -187,8 +162,6 @@ jobs:
- run: ctest --test-dir build-relinker --output-on-failure --timeout 120
register-context:
needs: eligibility
if: ${{ needs.eligibility.outputs.register-context == 'true' }}
strategy:
fail-fast: false
matrix:
@ -203,8 +176,6 @@ jobs:
- run: ctest --test-dir build/register-context --output-on-failure --repeat until-fail:3 --no-tests=error
write-watch:
needs: eligibility
if: ${{ needs.eligibility.outputs.write-watch == 'true' }}
strategy:
fail-fast: false
matrix:
@ -219,7 +190,7 @@ jobs:
linux:
needs: eligibility
if: ${{ (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.linux)) && needs.eligibility.outputs.full == 'true' }}
if: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && needs.eligibility.outputs.full == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@ -244,17 +215,8 @@ jobs:
key: linux
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
evict-old-files: job
- name: Install lavapipe and the SDL audio and XRandR development files
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libvulkan1 mesa-vulkan-drivers libpipewire-0.3-dev libpulse-dev libasound2-dev libxrandr-dev
- id: mesa
run: |
echo "version=$(dpkg-query -W -f='${Version}' mesa-vulkan-drivers)" >> "$GITHUB_OUTPUT"
echo "cpu=$(grep -m1 '^flags' /proc/cpuinfo | sha256sum | cut -c1-12)" >> "$GITHUB_OUTPUT"
- uses: actions/cache/restore@v6
with:
path: .mesa-shader-cache
key: mesa-${{ steps.mesa.outputs.version }}-${{ steps.mesa.outputs.cpu }}-${{ github.sha }}
restore-keys: mesa-${{ steps.mesa.outputs.version }}-${{ steps.mesa.outputs.cpu }}-
- name: Install lavapipe for the GPU execution tests
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libvulkan1 mesa-vulkan-drivers
- run: cmake -S . -B build $CONFIGURE -DANYPS5_ENABLE_SPIRV_TOOLS=ON
- name: Check that every test executable is registered with ctest
run: python3 tools/check_test_registration.py build
@ -264,12 +226,6 @@ jobs:
env:
ANYPS5_REQUIRE_VULKAN: 1
GALLIUM_OVERRIDE_CPU_CAPS: avx
MESA_SHADER_CACHE_DIR: ${{ github.workspace }}/.mesa-shader-cache
- if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && !cancelled() }}
uses: actions/cache/save@v6
with:
path: .mesa-shader-cache
key: mesa-${{ steps.mesa.outputs.version }}-${{ steps.mesa.outputs.cpu }}-${{ github.sha }}
- run: python3 tools/check_skips.py build/ctest.xml
- name: Report tests slower than 30 s
if: ${{ !cancelled() }}
@ -324,7 +280,7 @@ jobs:
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
evict-old-files: job
- shell: cmd
run: cmake -S . -B build %CONFIGURE% "-DANYPS5_AGC_EXECUTION_TESTS=F16ToF32Unpack;F64FmaSubnormals;RegistrationPrepareShutdown"
run: cmake -S . -B build %CONFIGURE%
- name: Check that every test executable is registered with ctest
shell: cmd
run: python tools/check_test_registration.py build

View File

@ -9,10 +9,6 @@ on:
permissions: {}
concurrency:
group: conflict-label
cancel-in-progress: true
jobs:
label:
runs-on: ubuntu-latest

View File

@ -24,7 +24,7 @@ jobs:
force_label() { gh label create "$1" --color "$2" --description "$3" --force; }
force_label 'needs rebase' b60205 'Conflicts with main, rebase needed'
force_label 'open PR limit' d93f0b 'Closed over the open PR limit: 1 without merged PRs, 20 with at least 1, none from 20'
force_label 'intake paused' d93f0b 'Closed while PR intake is limited to authors with at least 10 PRs merged into main'
force_label ci 5319e7 '.github'
force_label agc 1d76db 'libSceAgc*'
force_label shader 1d76db 'core/shader'
@ -35,4 +35,3 @@ jobs:
label prx c5def5 'Other PRX libraries'
label build bfd4f2 'CMake, submodules, 3rdparty'
label docs 0075ca 'Documentation'
label stale ededed 'Conflicting with main and no activity for 7 days'

View File

@ -1,55 +0,0 @@
name: Pages
on:
push:
branches: [main]
workflow_dispatch:
release:
types: [published]
workflow_run:
workflows: [Release]
types: [completed]
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
pages:
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v7
with:
ref: main
fetch-depth: 0
- name: Build web converter
shell: bash
run: |
git clone --depth 1 https://github.com/emscripten-core/emsdk.git "$RUNNER_TEMP/emsdk"
"$RUNNER_TEMP/emsdk/emsdk" install 6.0.3
"$RUNNER_TEMP/emsdk/emsdk" activate 6.0.3
source "$RUNNER_TEMP/emsdk/emsdk_env.sh"
emcmake cmake -S . -B build-wasm -DANYPS5_BUILD_WASM=ON -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release
cmake --build build-wasm --target relinker_web --parallel 2
- name: Test web converter
run: python3 tools/test_web.py --build build-wasm
- name: Download latest release libraries
env:
GITHUB_TOKEN: ${{ github.token }}
run: python3 tools/build_web.py --build build-wasm --libraries
- run: python3 tools/progress.py build-wasm/web
- run: python3 tools/progress_history.py build-wasm/web
- uses: actions/upload-pages-artifact@v5
with:
path: build-wasm/web
- id: deployment
uses: actions/deploy-pages@v5

View File

@ -3,7 +3,7 @@ name: PR intake
on:
pull_request_target:
branches: [main]
types: [opened, reopened]
types: [opened]
permissions: {}
@ -27,6 +27,7 @@ jobs:
if (pr.state !== 'open' || pr.base.ref !== 'main') {
return;
}
const minimumMergedPRs = 10;
const { data: mergedPRs } = await github.rest.search.issuesAndPullRequests({
q: `repo:${owner}/${repo} is:pr is:merged base:main author:${pr.user.login}`,
per_page: 1
@ -34,20 +35,10 @@ jobs:
if (mergedPRs.incomplete_results || !Number.isSafeInteger(mergedPRs.total_count) || mergedPRs.total_count < 0) {
throw new Error('Could not reliably count merged pull requests');
}
const merged = mergedPRs.total_count;
if (merged >= 20) {
if (mergedPRs.total_count >= minimumMergedPRs) {
return;
}
const allowedOpen = merged >= 1 ? 20 : 1;
const openPRs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', base: 'main', per_page: 100
});
const open = new Set(openPRs.filter((p) => p.user.login === pr.user.login).map((p) => p.number));
open.add(pull_number);
if (open.size <= allowedOpen) {
return;
}
const name = 'open PR limit';
const name = 'intake paused';
try {
await github.rest.issues.getLabel({ owner, repo, name });
} catch (error) {
@ -57,7 +48,7 @@ jobs:
try {
await github.rest.issues.createLabel({
owner, repo, name, color: 'd93f0b',
description: 'Closed over the open PR limit: 1 without merged PRs, 20 with at least 1, none from 20'
description: 'Closed while PR intake is limited to authors with at least 10 PRs merged into main'
});
} catch (createError) {
if (createError.status !== 422) {
@ -67,7 +58,7 @@ jobs:
}
}
await github.rest.issues.addLabels({ owner, repo, issue_number: pull_number, labels: [name] });
const body = 'Thank you for your contribution! Authors with no pull requests merged into `main` can have 1 open pull request at a time, authors with at least 1 merged can have 20, and from 20 merged there is no limit. This PR has been automatically closed and labeled "open PR limit" because that limit was reached. This is not a judgment of the quality of your changes: reopen it once one of your open pull requests is merged or closed.';
const body = 'Thank you for your interest in the project! We are sorry, but we cannot accept more PRs while we work through the existing backlog. This PR has been automatically closed and labeled "intake paused". This is not a judgment of the quality of your changes.';
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pull_number, per_page: 100
});

View File

@ -22,30 +22,15 @@ jobs:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/cache/restore@v6
with:
path: ${{ runner.temp }}/overlap-state.json
key: pr-overlap-${{ github.run_id }}
restore-keys: pr-overlap-
- env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
out=$RUNNER_TEMP/overlap
state=$RUNNER_TEMP/overlap-state.json
python3 tools/pr_overlap.py --base "${{ github.event.repository.default_branch }}" --out "$out"
[ -s "$state" ] || echo '{}' > "$state"
record() { jq --arg pr "$1" --arg h "$2" '.[$pr] = $h' "$state" > "$state.new" && mv "$state.new" "$state"; }
shopt -s nullglob
marker='<!-- pr-overlap -->'
prev=
for file in "$out"/*.md; do
[ -z "$prev" ] || record "$prev" "$hash"
pr=$(basename "$file" .md)
hash=$(sha256sum "$file" | cut -d' ' -f1)
prev=
[ "$(jq -r --arg pr "$pr" '.[$pr] // empty' "$state")" = "$hash" ] && continue
prev=$pr
comment=$(gh api "repos/$GH_REPO/issues/$pr/comments" --paginate \
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$marker\"))) | [.id, .body] | @json" | head -n1)
id=$(jq -r '.[0] // empty' <<< "${comment:-null}")
@ -60,11 +45,3 @@ jobs:
gh api -X DELETE "repos/$GH_REPO/issues/comments/$id"
fi
done
[ -z "$prev" ] || record "$prev" "$hash"
reports=$(for file in "$out"/*.md; do basename "$file" .md; done | jq -R . | jq -sc .)
jq -c --argjson keep "$reports" 'with_entries(select(.key | IN($keep[])))' "$state" > "$state.new" && mv "$state.new" "$state"
- if: always()
uses: actions/cache/save@v6
with:
path: ${{ runner.temp }}/overlap-state.json
key: pr-overlap-${{ github.run_id }}

30
.github/workflows/progress.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Progress
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
progress:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v7
- run: python3 tools/progress.py out
- uses: actions/upload-pages-artifact@v5
with:
path: out
- id: deployment
uses: actions/deploy-pages@v5

View File

@ -60,9 +60,9 @@ jobs:
with:
key: ${{ matrix.platform }}
save: false
- name: Install lavapipe for the GPU execution tests and the audio development files
- name: Install lavapipe for the GPU execution tests
if: matrix.platform == 'linux'
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libvulkan1 mesa-vulkan-drivers libpipewire-0.3-dev libpulse-dev libasound2-dev libxrandr-dev
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends libvulkan1 mesa-vulkan-drivers
- name: Configure Linux
if: matrix.platform == 'linux'
run: cmake -S . -B build $CONFIGURE

View File

@ -1,28 +0,0 @@
name: Stale conflicts
on:
schedule:
- cron: '30 3 * * *'
workflow_dispatch:
permissions: {}
jobs:
warn:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v11
with:
only-pr-labels: needs rebase
days-before-issue-stale: -1
days-before-issue-close: -1
days-before-pr-stale: 7
days-before-pr-close: -1
stale-pr-label: stale
stale-pr-message: 'This pull request has conflicted with `main` and had no activity for 7 days. Rebase it on current `main` so it can be reviewed and merged. It will not be closed automatically.'
remove-pr-stale-when-updated: true
exempt-draft-pr: true
operations-per-run: 200

6
.gitmodules vendored
View File

@ -28,12 +28,6 @@
[submodule "3rdparty/stb"]
path = 3rdparty/stb
url = https://github.com/nothings/stb.git
[submodule "3rdparty/tracy"]
path = 3rdparty/tracy
url = https://github.com/wolfpld/tracy.git
[submodule "3rdparty/libjpeg-turbo"]
path = 3rdparty/libjpeg-turbo
url = https://github.com/libjpeg-turbo/libjpeg-turbo.git
[submodule "3rdparty/libusb-cmake"]
path = 3rdparty/libusb-cmake
url = https://github.com/libusb/libusb-cmake.git

@ -1 +0,0 @@
Subproject commit bea6567b63796ab27c1c33257d230284fb2d8316

1
3rdparty/tracy vendored

@ -1 +0,0 @@
Subproject commit 30997d5ca6bb632cc10807a1da8a6d3de0aeeb3c

View File

@ -10,19 +10,6 @@ endfunction()
option(BUILD_TESTING "Build and register relinker tests" OFF)
option(ANYPS5_RELINKER_ONLY "Build the relinker without system libraries or third-party dependencies" OFF)
option(ANYPS5_BUILD_WASM "Build the browser relinker" OFF)
if(ANYPS5_BUILD_WASM)
if(NOT EMSCRIPTEN)
message(FATAL_ERROR "ANYPS5_BUILD_WASM requires emcmake and Emscripten")
endif()
if(BUILD_TESTING)
message(FATAL_ERROR "Use the native build for C++ tests and the browser tests for WASM")
endif()
set(ANYPS5_RELINKER_CLI OFF CACHE BOOL "Build the native relinker command line" FORCE)
add_subdirectory(core/relinker)
return()
endif()
if(BUILD_TESTING)
enable_testing()
if(MINGW)
@ -37,9 +24,7 @@ if(BUILD_TESTING)
endif()
option(ANYPS5_ENABLE_SPIRV_TOOLS "Enable SPIRV-Tools validation and optimization" OFF)
option(ANYPS5_ENABLE_ASAN "Build Linux targets with AddressSanitizer and use an ASan-compatible guest arena range" OFF)
option(APS5_ENABLE_TIMING_LOG "Write frame timings asynchronously to frame-timing.log" OFF)
option(APS5_ENABLE_TRACY "Build the Tracy profiler client and report performance timer scopes as Tracy zones" OFF)
option(ANYPS5_USE_SYSTEM_DEPS "Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the bundled submodules" OFF)
add_compile_definitions(APS5_ENABLE_TIMING_LOG=$<BOOL:${APS5_ENABLE_TIMING_LOG}>)
include(cmake/SystemDeps.cmake)
@ -47,35 +32,11 @@ include(cmake/SystemDeps.cmake)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(ANYPS5_ENABLE_ASAN)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "ANYPS5_ENABLE_ASAN is supported only on Linux")
endif()
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang)$")
message(FATAL_ERROR "ANYPS5_ENABLE_ASAN requires GCC or Clang")
endif()
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
add_compile_definitions(ANYPS5_ENABLE_ASAN=1)
elseif(NOT APPLE)
if(NOT APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
endif()
if(BUILD_TESTING)
add_test_executable(shader_cache_file_read_tests
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/CacheFileReadTests.cpp)
target_include_directories(shader_cache_file_read_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler)
add_test(NAME shader_cache_file_read COMMAND shader_cache_file_read_tests)
set_tests_properties(shader_cache_file_read PROPERTIES TIMEOUT 20)
add_test_executable(request_serializer_tests
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/tests/RequestSerializerTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp)
target_include_directories(request_serializer_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler
${CMAKE_CURRENT_SOURCE_DIR}/core/shader/recompiler/ControlFlow/include)
add_test(NAME request_serializer COMMAND request_serializer_tests)
set_tests_properties(request_serializer PROPERTIES TIMEOUT 20)
find_package(Python3 QUIET COMPONENTS Interpreter)
if(Python3_Interpreter_FOUND)
add_test(NAME pr_overlap_encoding COMMAND ${Python3_EXECUTABLE}
@ -84,57 +45,22 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_dependencies.py)
add_test(NAME pr_overlap_tools COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap.py)
add_test(NAME progress_history COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_history.py)
add_test(NAME progress_report COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress.py)
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}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_render_sliver.py)
add_test(NAME progress_wrappers COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_wrappers.py)
add_test(NAME progress_read_encoding COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_read_encoding.py)
add_test(NAME import_audit COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_import_audit.py)
add_test(NAME conventions_function_bodies COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_comments.py)
add_test(NAME conventions_wrapped_declarations COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_wrapped_declarations.py)
add_test(NAME conventions_ci_gate COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_ci_gate.py)
add_test(NAME test_registration COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_test_registration.py)
add_test(NAME check_skips COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips.py)
add_test(NAME stubgen COMMAND ${Python3_EXECUTABLE}
${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 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}
${CMAKE_CURRENT_SOURCE_DIR}/tools/hw-oracle/test_hw_oracle.py)
add_test(NAME pr_overlap_exports COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_exports.py)
add_test(NAME pr_overlap_wrapped_exports COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_wrapped_exports.py)
add_test(NAME pr_overlap_own_branch COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_own_branch.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}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_package_release.py)
add_test(NAME pad_input COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pad_input.py)
set_tests_properties(pad_input PROPERTIES TIMEOUT 20)
add_test(NAME trace_digest COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_trace_digest.py)
add_test(NAME conventions_body_bom COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_body_bom.py)
add_test(NAME conventions_agent_files COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_agent_files.py)
endif()
endif()
@ -163,22 +89,6 @@ if(NOT ANYPS5_USE_SYSTEM_DEPS)
set(CMAKE_EXE_LINKER_FLAGS "")
add_subdirectory(3rdparty/SDL2)
set(CMAKE_EXE_LINKER_FLAGS "${ANYPS5_EXE_LINKER_FLAGS}")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
get_directory_property(ANYPS5_SDL_X11_XRANDR DIRECTORY 3rdparty/SDL2 DEFINITION HAVE_X11_XRANDR)
if(NOT ANYPS5_SDL_X11_XRANDR)
message(FATAL_ERROR "SDL has no XRandR support, so X11 display refresh rates cannot be queried. "
"Install libxrandr-dev (Debian, Ubuntu) or libXrandr-devel (Fedora), "
"enable SDL_X11_XRANDR, then rerun configuration.")
endif()
get_directory_property(ANYPS5_SDL_PIPEWIRE DIRECTORY 3rdparty/SDL2 DEFINITION HAVE_PIPEWIRE)
get_directory_property(ANYPS5_SDL_PULSEAUDIO DIRECTORY 3rdparty/SDL2 DEFINITION HAVE_PULSEAUDIO)
get_directory_property(ANYPS5_SDL_ALSA DIRECTORY 3rdparty/SDL2 DEFINITION HAVE_ALSA)
if(NOT ANYPS5_SDL_PIPEWIRE AND NOT ANYPS5_SDL_PULSEAUDIO AND NOT ANYPS5_SDL_ALSA)
message(FATAL_ERROR "SDL found no PipeWire, PulseAudio or ALSA development files, so audio output would be silent. "
"Install libpipewire-0.3-dev, libpulse-dev and libasound2-dev (Debian, Ubuntu) "
"or pipewire-devel, pulseaudio-libs-devel and alsa-lib-devel (Fedora), then rerun configuration.")
endif()
endif()
endif()
set(ARCHITECTURE x86_64)
add_subdirectory(3rdparty/ffmpeg-core)
@ -188,10 +98,6 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(ffmpeg INTERFACE LINKER:--exclude-libs,ALL)
endif()
if(APS5_ENABLE_TRACY)
include(cmake/Tracy.cmake)
endif()
set(FT_DISABLE_ZLIB ON CACHE BOOL "" FORCE)
set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE)
set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE)
@ -208,14 +114,6 @@ if(NOT ANYPS5_USE_SYSTEM_DEPS)
endif()
endif()
set(LIBUSB_BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(LIBUSB_INSTALL_TARGETS OFF CACHE BOOL "" FORCE)
set(LIBUSB_BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(LIBUSB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(LIBUSB_ENABLE_UDEV OFF CACHE BOOL "" FORCE)
add_subdirectory(3rdparty/libusb-cmake EXCLUDE_FROM_ALL)
set_target_properties(usb-1.0 PROPERTIES POSITION_INDEPENDENT_CODE ON)
add_subdirectory(core/shader/recompiler)
add_subdirectory(core/Decoder/Jpeg)
add_subdirectory(core/Decoder/Png)

View File

@ -5,7 +5,7 @@
- Follow the [coding conventions](docs/dev/CONVENTIONS.md): naming, no comments except [technical debt](docs/dev/TechnicalDebt.md), Conventional Commits.
- Every function either does exactly what it is supposed to or throws. Unimplemented exports call `NotImplemented_nid_no_patch(__func__)` (see [libSceAudioIn](core/libs/prx/libSceAudioIn/Export.cpp)).
- Pull requests that add or change shader instruction semantics say where they come from: measured on hardware (which GPU and what was checked, e.g. with the [hardware oracle](docs/dev/HW_ORACLE.md)) or the exact source (ISA section, LLVM, ACO or Mesa file). Reviewers check the semantics on hardware. Cases the source doesn't settle throw, and behaviour not verified on hardware is recorded in [technical debt](docs/dev/TechnicalDebt.md).
- A silent stub is allowed only when it unblocks a title and only affects the UI; add it under Silent stubs in its library's [technical debt](docs/dev/debt/) file.
- A silent stub is allowed only when it unblocks a title and only affects the UI; add it to [silent stubs](docs/dev/TechnicalDebt.md#silent-stubs).
- Missing imports fail at startup with a clear error; don't replace them with fallbacks that keep running.
- Implement the general behaviour of a function, not what one title happens to need.
- Don't add replacements for modules that titles ship themselves in `sce_module/`, `sce_modules/` or `prx/`: engine or middleware modules (Cohtml, FMOD, GOG Galaxy) and SDK libraries that only wrap other system libraries (NpCppWebApi over NpWebApi2). The relinker converts and loads the title's own module, and a host library with the same name is left out of `DT_NEEDED`. Only system libraries, which reach the kernel or the hardware, are reimplemented in [core/libs/prx](core/libs/prx).
@ -37,9 +37,9 @@ For pull requests, full Linux and Windows builds and tests run only when a colla
gh workflow run build.yml --repo boykopovar/AnyPS5 --ref main --field pr=1234
```
This manual run also builds and tests the relinker on Linux, Windows and macOS, regardless of the author's previous contributions or the changed files. The relinker matrix also runs automatically when a pull request targeting `main` is opened, updated or reopened, if its author already has a commit in `main` and it changes a file the relinker-only build uses; full builds never run automatically for pull requests. Every job that runs automatically for pull requests runs only when the pull request changes its files: a `paths` filter on the `pull_request` trigger, or `needs: eligibility` with an `if` on the files output it sets in `build.yml`. The Conventions check fails a workflow change that adds a job without one. All build jobs build the selected pull request merged into the current `main` (GitHub's merge commit), so a pull request that no longer merges cleanly must be rebased first. New commits require another manual run for full builds.
This manual run also builds and tests the relinker on Linux, Windows and macOS, regardless of the author's previous contributions or the changed files. The relinker matrix also runs automatically when a pull request targeting `main` is opened, updated or reopened, if its author already has a commit in `main`; full builds never run automatically for pull requests. All build jobs build the selected pull request merged into the current `main` (GitHub's merge commit), so a pull request that no longer merges cleanly must be rebased first. New commits require another manual run for full builds.
The Conventions check runs automatically for every contributor, including first-time contributors. It uses the base branch checker and reads pull request Git objects without executing pull request code. The Conventions check runs on every pull request and fails when a rule on this page is broken. It accepts code comments only when the pull request also changes [technical debt](docs/dev/TechnicalDebt.md), the index or a file in `docs/dev/debt/`, and only UTF-8 text files. Run it locally before pushing:
The Conventions check runs automatically for every contributor, including first-time contributors. It uses the base branch checker and reads pull request Git objects without executing pull request code. The Conventions check runs on every pull request and fails when a rule on this page is broken. It accepts code comments only when the pull request also changes [TechnicalDebt](docs/dev/TechnicalDebt.md), and only UTF-8 text files. Run it locally before pushing:
```
python3 tools/check_conventions.py --base origin/main
@ -50,20 +50,13 @@ python3 tools/check_conventions.py --base origin/main
The [pull request template](.github/pull_request_template.md) is the checklist for these rules.
- One branch per topic, based on current `main`. Follow-up work goes in a new pull request, not into an open one.
- 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.
- 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 stop or a wrong result seen on a title, name it on the checklist line with the title ID and its [Game bring-up](https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up) discussion (`Unblocks: PPSA01325 #3232`; `none` otherwise). Reviewers review these first.
- Fixes a title needs go to `main` as pull requests. A Game bring-up discussion lists the open pull requests and local changes the title still needs; update the list when one is merged, closed or replaced.
- The PR overlap comment lists open pull requests that change the same lines or exports; agree with their authors which one goes first and declare it with `Depends on`.
- If a pull request needs another one first, say so in the description (`Depends on #N`).
- Run the tests before opening the pull request and describe what was tested (OS, title or homebrew).
- A pull request that claims a change in speed shows the summary lines of `main` and of the change, from the same machine and scene, and attaches the traces, captured as the [Performance report](.github/ISSUE_TEMPLATE/performance.yml) issue form describes. A slow scene is reported with the same form.
- Changes to the AGC driver, the shader recompiler or VideoOut name each GPU the GPU tests ran on (vendor, model, driver, OS) on the `GPUs tested` checklist line. CI runs these tests only on lavapipe, and hardware drivers fail where lavapipe and other vendors pass, so a reviewer can ask for a run on another vendor before merging.
- Investigation notes, reports, screenshots and logs go in the pull request, not in the repository. Images for documentation go in the [gist](https://gist.github.com/boykopovar/0e53f2e1426f29ecd41e3b51540b8a90) comments.
- Say whether the change was written with AI assistance. The author of the pull request is responsible for every line of it.
## Documentation
Text in the repository is dry, strict and concise. Record known gaps, unknown NIDs and signatures in [technical debt](docs/dev/TechnicalDebt.md) instead of new files. An entry goes in `docs/dev/debt/<name>.md`, named after the first `core/` path it links (`<name>` for `core/libs/prx/<name>/...`, otherwise for `core/<name>/...`), under the same `###` sections as the index; entries that link neither stay in the index. A pull request whose `TechnicalDebt.md` change conflicts after the split rebases and runs `python3 tools/debt_split.py resolve` during the stopped rebase, which moves its added, edited and removed entries to their files; `python3 tools/debt_split.py split` moves entries left in the index.
Text in the repository is dry, strict and concise. Record known gaps, unknown NIDs and signatures in [technical debt](docs/dev/TechnicalDebt.md) instead of new files.

View File

@ -2,33 +2,29 @@
Tool for automatic executables porting to Linux and Windows.
[**Web converter**](https://boykopovar.github.io/AnyPS5/) - files stay on your device.
![anyps5_collage1](https://github.com/user-attachments/assets/0f8e13e8-1cb7-4c69-8075-39978e138994)
[Discord](https://discord.gg/BHFztBPUe)
Includes a [relinker](core/relinker) that converts executable to the target system's native format and implementations of [system prx libraries](core/libs/prx) suitable for dynamic linking. No emulation or separate runtime process.
[Usage](docs/user/USAGE.md), [Build instructions](docs/dev/BUILD.md), [Architecture](docs/dev/ARCHITECTURE.md), [Technical debt of the project](docs/dev/TechnicalDebt.md), [code style conventions](docs/dev/CONVENTIONS.md), [contributing](CONTRIBUTING.md)
## Status
[![libraries](https://boykopovar.github.io/AnyPS5/badge-libraries.svg)](https://boykopovar.github.io/AnyPS5/progress.html) [![shaders](https://boykopovar.github.io/AnyPS5/badge-shaders.svg)](https://boykopovar.github.io/AnyPS5/progress.html)
[![libraries](https://boykopovar.github.io/AnyPS5/badge-libraries.svg)](https://boykopovar.github.io/AnyPS5/) [![shaders](https://boykopovar.github.io/AnyPS5/badge-shaders.svg)](https://boykopovar.github.io/AnyPS5/)
[![progress map](https://boykopovar.github.io/AnyPS5/progress.svg)](https://boykopovar.github.io/AnyPS5/progress.html)
[Progress history](https://boykopovar.github.io/AnyPS5/history.html)
[![progress map](https://boykopovar.github.io/AnyPS5/progress.svg)](https://boykopovar.github.io/AnyPS5/)
<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>
[List of verified games](docs/user/COMPATIBILITY.md)
Dreaming Sarah (2D platformer) runs at a stable 60 fps on a GTX 1050 Ti / i5-7500 3.4GHz.
Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates.
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`).
## Compatibility
Per-game progress (stage reached, open blockers, libraries and GPU coverage) is tracked on the [blocker map](https://alxcbtw.github.io/anyps5-roadmap/), a community project built from public GitHub data ([source](https://github.com/AlxcBTW/anyps5-roadmap)). Test reports go in the [Game bring-up](https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up) discussions, one thread per title.
See the [game compatibility list](docs/user/COMPATIBILITY.md) for tested games and known issues.
## Input mapping

View File

@ -1,21 +0,0 @@
set(TRACY_ENABLE ON CACHE BOOL "" FORCE)
set(TRACY_STATIC OFF CACHE BOOL "" FORCE)
set(TRACY_ON_DEMAND ON CACHE BOOL "" FORCE)
set(TRACY_ONLY_LOCALHOST ON CACHE BOOL "" FORCE)
set(TRACY_NO_CRASH_HANDLER ON CACHE BOOL "" FORCE)
set(TRACY_NO_CODE_TRANSFER ON CACHE BOOL "" FORCE)
set(TRACY_NO_SAMPLING ON CACHE BOOL "" FORCE)
set(TRACY_NO_SYSTEM_TRACING ON CACHE BOOL "" FORCE)
set(TRACY_NO_CALLSTACK ON CACHE BOOL "" FORCE)
set(TRACY_NO_FRAME_IMAGE ON CACHE BOOL "" FORCE)
set(TRACY_NO_VERIFY ON CACHE BOOL "" FORCE)
set(CMAKE_DISABLE_FIND_PACKAGE_rocprofiler-sdk ON)
add_subdirectory(${CMAKE_SOURCE_DIR}/3rdparty/tracy ${CMAKE_BINARY_DIR}/3rdparty/tracy EXCLUDE_FROM_ALL)
if(MINGW)
target_sources(TracyClient PRIVATE ${CMAKE_CURRENT_LIST_DIR}/TracyThreadExit.cpp)
target_compile_definitions(TracyClient PRIVATE TRACY_DELAYED_INIT)
get_target_property(tracyInterfaceDefinitions TracyClient INTERFACE_COMPILE_DEFINITIONS)
list(FILTER tracyInterfaceDefinitions EXCLUDE REGEX "^(WINVER|_WIN32_WINNT)=")
set_target_properties(TracyClient PROPERTIES INTERFACE_COMPILE_DEFINITIONS "${tracyInterfaceDefinitions}")
endif()

View File

@ -1,3 +0,0 @@
extern "C" int __cxa_thread_atexit(void (*)(void*), void*, void*) {
return 0;
}

View File

@ -25,14 +25,7 @@ target_include_directories(nid_patcher PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/i
target_compile_features(nid_patcher PRIVATE cxx_std_20)
if(MINGW)
target_link_options(nid_patcher PRIVATE -municode -static -static-libgcc -static-libstdc++)
include(CheckCXXSourceCompiles)
set(CMAKE_REQUIRED_FLAGS -fno-asynchronous-unwind-tables)
check_cxx_source_compiles("#ifdef __USING_SJLJ_EXCEPTIONS__\n#error\n#endif\nint main() { return 0; }" ANYPS5_MINGW_DWARF_EXCEPTIONS)
unset(CMAKE_REQUIRED_FLAGS)
if(NOT ANYPS5_MINGW_DWARF_EXCEPTIONS)
message(FATAL_ERROR "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} selects SjLj exceptions with -fno-asynchronous-unwind-tables; the libraries need DWARF unwinding. Use the MinGW-w64 toolchain listed in docs/dev/BUILD.md.")
endif()
target_link_options(nid_patcher PRIVATE -static -static-libgcc -static-libstdc++)
add_compile_options(-fno-asynchronous-unwind-tables)
endif()
@ -62,7 +55,6 @@ target_include_directories(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
configure_windows_unwind(libc)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(libc PRIVATE LINKER:-z,now)
target_link_options(libc INTERFACE LINKER:--no-as-needed)
endif()
set(UNPATCHED_DIR ${CMAKE_CURRENT_BINARY_DIR}/libs/unpatched)
@ -109,9 +101,9 @@ foreach(LIB_DIR ${LIBS_DIRS})
endif()
if(LIB_NAME STREQUAL "libkernel")
target_link_libraries(${LIB_NAME} PUBLIC libc)
target_link_libraries(${LIB_NAME} PRIVATE libc)
else()
target_link_libraries(${LIB_NAME} PRIVATE libkernel PUBLIC libc)
target_link_libraries(${LIB_NAME} PRIVATE libkernel libc)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(${LIB_NAME} PRIVATE LINKER:-z,now)
@ -149,79 +141,26 @@ foreach(LIB_DIR ${LIBS_DIRS})
endif()
endforeach()
if(APS5_ENABLE_TRACY)
set_target_properties(TracyClient PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${UNPATCHED_DIR}
LIBRARY_OUTPUT_DIRECTORY ${UNPATCHED_DIR}
ARCHIVE_OUTPUT_DIRECTORY ${IMPLIB_DIR}
)
set(TRACY_CLIENT_COPIED ${CMAKE_CURRENT_BINARY_DIR}/TracyClient.copied)
add_custom_command(
OUTPUT ${TRACY_CLIENT_COPIED}
COMMAND ${CMAKE_COMMAND} -E make_directory ${PATCHED_DIR}
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:TracyClient> ${PATCHED_DIR}
COMMAND ${CMAKE_COMMAND} -E touch ${TRACY_CLIENT_COPIED}
DEPENDS TracyClient
COMMENT "Copying the Tracy client next to the patched libraries"
)
list(APPEND PATCHED_OUTPUTS ${TRACY_CLIENT_COPIED})
endif()
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
if(BUILD_TESTING)
add_test_executable(guest_posix_scalar_io_tests tests/GuestPosixScalarIo.cpp)
target_include_directories(guest_posix_scalar_io_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_posix_scalar_io_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_posix_scalar_io_tests)
add_test(NAME guest_posix_scalar_io COMMAND guest_posix_scalar_io_tests)
if(WIN32)
set_tests_properties(guest_posix_scalar_io PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(nid_buffer_validation_tests nid/tests/BufferValidation.cpp)
target_include_directories(nid_buffer_validation_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/include)
target_compile_features(nid_buffer_validation_tests PRIVATE cxx_std_20)
if(MINGW)
target_compile_options(nid_buffer_validation_tests PRIVATE -fasynchronous-unwind-tables)
endif()
add_test(NAME nid_buffer_validation COMMAND nid_buffer_validation_tests)
set_tests_properties(nid_buffer_validation PROPERTIES TIMEOUT 10)
find_package(Python3 QUIET COMPONENTS Interpreter)
if(Python3_Interpreter_FOUND)
add_test(NAME nid_gnu_hash COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_gnu_hash.py $<TARGET_FILE:nid_patcher>)
add_test(NAME nid_patcher_macho COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_macho_nids.py $<TARGET_FILE:nid_patcher>)
add_test(NAME nid_patcher_native_paths COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_native_paths.py $<TARGET_FILE:nid_patcher>)
set_tests_properties(nid_patcher_native_paths PROPERTIES TIMEOUT 20)
endif()
add_test_executable(patched_libraries_tests tests/PatchedLibraries.cpp)
add_dependencies(patched_libraries_tests libs)
if(Python3_Interpreter_FOUND)
add_test(NAME nid_pe_references COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_pe_references.py $<TARGET_FILE:nid_patcher>)
endif()
if(MINGW)
target_compile_options(patched_libraries_tests PRIVATE -fasynchronous-unwind-tables)
target_link_options(patched_libraries_tests PRIVATE -static -static-libgcc -static-libstdc++)
endif()
add_test(NAME patched_libraries COMMAND patched_libraries_tests ${PATCHED_DIR})
if(Python3_Interpreter_FOUND)
add_test(NAME nid_elf_layout COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_elf_layout.py $<TARGET_FILE:nid_patcher>)
endif()
add_test_executable(guest_kernel_sys_tests tests/GuestKernelSys.cpp)
target_include_directories(guest_kernel_sys_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libkernel libc)
configure_windows_unwind(guest_kernel_sys_tests)
add_test(NAME guest_kernel_sys COMMAND guest_kernel_sys_tests)
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>)
endif()
if(WIN32)
set_tests_properties(guest_kernel_sys PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel_sys>")
@ -229,61 +168,17 @@ if(BUILD_TESTING)
if(APPLE)
add_subdirectory(tests/write_watch)
endif()
if(WIN32)
add_test_executable(windows_write_watch_tests tests/WindowsWriteWatch.cpp)
target_include_directories(windows_write_watch_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(windows_write_watch_tests PRIVATE _WIN32_WINNT=0x0A00)
if(MINGW)
target_compile_options(windows_write_watch_tests PRIVATE -fasynchronous-unwind-tables)
endif()
add_test(NAME windows_write_watch COMMAND windows_write_watch_tests)
set_tests_properties(windows_write_watch PROPERTIES TIMEOUT 20)
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 libc)
configure_windows_unwind(guest_offline_imports_tests)
add_test(NAME guest_offline_imports COMMAND guest_offline_imports_tests)
if(Python3_Interpreter_FOUND)
add_test(NAME nid_shared_strings COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/nid/tests/test_shared_strings.py $<TARGET_FILE:nid_patcher>)
endif()
if(WIN32)
set_tests_properties(guest_offline_imports PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttp>")
endif()
add_test_executable(guest_signin_dialog_tests tests/GuestSigninDialog.cpp)
target_include_directories(guest_signin_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_signin_dialog_tests PRIVATE libSceSigninDialog libc)
configure_windows_unwind(guest_signin_dialog_tests)
add_test(NAME guest_signin_dialog COMMAND guest_signin_dialog_tests)
if(WIN32)
set_tests_properties(guest_signin_dialog PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSigninDialog>")
endif()
add_test_executable(guest_netctl_ap_dialog_tests tests/GuestNetCtlApDialog.cpp)
target_include_directories(guest_netctl_ap_dialog_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_netctl_ap_dialog_tests PRIVATE libSceNetCtlApDialog libc)
configure_windows_unwind(guest_netctl_ap_dialog_tests)
add_test(NAME guest_netctl_ap_dialog COMMAND guest_netctl_ap_dialog_tests)
if(WIN32)
set_tests_properties(guest_netctl_ap_dialog PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNetCtlApDialog>")
endif()
add_test_executable(guest_np_auth_tests tests/GuestNpAuth.cpp)
target_include_directories(guest_np_auth_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_np_auth_tests PRIVATE libSceNpAuth libc)
configure_windows_unwind(guest_np_auth_tests)
add_test(NAME guest_np_auth COMMAND guest_np_auth_tests)
if(WIN32)
set_tests_properties(guest_np_auth PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceNpAuth>")
endif()
add_test_executable(guest_rudp_status_tests tests/GuestRudpStatus.cpp)
target_include_directories(guest_rudp_status_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_rudp_status_tests PRIVATE libSceRudp libc)
@ -294,26 +189,6 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRudp>")
endif()
add_test_executable(guest_rudp_context_queries_tests tests/GuestRudpContextQueries.cpp)
target_include_directories(guest_rudp_context_queries_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_rudp_context_queries_tests PRIVATE libSceRudp libc)
configure_windows_unwind(guest_rudp_context_queries_tests)
add_test(NAME guest_rudp_context_queries COMMAND guest_rudp_context_queries_tests)
if(WIN32)
set_tests_properties(guest_rudp_context_queries PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRudp>")
endif()
add_test_executable(guest_rudp_pump_tests tests/GuestRudpPump.cpp)
target_include_directories(guest_rudp_pump_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_rudp_pump_tests PRIVATE libSceRudp libc)
configure_windows_unwind(guest_rudp_pump_tests)
add_test(NAME guest_rudp_pump COMMAND guest_rudp_pump_tests)
if(WIN32)
set_tests_properties(guest_rudp_pump PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceRudp>")
endif()
add_test_executable(guest_memory_pool_stats_tests tests/GuestMemoryPoolStats.cpp)
target_include_directories(guest_memory_pool_stats_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_memory_pool_stats_tests PRIVATE libSceSsl libSceHttp2 libSceNet libkernel libc)
@ -375,11 +250,6 @@ if(BUILD_TESTING)
endif()
add_test(NAME host_thread_local COMMAND host_thread_local_tests)
set_tests_properties(host_thread_local PROPERTIES TIMEOUT 15)
if(MINGW)
add_test(NAME prx_thread_local_destructors COMMAND ${CMAKE_COMMAND} -DNM=${CMAKE_NM} -DOBJDUMP=${CMAKE_OBJDUMP}
-DMODULES=$<TARGET_FILE_DIR:libc> -P ${CMAKE_CURRENT_SOURCE_DIR}/tests/ThreadLocalDestructors.cmake)
set_tests_properties(prx_thread_local_destructors PROPERTIES TIMEOUT 300)
endif()
add_test_executable(savedata_core_write_replacement_tests tests/SaveDataWriteReplacement.cpp)
target_include_directories(savedata_core_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
@ -399,16 +269,6 @@ if(BUILD_TESTING)
configure_windows_unwind(savedata_replace_file_failure_tests)
add_test(NAME savedata_replace_file_failure COMMAND savedata_replace_file_failure_tests)
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)
configure_windows_unwind(guest_crt_tests)
add_test(NAME guest_crt COMMAND guest_crt_tests)
if(WIN32)
set_tests_properties(guest_crt PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAmpr>")
endif()
if(MINGW)
target_compile_options(savedata_core_write_replacement_tests PRIVATE -fasynchronous-unwind-tables)
target_compile_options(savedata_native_write_replacement_tests PRIVATE -fasynchronous-unwind-tables)
@ -421,16 +281,6 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceSaveData.native>")
endif()
add_test_executable(guest_ppsa01530_exports_tests tests/GuestPPSA01530Exports.cpp)
target_include_directories(guest_ppsa01530_exports_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_ppsa01530_exports_tests PRIVATE libkernel libSceJson2 libSceNet libScePad libc)
configure_windows_unwind(guest_ppsa01530_exports_tests)
add_test(NAME guest_ppsa01530_exports COMMAND guest_ppsa01530_exports_tests)
if(WIN32)
set_tests_properties(guest_ppsa01530_exports PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
endif()
add_test_executable(guest_posix_random_tests tests/GuestPosixRandom.cpp)
target_include_directories(guest_posix_random_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_posix_random_tests PRIVATE libScePosixForWebKit)
@ -440,24 +290,4 @@ if(BUILD_TESTING)
set_tests_properties(guest_posix_random PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libScePosixForWebKit>")
endif()
add_test_executable(MemoryPoolTest
tests/MemoryPoolTest.cpp
prx/libkernel/DirectMemory/MemoryPool.cpp
)
target_include_directories(MemoryPoolTest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(MINGW)
target_compile_options(MemoryPoolTest PRIVATE -fasynchronous-unwind-tables)
endif()
add_test(NAME MemoryPoolTest COMMAND MemoryPoolTest)
add_test_executable(guest_acm_tests tests/GuestAcm.cpp)
target_include_directories(guest_acm_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(guest_acm_tests PRIVATE libSceAcm libc)
configure_windows_unwind(guest_acm_tests)
add_test(NAME guest_acm COMMAND guest_acm_tests)
if(WIN32)
set_tests_properties(guest_acm PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAcm>")
endif()
endif()

View File

@ -16,23 +16,6 @@ struct alignas(16) __m128 { float v[4]; };
using Bool = std::uint8_t;
struct SonyLconv {
char* monetary[7];
char chars[14];
char pad[2];
char* decimal_point;
char* grouping;
char* thousands_sep;
char* frac_grouping;
char* frac_sep;
char* false_text;
char* true_text;
char* no_text;
char* yes_text;
};
static_assert(offsetof(SonyLconv, decimal_point) == 0x48);
static_assert(sizeof(SonyLconv) == 0x90);
using KernelModule = std::int32_t;
using KernelCpumask = std::uint64_t;
using KernelUseconds = unsigned int;
@ -95,14 +78,6 @@ struct KernelAioRwRequest {
std::int32_t fd;
};
struct KernelAioSchedulingParam {
std::int32_t schedulingWindowSize;
std::int32_t delayedCountLimit;
std::uint32_t enableSplit;
std::uint32_t splitSize;
std::uint32_t splitChunkSize;
};
struct KernelBatchMapEntry {
void* start;
std::uint64_t offset;
@ -280,12 +255,6 @@ struct ModuleInfoForUnwind {
std::uint64_t seg0_size;
};
struct InternalSegmentInfo {
std::uint64_t address;
std::uint64_t size;
};
static_assert(sizeof(InternalSegmentInfo) == 0x10);
struct MallocReplace {
std::uint64_t size = sizeof(MallocReplace);
void* malloc_initialize = nullptr;
@ -1265,16 +1234,9 @@ struct NetCtlNatInfo { std::uint8_t opaque[128]; };
union NetCtlInfo { std::uint8_t opaque[256]; };
struct NetCtlIfStat {
std::uint32_t device;
std::uint64_t tx_bytes;
std::uint64_t rx_bytes;
std::uint32_t reserved[8];
};
using NetCtlCallback = void (*)(int, void*);
struct HttpEpoll;
struct HttpEpoll {};
using HttpEpollHandle = HttpEpoll*;
using HttpsCallback = int (*)(int, unsigned int, void* const*, int, void*);
using HttpRedirectCallback = int (*)(int, std::int32_t, std::int32_t*, const char*, void*);
@ -1461,12 +1423,7 @@ struct SaveDataMountInfo {
struct SceSaveDataTitleId { char data[10]; char pad[2]; };
struct SceSaveDataDirName { char data[32]; };
struct SaveDataSearchInfo {
std::uint64_t blocks;
std::uint64_t free_blocks;
std::uint8_t reserved[32];
};
static_assert(sizeof(SaveDataSearchInfo) == 48);
struct SaveDataSearchInfo { std::uint8_t opaque[128]; };
struct SaveDataMemoryData { void* buf; std::size_t buf_size; std::size_t offset; };
struct SaveDataMount3 {
@ -1589,13 +1546,6 @@ struct SaveDataBackup {
std::uint8_t reserved[32];
};
struct SaveDataConvert {
std::int32_t user_id;
std::int32_t pad;
const SceSaveDataDirName* dir_name;
std::uint8_t unknown[48];
};
struct AppContentInitParam { char reserved[32]; };
struct AppContentBootParam {
@ -1615,50 +1565,8 @@ struct ContentExportInitParam2 {
std::int64_t reserved1;
};
struct ContentExportParam {
char title[257];
char comment[257];
char content_type[65];
};
static_assert(sizeof(ContentExportParam) == 579);
struct ContentExportCallbackParam {
std::size_t content_length;
void* func;
void* user_data;
};
struct ContentSearchInitParam { std::size_t memory_size; };
struct ContentSearchColumnValue {
std::uint32_t size;
std::int64_t value;
};
struct ContentSearchContentColumn {
std::int32_t column_type;
std::int32_t column_operator;
const ContentSearchColumnValue* value;
std::int32_t logical_connector;
};
struct ContentSearchContentColumnSet {
const ContentSearchContentColumn* columns;
std::int32_t columns_length;
std::int32_t logical_connector;
};
struct ContentSearchContentOrderByCondition {
std::int32_t column_type;
std::int32_t sort_type;
};
static_assert(sizeof(ContentSearchColumnValue) == 16);
static_assert(sizeof(ContentSearchContentColumn) == 24);
static_assert(offsetof(ContentSearchContentColumn, logical_connector) == 16);
static_assert(sizeof(ContentSearchContentColumnSet) == 16);
static_assert(sizeof(ContentSearchContentOrderByCondition) == 8);
struct PngEncCreateParam {
std::uint32_t this_size;
std::uint32_t attribute;

View File

@ -1,15 +0,0 @@
#ifndef NID_NATIVEPATH_HPP
#define NID_NATIVEPATH_HPP
#include <filesystem>
#include <string>
namespace Nid::Internal {
inline std::filesystem::path NativePath(const std::string& path) {
return std::filesystem::path(std::u8string(path.begin(), path.end()));
}
}
#endif

View File

@ -61,7 +61,7 @@ inline std::string StripNidPostfix(const std::string& name) {
template<typename T>
T Read(const std::vector<std::uint8_t>& buf, std::size_t offset) {
if (offset > buf.size() || sizeof(T) > buf.size() - offset) throw std::runtime_error("read out of bounds");
if (offset + sizeof(T) > buf.size()) throw std::runtime_error("read out of bounds");
T v;
std::memcpy(&v, buf.data() + offset, sizeof(T));
return v;
@ -69,7 +69,7 @@ T Read(const std::vector<std::uint8_t>& buf, std::size_t offset) {
template<typename T>
void Write(std::vector<std::uint8_t>& buf, std::size_t offset, const T& v) {
if (offset > buf.size() || sizeof(T) > buf.size() - offset) throw std::runtime_error("write out of bounds");
if (offset + sizeof(T) > buf.size()) throw std::runtime_error("write out of bounds");
std::memcpy(buf.data() + offset, &v, sizeof(T));
}
@ -78,7 +78,6 @@ inline std::string ReadCStr(const std::vector<std::uint8_t>& buf, std::size_t of
std::string s;
while (offset < buf.size() && buf[offset] != 0u)
s.push_back(static_cast<char>(buf[offset++]));
if (offset == buf.size()) throw std::runtime_error("unterminated cstr");
return s;
}

View File

@ -15,11 +15,6 @@ namespace Nid {
namespace {
void RequireElfRange(const std::vector<std::uint8_t>& elf, std::uint64_t offset, std::uint64_t size, const char* table) {
if (offset > elf.size() || size > elf.size() - offset)
throw std::runtime_error(std::string("invalid ELF ") + table + " range");
}
std::uint32_t GnuHash(const std::string& name) {
std::uint32_t h = 5381u;
for (const unsigned char c : name)
@ -41,18 +36,6 @@ GnuHashLayout ReadGnuHashLayout(const std::vector<std::uint8_t>& elf, std::size_
return layout;
}
void ValidateGnuHash(const std::vector<std::uint8_t>& elf, std::size_t offset, std::size_t size, std::size_t symCount) {
if (offset > elf.size() || size > elf.size() - offset || size < 16u)
throw std::runtime_error("invalid gnu.hash section range");
const auto layout = ReadGnuHashLayout(elf, offset);
if (layout.SymOffset > symCount || !layout.BloomSize || (layout.BloomSize & (layout.BloomSize - 1u)) ||
layout.BloomShift >= 32u || (!layout.NBuckets && layout.SymOffset != symCount))
throw std::runtime_error("invalid gnu.hash geometry");
const std::uint64_t required = 16u + std::uint64_t{layout.BloomSize} * 8u +
std::uint64_t{layout.NBuckets} * 4u + (symCount - layout.SymOffset) * 4u;
if (required > size) throw std::runtime_error("gnu.hash section too small to rebuild in place");
}
void ReorderDynSymForGnuHash(
std::vector<std::uint8_t>& elf,
std::size_t dynSymOffset,
@ -221,65 +204,49 @@ void RemapDynamicNeededOffsets(
}
}
struct VersionNameField {
std::size_t Offset;
std::uint32_t Name;
};
std::vector<VersionNameField> ReadVersionNameFields(const std::vector<std::uint8_t>& elf, const Elf64_Ehdr& ehdr) {
using namespace Internal;
std::vector<VersionNameField> fields;
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
const auto section = Read<Elf64_Shdr>(elf, static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr));
if (section.sh_type != kShtGnuVerneed || section.sh_size == 0u) continue;
if (section.sh_offset > elf.size() || section.sh_size > elf.size() - section.sh_offset)
throw std::runtime_error("invalid .gnu.version_r section range");
const auto location = [&](std::uint64_t relative, std::size_t size) {
if (relative > section.sh_size || size > section.sh_size - relative)
throw std::runtime_error(".gnu.version_r record outside section");
return static_cast<std::size_t>(section.sh_offset + relative);
};
const auto advance = [&](std::uint64_t current, std::uint32_t step, std::size_t size) {
if (step < size || step > section.sh_size - current)
throw std::runtime_error("invalid .gnu.version_r record link");
return current + step;
};
std::uint64_t current = 0u;
while (true) {
const auto offset = location(current, sizeof(Elf64_Verneed));
const auto need = Read<Elf64_Verneed>(elf, offset);
if (need.vn_version != 1u) throw std::runtime_error("unsupported .gnu.version_r structure version");
fields.push_back({offset + offsetof(Elf64_Verneed, vn_file), need.vn_file});
if (need.vn_cnt != 0u) {
auto auxiliary = advance(current, need.vn_aux, sizeof(Elf64_Verneed));
for (std::uint16_t index = 0u; index < need.vn_cnt; ++index) {
const auto auxOffset = location(auxiliary, sizeof(Elf64_Vernaux));
const auto aux = Read<Elf64_Vernaux>(elf, auxOffset);
fields.push_back({auxOffset + offsetof(Elf64_Vernaux, vna_name), aux.vna_name});
if (index + 1u == need.vn_cnt) {
if (aux.vna_next != 0u) throw std::runtime_error(".gnu.version_r auxiliary count mismatch");
} else {
auxiliary = advance(auxiliary, aux.vna_next, sizeof(Elf64_Vernaux));
}
}
}
if (need.vn_next == 0u) break;
current = advance(current, need.vn_next, sizeof(Elf64_Verneed));
}
}
return fields;
}
void RemapVerneedOffsets(
std::vector<std::uint8_t>& elf,
const Elf64_Ehdr& ehdr,
const std::vector<std::pair<std::uint32_t, std::uint32_t>>& oldToNewOffset
) {
using namespace Internal;
for (const auto& field : ReadVersionNameFields(elf, ehdr)) {
const auto mapped = std::find_if(oldToNewOffset.begin(), oldToNewOffset.end(),
[&](const auto& entry) { return entry.first == field.Name; });
if (mapped != oldToNewOffset.end()) Write(elf, field.Offset, mapped->second);
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
const auto shdr = Read<Elf64_Shdr>(elf, shOffset);
if (shdr.sh_type != kShtGnuVerneed) continue;
const std::size_t verneedOffset = static_cast<std::size_t>(shdr.sh_offset);
std::size_t entryOffset = verneedOffset;
while (true) {
const auto verneed = Read<Elf64_Verneed>(elf, entryOffset);
const auto mappedFile = std::find_if(
oldToNewOffset.begin(), oldToNewOffset.end(),
[&](const auto& entry) { return entry.first == verneed.vn_file; }
);
if (mappedFile != oldToNewOffset.end())
Write(elf, entryOffset + offsetof(Elf64_Verneed, vn_file), mappedFile->second);
std::size_t auxOffset = entryOffset + verneed.vn_aux;
for (std::uint16_t a = 0u; a < verneed.vn_cnt; ++a) {
const auto vernaux = Read<Elf64_Vernaux>(elf, auxOffset);
const auto mappedName = std::find_if(
oldToNewOffset.begin(), oldToNewOffset.end(),
[&](const auto& entry) { return entry.first == vernaux.vna_name; }
);
if (mappedName != oldToNewOffset.end())
Write(elf, auxOffset + offsetof(Elf64_Vernaux, vna_name), mappedName->second);
if (vernaux.vna_next == 0u) break;
auxOffset += vernaux.vna_next;
}
if (verneed.vn_next == 0u) break;
entryOffset += verneed.vn_next;
}
}
}
@ -308,8 +275,35 @@ std::vector<std::uint32_t> CollectDynamicNameOffsets(const std::vector<std::uint
}
std::vector<std::uint32_t> CollectVerneedNameOffsets(const std::vector<std::uint8_t>& elf, const Elf64_Ehdr& ehdr) {
using namespace Internal;
std::vector<std::uint32_t> offsets;
for (const auto& field : ReadVersionNameFields(elf, ehdr)) offsets.push_back(field.Name);
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
const auto shdr = Read<Elf64_Shdr>(elf, shOffset);
if (shdr.sh_type != kShtGnuVerneed) continue;
std::size_t entryOffset = static_cast<std::size_t>(shdr.sh_offset);
while (true) {
const auto verneed = Read<Elf64_Verneed>(elf, entryOffset);
offsets.push_back(verneed.vn_file);
std::size_t auxOffset = entryOffset + verneed.vn_aux;
for (std::uint16_t a = 0u; a < verneed.vn_cnt; ++a) {
const auto vernaux = Read<Elf64_Vernaux>(elf, auxOffset);
offsets.push_back(vernaux.vna_name);
if (vernaux.vna_next == 0u) break;
auxOffset += vernaux.vna_next;
}
if (verneed.vn_next == 0u) break;
entryOffset += verneed.vn_next;
}
}
return offsets;
}
@ -322,9 +316,6 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
if (elf[4] != 2) throw std::runtime_error("not ELF64");
const auto ehdr = Read<Elf64_Ehdr>(elf, 0u);
if (ehdr.e_ident[5] != 1 || ehdr.e_shentsize != sizeof(Elf64_Shdr))
throw std::runtime_error("unsupported ELF byte order or section header size");
RequireElfRange(elf, ehdr.e_shoff, std::uint64_t{ehdr.e_shnum} * sizeof(Elf64_Shdr), "section table");
std::size_t dynSymOffset = 0u, dynSymSize = 0u;
std::size_t gnuHashOffset = 0u, gnuHashSize = 0u;
@ -335,9 +326,6 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + i * sizeof(Elf64_Shdr);
const auto shdr = Read<Elf64_Shdr>(elf, shOffset);
if (shdr.sh_type == kShtDynsym) {
if (shdr.sh_entsize != sizeof(Elf64_Sym) || shdr.sh_size % sizeof(Elf64_Sym) || shdr.sh_link >= ehdr.e_shnum)
throw std::runtime_error("invalid ELF dynsym layout");
RequireElfRange(elf, shdr.sh_offset, shdr.sh_size, "dynsym");
dynSymOffset = static_cast<std::size_t>(shdr.sh_offset);
dynSymSize = static_cast<std::size_t>(shdr.sh_size);
dynSymSectionIndex = i;
@ -345,6 +333,7 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
if (shdr.sh_type == kShtGnuHash) {
gnuHashOffset = static_cast<std::size_t>(shdr.sh_offset);
gnuHashSize = static_cast<std::size_t>(shdr.sh_size);
gnuHashSymOffset = Read<std::uint32_t>(elf, gnuHashOffset + 4u);
}
if (shdr.sh_type == kShtGnuVersym)
versymOffset = static_cast<std::size_t>(shdr.sh_offset);
@ -356,10 +345,6 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
const std::size_t symCount = dynSymSize / sizeof(Elf64_Sym);
if (symCount == 0u) throw std::runtime_error(".dynsym is empty");
if (gnuHashOffset != 0u) {
ValidateGnuHash(elf, gnuHashOffset, gnuHashSize, symCount);
gnuHashSymOffset = Read<std::uint32_t>(elf, gnuHashOffset + 4u);
}
const std::size_t dynStrSectionLink = [&]() -> std::size_t {
for (std::uint16_t i = 0u; i < ehdr.e_shnum; ++i) {
@ -374,8 +359,6 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
{
const std::size_t shOffset = static_cast<std::size_t>(ehdr.e_shoff) + dynStrSectionLink * sizeof(Elf64_Shdr);
const auto shdr = Read<Elf64_Shdr>(elf, shOffset);
if (shdr.sh_type != 3) throw std::runtime_error("invalid ELF dynstr section type");
RequireElfRange(elf, shdr.sh_offset, shdr.sh_size, "dynstr");
dynStrOffset = static_cast<std::size_t>(shdr.sh_offset);
dynStrSize = static_cast<std::size_t>(shdr.sh_size);
}
@ -444,7 +427,7 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
const auto addName = [&](std::uint32_t oldOffset, std::string value) {
const auto existing = std::find_if(
names.begin(), names.end(),
[&](const auto& entry) { return entry.first == oldOffset && entry.second == value; }
[&](const auto& entry) { return entry.first == oldOffset; }
);
if (existing == names.end()) names.emplace_back(oldOffset, std::move(value));
};
@ -480,9 +463,8 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
newDynStr.push_back(0u);
}
std::vector<std::pair<std::uint32_t, std::uint32_t>> preservedToNewOffset;
for (const auto offset : preservedOffsets)
preservedToNewOffset.emplace_back(offset, valueOffsets.at(ReadCStr(origDynStr, offset)));
std::vector<std::pair<std::uint32_t, std::uint32_t>> oldToNewOffset;
for (const auto& name : names) oldToNewOffset.emplace_back(name.first, valueOffsets.at(name.second));
if (newDynStr.size() > dynStrSize) {
throw std::runtime_error(
@ -493,15 +475,25 @@ void ElfNidPatcher::PatchNids(std::vector<std::uint8_t>& elf, const std::string&
newDynStr.resize(dynStrSize, 0u);
std::vector<std::string> newSymbolNames(symCount);
for (const auto& use : uses) {
newSymbolNames[use.symIndex] = use.newValue;
Write(elf, dynSymOffset + use.symIndex * sizeof(Elf64_Sym) + offsetof(Elf64_Sym, st_name), valueOffsets.at(use.newValue));
for (std::size_t i = 1u; i < symCount; ++i) {
const std::size_t symOffset = dynSymOffset + i * sizeof(Elf64_Sym);
const auto sym = Read<Elf64_Sym>(elf, symOffset);
if (sym.st_name == 0u) continue;
const auto mapped = std::find_if(
oldToNewOffset.begin(), oldToNewOffset.end(),
[&](const auto& entry) { return entry.first == sym.st_name; }
);
if (mapped == oldToNewOffset.end()) continue;
newSymbolNames[i] = ReadCStr(newDynStr, mapped->second);
Write(elf, symOffset + offsetof(Elf64_Sym, st_name), mapped->second);
}
std::memcpy(elf.data() + dynStrOffset, newDynStr.data(), newDynStr.size());
RemapDynamicNeededOffsets(elf, ehdr, preservedToNewOffset);
RemapVerneedOffsets(elf, ehdr, preservedToNewOffset);
RemapDynamicNeededOffsets(elf, ehdr, oldToNewOffset);
RemapVerneedOffsets(elf, ehdr, oldToNewOffset);
if (gnuHashOffset != 0u) {
const auto bucketCount = Read<std::uint32_t>(elf, gnuHashOffset);

View File

@ -3,7 +3,6 @@
#include <nid/PeNidPatcher.hpp>
#include <nid/MachONidPatcher.hpp>
#include <nid/NidPatcherUtils.hpp>
#include <nid/NativePath.hpp>
#include <bit>
#include <cstring>
#include <fstream>
@ -50,12 +49,7 @@ std::unordered_set<std::string> ReadPeExports(const std::vector<std::uint8_t>& b
if (!directory.VirtualAddress || directory.Size < sizeof(PeExportDirectory)) throw std::runtime_error("missing reference PE exports");
const auto sections = optional + optionalSize;
RequireRange(binary, sections, std::uint64_t{sectionCount} * sizeof(PeSectionHeader));
const auto headersSize = ReadValue<std::uint32_t>(binary, optional + 60);
const auto mapRva = [&](std::uint32_t rva, std::uint64_t size) -> std::uint64_t {
if (rva < headersSize && size <= headersSize - rva) {
RequireRange(binary, rva, size);
return rva;
}
for (std::uint16_t index = 0; index < sectionCount; ++index) {
const auto section = ReadValue<PeSectionHeader>(binary, sections + std::uint64_t{index} * sizeof(PeSectionHeader));
if (rva < section.VirtualAddress) continue;
@ -70,20 +64,14 @@ std::unordered_set<std::string> ReadPeExports(const std::vector<std::uint8_t>& b
const auto table = ReadValue<PeExportDirectory>(binary, mapRva(directory.VirtualAddress, sizeof(PeExportDirectory)));
if (!table.NumberOfNames) throw std::runtime_error("reference PE has no named exports");
const auto names = mapRva(table.AddressOfNames, std::uint64_t{table.NumberOfNames} * 4);
const auto ordinals = mapRva(table.AddressOfNameOrdinals, std::uint64_t{table.NumberOfNames} * 2);
const auto functions = mapRva(table.AddressOfFunctions, std::uint64_t{table.NumberOfFunctions} * 4);
std::unordered_set<std::string> result;
for (std::uint32_t index = 0; index < table.NumberOfNames; ++index) {
const auto ordinal = ReadValue<std::uint16_t>(binary, ordinals + std::uint64_t{index} * 2);
if (ordinal >= table.NumberOfFunctions) throw std::runtime_error("invalid reference PE export ordinal");
if (!ReadValue<std::uint32_t>(binary, functions + std::uint64_t{ordinal} * 4)) continue;
const auto rva = ReadValue<std::uint32_t>(binary, names + std::uint64_t{index} * 4);
const auto offset = mapRva(rva, 1);
auto name = ReadName(binary, offset, binary.size() - offset);
mapRva(rva, name.size() + 1);
if (!result.insert(name).second) throw std::runtime_error("duplicate reference export: " + name);
}
if (result.empty()) throw std::runtime_error("reference PE has no named exports");
return result;
}
@ -122,7 +110,7 @@ std::string NormalizeExportName(const std::string& name) {
std::unordered_set<std::string> ReadExportExclusions(const std::string& path) {
if constexpr (std::endian::native != std::endian::little) throw std::runtime_error("export references require a little-endian host");
std::ifstream input(Internal::NativePath(path), std::ios::binary | std::ios::ate);
std::ifstream input(path, std::ios::binary | std::ios::ate);
if (!input) throw std::runtime_error("cannot open export reference: " + path);
const auto size = input.tellg();
if (size <= 0) throw std::runtime_error("empty or unreadable export reference: " + path);

View File

@ -1,6 +1,5 @@
#include <nid/BinaryPatcherFactory.hpp>
#include <nid/ExportExclusions.hpp>
#include <nid/NativePath.hpp>
#include <fstream>
#include <iostream>
#include <stdexcept>
@ -10,20 +9,20 @@
namespace {
std::vector<std::uint8_t> _readFile(const std::string& path) {
std::ifstream f(Nid::Internal::NativePath(path), std::ios::binary);
std::ifstream f(path, std::ios::binary);
if (!f) throw std::runtime_error("cannot open: " + path);
return {std::istreambuf_iterator<char>(f), {}};
}
void _writeFile(const std::string& path, const std::vector<std::uint8_t>& data) {
std::ofstream f(Nid::Internal::NativePath(path), std::ios::binary);
std::ofstream f(path, std::ios::binary);
if (!f) throw std::runtime_error("cannot write: " + path);
f.write(reinterpret_cast<const char*>(data.data()), static_cast<std::streamsize>(data.size()));
}
}
int Run(int argc, char* argv[]) {
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "Usage: nid_patcher <library_name> [--preserve-exports <unpatched-library>] <file> [file2 ...]\n";
return 1;
@ -66,27 +65,3 @@ int Run(int argc, char* argv[]) {
return 0;
}
#ifdef _WIN32
int wmain(int argc, wchar_t* argv[]) {
try {
std::vector<std::string> values;
values.reserve(static_cast<std::size_t>(argc));
for (int index = 0; index < argc; ++index) {
const auto utf8 = std::filesystem::path(argv[index]).u8string();
values.emplace_back(utf8.begin(), utf8.end());
}
std::vector<char*> arguments;
arguments.reserve(values.size());
for (auto& value : values) arguments.push_back(value.data());
return Run(argc, arguments.data());
} catch (const std::exception& error) {
std::cerr << "FAIL: " << error.what() << '\n';
return 2;
}
}
#else
int main(int argc, char* argv[]) {
return Run(argc, argv);
}
#endif

View File

@ -1,60 +0,0 @@
#include <nid/NidPatcherUtils.hpp>
#include <cstdint>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <vector>
namespace {
void Require(bool condition) {
if (!condition) throw std::runtime_error("NID buffer validation failed");
}
template<typename TAction>
void RequireRejected(TAction action) {
try { action(); }
catch (const std::runtime_error&) { return; }
throw std::runtime_error("Malformed NID buffer was accepted");
}
}
int main() {
try {
using namespace Nid::Internal;
const std::vector<std::uint8_t> unterminated{'n', 'a', 'm', 'e'};
RequireRejected([&] { ReadCStr(unterminated, 0); });
RequireRejected([&] { ReadCStr(unterminated, 3); });
const std::vector<std::uint8_t> strings{'a', 0, 'b', 'c', 0};
Require(ReadCStr(strings, 0) == "a");
Require(ReadCStr(strings, 1).empty());
Require(ReadCStr(strings, 2) == "bc");
Require(ReadCStr(strings, 4).empty());
RequireRejected([&] { ReadCStr(strings, strings.size()); });
RequireRejected([&] { ReadCStr({}, 0); });
std::vector<std::uint8_t> bytes(8, 0xa5);
const std::uint64_t value = 0x1020304050607080;
Write(bytes, 0, value);
Require(Read<std::uint64_t>(bytes, 0) == value);
Write<std::uint32_t>(bytes, 4, 0x12345678);
Require(Read<std::uint32_t>(bytes, 4) == 0x12345678);
const auto before = bytes;
for (const auto offset : {std::size_t{5}, std::size_t{8}, std::size_t{9},
std::numeric_limits<std::size_t>::max(), std::numeric_limits<std::size_t>::max() - 1,
std::numeric_limits<std::size_t>::max() - 3}) {
RequireRejected([&] { Read<std::uint32_t>(bytes, offset); });
RequireRejected([&] { Write<std::uint32_t>(bytes, offset, 0); });
Require(bytes == before);
}
RequireRejected([&] { Read<std::uint64_t>(bytes, 1); });
std::vector<std::uint8_t> empty;
RequireRejected([&] { Read<std::uint8_t>(empty, 0); });
RequireRejected([&] { Write<std::uint8_t>(empty, 0, 0); });
std::cout << "NID buffer validation passed\n";
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -1,48 +0,0 @@
import struct
import sys
import unittest
from pathlib import Path
from test_symbol_versions import build_image, symbols, patch, SHOFF
class ElfLayoutTests(unittest.TestCase):
def test_declared_layout_must_match_supported_tables(self):
changes = ((5, "B", 2), (0x3a, "H", 32),
(SHOFF + 64 + 56, "Q", 8),
(SHOFF + 64 + 32, "Q", len(symbols(False)) * 24 + 1),
(SHOFF + 128 + 4, "I", 1))
for offset, kind, value in changes:
with self.subTest(offset=offset, value=value):
image = build_image(symbols(False), False)
struct.pack_into("<" + kind, image, offset, value)
self.reject(image)
def test_table_ranges_and_links_are_checked(self):
changes = ((0x28, "Q", 0x10000),
(SHOFF + 64 + 24, "Q", 0xff0),
(SHOFF + 64 + 40, "I", 100),
(SHOFF + 128 + 24, "Q", 0xff0),
(SHOFF + 128 + 32, "Q", 0xffffffffffffffff))
for offset, kind, value in changes:
with self.subTest(offset=offset, value=value):
image = build_image(symbols(False), False)
struct.pack_into("<" + kind, image, offset, value)
self.reject(image)
def reject(self, image):
result, after = patch(PATCHER, bytes(image), "layout.so")
self.assertEqual(result.returncode, 2, (result.returncode, result.stderr))
self.assertRegex(result.stderr.decode(), "ELF|dynsym|dynstr")
self.assertEqual(after, image)
def test_valid_supported_layout_still_patches(self):
image = build_image(symbols(False), False)
result, after = patch(PATCHER, bytes(image), "valid-layout.so")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertNotEqual(after, image)
if __name__ == "__main__":
PATCHER = Path(sys.argv.pop(1)).resolve()
unittest.main()

View File

@ -1,75 +0,0 @@
import struct
import sys
import unittest
from pathlib import Path
from test_symbol_versions import build_image, symbols, patch, HASH_OFFSET, SHOFF, read_dynsym, gnu_hash
class GnuHashTests(unittest.TestCase):
def image(self, buckets=4, bloom=1, shift=6, symoffset=3):
image = build_image(symbols(False), False)
count = len(symbols(False))
struct.pack_into("<IIII", image, HASH_OFFSET, buckets, symoffset, bloom, shift)
size = 16 + 8 * bloom + 4 * buckets + 4 * max(0, count - symoffset)
struct.pack_into("<Q", image, SHOFF + 3 * 64 + 32, size)
return image
def test_non_power_of_two_bloom_is_rejected(self):
self.reject(self.image(bloom=3))
def test_hash_shift_outside_word_is_rejected(self):
self.reject(self.image(shift=32))
def test_unsafe_geometry_is_rejected(self):
for fields in ({"bloom": 0}, {"buckets": 0}, {"symoffset": 100}):
with self.subTest(fields=fields):
self.reject(self.image(**fields))
def test_declared_section_extent_is_checked(self):
for size in (12, 16, 0x10000):
with self.subTest(size=size):
image = self.image()
struct.pack_into("<Q", image, SHOFF + 3 * 64 + 32, size)
self.reject(image)
def reject(self, image):
result, after = patch(PATCHER, bytes(image), "geometry.so")
self.assertEqual(result.returncode, 2, (result.returncode, result.stderr))
self.assertIn(b"gnu.hash", result.stderr)
self.assertEqual(after, image)
def test_rebuilt_hash_finds_every_export(self):
image = self.image(bloom=2)
result, after = patch(PATCHER, image, "valid.so")
self.assertEqual(result.returncode, 0, result.stderr)
entries = read_dynsym(after)
buckets, first, bloom, shift = struct.unpack_from("<4I", after, HASH_OFFSET)
bucket_offset = HASH_OFFSET + 16 + bloom * 8
chain_offset = bucket_offset + buckets * 4
for index in range(first, len(entries)):
name = entries[index][0]
hashed = gnu_hash(name)
word = struct.unpack_from("<Q", after, HASH_OFFSET + 16 + ((hashed // 64) % bloom) * 8)[0]
self.assertTrue(word & (1 << (hashed % 64)))
self.assertTrue(word & (1 << ((hashed >> shift) % 64)))
start = struct.unpack_from("<I", after, bucket_offset + (hashed % buckets) * 4)[0]
found = False
for candidate in range(start, len(entries)):
value = struct.unpack_from("<I", after, chain_offset + (candidate - first) * 4)[0]
if (value | 1) == (hashed | 1) and entries[candidate][0] == name:
found = True
if value & 1:
break
self.assertTrue(found, name)
def test_empty_hash_with_no_buckets_is_supported(self):
image = self.image(buckets=0, symoffset=len(symbols(False)))
result, after = patch(PATCHER, image, "empty.so")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(struct.unpack_from("<I", after, HASH_OFFSET)[0], 0)
if __name__ == "__main__":
PATCHER = Path(sys.argv.pop(1)).resolve()
unittest.main()

View File

@ -1,56 +0,0 @@
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from test_macho_nids import build_dylib, exports_of, nid
PATCHER = Path(sys.argv.pop(1)).resolve()
class NativePathTests(unittest.TestCase):
def invoke(self, *arguments):
return subprocess.run([str(PATCHER), "libtest", *map(str, arguments)],
capture_output=True, encoding="utf-8", errors="replace", timeout=10)
def test_non_ascii_directory_and_filename_patch_the_intended_file(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary) / "\u6e2c\u8a66"
root.mkdir()
file = root / "\u043c\u043e\u0434\u0443\u043b\u044c.prx"
file.write_bytes(build_dylib([("_puts_nid_postfix", 0, 0x100)], []))
result = self.invoke(file)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(exports_of(file.read_bytes()), {"_" + nid("puts"): (0, 0x100)})
self.assertIn(str(file), result.stdout)
self.assertEqual(list(root.iterdir()), [file])
def test_non_ascii_export_reference_preserves_the_named_export(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
reference = root / "\u6e2c\u8a66-reference.prx"
original = build_dylib([("_puts", 0, 0x100)], [])
reference.write_bytes(original)
file = root / "target.prx"
file.write_bytes(build_dylib([("_puts_nid_postfix", 0, 0x100), ("_exit_nid_postfix", 0, 0x108)], []))
result = self.invoke("--preserve-exports", reference, file)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(exports_of(file.read_bytes()), {"_puts_nid_postfix": (0, 0x100), "_" + nid("exit"): (0, 0x108)})
self.assertEqual(reference.read_bytes(), original)
def test_missing_unicode_input_does_not_modify_another_file(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
existing = root / "existing.prx"
existing.write_bytes(b"unchanged")
missing = root / "\u6e2c\u8a66-missing.prx"
result = self.invoke(missing)
self.assertEqual(result.returncode, 2)
self.assertIn(str(missing), result.stderr)
self.assertFalse(missing.exists())
self.assertEqual(existing.read_bytes(), b"unchanged")
if __name__ == "__main__":
unittest.main()

View File

@ -1,69 +0,0 @@
import struct
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from test_macho_nids import build_dylib, exports_of, nid
def reference(*, headers=False, address=0x2000, ordinal=0):
image = bytearray(1280)
image[:2] = b"MZ"
struct.pack_into("<I", image, 0x3c, 0x40)
struct.pack_into("<IHHIIIHH", image, 0x40, 0x4550, 0x8664, 2, 0, 0, 0, 240, 0x2022)
optional = 0x58
struct.pack_into("<H", image, optional, 0x20b)
struct.pack_into("<I", image, optional + 60, 512)
struct.pack_into("<I", image, optional + 108, 16)
start, rva = (416, 416) if headers else (512, 0x1000)
struct.pack_into("<II", image, optional + 112, rva, 80)
for index, (name, va, offset, size) in enumerate(((b".edata", 0x1000, 512, 256), (b".text", 0x2000, 768, 512))):
struct.pack_into("<8sIIIIIIHHI", image, optional + 240 + index * 40,
name, size, va, size, offset, 0, 0, 0, 0, 0x40000040)
struct.pack_into("<IIHHIIIIIII", image, start, 0, 0, 0, 0, rva + 50, 1, 1, 1, rva + 40, rva + 44, rva + 48)
struct.pack_into("<IIH", image, start + 40, address, rva + 50, ordinal)
name = b"puts_nid_postfix\0"
image[start + 50:start + 50 + len(name)] = name
return image
class ReferenceTests(unittest.TestCase):
def patch_with_reference(self, data):
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
source, ref = root / "target.dylib", root / "reference.bin"
original = build_dylib([("_puts_nid_postfix", 0, 0x100), ("_exit_nid_postfix", 0, 0x108)], [])
source.write_bytes(original)
ref.write_bytes(data)
result = subprocess.run([str(PATCHER), "libc", str(source), "--preserve-exports", str(ref)], capture_output=True, text=True)
self.assertEqual(ref.read_bytes(), data)
return result, source.read_bytes(), original
def test_header_resident_export_tables(self):
result, image, _ = self.patch_with_reference(reference(headers=True))
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(exports_of(image), {"_puts_nid_postfix": (0, 0x100), "_" + nid("exit"): (0, 0x108)})
def test_section_resident_export_tables(self):
result, image, _ = self.patch_with_reference(reference())
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("_puts_nid_postfix", exports_of(image))
def test_null_function_slot_does_not_preserve_a_name(self):
result, image, original = self.patch_with_reference(reference(address=0))
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("no named exports", result.stdout + result.stderr)
self.assertEqual(image, original)
def test_invalid_name_ordinal_is_rejected_without_changing_target(self):
result, image, original = self.patch_with_reference(reference(ordinal=1))
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("ordinal", result.stdout + result.stderr)
self.assertEqual(image, original)
if __name__ == "__main__":
PATCHER = Path(sys.argv.pop(1)).resolve()
unittest.main()

View File

@ -1,58 +0,0 @@
import struct
import sys
import unittest
from pathlib import Path
from test_symbol_versions import build_image, symbols, patch, SHOFF, DYNSYM_OFFSET, read_dynsym, sections, DEFINITIONS
class SharedStringTests(unittest.TestCase):
def test_dynamic_names_keep_original_string_when_export_shares_offset(self):
for tag in (1, 14, 15, 29):
with self.subTest(tag=tag):
data = build_image(symbols(False), False)
name_offset = struct.unpack_from("<I", data, DYNSYM_OFFSET + 3 * 24)[0]
struct.pack_into("<H", data, 0x3c, 5)
struct.pack_into("<IIQQQQIIQQ", data, SHOFF + 4 * 64, 0, 6, 2, 0, 0x580, 32, 2, 0, 8, 16)
struct.pack_into("<qQqQ", data, 0x580, tag, name_offset, 0, 0)
result, after = patch(PATCHER, bytes(data), "shared-dynamic.so")
self.assertEqual(result.returncode, 0, result.stderr)
strings = sections(after)[2]
new_offset = struct.unpack_from("<Q", after, 0x588)[0]
start = strings[4] + new_offset
self.assertEqual(after[start:after.index(0, start)].decode(), DEFINITIONS[0][0])
renamed = next(entry[0] for entry in read_dynsym(after) if entry[2] == DEFINITIONS[0][2])
self.assertNotEqual(renamed, DEFINITIONS[0][0])
def test_version_requirement_strings_keep_original_names(self):
data = build_image(symbols(False), False)
name_offset = struct.unpack_from("<I", data, DYNSYM_OFFSET + 3 * 24)[0]
struct.pack_into("<H", data, 0x3c, 5)
struct.pack_into("<IIQQQQIIQQ", data, SHOFF + 4 * 64, 0, 0x6ffffffe, 2, 0, 0x580, 32, 2, 1, 8, 0)
struct.pack_into("<HHIII", data, 0x580, 1, 1, name_offset, 16, 0)
struct.pack_into("<IHHII", data, 0x590, 0, 0, 2, name_offset, 0)
result, after = patch(PATCHER, bytes(data), "shared-version.so")
self.assertEqual(result.returncode, 0, result.stderr)
strings = sections(after)[2]
for field in (0x584, 0x598):
offset = struct.unpack_from("<I", after, field)[0]
start = strings[4] + offset
self.assertEqual(after[start:after.index(0, start)].decode(), DEFINITIONS[0][0])
renamed = next(entry[0] for entry in read_dynsym(after) if entry[2] == DEFINITIONS[0][2])
self.assertNotEqual(renamed, DEFINITIONS[0][0])
def test_local_symbol_keeps_name_shared_with_renamed_global(self):
entries = symbols(False)
entries[1] = (DEFINITIONS[0][0], 1, 9, 2, 0x1500)
data = build_image(entries, False)
data[DYNSYM_OFFSET + 24 + 4] = 2
result, after = patch(PATCHER, bytes(data), "shared-local.so")
self.assertEqual(result.returncode, 0, result.stderr)
names = {value: name for name, _, value in read_dynsym(after)}
self.assertEqual(names[0x1500], DEFINITIONS[0][0])
self.assertNotEqual(names[DEFINITIONS[0][2]], DEFINITIONS[0][0])
if __name__ == "__main__":
PATCHER = Path(sys.argv.pop(1)).resolve()
unittest.main()

View File

@ -1,76 +0,0 @@
import struct
import sys
import unittest
from pathlib import Path
from test_symbol_versions import build_image, symbols, patch, SHOFF, DYNSTR_OFFSET, sections
VERNEED = 0x580
def image(*, auxiliary=16, count=1, size=32, version=1, auxnext=0, nextrecord=0):
data = build_image(symbols(False), False)
strings = sections(data)[2]
filename = strings[5]
names = b"libfixture.so\0FIXTURE_1\0"
data[DYNSTR_OFFSET + filename:DYNSTR_OFFSET + filename + len(names)] = names
struct.pack_into("<Q", data, SHOFF + 2 * 64 + 32, filename + len(names))
struct.pack_into("<H", data, 0x3c, 5)
struct.pack_into("<IIQQQQIIQQ", data, SHOFF + 4 * 64, 0, 0x6ffffffe, 2, 0, VERNEED, size, 2, 1, 8, 0)
struct.pack_into("<HHIII", data, VERNEED, version, count, filename, auxiliary, nextrecord)
struct.pack_into("<IHHII", data, VERNEED + auxiliary, 0, 0, 2, filename + len(b"libfixture.so\0"), auxnext)
return data
class VersionRequirementsTests(unittest.TestCase):
def test_auxiliary_record_cannot_leave_declared_section(self):
self.reject(image(auxiliary=32))
def test_auxiliary_count_must_match_chain(self):
self.reject(image(count=2))
def test_unsupported_structure_version_is_rejected(self):
self.reject(image(version=2))
def test_invalid_forward_links_and_section_ranges_are_rejected(self):
for fields in ({"auxnext": 16}, {"nextrecord": 64}, {"size": 15},
{"size": 0x10000}, {"auxiliary": 8}, {"count": 2, "auxnext": 8}):
with self.subTest(fields=fields):
self.reject(image(**fields))
def test_valid_multiple_auxiliaries_and_requirements(self):
data = image(count=2, size=80, auxnext=16, nextrecord=48)
original_name = struct.unpack_from("<I", data, VERNEED + 24)[0]
original_file = struct.unpack_from("<I", data, VERNEED + 4)[0]
struct.pack_into("<IHHII", data, VERNEED + 32, 0, 0, 3, original_name, 0)
struct.pack_into("<HHIII", data, VERNEED + 48, 1, 1, original_file, 16, 0)
struct.pack_into("<IHHII", data, VERNEED + 64, 0, 0, 4, original_name, 0)
result, after = patch(PATCHER, bytes(data), "multiple-versions.so")
self.assertEqual(result.returncode, 0, result.stderr)
strings = sections(after)[2]
for position, expected in ((4, b"libfixture.so"), (24, b"FIXTURE_1"),
(40, b"FIXTURE_1"), (52, b"libfixture.so"), (72, b"FIXTURE_1")):
offset = struct.unpack_from("<I", after, VERNEED + position)[0]
start = strings[4] + offset
self.assertEqual(after[start:after.index(0, start)], expected)
def reject(self, data):
result, after = patch(PATCHER, bytes(data), "versions.so")
self.assertEqual(result.returncode, 2, (result.returncode, result.stderr))
self.assertIn(b"version_r", result.stderr)
self.assertEqual(after, data)
def test_valid_names_are_preserved_when_strings_are_rebuilt(self):
data = image()
result, after = patch(PATCHER, bytes(data), "valid-versions.so")
self.assertEqual(result.returncode, 0, result.stderr)
strings = sections(after)[2]
for position, expected in ((VERNEED + 4, b"libfixture.so"), (VERNEED + 16 + 8, b"FIXTURE_1")):
offset = struct.unpack_from("<I", after, position)[0]
start = strings[4] + offset
self.assertEqual(after[start:after.index(0, start)], expected)
if __name__ == "__main__":
PATCHER = Path(sys.argv.pop(1)).resolve()
unittest.main()

View File

@ -0,0 +1,54 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI ErrorDialogClose(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI ErrorDialogOpen(const void* param) {
(void)param;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI ShareGetCurrentStatus(uint32_t feature_flag, ShareCurrentStatus* status) {
(void)feature_flag;
(void)status;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI ShareInitialize(size_t heap_size, int thread_priority, uint64_t affinity_mask) {
(void)heap_size;
(void)thread_priority;
(void)affinity_mask;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI ShareTerminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI SystemServiceParamGetInt(int param_id, int* value) {
(void)param_id;
(void)value;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI SystemServiceParamGetString(int param_id, char* buf, size_t buf_size) {
(void)param_id;
(void)buf;
(void)buf_size;
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -0,0 +1,35 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI RemotePlayGetConnectionStatus(int user_id, int* status) {
(void)user_id;
(void)status;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI ShareGetCurrentStatus(uint32_t feature_flag, ShareCurrentStatus* status) {
(void)feature_flag;
(void)status;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI ShareInitialize(size_t heap_size, int thread_priority, uint64_t affinity_mask) {
(void)heap_size;
(void)thread_priority;
(void)affinity_mask;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI ShareTerminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -53,15 +53,12 @@ int APS5_VABI sceAcm_ConvReverb_SharedInput(void) {
return 0;
}
int APS5_VABI sceAcm_FFT(void) {
int APS5_VABI sceAcm_FFT() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAcm_Panner(void) {
return 0;
}
int APS5_VABI sceAcmBatchJobPriority(void) {
int APS5_VABI sceAcm_Panner() {
NotImplemented_nid_no_patch(__func__);
return 0;
}

View File

@ -4,34 +4,17 @@
#include "prx/libSceAgc/Command/include/Memory.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include "prx/libSceAgc/DcbState/include/Marker.hpp"
#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp"
#include "prx/libSceAgcDriver/Resource/include/Registration.hpp"
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
std::uint32_t* RejectUnregisteredWorkloadStream(CommandBuffer* buf, std::uint32_t streamId, const char* function) {
if (buf == nullptr || !AgcDriverWorkloadStreamRegistered_nid_postfix(streamId)) return nullptr;
NotImplemented_nid_no_patch(function);
return nullptr;
}
}
extern "C" {
std::uint32_t* APS5_VABI sceAgcAcbAtomicGds_0900(CommandBuffer* buf, std::uint32_t control, std::uint32_t mode, std::uint32_t gdsOp, std::uint32_t src, std::uint16_t offset, std::uint16_t index, std::uint32_t mask, std::uint32_t data, std::uint64_t compareData, std::uint64_t extraData) {
const auto operation = (control & 0xffu) | ((control & 0x200u) << 7u) |
((control & 0x20u) << 13u) | ((control & 0x40u) << 14u) |
((control & 0x20u) == 0 && mode == 1 ? 0x20000u : 0u);
return Agc::Command::Emit(buf, 0x1du, {
operation, ((gdsOp & 1u) << 8u) | (src & 0x3fu), offset, index,
mask & 0x00ff00ffu, data, static_cast<std::uint32_t>(compareData),
static_cast<std::uint32_t>(compareData >> 32u), static_cast<std::uint32_t>(extraData),
static_cast<std::uint32_t>(extraData >> 32u)}, __func__);
// unknown signature
void* APS5_VABI sceAgcAcbAtomicGds_0900(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
}
std::uint32_t* APS5_VABI sceAgcAcbJump(CommandBuffer* buf, std::uint8_t cachePolicy, const std::uint32_t* target, std::uint32_t sizeInDwords) {
@ -63,12 +46,11 @@ std::uint32_t APS5_VABI sceAgcAcbRewindGetSize() {
}
std::uint32_t* APS5_VABI sceAgcAcbWaitUntilSafeForRendering(CommandBuffer* buf, std::uint32_t videoOutHandle, std::uint32_t displayBufferIndex) {
auto* packet = Agc::Command::Allocate(buf, AgcDriver::RenderingWaitPacketWords, __func__);
packet[0] = AgcDriver::RenderingWaitPacketHeader;
packet[1] = videoOutHandle;
packet[2] = displayBufferIndex;
packet[3] = 0;
return packet;
(void)buf;
(void)videoOutHandle;
(void)displayBufferIndex;
NotImplemented_nid_no_patch(__func__);
return nullptr;
}
std::uint32_t* APS5_VABI sceAgcAcbSetFlip(CommandBuffer* buf, std::uint32_t videoOutHandle, std::int32_t displayBufferIndex, std::uint32_t flipMode, std::int64_t flipArg) {
@ -81,21 +63,6 @@ std::uint32_t* APS5_VABI sceAgcAcbSetFlip(CommandBuffer* buf, std::uint32_t vide
return nullptr;
}
std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadsActive(CommandBuffer* buf, std::uint32_t streamId, const std::uint32_t* workloadIds, std::uint32_t workloadCount) {
(void)workloadIds;
(void)workloadCount;
return RejectUnregisteredWorkloadStream(buf, streamId, __func__);
}
std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadComplete(CommandBuffer* buf, std::uint32_t streamId, std::uint32_t workloadId) {
(void)workloadId;
return RejectUnregisteredWorkloadStream(buf, streamId, __func__);
}
std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadStreamInactive(CommandBuffer* buf, std::uint32_t streamId) {
return RejectUnregisteredWorkloadStream(buf, streamId, __func__);
}
uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, uint32_t color) {
(void)color;
return Agc::Marker::Push(buf, str, __func__);

View File

@ -29,11 +29,9 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Predication.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Registers.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DcbState/src/Workload.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Debug.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/PacketInfo.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Platform.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/RegisterDefaults.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Semaphore.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/ShaderFusion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Misc/src/Suspend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Patch/src/CondExec.cpp
@ -48,7 +46,6 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Predication/src/Predication.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/CreateShader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/GsOversubscription.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/GsPrimPayload.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/InterpolantMapping.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/LinkShaders.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/PrimState.cpp
@ -70,7 +67,7 @@ endforeach()
add_test_executable(agc_command_tests EXCLUDE_FROM_ALL tests/Commands.cpp)
target_include_directories(agc_command_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_command_tests PRIVATE libSceAgc libSceAgcDriver libc)
target_link_libraries(agc_command_tests PRIVATE libSceAgc libc)
configure_windows_unwind(agc_command_tests)
if(BUILD_TESTING)
set_target_properties(agc_command_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
@ -96,19 +93,6 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_semaphore_tests EXCLUDE_FROM_ALL tests/Semaphore.cpp)
target_include_directories(agc_semaphore_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_semaphore_tests PRIVATE libSceAgc libc)
configure_windows_unwind(agc_semaphore_tests)
if(BUILD_TESTING)
set_target_properties(agc_semaphore_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_test(NAME agc_semaphore_tests COMMAND agc_semaphore_tests)
if(WIN32)
set_tests_properties(agc_semaphore_tests PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_packet_info_tests EXCLUDE_FROM_ALL tests/PacketInfo.cpp)
target_include_directories(agc_packet_info_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_packet_info_tests PRIVATE libSceAgc libc)
@ -306,22 +290,6 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_gs_prim_payload_tests EXCLUDE_FROM_ALL
tests/GsPrimPayload.cpp
Shader/src/GsPrimPayload.cpp
)
target_include_directories(agc_gs_prim_payload_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_gs_prim_payload_tests PRIVATE libc)
configure_windows_unwind(agc_gs_prim_payload_tests)
if(BUILD_TESTING)
set_target_properties(agc_gs_prim_payload_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_test(NAME agc_gs_prim_payload_tests COMMAND agc_gs_prim_payload_tests)
if(WIN32)
set_tests_properties(agc_gs_prim_payload_tests PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_dma_data_tests EXCLUDE_FROM_ALL
tests/DmaData.cpp
Command/src/Packet.cpp

View File

@ -13,7 +13,8 @@
extern "C" {
// unknown signature
void* APS5_VABI sceAgcDcbAtomicGds_0900(void) {
APS5_EXPORT("zARR5aCmkoY", sceAgcDcbA_zARR5aCmkoY);
void* APS5_VABI sceAgcDcbA_zARR5aCmkoY(void) {
NotImplemented_nid_no_patch(__func__);
return nullptr;
}
@ -34,7 +35,8 @@ std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t o
}
std::uint32_t* APS5_VABI sceAgcDcbClearState(CommandBuffer* buf, std::uint32_t command) {
return Agc::Command::Emit(buf, 0x12u, {command & 0xfu}, __func__);
Agc::Command::CheckBits(command, 0xfu, __func__);
return Agc::Command::Emit(buf, 0x12u, {command}, __func__);
}
uint32_t* APS5_VABI sceAgcDcbRewind(CommandBuffer* buf, uint32_t initial_state) {

View File

@ -36,6 +36,7 @@ std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t ev
Agc::Command::CheckAddress(guestAddress, 8, __func__);
return Agc::Command::Emit(buf, 0x46u, {0x100u | eventType, static_cast<std::uint32_t>(guestAddress), static_cast<std::uint32_t>(guestAddress >> 32u)}, __func__);
}
Agc::Command::Require(address == nullptr, __func__, "this event does not use an address");
const auto eventIndex = eventType == 7 || eventType == 15 || eventType == 16 ? 0x400u : 0u;
return Agc::Command::Emit(buf, 0x46u, {eventIndex | eventType}, __func__);
}

View File

@ -7,8 +7,6 @@
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
static constexpr int SCE_AGC_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80890001);
extern "C" {
int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version) {
@ -18,7 +16,6 @@ int APS5_VABI sceAgcInit_0090(std::uint32_t* state, std::uint32_t version) {
}
int APS5_VABI sceAgcInit(std::uint32_t version) {
if (version >= 14) return SCE_AGC_ERROR_INVALID_ARGUMENT;
Agc::Command::GetRegisterDefaults(version, false, __func__);
Agc::Command::GetRegisterDefaults(version, true, __func__);
return 0;

View File

@ -1,62 +1,26 @@
#include "prx/libSceAgc/DcbState/include/Workload.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include "prx/libSceAgcDriver/Resource/include/Registration.hpp"
#include <cstdint>
#include <cstddef>
#include <cstring>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr std::uint32_t OpcodeNop = 0x10;
constexpr std::uint32_t ActivePacketWords = 18;
constexpr std::uint32_t CompletePacketWords = 12;
constexpr std::uint32_t MaxWorkloadId = 63;
constexpr std::uint32_t MaxActiveCount = 63;
uint32_t* NewPacket(CommandBuffer* buf, std::uint32_t words, const char* function) {
auto* packet = Agc::Command::Allocate(buf, words, function);
std::memset(packet, 0, words * sizeof(std::uint32_t));
packet[0] = Agc::Command::Header(OpcodeNop, words, 0);
return packet;
}
}
extern "C" {
uint32_t* APS5_VABI sceAgcDcbSetWorkloadComplete(CommandBuffer* buf, uint32_t stream_id, uint32_t workload_id) {
if (buf == nullptr || workload_id > MaxWorkloadId || !AgcDriverWorkloadStreamRegistered_nid_postfix(stream_id)) return nullptr;
const std::uint64_t clearMask = ~(std::uint64_t{1} << workload_id);
auto* packet = NewPacket(buf, CompletePacketWords, __func__);
packet[1] = stream_id;
packet[2] = workload_id;
packet[3] = static_cast<std::uint32_t>(clearMask);
packet[4] = static_cast<std::uint32_t>(clearMask >> 32);
return packet;
(void)buf;
(void)stream_id;
(void)workload_id;
NotImplemented_nid_no_patch(__func__);
return nullptr;
}
uint32_t* APS5_VABI sceAgcDcbSetWorkloadsActive(CommandBuffer* buf, uint32_t stream_id, const uint32_t* workload_ids, uint32_t workload_count) {
if (buf == nullptr || workload_ids == nullptr || workload_count == 0 || workload_count > MaxActiveCount || !AgcDriverWorkloadStreamRegistered_nid_postfix(stream_id)) return nullptr;
std::uint64_t mask = 0;
for (std::uint32_t index = 0; index < workload_count; ++index) {
const auto id = workload_ids[index];
if (id > MaxWorkloadId) return nullptr;
const std::uint64_t bit = std::uint64_t{1} << id;
if ((mask & bit) != 0) return nullptr;
mask |= bit;
}
auto* packet = NewPacket(buf, ActivePacketWords, __func__);
packet[1] = stream_id;
packet[2] = static_cast<std::uint32_t>(mask);
packet[3] = static_cast<std::uint32_t>(mask >> 32);
return packet;
}
uint32_t* APS5_VABI sceAgcDcbSetWorkloadStreamInactive(CommandBuffer* buf, uint32_t streamId) {
if (buf == nullptr || !AgcDriverWorkloadStreamRegistered_nid_postfix(streamId)) return nullptr;
(void)buf;
(void)stream_id;
(void)workload_ids;
(void)workload_count;
NotImplemented_nid_no_patch(__func__);
return nullptr;
}

View File

@ -1,4 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_DEBUG_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_DEBUG_HPP
#endif

View File

@ -1,4 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_SEMAPHORE_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_SEMAPHORE_HPP
#endif

View File

@ -1,14 +0,0 @@
#include "prx/libSceAgc/Misc/include/Debug.hpp"
#include <cstdint>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
extern "C" {
int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId) {
APS5_LOG_OUT("sceAgcDebugRaiseException exceptionId=0x%08x", static_cast<unsigned>(exceptionId));
return 0;
}
}

View File

@ -1,61 +0,0 @@
#include "prx/libSceAgc/Misc/include/Semaphore.hpp"
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <limits>
#include <mutex>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr std::uint64_t semaphoreMemoryAlignment = 0x4000u;
constexpr std::uint64_t semaphoreLabelBytes = 32u;
constexpr std::uint64_t semaphoreCounterBytes = 8u;
std::mutex semaphoreMutex;
std::uint8_t* semaphoreMemory = nullptr;
std::uint64_t semaphoreBytes = 0;
bool hasOnlyZeroLabelTails(const std::uint8_t* memory, std::uint64_t sizeInBytes) {
for (std::uint64_t label = 0; label < sizeInBytes; label += semaphoreLabelBytes) {
for (std::uint64_t byte = semaphoreCounterBytes; byte < semaphoreLabelBytes; ++byte) {
if (memory[label + byte] != 0) return false;
}
}
return true;
}
}
extern "C" {
int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes) {
const auto address = reinterpret_cast<std::uintptr_t>(memory);
std::lock_guard lock(semaphoreMutex);
Agc::Command::Require(semaphoreMemory == nullptr, __func__, "the AMM semaphore memory is already registered; the console's error is unknown");
Agc::Command::Require(memory != nullptr && sizeInBytes != 0, __func__, "null or empty AMM semaphore memory; the console's error is unknown");
Agc::Command::Require(((address | sizeInBytes) & (semaphoreMemoryAlignment - 1u)) == 0, __func__, "AMM semaphore memory or size is not 16 KiB aligned; the console's error is unknown");
Agc::Command::Require(sizeInBytes - 1u <= std::numeric_limits<std::uintptr_t>::max() - address, __func__, "AMM semaphore memory wraps the address space");
auto* labels = static_cast<std::uint8_t*>(memory);
Agc::Command::Require(hasOnlyZeroLabelTails(labels, sizeInBytes), __func__, "AMM semaphore labels hold data past their first 8 bytes; whether registration clears it is unknown");
for (std::uint64_t label = 0; label < sizeInBytes; label += semaphoreLabelBytes) {
std::memset(labels + label, 0, semaphoreCounterBytes);
}
semaphoreMemory = labels;
semaphoreBytes = sizeInBytes;
return 0;
}
int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label) {
std::lock_guard lock(semaphoreMutex);
Agc::Command::Require(semaphoreMemory != nullptr, __func__, "no AMM semaphore memory is registered; the console's error is unknown");
Agc::Command::Require(index < semaphoreBytes / semaphoreLabelBytes, __func__, "the semaphore label lies outside the AMM semaphore memory; the console's error is unknown");
Agc::Command::Require(label != nullptr, __func__, "null semaphore label output; the console's error is unknown");
*label = semaphoreMemory + static_cast<std::uint64_t>(index) * semaphoreLabelBytes;
return 0;
}
}

View File

@ -19,13 +19,11 @@ constexpr std::uint32_t VGT_SHADER_STAGES_EN = 0x2D5u;
constexpr std::uint32_t VGT_PRIMITIVE_TYPE = 0x242u;
constexpr std::uint32_t GE_CNTL = 0x25Bu;
constexpr std::uint32_t GE_USER_VGPR1 = 0x25Cu;
constexpr std::uint32_t GE_USER_VGPR_EN = 0x262u;
constexpr std::uint32_t GE_PC_ALLOC = 0x260u;
constexpr std::uint32_t SPI_SHADER_PGM_RSRC4_GS = 0x081u;
constexpr std::uint32_t SPI_VS_OUT_CONFIG = 0x1B1u;
constexpr std::uint32_t SPI_SHADER_IDX_FORMAT = 0x1C2u;
constexpr std::uint32_t GE_MAX_OUTPUT_PER_SUBGROUP = 0x1FFu;
constexpr std::uint32_t PA_CL_VS_OUT_CNTL = 0x207u;
constexpr std::uint32_t VGT_GS_ONCHIP_CNTL = 0x291u;

View File

@ -1,39 +0,0 @@
#include <cstdint>
#include <stdexcept>
#include <string>
#include <prx/libc/include/General.hpp>
#include "SceShaders.hpp"
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
namespace {
constexpr std::uint32_t IDX0_EXPORT_FORMAT_MASK = 0xFu;
constexpr std::uint32_t SPI_SHADER_2COMP = 2u;
constexpr std::uint32_t TwoComponentPayloadBytes = 8u;
}
extern "C" {
int APS5_VABI sceAgcGetGsPrimPayload(std::uint32_t* payload, const Shader* gs) {
if (payload == nullptr || gs == nullptr) {
throw std::runtime_error(std::string(__func__) + ": payload or gs is null");
}
if (gs->num_cx_registers != 0 && gs->cx_registers == nullptr) {
throw std::runtime_error(std::string(__func__) + ": gs->cx_registers is null but num_cx_registers != 0");
}
std::uint32_t bytes = 0;
for (std::uint32_t i = 0; i < gs->num_cx_registers; ++i) {
const ShaderRegister& reg = gs->cx_registers[i];
if (reg.offset == ShaderRegs::SPI_SHADER_IDX_FORMAT && (reg.value & IDX0_EXPORT_FORMAT_MASK) == SPI_SHADER_2COMP) {
bytes = TwoComponentPayloadBytes;
break;
}
}
*payload = bytes;
return 0;
}
}

View File

@ -53,21 +53,10 @@ std::uint32_t CreateInterpolantValueSplitF16(std::uint32_t psWord, const ShaderS
: CreateInterpolantValue(psWord, gsSemantic);
}
std::uint32_t UpdateInterpolantValue(std::uint32_t psWord, const ShaderSemantic* gsSemantic) {
const std::uint32_t f16 = (psWord >> 20u) & 0x3u;
if (f16 != 0 && f16 != InterpolantF16HiOnly && ((psWord >> 28u) & 0x3u) != ((psWord >> 30u) & 0x3u)) {
throw std::runtime_error(std::string(__func__) + ": DEFAULT_VAL of an f16 input with different low and high defaults is unknown");
}
return CreateInterpolantValueSplitF16(psWord, gsSemantic);
}
int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue, bool fillIdentityTail) {
int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue) {
if (regs == nullptr) {
throw std::runtime_error(std::string(fn) + ": regs is null");
}
if (ps == nullptr && !fillIdentityTail) {
throw std::runtime_error(std::string(fn) + ": ps is null");
}
if (ps != nullptr && ps->num_input_semantics > 32) {
throw std::runtime_error(std::string(fn) + ": input semantic count exceeds 32");
}
@ -77,14 +66,13 @@ int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader*
auto* output = regs;
regs = values.data();
if (ps == nullptr || ps->num_input_semantics == 0) {
const std::uint32_t count = fillIdentityTail ? InterpolantRegisterCount : 0;
if (fillIdentityTail) FillIdentityInterpolants(regs, 0);
FillIdentityInterpolants(regs, 0);
if (ps != nullptr) {
AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, count}, {});
AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, 32}, {});
if (gs != nullptr) AgcDriverResolveGraphicsAbi_nid_postfix(gs, ps, 0);
}
transaction.Commit();
std::copy_n(values.begin(), count, output);
std::copy(values.begin(), values.end(), output);
return 0;
}
@ -110,12 +98,11 @@ int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader*
SetInterpolantRegister(regs, i, createValue(ShaderSemanticWord(psSemantic), gsSemantic));
}
const std::uint32_t count = fillIdentityTail ? InterpolantRegisterCount : ps->num_input_semantics;
if (fillIdentityTail) FillIdentityInterpolants(regs, ps->num_input_semantics);
AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, count}, {});
FillIdentityInterpolants(regs, ps->num_input_semantics);
AgcDriverResolveShaderAbi_nid_postfix(ps, {regs, 32}, {});
AgcDriverResolveGraphicsAbi_nid_postfix(gs, ps, 0);
transaction.Commit();
std::copy_n(values.begin(), count, output);
std::copy(values.begin(), values.end(), output);
return 0;
}
@ -124,17 +111,12 @@ int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader*
extern "C" {
int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps) {
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue, true);
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue);
}
APS5_EXPORT("dbOlWdppb4o", sceAgcUnknownCreateInterpolantMapping);
int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps) {
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16, true);
}
APS5_EXPORT("vieBRwlh1Lw", sceAgcUnknown_vieBRwlh1Lw);
int APS5_VABI sceAgcUnknown_vieBRwlh1Lw(ShaderRegister* regs, const Shader* gs, const Shader* ps) {
return CreateInterpolantMapping(__func__, regs, gs, ps, UpdateInterpolantValue, false);
return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16);
}
}

View File

@ -11,18 +11,6 @@
#include "prx/libSceAgc/Shader/include/ShaderUtils.hpp"
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
namespace {
void RequireSpecialOffset(const ShaderRegister& reg, std::uint32_t expected, const char* name) {
if (reg.offset != expected) {
char message[160];
std::snprintf(message, sizeof(message), "sceAgcCreatePrimState: %s special register at offset 0x%x, expected 0x%x", name, static_cast<unsigned>(reg.offset), static_cast<unsigned>(expected));
throw std::runtime_error(message);
}
}
}
extern "C" {
int APS5_VABI sceAgcCreatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_regs, const Shader* hs, const Shader* gs, std::uint32_t prim_type) {
@ -37,17 +25,21 @@ int APS5_VABI sceAgcCreatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_
throw std::runtime_error(std::string(__func__) + ": invalid shader type or missing special registers");
}
(void)GraphicsPrimTypeToGsOut(prim_type);
RequireSpecialOffset(gs->specials->vgt_shader_stages_en, ShaderRegs::VGT_SHADER_STAGES_EN, "VGT_SHADER_STAGES_EN");
if (hs != nullptr) RequireSpecialOffset(hs->specials->vgt_shader_stages_en, ShaderRegs::VGT_SHADER_STAGES_EN, "VGT_SHADER_STAGES_EN");
RequireSpecialOffset(gs->specials->ge_cntl, ShaderRegs::GE_CNTL, "GE_CNTL");
const auto validStages = [](const Shader* shader) { return shader->specials->vgt_shader_stages_en.offset == ShaderRegs::VGT_SHADER_STAGES_EN; };
if (!validStages(gs) || (hs != nullptr && !validStages(hs))) {
throw std::runtime_error(std::string(__func__) + ": invalid context register offsets");
}
if (gs->specials->ge_cntl.offset != ShaderRegs::GE_CNTL || gs->specials->ge_user_vgpr_en.offset != ShaderRegs::GE_USER_VGPR_EN || (hs != nullptr && hs->specials->ge_user_vgpr_en.offset != ShaderRegs::GE_USER_VGPR_EN)) {
throw std::runtime_error(std::string(__func__) + ": invalid user configuration register offsets");
}
std::array<ShaderRegister, 2> contextValues{gs->specials->vgt_shader_stages_en, gs->specials->vgt_gs_out_prim_type};
if (hs != nullptr) {
contextValues[0].value |= hs->specials->vgt_shader_stages_en.value;
}
if ((contextValues[0].value & ShaderRegs::VGT_SHADER_STAGES_GS_BIT) != 0) {
RequireSpecialOffset(gs->specials->vgt_gs_out_prim_type, ShaderRegs::VGT_GS_OUT_PRIM_TYPE, "VGT_GS_OUT_PRIM_TYPE");
if (gs->specials->vgt_gs_out_prim_type.offset != ShaderRegs::VGT_GS_OUT_PRIM_TYPE) throw std::runtime_error(std::string(__func__) + ": invalid context register offsets");
} else if (hs != nullptr) {
RequireSpecialOffset(hs->specials->vgt_gs_out_prim_type, ShaderRegs::VGT_GS_OUT_PRIM_TYPE, "VGT_GS_OUT_PRIM_TYPE");
if (hs->specials->vgt_gs_out_prim_type.offset != ShaderRegs::VGT_GS_OUT_PRIM_TYPE) throw std::runtime_error(std::string(__func__) + ": invalid context register offsets");
contextValues[1] = hs->specials->vgt_gs_out_prim_type;
} else {
contextValues[1] = {ShaderRegs::VGT_GS_OUT_PRIM_TYPE, GraphicsPrimTypeToGsOut(prim_type)};

View File

@ -15,6 +15,21 @@ int APS5_VABI sceAgcAcbSetMarkerSpan() {
return 0;
}
int APS5_VABI sceAgcAcbSetWorkloadComplete() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcAcbSetWorkloadStreamInactive() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcAcbSetWorkloadsActive() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcAcquireMemSetEngine() {
NotImplemented_nid_no_patch(__func__);
return 0;
@ -30,11 +45,31 @@ int APS5_VABI sceAgcDcbSetMarkerSpan() {
return 0;
}
int APS5_VABI sceAgcDcbSetWorkloadStreamInactive() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcDebugRaiseException() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcGetDefaultCxStateFlat() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcGetSemaphoreLabel() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcSetAmmSemaphoreMemory() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcSetSemaphoreMemory() {
NotImplemented_nid_no_patch(__func__);
return 0;
@ -45,6 +80,11 @@ int APS5_VABI sceAgcCbMemsetExclusive() {
return 0;
}
int APS5_VABI sceAgcGetGsPrimPayload() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcSetShaderInstrumentation() {
NotImplemented_nid_no_patch(__func__);
return 0;
@ -55,4 +95,10 @@ int APS5_VABI sceAgcGetShaderInstrumentation() {
return 0;
}
APS5_EXPORT("vieBRwlh1Lw", sceAgcUnknown_vieBRwlh1Lw);
int APS5_VABI sceAgcUnknown_vieBRwlh1Lw(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -10,12 +10,9 @@
#include <cstring>
#include <string>
extern "C" std::uint32_t* APS5_VABI sceAgcDcbEventWrite(CommandBuffer* buf, std::uint8_t eventType, const volatile void* address);
extern "C" std::uint64_t APS5_VABI sceAgcDcbEventWriteGetSize(std::uint8_t eventType);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbResetQueue(CommandBuffer* buf, std::uint32_t op, std::uint32_t state);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbClearState(CommandBuffer* buf, std::uint32_t command);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetFlip(CommandBuffer* buf, std::uint32_t handle, std::int32_t index, std::uint32_t mode, std::int64_t argument);
extern "C" std::uint32_t* APS5_VABI sceAgcAcbWaitUntilSafeForRendering(CommandBuffer* buf, std::uint32_t handle, std::uint32_t index);
extern "C" int APS5_VABI sceAgcSuspendPoint();
extern "C" int APS5_VABI sceAgcInit(std::uint32_t version);
extern "C" void* APS5_VABI sceAgcGetRegisterDefaults();
@ -32,14 +29,6 @@ extern "C" std::uint32_t* APS5_VABI sceAgcCbSetShRegisterRangeDirect(CommandBuff
extern "C" std::uint32_t* APS5_VABI sceAgcDcbContextStateOp_0100(CommandBuffer* buf, std::uint32_t operation);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbPushMarker(CommandBuffer* buf, const char* str, std::uint32_t color);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbPopMarker(CommandBuffer* buf);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetWorkloadComplete(CommandBuffer* buf, std::uint32_t stream_id, std::uint32_t workload_id);
extern "C" int APS5_VABI sceAgcDriverRegisterWorkloadStream(std::uint32_t stream_id, const void* stream);
extern "C" int APS5_VABI sceAgcDriverUnregisterWorkloadStream(std::uint32_t stream_id);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetWorkloadsActive(CommandBuffer* buf, std::uint32_t stream_id, const std::uint32_t* workload_ids, std::uint32_t workload_count);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetWorkloadStreamInactive(CommandBuffer* buf, std::uint32_t streamId);
extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadsActive(CommandBuffer* buf, std::uint32_t streamId, const std::uint32_t* workloadIds, std::uint32_t workloadCount);
extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadComplete(CommandBuffer* buf, std::uint32_t streamId, std::uint32_t workloadId);
extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetWorkloadStreamInactive(CommandBuffer* buf, std::uint32_t streamId);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetMarker(CommandBuffer* buf, const char* str, std::uint32_t color);
extern "C" std::uint32_t* APS5_VABI sceAgcAcbPushMarker(CommandBuffer* buf, const char* str, std::uint32_t color);
extern "C" std::uint32_t* APS5_VABI sceAgcAcbPopMarker(CommandBuffer* buf);
@ -47,13 +36,9 @@ extern "C" std::uint32_t* APS5_VABI sceAgcAcbSetMarker(CommandBuffer* buf, const
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetIndexBuffer(CommandBuffer* buf, std::uint64_t indexAddress);
extern "C" std::uint32_t* APS5_VABI sceAgcDcbSetBaseIndirectArgs(CommandBuffer* buf, std::uint32_t shaderType, const volatile void* indirectBaseAddr);
extern "C" std::uint32_t* APS5_VABI sceAgcSetNop(CommandBuffer* buf, std::uint32_t sizeDw);
extern "C" int APS5_VABI sceAgcDebugRaiseException(std::uint32_t exceptionId);
extern "C" std::uint32_t* APS5_VABI sceAgcAcbAtomicGds_0900(CommandBuffer* buf, std::uint32_t control, std::uint32_t mode, std::uint32_t gdsOp, std::uint32_t src, std::uint16_t offset, std::uint16_t index, std::uint32_t mask, std::uint32_t data, std::uint64_t compareData, std::uint64_t extraData);
namespace {
constexpr int SCE_AGC_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80890001);
void check(bool condition, const char* message) {
if (!condition) {
throw std::runtime_error(message);
@ -148,14 +133,12 @@ void testClearState() {
auto* packet = sceAgcDcbClearState(&storage.buffer, command);
check(packet == storage.words.data() + command * 2 && packet[0] == 0xc0001200u && packet[1] == command, "incorrect CLEAR_STATE packet");
}
auto* maskedZero = sceAgcDcbClearState(&storage.buffer, 0x10u);
check(maskedZero == storage.words.data() + 32 && maskedZero[0] == 0xc0001200u && maskedZero[1] == 0, "CLEAR_STATE did not mask the command to four bits");
auto* maskedF = sceAgcDcbClearState(&storage.buffer, 0xffffffffu);
check(maskedF == storage.words.data() + 34 && maskedF[0] == 0xc0001200u && maskedF[1] == 0xfu, "CLEAR_STATE did not mask the command to four bits");
check(storage.buffer.cursor_up == storage.words.data() + 36 && storage.words[36] == 0xdeadbeefu, "incorrect CLEAR_STATE cursor advance");
check(storage.buffer.cursor_up == storage.words.data() + 32 && storage.words[32] == 0xdeadbeefu, "incorrect CLEAR_STATE cursor advance");
const auto before = storage.words;
expectFailure([&] { sceAgcDcbClearState(&storage.buffer, 0x10u); });
expectFailure([&] { sceAgcDcbClearState(&storage.buffer, 0xffffffffu); });
expectFailure([] { sceAgcDcbClearState(nullptr, 0); });
check(storage.words == before && storage.buffer.cursor_up == storage.words.data() + 36, "invalid CLEAR_STATE modified the buffer");
check(storage.words == before && storage.buffer.cursor_up == storage.words.data() + 32, "invalid CLEAR_STATE modified the buffer");
}
struct ContextGrowth {
@ -220,91 +203,6 @@ void testMarkers() {
expectFailure([] { sceAgcAcbSetMarker(nullptr, "frame", 0); });
}
void testWorkloads() {
Storage dcb;
const std::uint32_t ids[2] = {3, 4};
check(sceAgcDcbSetWorkloadsActive(&dcb.buffer, 1, ids, 2) == nullptr, "workloads active packet for an unregistered stream");
check(sceAgcDcbSetWorkloadComplete(&dcb.buffer, 1, 3) == nullptr, "workload complete packet for an unregistered stream");
check(dcb.buffer.cursor_up == dcb.words.data(), "unregistered stream advanced the cursor");
const std::uint32_t record[8] = {};
check(sceAgcDriverRegisterWorkloadStream(1, record) == 0, "workload stream registration failed");
const auto* active = sceAgcDcbSetWorkloadsActive(&dcb.buffer, 1, ids, 2);
check(active == dcb.words.data() && active[0] == Agc::Command::Header(0x10, 18, 0) && active[1] == 1 && active[2] == 0x18 && active[3] == 0, "workloads active packet");
for (std::uint32_t word = 4; word < 18; ++word) check(active[word] == 0, "workloads active packet tail is not zero");
const auto* complete = sceAgcDcbSetWorkloadComplete(&dcb.buffer, 1, 33);
check(complete == dcb.words.data() + 18 && complete[0] == Agc::Command::Header(0x10, 12, 0) && complete[1] == 1 && complete[2] == 33 && complete[3] == 0xffffffffu && complete[4] == 0xfffffffdu, "workload complete packet");
for (std::uint32_t word = 5; word < 12; ++word) check(complete[word] == 0, "workload complete packet tail is not zero");
check(dcb.buffer.cursor_up == dcb.words.data() + 30, "incorrect workload cursor advance");
const std::uint32_t duplicate[2] = {5, 5};
const std::uint32_t outOfRange[1] = {64};
const std::uint32_t top[1] = {63};
check(sceAgcDcbSetWorkloadsActive(&dcb.buffer, 1, duplicate, 2) == nullptr, "duplicate workload ids accepted");
check(sceAgcDcbSetWorkloadsActive(&dcb.buffer, 1, outOfRange, 1) == nullptr, "workload id 64 accepted");
check(sceAgcDcbSetWorkloadsActive(&dcb.buffer, 1, top, 1) != nullptr, "workload id 63 rejected");
check(sceAgcDcbSetWorkloadsActive(&dcb.buffer, 1, ids, 0) == nullptr, "workload count 0 accepted");
check(sceAgcDcbSetWorkloadsActive(&dcb.buffer, 1, nullptr, 2) == nullptr, "null workload ids accepted");
check(sceAgcDcbSetWorkloadsActive(&dcb.buffer, 0, ids, 2) == nullptr, "stream id 0 accepted");
check(sceAgcDcbSetWorkloadsActive(&dcb.buffer, 32, ids, 2) == nullptr, "stream id 32 accepted");
check(sceAgcDcbSetWorkloadComplete(&dcb.buffer, 1, 64) == nullptr, "workload complete id 64 accepted");
check(sceAgcDcbSetWorkloadsActive(nullptr, 1, ids, 2) == nullptr, "null command buffer accepted");
check(sceAgcDcbSetWorkloadComplete(nullptr, 1, 3) == nullptr, "null command buffer accepted for complete");
check(sceAgcDriverUnregisterWorkloadStream(1) == 0, "workload stream unregistration failed");
check(sceAgcDcbSetWorkloadComplete(&dcb.buffer, 1, 3) == nullptr, "workload complete packet after unregistration");
}
void testWorkloadStreams() {
struct Vector {
std::uint32_t streamId;
std::uintptr_t workloadIds;
std::uint32_t workloadCount;
std::uint32_t workloadId;
};
const std::array<Vector, 3> vectors{{
{0x11111111u, 0x22222222u, 0x33333333u, 0x22222222u},
{1, 2, 3, 2},
{0x22222222u, static_cast<std::uintptr_t>(0x3333333344444444ull), 0, 0x44444444u}}};
for (const auto& vector : vectors) {
Storage storage;
storage.words.fill(0xdeadbeefu);
const auto before = storage.words;
const auto* ids = reinterpret_cast<const std::uint32_t*>(vector.workloadIds);
check(sceAgcDcbSetWorkloadsActive(&storage.buffer, vector.streamId, ids, vector.workloadCount) == nullptr, "DCB workloads active returned a packet for an unregistered stream");
check(sceAgcDcbSetWorkloadComplete(&storage.buffer, vector.streamId, vector.workloadId) == nullptr, "DCB workload complete returned a packet for an unregistered stream");
check(sceAgcDcbSetWorkloadStreamInactive(&storage.buffer, vector.streamId) == nullptr, "DCB workload stream inactive returned a packet for an unregistered stream");
check(sceAgcAcbSetWorkloadsActive(&storage.buffer, vector.streamId, ids, vector.workloadCount) == nullptr, "ACB workloads active returned a packet for an unregistered stream");
check(sceAgcAcbSetWorkloadComplete(&storage.buffer, vector.streamId, vector.workloadId) == nullptr, "ACB workload complete returned a packet for an unregistered stream");
check(sceAgcAcbSetWorkloadStreamInactive(&storage.buffer, vector.streamId) == nullptr, "ACB workload stream inactive returned a packet for an unregistered stream");
check(storage.words == before && storage.buffer.cursor_up == storage.words.data(), "a workload call for an unregistered stream wrote to the buffer");
}
const std::uint32_t ids[2] = {3, 4};
Storage exhausted;
exhausted.buffer.cursor_down = exhausted.words.data() + 3;
const auto before = exhausted.words;
check(sceAgcDcbSetWorkloadStreamInactive(&exhausted.buffer, 1) == nullptr && sceAgcAcbSetWorkloadsActive(&exhausted.buffer, 1, ids, 2) == nullptr &&
sceAgcAcbSetWorkloadComplete(&exhausted.buffer, 1, 3) == nullptr && sceAgcAcbSetWorkloadStreamInactive(&exhausted.buffer, 1) == nullptr,
"a workload call for an unregistered stream on an exhausted buffer returned a packet");
check(exhausted.words == before && exhausted.buffer.cursor_up == exhausted.words.data(), "a workload call for an unregistered stream modified an exhausted buffer");
check(sceAgcDcbSetWorkloadStreamInactive(nullptr, 1) == nullptr && sceAgcAcbSetWorkloadsActive(nullptr, 1, ids, 2) == nullptr &&
sceAgcAcbSetWorkloadComplete(nullptr, 1, 3) == nullptr && sceAgcAcbSetWorkloadStreamInactive(nullptr, 1) == nullptr,
"a workload call with a null command buffer returned a packet");
const std::uint32_t record[8] = {};
check(sceAgcDriverRegisterWorkloadStream(1, record) == 0, "workload stream registration failed");
Storage registered;
registered.words.fill(0xdeadbeefu);
const auto untouched = registered.words;
expectFailure([&] { sceAgcDcbSetWorkloadStreamInactive(&registered.buffer, 1); });
expectFailure([&] { sceAgcAcbSetWorkloadsActive(&registered.buffer, 1, ids, 2); });
expectFailure([&] { sceAgcAcbSetWorkloadComplete(&registered.buffer, 1, 3); });
expectFailure([&] { sceAgcAcbSetWorkloadStreamInactive(&registered.buffer, 1); });
check(registered.words == untouched && registered.buffer.cursor_up == registered.words.data(), "a workload call that threw modified the buffer");
check(sceAgcDcbSetWorkloadStreamInactive(nullptr, 1) == nullptr && sceAgcAcbSetWorkloadsActive(nullptr, 1, ids, 2) == nullptr &&
sceAgcAcbSetWorkloadComplete(nullptr, 1, 3) == nullptr && sceAgcAcbSetWorkloadStreamInactive(nullptr, 1) == nullptr,
"a workload call with a null command buffer for a registered stream returned a packet");
check(sceAgcDriverUnregisterWorkloadStream(1) == 0, "workload stream unregistration failed");
}
void testIndexBuffer() {
Storage storage;
alignas(4) std::uint16_t indices[2]{};
@ -415,17 +313,6 @@ void testFlip() {
exhausted.buffer.cursor_down = exhausted.words.data() + 5;
expectFailure([&] { sceAgcDcbSetFlip(&exhausted.buffer, 1, 0, 1, 0); });
check(exhausted.buffer.cursor_up == exhausted.words.data(), "failed flip allocation advanced cursor");
Storage acb;
acb.words.fill(0xdeadbeefu);
auto* wait = sceAgcAcbWaitUntilSafeForRendering(&acb.buffer, 0xfedcba98u, 2);
const std::array<std::uint32_t, 4> expectedWait{0xc0021018u, 0xfedcba98u, 2, 0};
check(wait == acb.words.data() && std::equal(expectedWait.begin(), expectedWait.end(), wait), "compute rendering wait packet mismatch");
check(acb.buffer.cursor_up == wait + 4 && wait[4] == 0xdeadbeefu, "compute rendering wait overran allocation");
expectFailure([] { sceAgcAcbWaitUntilSafeForRendering(nullptr, 1, 0); });
Storage full;
full.buffer.cursor_down = full.words.data() + 3;
expectFailure([&] { sceAgcAcbWaitUntilSafeForRendering(&full.buffer, 1, 0); });
check(full.buffer.cursor_up == full.words.data(), "failed compute rendering wait advanced cursor");
check(sceAgcSuspendPoint() == 0, "empty suspend failed");
}
@ -514,111 +401,6 @@ void testMemory() {
check(storage.words == beforeMask, "invalid mask patch modified packet memory");
}
struct AtomicGdsAllocation {
Storage storage;
std::uint32_t capacity = 15;
std::uint32_t calls = 0;
bool accepted = true;
};
bool APS5_VABI growAtomicGds(CommandBuffer* buffer, std::uint32_t count, void* userData) {
auto& allocation = *static_cast<AtomicGdsAllocation*>(userData);
check(count == 15, "atomic GDS allocation omitted packet or reserved words");
++allocation.calls;
if (!allocation.accepted) return false;
buffer->bottom = allocation.storage.words.data();
buffer->top = buffer->bottom + allocation.capacity;
buffer->cursor_up = buffer->bottom;
buffer->cursor_down = buffer->top;
return true;
}
void testAtomicGds() {
Storage storage;
storage.words.fill(0xdeadbeefu);
storage.buffer.cursor_up += 2;
auto* packet = sceAgcAcbAtomicGds_0900(&storage.buffer, 0xffffffffu, 1, 0xffffffffu, 0xffffffffu,
0xabcd, 0xfedc, 0x12345678u, 0x87654321u, 0xfedcba9876543210ull, 0x0123456789abcdefull);
const std::array<std::uint32_t, 11> expected{
0xc0091d00u, 0x001500ffu, 0x13fu, 0xabcdu, 0xfedcu, 0x00340078u,
0x87654321u, 0x76543210u, 0xfedcba98u, 0x89abcdefu, 0x01234567u};
check(packet == storage.words.data() + 2, "atomic GDS returned the wrong packet address");
check(std::equal(expected.begin(), expected.end(), packet), "atomic GDS packet or guest ABI mismatch");
check(storage.buffer.cursor_up == packet + expected.size(), "atomic GDS did not advance by eleven words");
check(storage.words[0] == 0xdeadbeefu && storage.words[1] == 0xdeadbeefu &&
std::all_of(storage.words.begin() + 13, storage.words.end(), [](auto word) { return word == 0xdeadbeefu; }),
"atomic GDS wrote outside its packet");
const std::array<std::array<std::uint32_t, 3>, 9> controls{{
{0, 0, 0}, {0, 1, 0x00020000u}, {0, 2, 0}, {0, 0xffffffffu, 0},
{0x20u, 1, 0x00040020u}, {0x40u, 1, 0x00120040u},
{0x200u, 1, 0x00030000u}, {0xfffffd00u, 0, 0}, {0xffu, 1, 0x001400ffu}}};
for (const auto& entry : controls) {
Storage controlStorage;
auto* controlPacket = sceAgcAcbAtomicGds_0900(&controlStorage.buffer, entry[0], entry[1],
0xfffffffeu, 0xffffffc0u, 0, 0, 0xff00ff00u, 0, 0, 0);
std::array<std::uint32_t, 11> controlExpected{};
controlExpected[0] = 0xc0091d00u;
controlExpected[1] = entry[2];
check(std::equal(controlExpected.begin(), controlExpected.end(), controlPacket), "atomic GDS control or field masking mismatch");
}
auto emit = [](CommandBuffer* buffer) {
return sceAgcAcbAtomicGds_0900(buffer, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
};
Storage exact;
exact.buffer.cursor_down = exact.words.data() + 15;
exact.buffer.reserved_dw = 4;
check(emit(&exact.buffer) == exact.words.data() && exact.buffer.cursor_up == exact.words.data() + 11,
"atomic GDS rejected an exact fit with reserved space");
const auto exactBefore = exact.words;
expectFailure([&] { emit(&exact.buffer); });
check(exact.words == exactBefore && exact.buffer.cursor_up == exact.words.data() + 11,
"atomic GDS overwrote reserved space");
Storage exhausted;
exhausted.buffer.cursor_down = exhausted.words.data() + 10;
const auto before = exhausted.words;
expectFailure([&] { emit(&exhausted.buffer); });
check(exhausted.words == before && exhausted.buffer.cursor_up == exhausted.words.data(),
"failed atomic GDS allocation modified the buffer");
expectFailure([&] { emit(nullptr); });
Storage noDown;
noDown.buffer.top = noDown.words.data() + 11;
noDown.buffer.cursor_down = nullptr;
check(emit(&noDown.buffer) == noDown.words.data() && noDown.buffer.cursor_up == noDown.buffer.top,
"atomic GDS did not honor a buffer with no down cursor");
AtomicGdsAllocation allocation;
CommandBuffer growing{nullptr, nullptr, nullptr, nullptr, growAtomicGds, &allocation, 4};
check(emit(&growing) == allocation.storage.words.data() && allocation.calls == 1 &&
growing.cursor_up == allocation.storage.words.data() + 11, "atomic GDS did not use the guest allocation callback");
const std::array<std::uint32_t, 11> zeroPacket{0xc0091d00u};
check(std::equal(zeroPacket.begin(), zeroPacket.end(), allocation.storage.words.begin()),
"atomic GDS callback allocation produced the wrong packet");
for (const bool accepted : {false, true}) {
AtomicGdsAllocation failure;
failure.accepted = accepted;
failure.capacity = 14;
const auto failureBefore = failure.storage.words;
CommandBuffer failed{nullptr, nullptr, nullptr, nullptr, growAtomicGds, &failure, 4};
expectFailure([&] { emit(&failed); });
check(failure.calls == 1 && failure.storage.words == failureBefore &&
failed.cursor_up == (accepted ? failure.storage.words.data() : nullptr),
"atomic GDS wrote a packet after failed or insufficient allocation");
}
}
void testDebugRaiseException() {
int result = -1;
try {
result = sceAgcDebugRaiseException(0x1234abcdu);
} catch (const std::exception& error) {
throw std::runtime_error(std::string("debug exception stopped the caller: ") + error.what());
}
check(result == 0, "debug exception did not return 0");
}
void testDefaults() {
std::uint32_t state = 0x12345678;
check(sceAgcInit_0090(&state, 8) == 0 && state == 0x12345678, "AGC initialization failed or modified caller state");
@ -626,10 +408,8 @@ void testDefaults() {
expectFailure([] { sceAgcInit_0090(nullptr, 8); });
expectFailure([&] { sceAgcInit_0090(&state, 14); });
check(sceAgcInit(8) == 0, "AGC version initialization failed");
check(sceAgcInit(13) == 0, "AGC version 13 initialization failed");
check(sceAgcInit(14) == SCE_AGC_ERROR_INVALID_ARGUMENT, "unsupported AGC version did not return invalid argument");
check(sceAgcInit(0xffffffffu) == SCE_AGC_ERROR_INVALID_ARGUMENT, "maximum AGC version did not return invalid argument");
check(sceAgcInit(0xffffc278u) == SCE_AGC_ERROR_INVALID_ARGUMENT, "pointer-valued AGC argument did not return invalid argument");
expectFailure([] { sceAgcInit(14); });
expectFailure([] { sceAgcInit(0xffffffffu); });
for (std::uint32_t version = 0; version < 14; ++version) {
for (const bool internal : {false, true}) {
auto* first = Agc::Command::GetRegisterDefaults(version, internal, __func__);
@ -643,47 +423,6 @@ void testDefaults() {
expectFailure([] { Agc::Command::GetRegisterDefaults(0xffffffffu, true, __func__); });
}
void testEventWrite() {
Storage storage;
const auto* samplePacket = sceAgcDcbEventWrite(&storage.buffer, 0x38, reinterpret_cast<const void*>(0x1000));
check(samplePacket == storage.words.data(), "incorrect event 0x38 return pointer");
check(storage.words[0] == 0xc0024600u, "incorrect event 0x38 packet header");
check(storage.words[1] == (0x100u | 0x38u), "incorrect event 0x38 first payload word");
check(storage.words[2] == 0x1000u, "incorrect event 0x38 low address word");
check(storage.words[3] == 0u, "incorrect event 0x38 high address word");
check(sceAgcDcbEventWriteGetSize(0x38) == 16, "incorrect event 0x38 size");
check(sceAgcDcbEventWriteGetSize(0x39) == 16, "incorrect event 0x39 size");
Storage sample39;
sceAgcDcbEventWrite(&sample39.buffer, 0x39, reinterpret_cast<const void*>(0x2008));
check(sample39.words[0] == 0xc0024600u && sample39.words[1] == (0x100u | 0x39u) && sample39.words[2] == 0x2008u && sample39.words[3] == 0u, "incorrect event 0x39 packet");
expectFailure([&] { sceAgcDcbEventWrite(&storage.buffer, 0x38, reinterpret_cast<const void*>(0x1001)); });
expectFailure([&] { sceAgcDcbEventWrite(&storage.buffer, 0x40, nullptr); });
Storage partialFlush;
sceAgcDcbEventWrite(&partialFlush.buffer, 7, nullptr);
check(partialFlush.words[0] == 0xc0004600u && partialFlush.words[1] == 0x407u, "incorrect event 7 partial flush packet");
check(sceAgcDcbEventWriteGetSize(7) == 8, "incorrect event 7 size");
Storage partialFlush15;
sceAgcDcbEventWrite(&partialFlush15.buffer, 15, nullptr);
check(partialFlush15.words[0] == 0xc0004600u && partialFlush15.words[1] == 0x40fu, "incorrect event 15 partial flush packet");
Storage partialFlush16;
sceAgcDcbEventWrite(&partialFlush16.buffer, 16, nullptr);
check(partialFlush16.words[0] == 0xc0004600u && partialFlush16.words[1] == 0x410u, "incorrect event 16 partial flush packet");
Storage cacheFlushNull;
sceAgcDcbEventWrite(&cacheFlushNull.buffer, 0x16, nullptr);
check(cacheFlushNull.words[0] == 0xc0004600u && cacheFlushNull.words[1] == 0x16u, "incorrect event 0x16 null address packet");
check(sceAgcDcbEventWriteGetSize(0x16) == 8, "incorrect event 0x16 size");
Storage cacheFlushWithAddress;
sceAgcDcbEventWrite(&cacheFlushWithAddress.buffer, 0x16, reinterpret_cast<const void*>(0x2d2a10000ull));
check(cacheFlushWithAddress.words[0] == 0xc0004600u && cacheFlushWithAddress.words[1] == 0x16u, "incorrect event 0x16 with address packet");
}
}
int main(int argc, char** argv) {
@ -695,13 +434,10 @@ int main(int argc, char** argv) {
return 0;
}
testPackets();
testEventWrite();
testNop();
testClearState();
testIndexedIndirectDraws();
testMarkers();
testWorkloads();
testWorkloadStreams();
testIndexBuffer();
testIndirectBase();
testContextState();
@ -710,8 +446,6 @@ int main(int argc, char** argv) {
testRegisterRange();
testPacketPayloadAddress();
testMemory();
testAtomicGds();
testDebugRaiseException();
testDefaults();
LibcRunShutdown_nid_postfix();
std::puts("AGC command tests passed");

View File

@ -1,119 +0,0 @@
#include "SceShaders.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <vector>
extern "C" int APS5_VABI sceAgcGetGsPrimPayload(std::uint32_t* payload, const Shader* gs);
namespace {
constexpr std::uint32_t Sentinel = 0xccccccccu;
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
template <typename TAction>
void expectFailure(TAction action, const char* message) {
try {
action();
} catch (const std::exception& error) {
check(error.what()[0] != '\0', "empty exception message");
return;
}
throw std::runtime_error(message);
}
Shader makeGs(std::vector<ShaderRegister>& cx) {
Shader gs{};
gs.cx_registers = cx.empty() ? nullptr : cx.data();
gs.num_cx_registers = static_cast<std::uint8_t>(cx.size());
return gs;
}
void expectPayload(const Shader& gs, std::uint32_t expected, const char* message) {
std::array<std::uint32_t, 2> out{Sentinel, Sentinel};
check(sceAgcGetGsPrimPayload(out.data(), &gs) == 0, message);
check(out[0] == expected, message);
check(out[1] == Sentinel, "the payload write went past four bytes");
}
void testNoRegisters() {
std::vector<ShaderRegister> cx;
expectPayload(makeGs(cx), 0, "a shader without context registers reported a payload");
}
void testTwoComponentIndexFormat() {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_SHADER_IDX_FORMAT, 2u}};
expectPayload(makeGs(cx), 8, "a two-component index export did not report 8 bytes");
}
void testOnlyTheLowNibbleCounts() {
std::vector<ShaderRegister> high{{ShaderRegs::SPI_SHADER_IDX_FORMAT, 0xfffffff2u}};
expectPayload(makeGs(high), 8, "bits above IDX0_EXPORT_FORMAT changed the payload");
for (const std::uint32_t format : {0u, 1u, 3u, 4u, 0x20u}) {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_SHADER_IDX_FORMAT, format}};
expectPayload(makeGs(cx), 0, "an index export format other than two components reported a payload");
}
}
void testOtherRegistersAreIgnored() {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_VS_OUT_CONFIG, 2u}, {ShaderRegs::GE_MAX_OUTPUT_PER_SUBGROUP, 2u}};
expectPayload(makeGs(cx), 0, "a register other than SPI_SHADER_IDX_FORMAT reported a payload");
}
void testSearchContinuesPastAMismatch() {
std::vector<ShaderRegister> cx{
{ShaderRegs::SPI_VS_OUT_CONFIG, 0u},
{ShaderRegs::SPI_SHADER_IDX_FORMAT, 4u},
{ShaderRegs::SPI_SHADER_IDX_FORMAT, 2u},
};
expectPayload(makeGs(cx), 8, "a non-matching SPI_SHADER_IDX_FORMAT entry stopped the search");
}
void testCountBoundsTheSearch() {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_VS_OUT_CONFIG, 0u}, {ShaderRegs::SPI_SHADER_IDX_FORMAT, 2u}};
Shader gs = makeGs(cx);
gs.num_cx_registers = 1;
expectPayload(gs, 0, "an entry past num_cx_registers was read");
}
void testRejections() {
std::vector<ShaderRegister> cx{{ShaderRegs::SPI_SHADER_IDX_FORMAT, 2u}};
const Shader gs = makeGs(cx);
std::uint32_t out = Sentinel;
expectFailure([&] { sceAgcGetGsPrimPayload(nullptr, &gs); }, "a null payload pointer was accepted");
expectFailure([&] { sceAgcGetGsPrimPayload(&out, nullptr); }, "a null shader was accepted");
Shader missing{};
missing.num_cx_registers = 1;
expectFailure([&] { sceAgcGetGsPrimPayload(&out, &missing); }, "a register count without a register array was accepted");
check(out == Sentinel, "a rejected call wrote the payload");
}
}
int main() {
try {
testNoRegisters();
testTwoComponentIndexFormat();
testOnlyTheLowNibbleCounts();
testOtherRegistersAreIgnored();
testSearchContinuesPastAMismatch();
testCountBoundsTheSearch();
testRejections();
LibcRunShutdown_nid_postfix();
std::puts("AGC GS primitive payload tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
try { LibcRunShutdown_nid_postfix(); }
catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); }
return 1;
}
}

View File

@ -13,14 +13,12 @@
extern "C" int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps);
extern "C" int APS5_VABI sceAgcUnknownCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs, const Shader* ps);
extern "C" int APS5_VABI sceAgcUnknown_vieBRwlh1Lw(ShaderRegister* regs, const Shader* gs, const Shader* ps);
namespace {
using Registers = std::array<ShaderRegister, 32>;
const Shader* mappedPixel = nullptr;
Registers preparedMapping{};
std::size_t preparedCount = 0;
unsigned mappings = 0;
unsigned links = 0;
@ -67,11 +65,6 @@ void testIdentity() {
regs = filled();
check(sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps) == 0, "mapping without inputs failed");
checkIdentity(regs, 0, "mapping without inputs is not the identity");
regs = filled();
check(sceAgcUnknown_vieBRwlh1Lw(regs.data(), nullptr, &ps) == 0, "update without inputs failed");
check(preparedCount == 0u, "update without inputs prepared registers");
const auto untouched = filled();
check(std::memcmp(regs.data(), untouched.data(), sizeof(regs)) == 0, "update without inputs wrote registers");
}
void testMapping() {
@ -106,15 +99,6 @@ void testMapping() {
check(split[4].value == 0x03180002u, "full f16 interpolant mapping changed");
checkIdentity(split, static_cast<std::uint32_t>(inputs.size()), "unused interpolants are not the identity");
auto update = filled();
const auto untouched = filled();
const std::size_t written = inputs.size() * sizeof(ShaderRegister);
check(sceAgcUnknown_vieBRwlh1Lw(update.data(), &gs, &ps) == 0, "update mapping failed");
check(preparedCount == inputs.size(), "update prepared registers past its inputs");
check(std::memcmp(update.data(), preparedMapping.data(), written) == 0, "prepared update differs from published registers");
check(std::memcmp(update.data(), split.data(), written) == 0, "update mapping differs from the split f16 mapping");
check(std::memcmp(update.data() + inputs.size(), untouched.data(), sizeof(update) - written) == 0, "update wrote past its inputs");
auto regular = filled();
check(sceAgcCreateInterpolantMapping_0100(regular.data(), &gs, &ps) == 0, "mapping failed");
check(std::memcmp(regular.data(), preparedMapping.data(), sizeof(regular)) == 0, "prepared mapping differs from published registers");
@ -122,51 +106,6 @@ void testMapping() {
checkIdentity(regular, static_cast<std::uint32_t>(inputs.size()), "unused interpolants are not the identity");
}
void testUpdateMapping() {
std::array<ShaderSemantic, 4> inputs{semantic(1, 0, 0), semantic(2, 0, 1), semantic(3, 0, 2), semantic(4, 0, 3)};
inputs[0].is_custom = 1;
inputs[0].default_value = 2;
inputs[1].default_value = 1;
inputs[1].default_value_hi = 1;
inputs[2].default_value_hi = 2;
inputs[3].is_custom = 1;
inputs[3].default_value = 3;
inputs[3].default_value_hi = 3;
std::array<ShaderSemantic, 3> outputs{semantic(1, 5, 0), semantic(2, 7, 1), semantic(4, 9, 3)};
Shader gs{};
gs.output_semantics = outputs.data();
gs.num_output_semantics = static_cast<std::uint16_t>(outputs.size());
Shader ps{};
ps.input_semantics = inputs.data();
ps.num_input_semantics = static_cast<std::uint32_t>(inputs.size());
auto update = filled();
const auto untouched = filled();
check(sceAgcUnknown_vieBRwlh1Lw(update.data(), &gs, &ps) == 0, "update mapping failed");
check(update[0].value == 0x00000625u, "custom interpolant update changed");
check(update[1].value == 0x01380107u, "low f16 half update changed");
check(update[2].value == 0x02480220u, "unmatched high f16 half update changed");
check(update[3].value == 0x03680709u, "full f16 interpolant update changed");
for (std::uint32_t i = 0; i < inputs.size(); ++i) {
check(update[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "updated register offset changed");
}
const std::size_t written = inputs.size() * sizeof(ShaderRegister);
check(std::memcmp(update.data() + inputs.size(), untouched.data(), sizeof(update) - written) == 0, "update wrote past its inputs");
auto create = filled();
check(sceAgcUnknownCreateInterpolantMapping(create.data(), &gs, &ps) == 0, "split f16 mapping failed");
check(std::memcmp(create.data(), update.data(), written) == 0, "update differs from the split f16 mapping");
checkIdentity(create, static_cast<std::uint32_t>(inputs.size()), "unused interpolants are not the identity");
for (const std::uint32_t index : {1u, 3u}) {
inputs[index].default_value = 0;
update = filled();
expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(update.data(), &gs, &ps); });
check(std::memcmp(update.data(), untouched.data(), sizeof(update)) == 0, "rejected update wrote registers");
inputs[index].default_value = inputs[index].default_value_hi;
}
}
void testRejections() {
std::array<ShaderSemantic, 33> inputs{};
Shader gs{};
@ -178,23 +117,18 @@ void testRejections() {
const auto saved = regs;
expectFailure([&] { sceAgcCreateInterpolantMapping_0100(regs.data(), &gs, &ps); });
expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), &gs, &ps); });
expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(regs.data(), &gs, &ps); });
check(std::memcmp(regs.data(), saved.data(), sizeof(regs)) == 0, "rejected mapping wrote registers");
ps.num_input_semantics = 1;
expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(regs.data(), nullptr, &ps); });
expectFailure([&] { sceAgcUnknownCreateInterpolantMapping(nullptr, &gs, &ps); });
expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(regs.data(), nullptr, &ps); });
expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(nullptr, &gs, &ps); });
expectFailure([&] { sceAgcUnknown_vieBRwlh1Lw(regs.data(), &gs, nullptr); });
}
}
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
check(shader != nullptr && context.size() <= preparedMapping.size() && primitive.empty(), "invalid mapping preparation call");
check(shader != nullptr && context.size() == preparedMapping.size() && primitive.empty(), "invalid mapping preparation call");
mappedPixel = shader;
std::copy(context.begin(), context.end(), preparedMapping.begin());
preparedCount = context.size();
++mappings;
}
@ -207,9 +141,8 @@ int main() {
try {
testIdentity();
testMapping();
testUpdateMapping();
testRejections();
check(mappings == 7u && links == 5u, "mapping preparation call count changed");
check(mappings == 3u && links == 2u, "mapping preparation call count changed");
LibcRunShutdown_nid_postfix();
std::puts("AGC interpolant mapping tests passed");
return 0;

View File

@ -1,164 +0,0 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <limits>
#include <memory>
#include <new>
#include <stdexcept>
#include <vector>
extern "C" int APS5_VABI sceAgcSetAmmSemaphoreMemory(void* memory, std::uint64_t sizeInBytes);
extern "C" int APS5_VABI sceAgcGetSemaphoreLabel(std::uint32_t index, void** label);
namespace {
constexpr std::uint64_t blockBytes = 0x4000u;
constexpr std::uint64_t labelBytes = 32u;
constexpr std::uint64_t counterBytes = 8u;
constexpr std::uint64_t memoryBytes = 2u * blockBytes;
constexpr std::uint64_t storageBytes = 4u * blockBytes;
constexpr std::uint32_t labelCount = static_cast<std::uint32_t>(memoryBytes / labelBytes);
constexpr std::uint8_t guardByte = 0xa5u;
constexpr std::uint8_t counterByte = 0x5au;
struct AlignedRelease {
void operator()(std::uint8_t* memory) const { ::operator delete(memory, std::align_val_t{blockBytes}); }
};
std::unique_ptr<std::uint8_t, AlignedRelease> storage;
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
template <typename TAction>
void expectFailure(TAction action) {
try {
action();
} catch (const std::runtime_error& error) {
check(error.what()[0] != '\0', "empty exception message");
return;
}
throw std::runtime_error("expected an exception");
}
std::uint8_t* memory() {
return storage.get() + blockBytes;
}
std::vector<std::uint8_t> snapshot() {
return {storage.get(), storage.get() + storageBytes};
}
void checkUnchanged(const std::vector<std::uint8_t>& before, const char* message) {
check(std::equal(before.begin(), before.end(), storage.get()), message);
}
void fillStorage() {
std::fill(storage.get(), storage.get() + storageBytes, guardByte);
for (std::uint64_t label = 0; label < memoryBytes; label += labelBytes) {
std::fill(memory() + label, memory() + label + counterBytes, counterByte);
std::fill(memory() + label + counterBytes, memory() + label + labelBytes, std::uint8_t{0});
}
}
void testUnregistered() {
std::array<void*, 3> labels{storage.get(), storage.get(), storage.get()};
expectFailure([&] { sceAgcGetSemaphoreLabel(0, &labels[1]); });
expectFailure([] { sceAgcGetSemaphoreLabel(0, nullptr); });
check(labels[0] == storage.get() && labels[1] == storage.get() && labels[2] == storage.get(), "a rejected semaphore label changed the output");
}
void testRegistrationRejections() {
const auto before = snapshot();
expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, memoryBytes); });
expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, 0); });
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), 0); });
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory() + 1, memoryBytes); });
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory() + labelBytes, blockBytes); });
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), blockBytes - 1u); });
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), blockBytes + labelBytes); });
expectFailure([] {
const auto top = std::numeric_limits<std::uintptr_t>::max() & ~static_cast<std::uintptr_t>(blockBytes - 1u);
sceAgcSetAmmSemaphoreMemory(reinterpret_cast<void*>(top), memoryBytes);
});
checkUnchanged(before, "a rejected registration changed memory");
testUnregistered();
}
void testUnsettledClearing() {
for (const std::uint64_t offset : {5u * labelBytes + counterBytes, memoryBytes - 1u}) {
memory()[offset] = 0x77u;
const auto before = snapshot();
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes); });
checkUnchanged(before, "a registration with unsettled clearing changed memory");
testUnregistered();
memory()[offset] = 0;
}
}
void testRegistration() {
const auto before = snapshot();
check(sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes) == 0, "registering the AMM semaphore memory failed");
check(std::all_of(memory(), memory() + memoryBytes, [](std::uint8_t byte) { return byte == 0; }), "registration did not clear the semaphore counters");
check(std::equal(before.begin(), before.begin() + blockBytes, storage.get()), "registration wrote before the semaphore memory");
check(std::equal(before.begin() + blockBytes + memoryBytes, before.end(), memory() + memoryBytes), "registration wrote past the semaphore memory");
}
void testLabels() {
for (const std::uint32_t index : {0u, 1u, 7u, labelCount / 2u, labelCount - 1u}) {
std::array<void*, 3> labels{storage.get(), nullptr, storage.get()};
check(sceAgcGetSemaphoreLabel(index, &labels[1]) == 0, "a semaphore label lookup failed");
check(labels[1] == memory() + index * labelBytes, "incorrect semaphore label address");
check(labels[0] == storage.get() && labels[2] == storage.get(), "a semaphore label lookup wrote past its output");
}
}
void testLabelRejections() {
for (const std::uint32_t index : {labelCount, labelCount + 1u, std::numeric_limits<std::uint32_t>::max()}) {
void* label = storage.get();
expectFailure([&] { sceAgcGetSemaphoreLabel(index, &label); });
check(label == storage.get(), "a rejected semaphore label changed the output");
}
expectFailure([] { sceAgcGetSemaphoreLabel(0, nullptr); });
}
void testRepeatedRegistration() {
std::fill(memory(), memory() + memoryBytes, std::uint8_t{0x3c});
const auto before = snapshot();
expectFailure([] { sceAgcSetAmmSemaphoreMemory(memory(), memoryBytes); });
expectFailure([] { sceAgcSetAmmSemaphoreMemory(storage.get(), blockBytes); });
expectFailure([] { sceAgcSetAmmSemaphoreMemory(nullptr, 0); });
checkUnchanged(before, "a repeated registration changed memory");
void* label = nullptr;
check(sceAgcGetSemaphoreLabel(labelCount - 1u, &label) == 0 && label == memory() + memoryBytes - labelBytes, "a repeated registration replaced the semaphore memory");
}
}
int main() {
try {
storage.reset(static_cast<std::uint8_t*>(::operator new(storageBytes, std::align_val_t{blockBytes})));
fillStorage();
testUnregistered();
testRegistrationRejections();
testUnsettledClearing();
testRegistration();
testLabels();
testLabelRejections();
testRepeatedRegistration();
storage.reset();
LibcRunShutdown_nid_postfix();
std::puts("AGC semaphore tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
try { LibcRunShutdown_nid_postfix(); }
catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); }
return 1;
}
}

View File

@ -7,7 +7,6 @@
#include <array>
#include <span>
#include <stdexcept>
#include <string>
#include <iostream>
extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister*, ShaderRegister*, const void*, const Shader*, const Shader*, std::uint32_t);
@ -49,30 +48,6 @@ void OptionalGeometryOutputRegister(ShaderSpecialRegs& special, const Shader& ve
special.vgt_shader_stages_en.value = 0;
special.vgt_gs_out_prim_type = {VGT_GS_OUT_PRIM_TYPE, 2};
}
void HandBuiltUserVgprRegister() {
using namespace ShaderRegs;
ShaderSpecialRegs handBuilt{};
handBuilt.ge_cntl = {GE_CNTL, 0x100u};
handBuilt.vgt_shader_stages_en = {VGT_SHADER_STAGES_EN, 0x2412010u};
handBuilt.vgt_gs_out_prim_type = {0x2ceu, 2};
handBuilt.ge_user_vgpr_en = {GE_USER_VGPR1, 0};
Shader preRaster{};
preRaster.type = static_cast<std::uint8_t>(ShaderBinaryType::Gs);
preRaster.specials = &handBuilt;
std::array<ShaderRegister, 2> context{};
std::array<ShaderRegister, 3> primitive{};
preparations = 0;
Require(sceAgcCreatePrimState(context.data(), primitive.data(), nullptr, &preRaster, 4) == 0);
Require(context[0].offset == VGT_SHADER_STAGES_EN && context[0].value == 0x2412010u && context[1].offset == VGT_GS_OUT_PRIM_TYPE && context[1].value == static_cast<std::uint32_t>(GsOutputPrimitiveType::Triangles));
Require(primitive[0].offset == GE_CNTL && primitive[0].value == 0x100u && primitive[1].offset == GE_USER_VGPR1 && primitive[1].value == 0 && primitive[2].offset == VGT_PRIMITIVE_TYPE && primitive[2].value == 4);
Require(preparations == 1 && stageCount == 1 && preparedPrimitive[1].offset == GE_USER_VGPR1 && preparedPrimitive[1].value == 0);
handBuilt.ge_cntl.offset = 0x25cu;
std::string message;
try { static_cast<void>(sceAgcCreatePrimState(context.data(), primitive.data(), nullptr, &preRaster, 4)); }
catch (const std::runtime_error& error) { message = error.what(); }
Require(message.find("GE_CNTL") != std::string::npos && message.find("0x25c") != std::string::npos);
}
}
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
@ -193,7 +168,6 @@ int main() {
Require(sceAgcCreateInterpolantMapping_0100(interpolants.data(), &vertex, nullptr) == 0);
Require(preparations == 0 && mappings == 0 && links == 0);
OptionalGeometryOutputRegister(special, vertex, hull);
HandBuiltUserVgprRegister();
std::cout << "shader helper preparation tests passed\n";
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';

View File

@ -111,7 +111,6 @@ add_custom_command(
set(agcDriverSources
${CMAKE_CURRENT_SOURCE_DIR}/Unimplemented.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/ComputePipelineCapture.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/DisplayBuffer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/DisplayFormat.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver/DeviceAccess.cpp
@ -162,8 +161,6 @@ set(agcDriverSources
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver/Synchronization/WaitMemory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver/Synchronization/WaitStatistics.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/WorkerSampler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/PerformanceTrace.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/TraceFile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/ThreadPriority.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/GuestMemory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/ShaderMemory.cpp
@ -176,7 +173,6 @@ set(agcDriverSources
${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/PresentationScaler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Draw.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/DepthSurface.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/MultisampleTarget.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ColorTargetLayout.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ColorTargetTransfer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/Pipeline.cpp
@ -221,22 +217,9 @@ set(agcDriverSources
${CMAKE_CURRENT_SOURCE_DIR}/Submit/src/Dcb.cpp
)
find_package(Git QUIET)
set(agcTraceRevisionHeader ${agcTextureDetileGeneratedRoot}/prx/libSceAgcDriver/TraceRevision.hpp)
add_custom_target(agc_trace_revision
COMMAND ${CMAKE_COMMAND} -DGIT=${GIT_EXECUTABLE} -DSOURCE=${CMAKE_SOURCE_DIR} -DOUTPUT=${agcTraceRevisionHeader} -P ${CMAKE_CURRENT_SOURCE_DIR}/trace_revision.cmake
BYPRODUCTS ${agcTraceRevisionHeader}
VERBATIM
)
set(agcZlibIncludeDir ${CMAKE_SOURCE_DIR}/3rdparty/libjpeg-turbo/src/spng/zlib)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/TraceFile.cpp PROPERTIES
INCLUDE_DIRECTORIES ${agcZlibIncludeDir}
)
add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL
${agcDriverSources}
)
add_dependencies(libSceAgcDriver agc_trace_revision)
target_include_directories(libSceAgcDriver PRIVATE
${LIBS_INCLUDE_DIR}
@ -262,12 +245,8 @@ target_link_libraries(libSceAgcDriver PRIVATE
SDL2-static
libc
libkernel
jpeg_turbo
${CMAKE_DL_LIBS}
)
if(APS5_ENABLE_TRACY)
target_link_libraries(libSceAgcDriver PUBLIC TracyClient)
endif()
set_target_properties(libSceAgcDriver PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(libSceAgcDriver)
@ -293,61 +272,16 @@ if(BUILD_TESTING)
set_tests_properties(agc_profile_output PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(agc_stack_walk_tests tests/StackWalk.cpp)
target_include_directories(agc_stack_walk_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_stack_walk_tests PRIVATE libc)
configure_windows_unwind(agc_stack_walk_tests)
add_test(NAME agc_stack_walk COMMAND agc_stack_walk_tests)
set_tests_properties(agc_stack_walk PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(agc_stack_walk PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(agc_performance_trace_tests tests/PerformanceTrace.cpp Execution/src/TraceFile.cpp)
target_include_directories(agc_performance_trace_tests PRIVATE ${LIBS_INCLUDE_DIR} ${agcZlibIncludeDir})
target_link_libraries(agc_performance_trace_tests PRIVATE libc jpeg_turbo)
configure_windows_unwind(agc_performance_trace_tests)
add_test(NAME agc_performance_trace COMMAND agc_performance_trace_tests)
set_tests_properties(agc_performance_trace PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(agc_performance_trace PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(WIN32)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/GuestMemory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/Driver/Lifecycle.cpp PROPERTIES COMPILE_DEFINITIONS _WIN32_WINNT=0x0602)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/GuestMemory.cpp PROPERTIES COMPILE_DEFINITIONS _WIN32_WINNT=0x0602)
endif()
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
endforeach()
add_test_executable(agc_driver_register_key_tests EXCLUDE_FROM_ALL tests/RegisterKey.cpp)
target_include_directories(agc_driver_register_key_tests PRIVATE
${LIBS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/core/shader/recompiler
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
)
target_link_libraries(agc_driver_register_key_tests PRIVATE
libSceAgcDriver
libc
libkernel
)
set_target_properties(agc_driver_register_key_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_register_key_tests)
if(BUILD_TESTING)
set_target_properties(agc_driver_register_key_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_driver_register_key COMMAND agc_driver_register_key_tests)
set_tests_properties(agc_driver_register_key PROPERTIES TIMEOUT 30 ENVIRONMENT "APS5_VERIFY_REGISTER_KEYS=1")
if(WIN32)
set_tests_properties(agc_driver_register_key PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_driver_tests EXCLUDE_FROM_ALL
tests/Submit.cpp
)
@ -379,10 +313,8 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_wait_shutdown PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_GPU_WAIT_TIMEOUT_MS=20"
TIMEOUT 10 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_driver_resolution_shutdown COMMAND agc_driver_wait_shutdown_tests --resolution-shutdown)
set_tests_properties(agc_driver_resolution_shutdown PROPERTIES TIMEOUT 10 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_wait_shutdown agc_driver_resolution_shutdown PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_wait_shutdown PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@ -458,15 +390,6 @@ if(BUILD_TESTING)
add_test(NAME agc_fragment_barycentric COMMAND agc_fragment_barycentric_tests)
endif()
add_test_executable(agc_pipeline_link_optimization_tests EXCLUDE_FROM_ALL tests/PipelineLinkOptimization.cpp)
target_include_directories(agc_pipeline_link_optimization_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
set_target_properties(agc_pipeline_link_optimization_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_pipeline_link_optimization_tests)
if(BUILD_TESTING)
set_target_properties(agc_pipeline_link_optimization_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_pipeline_link_optimization COMMAND agc_pipeline_link_optimization_tests)
endif()
add_test_executable(agc_subgroup_clock_tests EXCLUDE_FROM_ALL
tests/SubgroupClock.cpp
Execution/src/SubgroupClock.cpp
@ -507,10 +430,6 @@ set_target_properties(agc_spirv_narrow_constant_store_tests PROPERTIES CXX_EXTEN
configure_windows_unwind(agc_spirv_narrow_constant_store_tests)
if(ANYPS5_ENABLE_SPIRV_TOOLS)
target_link_libraries(agc_spirv_narrow_constant_store_tests PRIVATE SPIRV-Tools-static)
add_test_executable(agc_spirv13_image_tests EXCLUDE_FROM_ALL tests/Spirv13Image.cpp)
target_link_libraries(agc_spirv13_image_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_spirv13_image_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_spirv13_image_tests)
add_test_executable(agc_spirv_optimizer_tests EXCLUDE_FROM_ALL tests/SpirvOptimizer.cpp)
target_link_libraries(agc_spirv_optimizer_tests PRIVATE shader_recompiler_agc_driver libc SPIRV-Tools-static)
set_target_properties(agc_spirv_optimizer_tests PROPERTIES CXX_EXTENSIONS OFF)
@ -530,19 +449,6 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_spirv_branch_targets_tests EXCLUDE_FROM_ALL tests/SpirvBranchTargets.cpp)
target_link_libraries(agc_spirv_branch_targets_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_spirv_branch_targets_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_spirv_branch_targets_tests)
if(BUILD_TESTING)
set_target_properties(agc_spirv_branch_targets_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_spirv_branch_targets COMMAND agc_spirv_branch_targets_tests)
set_tests_properties(agc_spirv_branch_targets PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1")
if(WIN32)
set_tests_properties(agc_spirv_branch_targets PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_spirv_lds_atomic_lock_tests EXCLUDE_FROM_ALL tests/SpirvLdsAtomicLock.cpp)
target_link_libraries(agc_spirv_lds_atomic_lock_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_spirv_lds_atomic_lock_tests PROPERTIES CXX_EXTENSIONS OFF)
@ -566,26 +472,11 @@ target_link_libraries(agc_masked_select_tests PRIVATE shader_recompiler_agc_driv
set_target_properties(agc_masked_select_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_masked_select_tests)
add_test_executable(agc_ir_reclaim_tests EXCLUDE_FROM_ALL tests/IrReclaim.cpp)
target_link_libraries(agc_ir_reclaim_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_ir_reclaim_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_ir_reclaim_tests)
add_test_executable(agc_vector_window_tests EXCLUDE_FROM_ALL tests/VectorWindow.cpp)
target_link_libraries(agc_vector_window_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_vector_window_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_vector_window_tests)
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)
add_test_executable(agc_integer_narrowing_tests EXCLUDE_FROM_ALL tests/IntegerNarrowing.cpp)
target_link_libraries(agc_integer_narrowing_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_integer_narrowing_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_integer_narrowing_tests)
add_test_executable(agc_scalar_select_mask_tests EXCLUDE_FROM_ALL tests/ScalarSelectMask.cpp)
target_link_libraries(agc_scalar_select_mask_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_scalar_select_mask_tests PROPERTIES CXX_EXTENSIONS OFF)
@ -635,19 +526,6 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_vop3_interp_decode_tests EXCLUDE_FROM_ALL tests/Vop3InterpDecode.cpp)
target_link_libraries(agc_vop3_interp_decode_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_vop3_interp_decode_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_vop3_interp_decode_tests)
if(BUILD_TESTING)
set_target_properties(agc_vop3_interp_decode_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_vop3_interp_decode COMMAND agc_vop3_interp_decode_tests)
if(WIN32)
set_tests_properties(agc_vop3_interp_decode PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_pass_hazards_tests EXCLUDE_FROM_ALL tests/PassHazards.cpp Graphics/src/PassHazards.cpp)
target_include_directories(agc_pass_hazards_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_pass_hazards_tests PRIVATE libc)
@ -682,20 +560,6 @@ if(BUILD_TESTING)
endif()
endif()
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()
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)
@ -720,24 +584,6 @@ target_link_libraries(agc_formatted_buffer_once_tests PRIVATE shader_recompiler_
set_target_properties(agc_formatted_buffer_once_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_formatted_buffer_once_tests)
add_test_executable(agc_rect_list_position_invariant_tests EXCLUDE_FROM_ALL tests/RectListPositionInvariant.cpp)
target_link_libraries(agc_rect_list_position_invariant_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_rect_list_position_invariant_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_rect_list_position_invariant_tests)
if(BUILD_TESTING)
set_target_properties(agc_rect_list_position_invariant_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_rect_list_position_invariant COMMAND agc_rect_list_position_invariant_tests)
if(WIN32)
set_tests_properties(agc_rect_list_position_invariant PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_formatted_buffer_byte_stores_tests EXCLUDE_FROM_ALL tests/FormattedBufferByteStores.cpp)
target_link_libraries(agc_formatted_buffer_byte_stores_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_formatted_buffer_byte_stores_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_formatted_buffer_byte_stores_tests)
add_test_executable(agc_shader_disk_cache_tests EXCLUDE_FROM_ALL tests/ShaderDiskCache.cpp)
target_include_directories(agc_shader_disk_cache_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_shader_disk_cache_tests PRIVATE shader_recompiler_agc_driver libc)
@ -776,7 +622,6 @@ add_test_executable(agc_driver_flip_tests EXCLUDE_FROM_ALL
target_include_directories(agc_driver_flip_tests PRIVATE
${LIBS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include
)
target_link_libraries(agc_driver_flip_tests PRIVATE
@ -867,19 +712,6 @@ target_link_libraries(agc_driver_cmask_fast_clear_tests PRIVATE
set_target_properties(agc_driver_cmask_fast_clear_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_cmask_fast_clear_tests)
set(agcBufferUpdateSource ${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/BufferUpdate.comp)
set(agcBufferUpdateSpv ${agcTestShaderGeneratedRoot}/BufferUpdate.spv)
set(agcBufferUpdateHeader ${agcTestShaderGeneratedRoot}/BufferUpdate_spv.h)
add_custom_command(
OUTPUT "${agcBufferUpdateHeader}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTestShaderGeneratedRoot}"
COMMAND "$<TARGET_FILE:glslang-standalone>" -V --target-env vulkan1.1 -o "${agcBufferUpdateSpv}" "${agcBufferUpdateSource}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${agcBufferUpdateSpv} -DOUTPUT=${agcBufferUpdateHeader}
-DSYMBOL=BUFFER_UPDATE_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${agcBufferUpdateSource}" glslang-standalone
VERBATIM
)
add_test_executable(agc_driver_recorder_tests EXCLUDE_FROM_ALL
tests/Recorder.cpp
tests/ResidentPresent.cpp
@ -887,7 +719,6 @@ add_test_executable(agc_driver_recorder_tests EXCLUDE_FROM_ALL
${agcSampleLodShaderHeader}
${agcSampleArrayShaderHeader}
${agcCoverShaderHeaders}
${agcBufferUpdateHeader}
)
target_include_directories(agc_driver_recorder_tests PRIVATE
@ -931,62 +762,17 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_storage_baseline_settled COMMAND agc_driver_recorder_tests --settled-baseline-only)
add_test(NAME agc_storage_integer_clear COMMAND agc_driver_recorder_tests --integer-clear-only)
set_tests_properties(agc_storage_integer_clear PROPERTIES SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_storage_integer_clear PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_storage_upload_lifetime COMMAND agc_driver_recorder_tests --storage-upload-lifetime-only)
add_test(NAME agc_storage_upload_failure COMMAND agc_driver_recorder_tests --storage-upload-failure-only)
set_tests_properties(agc_storage_upload_lifetime agc_storage_upload_failure PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_storage_upload_lifetime agc_storage_upload_failure PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_storage_baseline_settled PROPERTIES SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_storage_baseline_settled PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_storage_written_buffer_order COMMAND agc_driver_recorder_tests --written-buffer-order-only)
set_tests_properties(agc_storage_written_buffer_order PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_storage_written_buffer_order PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_copied_direct_ordering COMMAND agc_driver_recorder_tests --copied-direct-only)
set_tests_properties(agc_copied_direct_ordering PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_copied_direct_ordering PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_host_import_unmap COMMAND agc_driver_recorder_tests --host-import-unmap-only)
set_tests_properties(agc_host_import_unmap PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_parked_import_unmap COMMAND agc_driver_recorder_tests --parked-import-unmap-only)
set_tests_properties(agc_parked_import_unmap PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000")
add_test(NAME agc_host_import_retirement COMMAND agc_driver_recorder_tests --host-import-retirement-only)
set_tests_properties(agc_host_import_retirement PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_unit_shadow COMMAND agc_driver_recorder_tests --unit-shadow-only)
set_tests_properties(agc_unit_shadow PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000")
if(WIN32)
add_test(NAME agc_storage_cpu_snapshot COMMAND agc_driver_recorder_tests --storage-cpu-snapshot-only)
set_tests_properties(agc_storage_cpu_snapshot PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 SKIP_RETURN_CODE 77
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_descriptor_pool_recycling COMMAND agc_driver_recorder_tests --descriptor-pool-only)
set_tests_properties(agc_descriptor_pool_recycling PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_draw_snapshot_cap COMMAND agc_driver_recorder_tests --draw-snapshot-cap-only)
set_tests_properties(agc_draw_snapshot_cap PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_multisample_guest_access COMMAND agc_driver_recorder_tests --multisample-only)
set_tests_properties(agc_multisample_guest_access PROPERTIES SKIP_RETURN_CODE 77)
add_test(NAME agc_draw_inputs_in_place COMMAND agc_driver_recorder_tests --draw-inputs-in-place-only)
set_tests_properties(agc_draw_inputs_in_place PROPERTIES SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_host_import_unmap agc_parked_import_unmap agc_host_import_retirement agc_unit_shadow agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_multisample_guest_access agc_draw_inputs_in_place PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
add_test(NAME agc_host_import_second_mapping COMMAND agc_driver_recorder_tests --host-import-second-mapping-only)
set_tests_properties(agc_host_import_second_mapping PROPERTIES SKIP_RETURN_CODE 77 ENVIRONMENT_MODIFICATION
set_tests_properties(agc_host_import_unmap agc_unit_shadow PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
@ -1005,20 +791,6 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_host_import_retire_tests EXCLUDE_FROM_ALL tests/HostImportRetireHold.cpp)
target_include_directories(agc_host_import_retire_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
target_link_libraries(agc_host_import_retire_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_host_import_retire_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_host_import_retire_tests)
if(BUILD_TESTING)
set_target_properties(agc_host_import_retire_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_host_import_retire COMMAND agc_host_import_retire_tests)
if(WIN32)
set_tests_properties(agc_host_import_retire PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_driver_pm4_tests EXCLUDE_FROM_ALL
tests/Pm4.cpp
)
@ -1048,11 +820,46 @@ add_test_executable(agc_driver_graphics_tests EXCLUDE_FROM_ALL
tests/GuestSamplerResource.cpp
tests/TextureDetiler.cpp
tests/DepthSurfaceReuse.cpp
Graphics/src/Draw.cpp
Graphics/src/DepthSurface.cpp
Graphics/src/Pipeline.cpp
Graphics/src/PipelineLibrary.cpp
Graphics/src/State.cpp
Graphics/src/ColorTargetLayout.cpp
Graphics/src/ColorTargetTransfer.cpp
Graphics/src/Resources.cpp
Graphics/src/PlaceholderImages.cpp
Graphics/src/ShaderResources.cpp
Graphics/src/ShaderAddressResources.cpp
Graphics/src/GuestBufferMemory.cpp
Graphics/src/BdaResources.cpp
Graphics/src/BufferAddress.cpp
Graphics/src/ShaderValidation.cpp
Graphics/src/GuestTextureResource.cpp
Graphics/src/GuestSamplerResource.cpp
Graphics/src/TextureFormat.cpp
Graphics/src/DccMetadata.cpp
Graphics/src/TextureTiling.cpp
Graphics/src/TextureDetiler.cpp
Graphics/src/Texture.cpp
Graphics/src/PassHazards.cpp
Graphics/src/UnitShadow.cpp
Graphics/src/Sampler.cpp
Graphics/src/Recorder.cpp
Graphics/src/ParallelCompare.cpp
Graphics/src/ShaderInputState.cpp
${agcTextureDetileShaderHeader}
${agcCmaskClearShaderHeader}
${agcMeshArgumentsShaderHeader}
${agcTextureDetileImageShaderHeader}
${agcSampleCounterShaderHeader}
${agcSampleDumpsShaderHeader}
Execution/src/GuestMemory.cpp
Execution/src/Pm4.cpp
)
target_include_directories(agc_driver_graphics_tests PRIVATE
${LIBS_INCLUDE_DIR}
$<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>
${agcTextureDetileGeneratedRoot}
${CMAKE_SOURCE_DIR}/core/shader/recompiler
${CMAKE_SOURCE_DIR}/core/shader/recompiler/IntermediateRepresentation/include
@ -1063,7 +870,7 @@ target_include_directories(agc_driver_graphics_tests PRIVATE
)
target_link_libraries(agc_driver_graphics_tests PRIVATE
libSceAgcDriver
shader_recompiler_agc_driver
libc
libkernel
)
@ -1071,71 +878,44 @@ target_link_libraries(agc_driver_graphics_tests PRIVATE
set_target_properties(agc_driver_graphics_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_graphics_tests)
foreach(agcVertexRangeShader IN ITEMS VertexRange.vert VertexRangeProducer.frag)
string(REPLACE "." "_" agcVertexRangeName "${agcVertexRangeShader}")
string(TOUPPER "${agcVertexRangeName}_SPV" agcVertexRangeSymbol)
set(agcVertexRangeSpv ${agcTestShaderGeneratedRoot}/${agcVertexRangeName}.spv)
set(agcVertexRangeHeader ${agcTestShaderGeneratedRoot}/${agcVertexRangeName}_spv.h)
add_custom_command(
OUTPUT "${agcVertexRangeHeader}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTestShaderGeneratedRoot}"
COMMAND "$<TARGET_FILE:glslang-standalone>" -V --target-env vulkan1.1 -o "${agcVertexRangeSpv}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/${agcVertexRangeShader}"
COMMAND ${CMAKE_COMMAND} -DINPUT=${agcVertexRangeSpv} -DOUTPUT=${agcVertexRangeHeader}
-DSYMBOL=${agcVertexRangeSymbol} -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/${agcVertexRangeShader}" glslang-standalone
VERBATIM
)
list(APPEND agcVertexRangeHeaders ${agcVertexRangeHeader})
endforeach()
target_sources(agc_driver_recorder_tests PRIVATE ${agcVertexRangeHeaders})
if(BUILD_TESTING)
add_test(NAME agc_driver_vertex_target_range_copied COMMAND agc_driver_recorder_tests --vertex-target-range-copied)
add_test(NAME agc_driver_vertex_target_range_imported COMMAND agc_driver_recorder_tests --vertex-target-range-imported)
set_tests_properties(agc_driver_vertex_target_range_copied agc_driver_vertex_target_range_imported PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_vertex_target_range_copied agc_driver_vertex_target_range_imported PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_driver_bda_device_tests EXCLUDE_FROM_ALL
tests/BdaDevice.cpp
tests/BdaShader.cpp
tests/BdaExecution.cpp
tests/BdaContracts.cpp
Execution/src/BdaFeatures.cpp
Graphics/src/Resources.cpp
Graphics/src/PlaceholderImages.cpp
Graphics/src/BufferAddress.cpp
)
target_include_directories(agc_driver_bda_device_tests PRIVATE
${LIBS_INCLUDE_DIR}
$<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>
${CMAKE_SOURCE_DIR}/core/shader/recompiler
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
)
target_link_libraries(agc_driver_bda_device_tests PRIVATE libSceAgcDriver SDL2-static libc)
target_link_libraries(agc_driver_bda_device_tests PRIVATE SDL2-static libc shader_recompiler_agc_driver)
set_target_properties(agc_driver_bda_device_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_bda_device_tests)
add_test_executable(agc_driver_mesh_tests EXCLUDE_FROM_ALL tests/Mesh.cpp)
add_test_executable(agc_driver_mesh_tests EXCLUDE_FROM_ALL
tests/Mesh.cpp
${agcDriverSources}
)
target_include_directories(agc_driver_mesh_tests PRIVATE
${LIBS_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/core/shader/recompiler
${agcTextureDetileGeneratedRoot}
${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}
${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR}
)
target_link_libraries(agc_driver_mesh_tests PRIVATE libSceAgcDriver SDL2-static libc)
target_link_libraries(agc_driver_mesh_tests PRIVATE shader_recompiler_agc_driver SDL2-static libc libkernel ${CMAKE_DL_LIBS})
set_target_properties(agc_driver_mesh_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_mesh_tests)
set(ANYPS5_AGC_EXECUTION_TESTS "" CACHE STRING "tests/execution sources to build, by file name without extension; the others build a stub that reports a skip. Empty builds all")
file(GLOB agcExecutionTestSources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/tests/execution/*.cpp)
set(agcExecutionTests)
foreach(agcExecutionTestSource IN LISTS agcExecutionTestSources)
get_filename_component(agcExecutionTestName ${agcExecutionTestSource} NAME_WE)
if(ANYPS5_AGC_EXECUTION_TESTS AND NOT agcExecutionTestName IN_LIST ANYPS5_AGC_EXECUTION_TESTS)
set(agcExecutionTestSource ${CMAKE_CURRENT_SOURCE_DIR}/tests/ExecutionTestNotBuilt.cpp)
endif()
string(REGEX REPLACE "([a-z0-9])([A-Z])" "\\1_\\2" agcExecutionTestName "${agcExecutionTestName}")
string(TOLOWER "agc_driver_${agcExecutionTestName}_tests" agcExecutionTest)
add_test_executable(${agcExecutionTest} EXCLUDE_FROM_ALL ${agcExecutionTestSource})
@ -1150,13 +930,6 @@ foreach(agcExecutionTestSource IN LISTS agcExecutionTestSources)
configure_windows_unwind(${agcExecutionTest})
list(APPEND agcExecutionTests ${agcExecutionTest})
endforeach()
foreach(agcExecutionTestName IN LISTS ANYPS5_AGC_EXECUTION_TESTS)
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/tests/execution/${agcExecutionTestName}.cpp)
message(FATAL_ERROR "ANYPS5_AGC_EXECUTION_TESTS names ${agcExecutionTestName}, which has no source in tests/execution")
endif()
endforeach()
target_link_libraries(agc_driver_gpu_map_on_fiber_tests PRIVATE libSceFiber)
target_include_directories(agc_driver_alpha_to_coverage_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
@ -1166,8 +939,6 @@ target_include_directories(agc_driver_swap_alt_blend_tests PRIVATE $<TARGET_PROP
target_include_directories(agc_driver_packed_unorm_color_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_color1555_target_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
foreach(stage IN ITEMS vert frag)
set(agcProvokingShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/tests/shaders/ProvokingVertex.${stage})
set(agcProvokingShaderSpv ${agcTestShaderGeneratedRoot}/ProvokingVertex_${stage}.spv)
@ -1188,8 +959,6 @@ target_include_directories(agc_driver_provoking_vertex_tests PRIVATE ${agcTestSh
target_include_directories(agc_driver_prepared_exports_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_buffer_descriptor_runtime_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_prepared_publication_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
target_include_directories(agc_driver_prepared_graphics_tests PRIVATE $<TARGET_PROPERTY:shader_recompiler_agc_driver,INTERFACE_INCLUDE_DIRECTORIES>)
@ -1203,12 +972,6 @@ target_include_directories(agc_driver_prepared_shaders_tests PRIVATE
if(BUILD_TESTING)
set_target_properties(agc_shader_mode_register_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_driver_readonly_resummarize COMMAND agc_driver_tests --readonly-resummarize)
set_tests_properties(agc_driver_readonly_resummarize PROPERTIES TIMEOUT 10)
if(WIN32)
set_tests_properties(agc_driver_readonly_resummarize PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_shader_mode_register COMMAND agc_shader_mode_register_tests)
set_tests_properties(agc_shader_mode_register PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 20)
if(WIN32)
@ -1216,17 +979,15 @@ if(BUILD_TESTING)
endif()
add_test(NAME agc_shader_bindless COMMAND agc_shader_memory_tests --bindless)
set_tests_properties(agc_shader_bindless PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_shader_srt_read_patching COMMAND agc_shader_memory_tests --srt-read-patching)
set_tests_properties(agc_shader_srt_read_patching PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_shader_frontend_pair COMMAND agc_shader_memory_tests --frontend-pair)
add_test(NAME agc_shader_frontend_pair_off COMMAND agc_shader_memory_tests --frontend-pair)
set_tests_properties(agc_shader_frontend_pair PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_FRONTEND_PAIR=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_shader_bindless agc_shader_srt_read_patching agc_shader_frontend_pair agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_shader_bindless agc_shader_frontend_pair agc_shader_frontend_pair_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_ir_reclaim_tests agc_vector_window_tests agc_constant_bit_test_tests agc_integer_narrowing_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests agc_formatted_buffer_byte_stores_tests ${agcExecutionTests})
if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests" OR agcTest STREQUAL "agc_driver_pipeline_optimized_readback_tests")
foreach(agcTest IN ITEMS agc_shared_write_tests agc_driver_tests agc_shader_memory_tests agc_structurizer_tests agc_spirv_module_tests agc_dpp_write_tests agc_masked_select_tests agc_constant_bit_test_tests agc_scalar_select_mask_tests agc_vop1_control_decode_tests agc_validate_program_tests agc_pass_hazards_tests agc_shader_disk_cache_tests agc_shader_device_profile_tests agc_placeholder_images_tests agc_shader_control_flow_tests agc_driver_flip_tests agc_driver_pm4_tests agc_driver_graphics_tests agc_driver_bda_device_tests agc_driver_mesh_tests agc_driver_cmask_fast_clear_tests agc_formatted_buffer_once_tests ${agcExecutionTests})
if(agcTest STREQUAL "agc_driver_constant_store_tests" OR agcTest STREQUAL "agc_driver_flat_apertures_tests")
continue()
endif()
string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}")
@ -1238,83 +999,15 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
add_test(NAME agc_driver_partially_resident_pending_stores COMMAND agc_driver_partially_resident_snapshot_tests --pending-stores)
set_tests_properties(agc_driver_partially_resident_pending_stores PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_partially_resident_pending_stores PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_sparse_buffer_holes COMMAND agc_driver_graphics_tests --sparse-buffer-holes)
set_tests_properties(agc_sparse_buffer_holes PROPERTIES TIMEOUT 30)
set_tests_properties(agc_driver_sparse_buffer_holes PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_sparse_buffer_holes PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_emulated_color_compare_srgb_decode COMMAND agc_driver_emulated_color_compare_tests)
set_tests_properties(agc_driver_emulated_color_compare_srgb_decode PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_SRGB_SHADER_DECODE=1"
SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_driver_emulated_color_compare agc_driver_emulated_color_compare_srgb_decode PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_emulated_color_compare_srgb_decode PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_shared_write_partial_collect COMMAND agc_shared_write_tests --partial-collect)
set_tests_properties(agc_shared_write_partial_collect PROPERTIES SKIP_RETURN_CODE 77 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
if(WIN32)
add_test(NAME agc_shared_write_partial_collect_two_pass COMMAND agc_shared_write_tests --partial-collect)
set_tests_properties(agc_shared_write_partial_collect_two_pass PROPERTIES SKIP_RETURN_CODE 77 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_SINGLE_PASS_COLLECT=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
set_tests_properties(agc_shared_write_partial_collect agc_shared_write_partial_collect_two_pass PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_target_properties(agc_driver_pipeline_optimized_readback_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
foreach(pipelineReadbackCase IN ITEMS d32 d16 color32 color64)
foreach(pipelineReadbackMode IN ITEMS default fast)
set(pipelineReadbackTest agc_driver_pipeline_optimized_readback_${pipelineReadbackCase})
set(pipelineReadbackEnvironment "ANYPS5_NO_SHADER_CACHE=1")
if(pipelineReadbackMode STREQUAL "fast")
string(APPEND pipelineReadbackTest "_fast")
list(APPEND pipelineReadbackEnvironment "APS5_NO_PIPELINE_LTO=1")
endif()
add_test(NAME ${pipelineReadbackTest} COMMAND agc_driver_pipeline_optimized_readback_tests ${pipelineReadbackCase})
set_tests_properties(${pipelineReadbackTest} PROPERTIES ENVIRONMENT "${pipelineReadbackEnvironment}" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(${pipelineReadbackTest} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endforeach()
endforeach()
add_test(NAME agc_vector_window_stage_inputs COMMAND agc_vector_window_tests --stage-inputs-only)
set_tests_properties(agc_vector_window_stage_inputs PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_tests_properties(agc_vector_window_stage_inputs agc_driver_relative_stage_inputs PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_vector_window_stage_inputs PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_layered_volume_vertex COMMAND agc_driver_layered_volume_draw_tests --vertex-only)
add_test(NAME agc_driver_layered_volume_vertex_no_gpl COMMAND agc_driver_layered_volume_draw_tests --vertex-only)
set_tests_properties(agc_driver_layered_volume_vertex agc_driver_layered_volume_vertex_no_gpl PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
set_property(TEST agc_driver_layered_volume_vertex_no_gpl APPEND PROPERTY ENVIRONMENT "APS5_NO_GPL=1")
if(WIN32)
set_tests_properties(agc_driver_layered_volume_vertex agc_driver_layered_volume_vertex_no_gpl PROPERTIES
ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_f64_denorm_preserve_spirv COMMAND agc_driver_f64_fma_subnormals_tests --verify-spv-denorm-mode)
set_tests_properties(agc_driver_f64_denorm_preserve_spirv PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_f64_denorm_preserve_spirv PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_f16_to_f32_unpack_spirv COMMAND agc_driver_f16_to_f32_unpack_tests --verify-spirv)
set_tests_properties(agc_driver_f16_to_f32_unpack_spirv PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_f16_to_f32_unpack_spirv PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_structurizer_intersection_off COMMAND agc_structurizer_tests)
set_tests_properties(agc_structurizer_intersection_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_DOM_INTERSECTION=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_structurizer_iteration_selection_off COMMAND agc_structurizer_tests)
set_tests_properties(agc_structurizer_iteration_selection_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_ITERATION_SELECTION=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_shader_control_flow_iteration_selection_off COMMAND agc_shader_control_flow_tests)
set_tests_properties(agc_shader_control_flow_iteration_selection_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_PERF_ITERATION_SELECTION=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_structurizer_intersection_off agc_structurizer_iteration_selection_off agc_shader_control_flow_iteration_selection_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_structurizer_intersection_off PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_gated_dispatch_capture_short_timeout COMMAND agc_driver_gated_dispatch_capture_tests)
set_tests_properties(agc_driver_gated_dispatch_capture_short_timeout PROPERTIES
@ -1325,16 +1018,10 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_driver_provoking_vertex PROPERTIES TIMEOUT 30)
add_test(NAME agc_driver_uint8x4_color_target_no_resident COMMAND agc_driver_uint8x4_color_target_tests)
set_tests_properties(agc_driver_uint8x4_color_target_no_resident PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_RESIDENT_TARGETS=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_uint8x4_color_target_no_resident PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_tests_properties(agc_driver_flat_store PROPERTIES RUN_SERIAL TRUE)
set_target_properties(agc_driver_flat_apertures_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
foreach(flatAperturesPart RANGE 13)
add_test(NAME agc_driver_flat_apertures_${flatAperturesPart} COMMAND agc_driver_flat_apertures_tests --part ${flatAperturesPart}/14)
foreach(flatAperturesPart RANGE 5)
add_test(NAME agc_driver_flat_apertures_${flatAperturesPart} COMMAND agc_driver_flat_apertures_tests --part ${flatAperturesPart}/6)
set_tests_properties(agc_driver_flat_apertures_${flatAperturesPart} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_flat_apertures_${flatAperturesPart} PROPERTIES ENVIRONMENT_MODIFICATION
@ -1344,29 +1031,14 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_storage_cache_budget PROPERTIES TIMEOUT 30)
add_test(NAME agc_driver_sampled_cache_budget_memory COMMAND agc_driver_sampled_cache_budget_tests memory)
set_tests_properties(agc_driver_sampled_cache_budget_memory PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77)
add_test(NAME agc_driver_resource_cache_storage_admission COMMAND agc_driver_sampled_cache_budget_tests resource-admission)
add_test(NAME agc_driver_resource_cache_oversized_storage_admission COMMAND agc_driver_sampled_cache_budget_tests resource-oversized)
add_test(NAME agc_driver_resource_cache_storage_clear COMMAND agc_driver_sampled_cache_budget_tests resource-clear)
set_tests_properties(agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_sampled_cache_budget_memory agc_driver_resource_cache_storage_admission agc_driver_resource_cache_oversized_storage_admission agc_driver_resource_cache_storage_clear PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
# The driver draw test again with the draw key off: the draw cache then keys on the decoded vertex
# stage, which must include PA_CL_VS_OUT_CNTL.
add_test(NAME agc_driver_vertex_clip_cull_draw_no_key COMMAND agc_driver_vertex_clip_cull_draw_tests no-key)
set_tests_properties(agc_driver_vertex_clip_cull_draw_no_key PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_DRAW_KEY=1" SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_driver_vertex_clip_cull_draw_no_key PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_sampled_cache_budget_memory PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_pm4_failure COMMAND agc_driver_pm4_tests failure)
set_tests_properties(agc_driver_pm4_failure PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77)
add_test(NAME agc_descriptor_pool_reset_failure COMMAND agc_driver_graphics_tests --descriptor-pool-reset-failure)
add_test(NAME agc_driver_address_space_teardown COMMAND agc_driver_graphics_tests --address-space-teardown)
set_tests_properties(agc_driver_address_space_teardown PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_pm4_failure agc_descriptor_pool_reset_failure agc_driver_address_space_teardown PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_pm4_failure PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
if(NOT WIN32)
@ -1374,22 +1046,20 @@ if(BUILD_TESTING)
set_tests_properties(agc_driver_pm4_priority_refused PROPERTIES ENVIRONMENT
"ANYPS5_NO_SHADER_CACHE=1;APS5_THREAD_PRIORITY=high;DBUS_SYSTEM_BUS_ADDRESS=unix:path=/nonexistent/aps5-no-rtkit" SKIP_RETURN_CODE 77)
endif()
add_test(NAME agc_driver_present_mode COMMAND agc_driver_flip_tests present-mode)
add_test(NAME agc_driver_flip_compute COMMAND agc_driver_flip_tests compute)
set_tests_properties(agc_driver_flip_compute PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77)
if(WIN32)
set_tests_properties(agc_driver_flip_compute agc_driver_present_mode PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(agc_driver_flip_compute PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_draw_cache_reuse_off COMMAND agc_driver_draw_cache_reuse_tests)
set_tests_properties(agc_driver_draw_cache_reuse_off PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_GPL=1" SKIP_RETURN_CODE 77)
set_tests_properties(agc_driver_draw_cache_reuse agc_driver_draw_cache_reuse_off PROPERTIES FAIL_REGULAR_EXPRESSION "FATAL:")
if(WIN32)
set_tests_properties(agc_driver_draw_cache_reuse_off PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
foreach(agcTest IN ITEMS agc_spirv_narrow_constant_store_tests agc_spirv13_image_tests agc_spirv_optimizer_tests agc_driver_constant_store_tests)
foreach(agcTest IN ITEMS agc_spirv_narrow_constant_store_tests agc_spirv_optimizer_tests agc_driver_constant_store_tests)
if(NOT TARGET ${agcTest})
continue()
endif()
@ -1407,9 +1077,6 @@ if(BUILD_TESTING)
${agcSpirvEnvironment} "$<TARGET_FILE:${agcTest}>")
set_tests_properties(${agcTestName} PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 60 SKIP_RETURN_CODE 77)
if(agcTest STREQUAL "agc_spirv13_image_tests")
set_tests_properties(${agcTestName} PROPERTIES TIMEOUT 30)
endif()
if(agcTest STREQUAL "agc_driver_constant_store_tests")
set_tests_properties(${agcTestName} PROPERTIES RUN_SERIAL TRUE)
endif()
@ -1419,7 +1086,7 @@ if(BUILD_TESTING)
endif()
endforeach()
endforeach()
foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable registered_workgroup registered_workgroup_sync)
foreach(preparedMode IN ITEMS indirect uncached debug debug_failure deferred deferred_undecodable)
set(preparedTest "agc_driver_prepared_shaders_${preparedMode}")
if(preparedMode STREQUAL "indirect")
add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --indirect)
@ -1427,8 +1094,6 @@ if(BUILD_TESTING)
add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --deferred)
elseif(preparedMode STREQUAL "deferred_undecodable")
add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --deferred-undecodable)
elseif(preparedMode STREQUAL "registered_workgroup" OR preparedMode STREQUAL "registered_workgroup_sync")
add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --registered-workgroup)
elseif(preparedMode STREQUAL "debug_failure")
add_test(NAME ${preparedTest} COMMAND agc_driver_prepared_shaders_tests --fail-before-registration)
set_tests_properties(${preparedTest} PROPERTIES WILL_FAIL TRUE TIMEOUT 30)
@ -1438,8 +1103,6 @@ if(BUILD_TESTING)
set_tests_properties(${preparedTest} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(preparedMode STREQUAL "uncached")
set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_NO_DISPATCH_CACHE=1")
elseif(preparedMode STREQUAL "registered_workgroup_sync")
set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_SYNC_SHADER_PREPARE=1")
elseif(preparedMode STREQUAL "debug" OR preparedMode STREQUAL "debug_failure")
set_property(TEST ${preparedTest} APPEND PROPERTY ENVIRONMENT "APS5_PROFILE_DRAW=1" "APS5_NO_UNLOCKED_DEVICE=1")
endif()
@ -1507,9 +1170,7 @@ if(AGC_BUILD_VISUAL_TEST)
SDL2-static
libc
libkernel
jpeg_turbo
)
add_dependencies(agc_driver_visual_test agc_trace_revision)
set_target_properties(agc_driver_visual_test PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_driver_visual_test)
@ -1561,63 +1222,6 @@ endif()
set_target_properties(agc_shader_replay PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_shader_replay)
add_executable(agc_compute_pipeline_replay EXCLUDE_FROM_ALL tools/ComputePipelineReplay.cpp)
target_include_directories(agc_compute_pipeline_replay 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 PRIVATE libSceAgcDriver libc)
set_target_properties(agc_compute_pipeline_replay PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_compute_pipeline_replay)
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_descriptor_layout_flags COMMAND agc_driver_graphics_tests --descriptor-layout-flags)
set_tests_properties(agc_compute_descriptor_layout_flags PROPERTIES TIMEOUT 30)
add_test(NAME agc_driver_covered_centroid_state COMMAND agc_driver_graphics_tests --covered-centroid-state)
set_tests_properties(agc_driver_covered_centroid_state PROPERTIES TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_compute_descriptor_layout_flags PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_driver_covered_centroid_state PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_compute_pipeline_capture COMMAND agc_compute_pipeline_capture_tests)
set_tests_properties(agc_compute_pipeline_capture PROPERTIES TIMEOUT 30)
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_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)
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")
if(WIN32)
set_tests_properties(agc_compute_pipeline_replay agc_compute_pipeline_capture_run PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(texture_format_lookup_tests ${LIBS_INCLUDE_DIR}/tests/TextureFormatLookup.cpp)
target_include_directories(texture_format_lookup_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR})
@ -1646,15 +1250,6 @@ if(BUILD_TESTING)
set_tests_properties(guest_agc_driver_resources PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_agc_driver_debug_unavailable_tests ${LIBS_INCLUDE_DIR}/tests/GuestAgcDriverDebugUnavailable.cpp)
target_include_directories(guest_agc_driver_debug_unavailable_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_agc_driver_debug_unavailable_tests PRIVATE libSceAgcDriver libc)
configure_windows_unwind(guest_agc_driver_debug_unavailable_tests)
add_test(NAME guest_agc_driver_debug_unavailable COMMAND guest_agc_driver_debug_unavailable_tests)
if(WIN32)
set_tests_properties(guest_agc_driver_debug_unavailable PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_rendering_wait_tests ${LIBS_INCLUDE_DIR}/tests/GuestRenderingWait.cpp)
target_include_directories(guest_rendering_wait_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_rendering_wait_tests PRIVATE libSceAgcDriver libc)
@ -1889,21 +1484,6 @@ add_test_executable(agc_position_invariance_tests EXCLUDE_FROM_ALL tests/Positio
target_link_libraries(agc_position_invariance_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_position_invariance_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_position_invariance_tests)
add_test_executable(agc_vertex_layer_export_tests EXCLUDE_FROM_ALL tests/VertexLayerExport.cpp)
target_link_libraries(agc_vertex_layer_export_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_vertex_layer_export_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_vertex_layer_export_tests)
if(BUILD_TESTING)
set_target_properties(agc_vertex_layer_export_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_vertex_layer_export COMMAND agc_vertex_layer_export_tests)
set_tests_properties(agc_vertex_layer_export PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1")
if(WIN32)
set_tests_properties(agc_vertex_layer_export PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(BUILD_TESTING)
set_target_properties(agc_position_invariance_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_position_invariance COMMAND agc_position_invariance_tests)
@ -1968,16 +1548,3 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
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()

View File

@ -1,77 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_COMPUTEPIPELINECAPTURE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_COMPUTEPIPELINECAPTURE_HPP
#include <vulkan/vulkan.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <span>
#include <string>
#include <vector>
namespace AgcDriver {
struct ComputeDescriptorBinding {
std::uint32_t binding = 0;
VkDescriptorType type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
std::uint32_t count = 1;
VkShaderStageFlags stages = VK_SHADER_STAGE_COMPUTE_BIT;
VkDescriptorBindingFlags flags = 0;
};
struct ComputeDescriptorSetLayout {
VkDescriptorSetLayoutCreateFlags flags = 0;
std::vector<ComputeDescriptorBinding> bindings;
};
struct ComputeSpecialization {
std::uint32_t id = 0;
std::uint32_t value = 0;
};
struct ComputePipelineCapture {
static constexpr std::uint32_t Version = 1;
static constexpr std::uint32_t MaxSpirvWords = 8u * 1024u * 1024u;
static constexpr std::size_t MaxFileBytes = 36u * 1024u * 1024u;
std::vector<std::uint32_t> spirv;
std::vector<ComputeDescriptorSetLayout> sets;
std::vector<VkPushConstantRange> pushConstants;
std::vector<ComputeSpecialization> specialization;
std::uint32_t requiredSubgroupSize = 0;
VkPipelineCreateFlags flags = 0;
VkPipelineShaderStageCreateFlags stageFlags = 0;
std::string deviceName;
std::uint32_t vendorId = 0;
std::uint32_t deviceId = 0;
std::uint32_t driverVersion = 0;
std::uint32_t apiVersion = 0;
std::array<std::uint8_t, VK_UUID_SIZE> pipelineCacheUuid{};
void Validate() const;
std::vector<std::byte> Encode() const;
static ComputePipelineCapture Decode(std::span<const std::byte> bytes);
static ComputePipelineCapture Read(const std::filesystem::path& path);
std::filesystem::path Write(const std::filesystem::path& directory) const;
std::string Identity() const;
};
struct ComputePipelineReplayResult {
double moduleMilliseconds = 0;
double layoutMilliseconds = 0;
double pipelineMilliseconds = 0;
double cacheMilliseconds = 0;
double totalMilliseconds = 0;
std::string deviceName;
std::uint32_t vendorId = 0;
std::uint32_t deviceId = 0;
std::uint32_t driverVersion = 0;
std::uint32_t apiVersion = 0;
std::array<std::uint8_t, VK_UUID_SIZE> pipelineCacheUuid{};
std::size_t initialCacheBytes = 0;
std::vector<std::byte> cache;
};
}
#endif

View File

@ -8,8 +8,6 @@ namespace AgcDriver::DriverDetail {
double TraceMs();
std::uint64_t SkippedShader();
void require(bool condition, const char* reason);
std::uint32_t readRegister(const Registers& registers, std::uint32_t offset);

View File

@ -37,9 +37,6 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
struct PreparedGraphicsStage {
std::shared_ptr<const ShaderSnapshot> snapshot;
PreparedShaders::Entry entry;
ShaderRecompiler::ShaderStage stage = ShaderRecompiler::ShaderStage::Vertex;
std::uint32_t pushOffset = 0;
std::shared_ptr<const ShaderRecompiler::RecompileResult> registration;
};
std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target);
@ -75,19 +72,6 @@ struct DrawEntryCounters {
std::chrono::steady_clock::time_point lastReport = std::chrono::steady_clock::now();
};
class DrawKeySightings {
public:
static constexpr std::size_t Ways = 4;
explicit DrawKeySightings(std::size_t keys);
bool SeenBefore(std::uint64_t key);
private:
std::vector<std::uint64_t> keys;
std::vector<std::uint8_t> filled;
std::vector<std::uint8_t> next;
std::size_t bucketMask = 0;
};
enum class DrawVerdict { Drawn, Nothing, Rejected };
struct StageCapture {

View File

@ -1,12 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_DRIVER_DRAW_DRAWREGISTERKEY_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_DRIVER_DRAW_DRAWREGISTERKEY_HPP
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
namespace AgcDriver::DriverDetail {
std::uint64_t RegisterStateKey(const QueueState& queue, bool allUserWords = false, bool incremental = true);
}
#endif

View File

@ -44,7 +44,6 @@ namespace AgcDriver::DriverDetail {
class Driver {
public:
static Driver& Get();
static std::uint64_t GpuMapImportThreads();
~Driver();
void Shutdown();
void WaitIdle();
@ -115,7 +114,6 @@ private:
static std::size_t drawCacheEntries();
void accountDrawVariant(const DispatchVariant& variant, bool added);
void insertDrawEntry(std::uint64_t key, std::vector<std::shared_ptr<DispatchVariant>>& fresh, std::shared_ptr<const DrawDecode> decode);
bool admitDrawKey(std::uint64_t key);
void eraseDrawEntry(std::uint64_t key, const std::shared_ptr<DrawEntry>& expected);
std::shared_ptr<const DrawRecipe> findDrawRecipe(std::uint64_t key, const std::vector<std::shared_ptr<DispatchVariant>>& stages);
void attachDrawRecipe(std::uint64_t key, const std::vector<std::shared_ptr<DispatchVariant>>& stages, std::shared_ptr<const DrawRecipe> recipe);
@ -311,7 +309,6 @@ private:
std::list<std::uint64_t> drawOrder;
std::mutex drawCacheMutex;
std::uint64_t drawCacheHits = 0, drawCacheEvictions = 0, drawCacheVariants = 0, drawCacheVariantBytes = 0;
std::optional<DrawKeySightings> drawKeySightings;
DrawEntryCounters drawEntryCounters;
@ -346,7 +343,6 @@ private:
std::uint64_t queue0Executing = 0;
std::atomic<std::uint32_t> orderHolders{0};
std::atomic<std::uint32_t> runningWorkers{0};
std::atomic<std::uint64_t> idleEpoch{0};
std::atomic<std::uint64_t> queue0Awaited{0};
std::atomic<std::uint32_t> flipsAhead{0};
std::atomic<std::uint32_t> flipHolders{0};

View File

@ -2,8 +2,6 @@
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_MEMORY_DWORDPATTERNFILL_HPP
#include "Recompiler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include <array>
#include <cstddef>
#include <cstdint>
@ -20,10 +18,6 @@ struct DwordPatternFill {
};
std::optional<DwordPatternFill> MatchDwordPatternFill(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute);
std::optional<DwordPatternFill> MatchZeroDwordFill(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute);
std::optional<Graphics::MemoryValueFill> MatchMemoryValueBufferFill(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute);
bool IsByteFill(const std::array<std::uint32_t, 4>& pattern);
std::optional<Graphics::DccKeys> KeysFillMemo(const std::array<std::uint32_t, 4>& pattern, bool coversStorageKeys, bool memoryValueFill);
}

View File

@ -5,7 +5,6 @@
#include "prx/libSceAgcDriver/Execution/include/QueueState.hpp"
#include <array>
#include <optional>
#include <span>
#include <atomic>
#include <condition_variable>
#include <cstddef>
@ -19,8 +18,6 @@
#include <string>
#include <vector>
struct Shader;
namespace ShaderRecompiler {
class ShaderPreparationContext;
}
@ -67,7 +64,6 @@ struct PreparedShaders : PreparedShaderState {
std::mutex mutex;
std::condition_variable settled;
std::exception_ptr failure;
std::atomic<bool> demanded{false};
bool pending = false;
};
@ -86,7 +82,6 @@ struct ShaderSnapshot {
std::shared_ptr<PreparedShaders> prepared = std::make_shared<PreparedShaders>();
std::shared_ptr<const RegisteredShaderState> registeredState;
std::shared_ptr<std::atomic<std::uint8_t>> workgroupUse = std::make_shared<std::atomic<std::uint8_t>>(0u);
std::shared_ptr<std::atomic<std::uint8_t>> positionVectorUse = std::make_shared<std::atomic<std::uint8_t>>(0u);
};
@ -95,22 +90,15 @@ std::shared_ptr<const ShaderSnapshot> ProgramSnapshot(const ShaderRegistry& shad
void NoteRawGraphicsDraw();
bool RawGraphicsDrawn();
bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset);
// The PA_CL_VS_OUT_CNTL position export bits a vertex program is compiled with: none for a program
// without pos1..pos3 exports, so its artifact does not depend on them.
std::uint32_t PositionExportLayout(const ShaderSnapshot* snapshot, std::size_t codeOffset, std::span<const std::uint32_t> code, std::uint32_t paClVsOutCntl);
bool VertexWorkgroupRequired(const QueueState& queue, const ShaderRegistry& registry);
std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation = nullptr);
std::uint64_t NullPixelProgramAddress();
ShaderSnapshot PrepareNullPixelProgram(const VulkanDevice& device);
std::optional<ShaderRecompiler::ShaderFloatMode> RegisteredFloatMode(const ShaderSnapshot& snapshot);
std::shared_ptr<const ShaderSnapshot> RegisteredHeader(const ShaderRegistry& registry, const Shader* shader);
std::shared_ptr<const ShaderSnapshot> RegisteredProgram(const ShaderRegistry& registry, std::uint64_t address, std::initializer_list<std::uint8_t> types);
void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const std::shared_ptr<const ShaderSnapshot>& snapshot);
void SubmitRegistrationPreparation(std::function<void()> task, const PreparedShaders* prepared = nullptr);
void PrioritizeRegistrationPreparation(PreparedShaders& prepared);
void SubmitLibraryPrebuild(std::function<void()> task);
void SubmitRegistrationPreparation(std::function<void()> task);
void CloseRegistrationPreparation();
void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr<const ShaderSnapshot>& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target);

View File

@ -30,7 +30,6 @@ void Read(std::uint64_t address, std::span<std::byte> destination, std::size_t a
// Variants for resources in heaps the guest commits on demand (partially resident textures, GPU
// scratch heaps): uncommitted pages read as zeros, are not compared, and are never stored.
void ReadCommitted(std::uint64_t address, std::span<std::byte> destination);
void ReadCommittedUnsynced(std::uint64_t address, std::span<std::byte> destination);
bool EqualsCommitted(std::uint64_t address, std::span<const std::byte> bytes);
// EqualsCommitted without the flush hook: the caller has decided that the recorded GPU work
// writing the range need not be waited for (the dispatch-cache validation in Driver.cpp).
@ -60,7 +59,7 @@ void WriteChanged(std::uint64_t address, std::span<const std::byte> current, std
// UnchangedSince is always false, so callers fall back to comparing bytes.
bool WriteWatched();
void Unwatch(std::uint64_t address, std::size_t bytes);
bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function<bool()>& import, bool staysWatched = true);
bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function<bool()>& import);
bool Watched(std::uint64_t address, std::size_t bytes);
std::uint64_t CollectWrites(std::uint64_t address, std::size_t bytes);
bool UnchangedSince(std::uint64_t address, std::size_t bytes, std::uint64_t generation);
@ -72,8 +71,6 @@ struct UnchangedQuery {
};
bool WrittenSince(std::uint64_t address, std::size_t bytes, std::uint64_t generation);
bool UnchangedSinceAll(std::span<const UnchangedQuery> queries);
std::uint64_t CollectCommittedWrites(std::uint64_t address, std::size_t bytes, std::vector<std::pair<std::uint64_t, std::uint64_t>>& ranges);
bool UnchangedCommittedSince(std::span<const std::pair<std::uint64_t, std::uint64_t>> ranges, std::uint64_t generation);
// Returns the generation the blocks were stamped with (0 when the arena is not write-watched):
// UnchangedSince(range, it) holds until the next store over the range.
std::uint64_t MarkWritten(std::uint64_t address, std::size_t bytes);
@ -104,7 +101,6 @@ std::uint64_t CollectWritesUncached(std::uint64_t address, std::size_t bytes);
// tracker mutex, so every collect that bumped before it has finished its walk and every later
// collect stamps newer.
std::uint64_t TrackerGeneration();
std::uint64_t NewestStamp(std::uint64_t address, std::size_t bytes);
// Whether no CPU write touched the range since `generation`: an uncached resetting collect of the
// range's pages first (a game store not collected yet becomes a block stamp), then the compare over
// the stamps collects make (dirty pages), not the MarkWritten stamps of the driver's own GPU label
@ -118,6 +114,7 @@ bool UnchangedSinceCollected(std::uint64_t address, std::size_t bytes, std::uint
// watched arena, or for a generation of 0, a block reads as changed.
constexpr std::uint8_t BlockUnchanged = 0;
constexpr std::uint8_t BlockWritten = 1;
constexpr std::uint8_t BlockMaybeWritten = 2;
bool ChangedBlocks(std::uint64_t address, std::size_t bytes, std::span<const std::uint64_t> generations, std::span<std::uint8_t> changed, std::span<std::uint8_t> cpu = {});
// Serializes device work: draws, dispatches, presentation and the deferred write-backs below. The
@ -152,7 +149,6 @@ GpuMutexType& GpuMutex();
// work the hold deferred to run without it (the recorder's release of the objects completed batches
// kept). One hook, set once; it must not take GpuMutex itself.
void SetGpuUnlockHook(void (*hook)());
std::uint64_t GpuHoldSerial();
// Debug aid (APS5_ASSERT_GPU_LOCK=1): aborts with a [lock] line naming `where` when the calling
// thread does not hold GpuMutex. Recording, submits, command batches and the detiler's pools must
// only be reached under it (a resource build's stage A runs without it, see ShaderResources).
@ -187,7 +183,6 @@ unsigned long long CodeOffset(const void* address);
// every read and store through this interface calls first. Code that reads guest memory directly
// calls FlushGpuWrites itself.
void SetFlushHook(void (*hook)(std::uint64_t address, std::size_t bytes));
void SetReadGuard(void (*guard)(std::uint64_t address, std::size_t bytes));
void FlushGpuWrites(std::uint64_t address, std::size_t bytes);
// Moves with every ForgetPages call (the libc invalidator: memory unmapped or re-registered): odd
// while the call stores its page states, moved past it after, so a reader that loads an even

View File

@ -1,8 +1,6 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PERFORMANCETIMER_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PERFORMANCETIMER_HPP
#include "prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp"
#include "prx/libSceAgcDriver/Execution/include/TracyZone.hpp"
#include <algorithm>
#include <chrono>
#include <cstddef>
@ -260,11 +258,11 @@ private:
class PerformanceTimer {
public:
explicit PerformanceTimer(const char* scope) : frame(PerformanceContext::Current()), trace(PerformanceTrace::Recording() ? PerformanceTrace::Thread() : nullptr), scope(scope) {
explicit PerformanceTimer(const char* scope) : frame(PerformanceContext::Current()), scope(scope) {
if (frame != nullptr) {
total = frame->Get(scope, "total");
}
if (frame != nullptr || trace != nullptr) start = Clock::now();
if (frame != nullptr) start = Clock::now();
previous = start;
}
@ -276,12 +274,8 @@ public:
}
void Finish() {
zone.End();
if (frame == nullptr && trace == nullptr) return;
const auto end = Clock::now();
if (trace != nullptr) trace->Add(scope, nullptr, start, end);
trace = nullptr;
if (frame == nullptr) return;
const auto end = Clock::now();
if (tail != nullptr) frame->Add(tail, end - previous);
frame->Add(total, end - start);
frame = nullptr;
@ -293,13 +287,10 @@ public:
}
void Mark(const char* stage, std::uint64_t bytes = 0) {
if (frame == nullptr && trace == nullptr) return;
if (frame == nullptr) return;
const auto now = Clock::now();
if (trace != nullptr) trace->Add(scope, stage, previous, now);
if (frame != nullptr) {
if (tail == nullptr) tail = frame->Get(scope, "tail");
frame->Add(frame->Get(scope, stage), now - previous, bytes);
}
if (tail == nullptr) tail = frame->Get(scope, "tail");
frame->Add(frame->Get(scope, stage), now - previous, bytes);
previous = now;
}
@ -307,9 +298,7 @@ private:
using Clock = FrameTiming::Clock;
FrameTiming* frame;
TraceThread* trace;
const char* scope;
[[no_unique_address]] TracyZone zone{scope};
FrameTiming::Metric* total = nullptr;
FrameTiming::Metric* tail = nullptr;
Clock::time_point start{};

View File

@ -1,437 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PERFORMANCETRACE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PERFORMANCETRACE_HPP
#include <algorithm>
#include <array>
#include <atomic>
#include <charconv>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace AgcDriver {
class VulkanDevice;
class TraceWindow {
public:
using Clock = std::chrono::steady_clock;
enum class Edge { None, Open, Close };
static std::optional<TraceWindow> Parse(const char* frames, const char* seconds) {
if (frames == nullptr && seconds == nullptr) return std::nullopt;
if (frames != nullptr && seconds != nullptr) throw std::invalid_argument("APS5_TRACE_FRAMES and APS5_TRACE_SECONDS cannot be set together");
const std::string_view name = frames != nullptr ? "APS5_TRACE_FRAMES" : "APS5_TRACE_SECONDS";
const std::string_view text = frames != nullptr ? frames : seconds;
const auto colon = text.find(':');
std::uint64_t start = 0;
std::uint64_t length = 0;
if (colon == std::string_view::npos || !number(text.substr(0, colon), start) || !number(text.substr(colon + 1), length) || length == 0)
throw std::invalid_argument(std::string(name) + "=" + std::string(text) + " is not <start>:<length> with a length above 0");
return TraceWindow(frames != nullptr, start, length);
}
Edge Present(std::uint64_t present, Clock::duration elapsed) {
if (state == State::Closed) return Edge::None;
if (state == State::Waiting) {
if (frames ? present < start : wholeSeconds(elapsed) < start) return Edge::None;
state = State::Open;
openedPresent = present;
openedAt = elapsed;
return Edge::Open;
}
if (frames ? present - openedPresent < length : wholeSeconds(elapsed - openedAt) < length) return Edge::None;
state = State::Closed;
return Edge::Close;
}
std::string Describe() const {
return std::string(frames ? "frames " : "seconds ") + std::to_string(start) + ":" + std::to_string(length);
}
private:
enum class State { Waiting, Open, Closed };
TraceWindow(bool frames, std::uint64_t start, std::uint64_t length) : frames(frames), start(start), length(length) {}
static bool number(std::string_view text, std::uint64_t& value) {
const auto end = text.data() + text.size();
const auto [last, error] = std::from_chars(text.data(), end, value);
return !text.empty() && error == std::errc{} && last == end;
}
static std::uint64_t wholeSeconds(Clock::duration elapsed) {
return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::seconds>(elapsed).count());
}
bool frames;
std::uint64_t start;
std::uint64_t length;
State state = State::Waiting;
std::uint64_t openedPresent = 0;
Clock::duration openedAt{};
};
class TraceThread {
public:
using Clock = std::chrono::steady_clock;
static constexpr std::size_t DefaultCapacity = 262144;
static constexpr Clock::duration MinimumSlice = std::chrono::microseconds(20);
struct Slice {
const char* scope;
const char* stage;
Clock::time_point begin;
Clock::duration length;
};
struct Total {
const char* scope;
const char* stage;
Clock::duration length;
std::uint64_t count;
};
struct Snapshot {
std::string name;
std::span<const Slice> slices;
std::vector<Total> totals;
bool dropped = false;
};
TraceThread(std::string name, const std::atomic<std::uint64_t>& presents, std::size_t capacity = DefaultCapacity)
: name(std::move(name)), presents(presents), slices(std::make_unique<Slice[]>(capacity)), capacity(capacity) {}
TraceThread(const TraceThread&) = delete;
TraceThread& operator=(const TraceThread&) = delete;
void Add(const char* scope, const char* stage, Clock::time_point begin, Clock::time_point end) {
const auto length = end - begin;
account(scope, stage, length);
const auto present = presents.load(std::memory_order_relaxed);
if (!sampled || present != sampledPresent) {
if (sampled) append({nullptr, nullptr, sampleBegin, folded});
sampled = true;
sampledPresent = present;
sampleBegin = begin;
folded = {};
}
if (length < MinimumSlice) folded += length;
else append({scope, stage, begin, length});
}
Snapshot Collect() const {
Snapshot snapshot{name, {slices.get(), used.load(std::memory_order_acquire)}, {}, false};
for (const auto& entry : entries) {
const auto* scope = entry.scope.load(std::memory_order_acquire);
if (scope == nullptr) continue;
snapshot.totals.push_back({scope, entry.stage.load(std::memory_order_relaxed), Clock::duration(entry.length.load(std::memory_order_relaxed)), entry.count.load(std::memory_order_relaxed)});
}
snapshot.dropped = dropped.load(std::memory_order_relaxed);
return snapshot;
}
private:
struct Entry {
std::atomic<const char*> scope{nullptr};
std::atomic<const char*> stage{nullptr};
std::atomic<Clock::rep> length{0};
std::atomic<std::uint64_t> count{0};
};
void append(const Slice& slice) {
const auto index = used.load(std::memory_order_relaxed);
if (index == capacity) {
dropped.store(true, std::memory_order_relaxed);
return;
}
slices[index] = slice;
used.store(index + 1, std::memory_order_release);
}
void account(const char* scope, const char* stage, Clock::duration length) {
const auto key = reinterpret_cast<std::uintptr_t>(scope) ^ (reinterpret_cast<std::uintptr_t>(stage) >> 3);
const auto hash = static_cast<std::size_t>((static_cast<std::uint64_t>(key) * 0x9e3779b97f4a7c15ull) >> 55);
for (std::size_t probe = 0; probe < entries.size(); ++probe) {
auto& entry = entries[(hash + probe) % entries.size()];
const auto* current = entry.scope.load(std::memory_order_relaxed);
if (current == nullptr) {
entry.stage.store(stage, std::memory_order_relaxed);
entry.scope.store(scope, std::memory_order_release);
} else if (current != scope || entry.stage.load(std::memory_order_relaxed) != stage) {
continue;
}
entry.length.store(entry.length.load(std::memory_order_relaxed) + length.count(), std::memory_order_relaxed);
entry.count.store(entry.count.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed);
return;
}
dropped.store(true, std::memory_order_relaxed);
}
const std::string name;
const std::atomic<std::uint64_t>& presents;
const std::unique_ptr<Slice[]> slices;
const std::size_t capacity;
std::atomic<std::size_t> used{0};
std::atomic<bool> dropped{false};
std::array<Entry, 512> entries;
bool sampled = false;
std::uint64_t sampledPresent = 0;
Clock::time_point sampleBegin{};
Clock::duration folded{};
};
struct TraceSummary {
std::string commit;
std::optional<bool> dirty;
std::vector<std::pair<std::string, std::string>> environment;
std::string gpu;
std::string driver;
std::string os;
std::uint32_t width = 0;
std::uint32_t height = 0;
std::string window;
std::uint64_t firstPresent = 0;
double gpuBusyMs = 0;
std::string trace;
};
struct TraceGpuRange {
const char* name;
double beginNs;
double endNs;
};
struct TraceClockSample {
double gpuNs = 0;
std::chrono::steady_clock::time_point cpu{};
};
class TraceFormat {
public:
using Clock = std::chrono::steady_clock;
static constexpr std::array<std::string_view, 5> WorkerBusyScopes{"Driver.Packet", "Driver.Suspend", "Driver.EndSubmission", "Driver.Rewind", "Driver.Completion"};
static constexpr std::string_view WorkerWaitScope = "Driver.WorkerWait";
static constexpr std::string_view WorkerPrefix = "queue worker ";
static constexpr std::size_t TopPhases = 8;
static void AppendString(std::string& output, std::string_view text) {
output += '"';
for (const char character : text) {
const auto byte = static_cast<unsigned char>(character);
if (character == '"' || character == '\\') {
output += '\\';
output += character;
} else if (byte < 0x20 || byte >= 0x7f) {
char escaped[8];
std::snprintf(escaped, sizeof escaped, "\\u%04x", static_cast<unsigned>(byte));
output += escaped;
} else {
output += character;
}
}
output += '"';
}
static std::string Name(const char* scope, const char* stage) {
return stage == nullptr ? std::string(scope) : std::string(scope) + "." + stage;
}
static std::string Events(std::uint64_t process, Clock::time_point origin, std::span<const TraceThread::Snapshot> threads, std::size_t presenter, std::span<const Clock::time_point> presents, std::uint64_t firstPresent) {
std::string output = "{\"traceEvents\":[\n";
bool first = true;
const auto event = [&](std::string_view name, const char* phase, Clock::time_point at, std::size_t tid) {
if (!first) output += ",\n";
first = false;
output += "{\"name\":";
AppendString(output, name);
char text[96];
std::snprintf(text, sizeof text, ",\"ph\":\"%s\",\"ts\":%.3f,\"pid\":%llu,\"tid\":%llu", phase, microseconds(at - origin), static_cast<unsigned long long>(process), static_cast<unsigned long long>(tid));
output += text;
};
event("process_name", "M", origin, 0);
output += ",\"args\":{\"name\":\"AnyPS5\"}}";
for (std::size_t index = 0; index < threads.size(); ++index) {
const auto& thread = threads[index];
const auto tid = index + 1;
event("thread_name", "M", origin, tid);
output += ",\"args\":{\"name\":";
AppendString(output, thread.name);
output += "}}";
std::vector<TraceThread::Slice> slices(thread.slices.begin(), thread.slices.end());
std::stable_sort(slices.begin(), slices.end(), [](const auto& a, const auto& b) { return a.begin != b.begin ? a.begin < b.begin : a.length > b.length; });
const auto folded = thread.name + " folded";
for (const auto& slice : slices) {
char text[64];
if (slice.scope == nullptr) {
event(folded, "C", slice.begin, tid);
std::snprintf(text, sizeof text, ",\"args\":{\"ms\":%.3f}}", milliseconds(slice.length));
} else {
event(Name(slice.scope, slice.stage), "X", slice.begin, tid);
std::snprintf(text, sizeof text, ",\"dur\":%.3f}", microseconds(slice.length));
}
output += text;
}
}
for (std::size_t index = 0; index + 1 < presents.size(); ++index) {
event("present", "i", presents[index], presenter + 1);
output += ",\"s\":\"g\",\"args\":{\"present\":" + std::to_string(firstPresent + index) + "}}";
}
output += "\n]}\n";
return output;
}
static std::vector<TraceThread::Slice> GpuSlices(std::span<const TraceGpuRange> ranges, const TraceClockSample& first, const TraceClockSample& last, Clock::time_point origin) {
const double gpuSpan = last.gpuNs - first.gpuNs;
const double scale = gpuSpan > 0 ? std::chrono::duration<double, std::nano>(last.cpu - first.cpu).count() / gpuSpan : 1.0;
const auto map = [&](double gpuNs) { return first.cpu + std::chrono::duration_cast<Clock::duration>(std::chrono::duration<double, std::nano>((gpuNs - first.gpuNs) * scale)); };
std::vector<TraceThread::Slice> slices;
for (const auto& range : ranges) {
const auto begin = map(range.beginNs);
const auto end = map(range.endNs);
if (end <= begin || begin < origin) continue;
slices.push_back({range.name, nullptr, begin, end - begin});
}
return slices;
}
static std::string Summary(const TraceSummary& summary, std::span<const TraceThread::Snapshot> threads, std::span<const Clock::time_point> presents) {
const auto count = presents.empty() ? std::size_t{0} : presents.size() - 1;
const auto span = presents.empty() ? Clock::duration{} : presents.back() - presents.front();
std::vector<double> frames;
for (std::size_t index = 1; index < presents.size(); ++index) frames.push_back(milliseconds(presents[index] - presents[index - 1]));
std::sort(frames.begin(), frames.end());
const auto percentile = [&](double fraction) { return frames.empty() ? 0.0 : frames[std::min(frames.size() - 1, static_cast<std::size_t>(fraction * static_cast<double>(frames.size())))]; };
const double perPresent = count != 0 ? 1.0 / static_cast<double>(count) : 0.0;
std::string output = "{\"commit\":";
AppendString(output, summary.commit);
output += ",\"dirty\":";
output += summary.dirty.has_value() ? (*summary.dirty ? "true" : "false") : "null";
output += ",\"env\":{";
for (std::size_t index = 0; index < summary.environment.size(); ++index) {
if (index != 0) output += ',';
AppendString(output, summary.environment[index].first);
output += ':';
AppendString(output, summary.environment[index].second);
}
output += "},\"gpu\":";
AppendString(output, summary.gpu);
output += ",\"driver\":";
AppendString(output, summary.driver);
output += ",\"os\":";
AppendString(output, summary.os);
output += ",\"resolution\":";
AppendString(output, std::to_string(summary.width) + "x" + std::to_string(summary.height));
output += ",\"window\":{\"spec\":";
AppendString(output, summary.window);
output += ",\"first_present\":" + std::to_string(summary.firstPresent);
output += ",\"ms\":" + number(milliseconds(span)) + "}";
output += ",\"presents\":" + std::to_string(count);
output += ",\"frame_ms\":{\"p50\":" + number(percentile(0.5)) + ",\"p90\":" + number(percentile(0.9)) + ",\"p99\":" + number(percentile(0.99)) + ",\"max\":" + number(frames.empty() ? 0.0 : frames.back()) + "}";
output += ",\"gpu_busy_ms_per_present\":" + number(summary.gpuBusyMs * perPresent);
output += ",\"worker_busy_pct\":{";
bool firstWorker = true;
std::map<std::string, Clock::duration> phases;
bool dropped = false;
for (const auto& thread : threads) {
dropped = dropped || thread.dropped;
Clock::duration busy{};
for (const auto& total : thread.totals) {
const std::string_view scope = total.scope;
if (total.stage == nullptr && std::find(WorkerBusyScopes.begin(), WorkerBusyScopes.end(), scope) != WorkerBusyScopes.end()) busy += total.length;
if (scope != WorkerWaitScope) phases[Name(total.scope, total.stage)] += total.length;
}
if (!thread.name.starts_with(WorkerPrefix)) continue;
if (!firstWorker) output += ',';
firstWorker = false;
AppendString(output, thread.name);
output += ':' + number(span != Clock::duration{} ? 100.0 * static_cast<double>(busy.count()) / static_cast<double>(span.count()) : 0.0);
}
output += "},\"top_phases_ms_per_frame\":{";
std::vector<std::pair<std::string, Clock::duration>> top(phases.begin(), phases.end());
std::stable_sort(top.begin(), top.end(), [](const auto& a, const auto& b) { return a.second > b.second; });
for (std::size_t index = 0; index < top.size() && index < TopPhases; ++index) {
if (index != 0) output += ',';
AppendString(output, top[index].first);
output += ':' + number(milliseconds(top[index].second) * perPresent);
}
output += "},\"trace_dropped\":";
output += dropped ? "true" : "false";
output += ",\"trace\":";
AppendString(output, summary.trace);
output += "}\n";
return output;
}
private:
static double microseconds(Clock::duration elapsed) {
return std::chrono::duration<double, std::micro>(elapsed).count();
}
static double milliseconds(Clock::duration elapsed) {
return std::chrono::duration<double, std::milli>(elapsed).count();
}
static std::string number(double value) {
char text[32];
std::snprintf(text, sizeof text, "%.3f", value);
return text;
}
};
class PerformanceTrace {
public:
static bool Configured() {
static const bool configured = std::getenv("APS5_TRACE_FRAMES") != nullptr || std::getenv("APS5_TRACE_SECONDS") != nullptr;
return configured;
}
static void BindThread(const char* name, std::size_t capacity = TraceThread::DefaultCapacity);
static void Present(std::uint32_t width, std::uint32_t height, const VulkanDevice* device);
static void GpuRange(const char* name, double beginNs, double endNs) {
if (!Recording()) return;
std::lock_guard lock(gpuMutex);
if (gpuRanges.size() == gpuRanges.capacity()) {
gpuDropped = true;
return;
}
gpuRanges.push_back({name, beginNs, endNs});
}
static bool Recording() {
return recording.load(std::memory_order_relaxed);
}
static TraceThread* Thread() {
return local;
}
static void StopOutput();
private:
inline static std::atomic<bool> recording{false};
inline static std::atomic<std::uint64_t> presentCount{0};
inline static thread_local TraceThread* local = nullptr;
inline static std::mutex gpuMutex;
inline static std::vector<TraceGpuRange> gpuRanges;
inline static bool gpuDropped = false;
};
}
#endif

View File

@ -1,14 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PIPELINELINKOPTIMIZATION_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PIPELINELINKOPTIMIZATION_HPP
#include <vulkan/vulkan.h>
namespace AgcDriver {
inline bool PipelineLinkTimeOptimizationUsable(VkDriverId driver) {
return driver != VK_DRIVER_ID_NVIDIA_PROPRIETARY;
}
}
#endif

View File

@ -1,27 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PRESENTMODE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_PRESENTMODE_HPP
#include <vulkan/vulkan.h>
#include <algorithm>
#include <span>
#include <stdexcept>
#include <string_view>
namespace AgcDriver {
inline VkPresentModeKHR SelectPresentMode(const char* requested, std::span<const VkPresentModeKHR> supported) {
VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
if (requested != nullptr) {
const std::string_view name(requested);
if (name == "mailbox") mode = VK_PRESENT_MODE_MAILBOX_KHR;
else if (name == "immediate") mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
else if (name == "fifo_latest_ready") mode = VK_PRESENT_MODE_FIFO_LATEST_READY_KHR;
else if (name != "fifo") throw std::runtime_error("Vulkan presentation: ANYPS5_PRESENT_MODE must be fifo, fifo_latest_ready, mailbox or immediate");
}
if (std::find(supported.begin(), supported.end(), mode) == supported.end()) throw std::runtime_error("Vulkan presentation: the surface does not support the requested present mode");
return mode;
}
}
#endif

View File

@ -9,10 +9,8 @@
#include <stdexcept>
#include <utility>
#include <map>
#include <memory>
#include <array>
#include <optional>
#include <span>
#include <string>
#include <vector>
@ -24,29 +22,6 @@ public:
using mapped_type = std::uint32_t;
using value_type = std::pair<const std::uint32_t, std::uint32_t>;
class Mask {
public:
explicit Mask(std::vector<std::uint64_t> bits) : bits(std::move(bits)) {}
bool Contains(std::uint32_t offset) const { return offset / 64 < bits.size() && ((bits[offset / 64] >> (offset % 64)) & 1u) != 0; }
private:
std::vector<std::uint64_t> bits;
};
class Reference {
public:
Reference(Registers& owner, std::uint32_t offset) : owner(owner), offset(offset) {}
operator std::uint32_t() const { return owner.values[offset]; }
Reference& operator=(std::uint32_t value) { owner.insert_or_assign(offset, value); return *this; }
Reference& operator=(const Reference& other) { return *this = static_cast<std::uint32_t>(other); }
Reference& operator|=(std::uint32_t value) { return *this = static_cast<std::uint32_t>(*this) | value; }
Reference& operator&=(std::uint32_t value) { return *this = static_cast<std::uint32_t>(*this) & value; }
Reference& operator^=(std::uint32_t value) { return *this = static_cast<std::uint32_t>(*this) ^ value; }
Reference& operator+=(std::uint32_t value) { return *this = static_cast<std::uint32_t>(*this) + value; }
private:
Registers& owner;
std::uint32_t offset;
};
class const_iterator {
public:
using iterator_category = std::forward_iterator_tag;
@ -81,18 +56,6 @@ public:
using iterator = const_iterator;
Registers() = default;
Registers(const Registers&) = default;
Registers& operator=(const Registers&) = default;
Registers(Registers&& other) noexcept : values(std::move(other.values)), present(std::move(other.present)), entries(std::exchange(other.entries, 0)), mask(std::move(other.mask)), fingerprint(std::exchange(other.fingerprint, 0)) {}
Registers& operator=(Registers&& other) noexcept {
if (this == &other) return *this;
values = std::move(other.values);
present = std::move(other.present);
entries = std::exchange(other.entries, 0);
mask = std::move(other.mask);
fingerprint = std::exchange(other.fingerprint, 0);
return *this;
}
Registers(std::initializer_list<std::pair<std::uint32_t, std::uint32_t>> entries) {
for (const auto& [offset, value] : entries) emplace(offset, value);
}
@ -104,48 +67,33 @@ public:
const_iterator upper_bound(std::uint32_t offset) const { return {this, nextPresent(static_cast<std::size_t>(offset) + 1)}; }
bool contains(std::uint32_t offset) const { return offset < values.size() && ((present[offset / 64] >> (offset % 64)) & 1u) != 0; }
std::size_t count(std::uint32_t offset) const { return contains(offset) ? 1 : 0; }
template <class TVisit>
void ForEach(TVisit&& visit) const {
for (std::size_t word = 0; word < present.size(); ++word) {
for (auto bits = present[word]; bits != 0; bits &= bits - 1) {
const auto offset = word * 64 + static_cast<std::size_t>(std::countr_zero(bits));
visit(static_cast<std::uint32_t>(offset), values[offset]);
}
}
}
std::size_t size() const { return entries; }
bool empty() const { return entries == 0; }
void clear() {
values.clear();
present.clear();
entries = 0;
fingerprint = 0;
}
std::pair<const_iterator, bool> emplace(std::uint32_t offset, std::uint32_t value) {
if (contains(offset)) return {const_iterator{this, offset}, false};
mark(offset) = value;
changeFingerprint(offset, value);
return {const_iterator{this, offset}, true};
}
std::pair<const_iterator, bool> insert_or_assign(std::uint32_t offset, std::uint32_t value) {
if (contains(offset)) {
if (values[offset] == value) return {const_iterator{this, offset}, false};
changeFingerprint(offset, values[offset]);
values[offset] = value;
changeFingerprint(offset, value);
return {const_iterator{this, offset}, false};
}
mark(offset) = value;
changeFingerprint(offset, value);
return {const_iterator{this, offset}, true};
}
Reference operator[](std::uint32_t offset) {
if (!contains(offset)) emplace(offset, 0);
return {*this, offset};
std::uint32_t& operator[](std::uint32_t offset) {
if (contains(offset)) return values[offset];
return mark(offset) = 0;
}
Reference at(std::uint32_t offset) {
std::uint32_t& at(std::uint32_t offset) {
if (!contains(offset)) throw std::out_of_range("register is not set");
return {*this, offset};
return values[offset];
}
const std::uint32_t& at(std::uint32_t offset) const {
if (!contains(offset)) throw std::out_of_range("register is not set");
@ -153,7 +101,6 @@ public:
}
std::size_t erase(std::uint32_t offset) {
if (!contains(offset)) return 0;
changeFingerprint(offset, values[offset]);
present[offset / 64] &= ~(std::uint64_t{1} << (offset % 64));
--entries;
return 1;
@ -166,31 +113,8 @@ public:
}
return a == end() && b == other.end();
}
std::uint64_t Fingerprint(const std::shared_ptr<const Mask>& selected) const {
if (mask != selected) {
fingerprint = RecomputeFingerprint(*selected);
mask = selected;
}
return fingerprint;
}
std::uint64_t RecomputeFingerprint(const Mask& selected) const {
std::uint64_t result = 0;
for (const auto [offset, value] : *this) {
if (selected.Contains(offset)) result ^= contribution(offset, value);
}
return result;
}
private:
static std::uint64_t contribution(std::uint32_t offset, std::uint32_t value) {
auto hash = ((static_cast<std::uint64_t>(offset) << 32u) | value) + 0x9e3779b97f4a7c15ull;
hash = (hash ^ (hash >> 30u)) * 0xbf58476d1ce4e5b9ull;
hash = (hash ^ (hash >> 27u)) * 0x94d049bb133111ebull;
return hash ^ (hash >> 31u);
}
void changeFingerprint(std::uint32_t offset, std::uint32_t value) {
if (mask != nullptr && mask->Contains(offset)) fingerprint ^= contribution(offset, value);
}
static constexpr std::size_t End = ~std::size_t{0};
std::uint32_t& mark(std::uint32_t offset) {
if (offset >= values.size()) {
@ -213,8 +137,6 @@ private:
std::vector<std::uint32_t> values;
std::vector<std::uint64_t> present;
std::size_t entries = 0;
mutable std::shared_ptr<const Mask> mask;
mutable std::uint64_t fingerprint = 0;
};
inline Registers BuildInitialContextRegisters() {
@ -240,7 +162,6 @@ inline Registers BuildInitialContextRegisters() {
result.emplace(0x31c + 0xfu * i, 0);
}
for (std::uint32_t i = 0; i < 16; ++i) {
result.emplace(0x2fe + i, 0);
result.emplace(0x94 + 2 * i, 0x80000000);
result.emplace(0x95 + 2 * i, 0x40004000);
result.emplace(0xb4 + 2 * i, 0);
@ -262,12 +183,11 @@ struct Predication {
};
struct QueueState {
static constexpr std::size_t ConstantRamWords = 0x3000;
Registers shader;
Registers context = InitialContextRegisters();
Registers userConfig{{0x24a, 0}, {0x24b, 0}};
std::optional<Registers> savedContext;
std::vector<std::uint32_t> constantRam;
std::array<std::uint32_t, 0x3000> constantRam{};
std::uint64_t indexBase = 0;
std::uint64_t drawIndirectBase = 0;
std::uint64_t dispatchIndirectBase = 0;
@ -280,17 +200,6 @@ struct QueueState {
void ClearContext() {
context = InitialContextRegisters();
}
std::span<const std::uint32_t> ConstantRam() const {
static const std::array<std::uint32_t, ConstantRamWords> unwritten{};
if (constantRam.empty()) return unwritten;
return constantRam;
}
std::span<std::uint32_t> WritableConstantRam() {
if (constantRam.empty()) constantRam.resize(ConstantRamWords);
return constantRam;
}
};
}

View File

@ -84,7 +84,6 @@ struct DrawRecipe {
std::optional<Graphics::State> masked;
std::set<std::uint32_t> fragmentOutputs;
VkPipelineStageFlags shaderStages = 0;
bool hasDepth = false;
};
}

View File

@ -16,7 +16,6 @@ public:
ShaderPreparationTransaction& operator=(const ShaderPreparationTransaction&) = delete;
PreparedShaderState& Edit(const ShaderSnapshot& snapshot);
const PreparedShaderState& Read(const ShaderSnapshot& snapshot) const;
void Hold();
void Commit();
private:

View File

@ -1,155 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_STACKWALK_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_STACKWALK_HPP
#include "prx/libc/include/exceptions/Unwind.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <iterator>
#include <map>
#include <span>
#include <string>
#include <utility>
#include <vector>
namespace AgcDriver {
class DwarfFrames {
public:
using Byte = LibcUnwind::Byte;
using Word = LibcUnwind::Word;
using Registers = std::array<Word, 17>;
enum class Step { Stepped, NoRecord, Failed };
DwarfFrames(Word base, Word end, const Byte* frames, const Byte* framesEnd) : base(base), end(end) {
for (const Byte* p = frames; p != nullptr && framesEnd - p >= 8;) {
const Byte* record = p;
const auto length = LibcUnwind::Read<std::uint32_t>(p);
if (length == 0) continue;
if (length == 0xffffffff || length < 4 || static_cast<Word>(framesEnd - p) < length) break;
const Byte* next = p + length;
if (LibcUnwind::Read<std::uint32_t>(p) != 0) {
LibcUnwind::Frame frame{};
const Byte* cursor = nullptr;
const char* augmentation = nullptr;
Byte lsdaEncoding = 0;
if (LibcUnwind::DecodeRange(frame, LibcUnwind::Lookup{0, record}, cursor, augmentation, lsdaEncoding)) index.emplace_back(frame.start, record);
}
p = next;
}
std::sort(index.begin(), index.end());
}
Word Base() const {
return base;
}
bool Contains(Word pc) const {
return pc >= base && pc < end;
}
bool Covers(Word pc) const {
LibcUnwind::Frame frame{};
return find(pc, frame) == Step::Stepped;
}
template<typename TRead>
Step Unwind(Registers& registers, bool leaf, TRead&& read) const {
const Word pc = registers[16] - (leaf ? 0 : 1);
LibcUnwind::Frame frame{};
if (const auto found = find(pc, frame); found != Step::Stepped) return found;
LibcUnwind::Rules initial;
if (!LibcUnwind::Instructions(frame.cieBegin, frame.cieEnd, frame, ~Word(0), initial, {})) return Step::Failed;
if constexpr (LibcUnwind::RuleCount > 32) {
if (frame.returnRegister == 32) {
initial.registers[16] = initial.registers[32];
initial.registers[32] = {};
frame.returnRegister = 16;
}
}
LibcUnwind::Rules rules = initial;
if (!LibcUnwind::Instructions(frame.begin, frame.end, frame, pc, rules, initial)) return Step::Failed;
if (rules.cfaExpression != nullptr || rules.cfaRegister >= 16 || frame.returnRegister >= 17) return Step::Failed;
const Word cfa = registers[rules.cfaRegister] + rules.cfaOffset;
if (cfa <= registers[7]) return Step::Failed;
Registers next = registers;
for (unsigned column = 0; column < next.size(); ++column) {
const auto& rule = rules.registers[column];
if (rule.kind == 0) continue;
if (rule.kind == 1) {
if (!read(cfa + rule.value, next[column])) return Step::Failed;
} else if (rule.kind == 2 && rule.value >= 0 && rule.value < 17) {
next[column] = registers[rule.value];
} else if (rule.kind == 4) {
next[column] = cfa + rule.value;
} else if (rule.kind == 5) {
next[column] = 0;
} else {
return Step::Failed;
}
}
next[7] = cfa;
next[16] = next[frame.returnRegister];
if (next[16] == 0 || (next[7] == registers[7] && next[16] == registers[16])) return Step::Failed;
registers = next;
return Step::Stepped;
}
private:
Step find(Word pc, LibcUnwind::Frame& frame) const {
const auto found = std::upper_bound(index.begin(), index.end(), pc, [](Word value, const auto& entry) { return value < entry.first; });
if (found == index.begin()) return Step::NoRecord;
const Byte* p = nullptr;
const char* augmentation = nullptr;
Byte lsdaEncoding = 0;
if (!LibcUnwind::DecodeRange(frame, LibcUnwind::Lookup{pc, std::prev(found)->second}, p, augmentation, lsdaEncoding)) return Step::Failed;
if (pc < frame.start || pc - frame.start >= frame.length) return Step::NoRecord;
if (*augmentation == 'z') {
const auto size = LibcUnwind::Uleb(p);
p += size;
}
frame.begin = p;
return Step::Stepped;
}
Word base;
Word end;
std::vector<std::pair<Word, const Byte*>> index;
};
class CollapsedStacks {
public:
void Add(std::span<const std::uint64_t> leafFirst, bool truncated) {
std::vector<std::uint64_t> stack(leafFirst.begin(), leafFirst.end());
if (truncated) {
stack.push_back(0);
++truncatedStacks;
}
++stacks[std::move(stack)];
}
std::uint64_t Truncated() const {
return truncatedStacks;
}
template<typename TName>
std::string Format(TName&& name) const {
std::string output;
for (const auto& [stack, count] : stacks) {
for (auto frame = stack.rbegin(); frame != stack.rend(); ++frame) {
if (frame != stack.rbegin()) output += ';';
output += *frame == 0 ? std::string("[truncated]") : name(*frame);
}
output += ' ' + std::to_string(count) + '\n';
}
return output;
}
private:
std::map<std::vector<std::uint64_t>, std::uint64_t> stacks;
std::uint64_t truncatedStacks = 0;
};
}
#endif

View File

@ -1,17 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_TRACEFILE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_TRACEFILE_HPP
#include <filesystem>
#include <string_view>
namespace AgcDriver {
class TraceFile {
public:
static void WriteGzip(const std::filesystem::path& path, std::string_view text);
static void AppendLine(const std::filesystem::path& path, std::string_view line);
};
}
#endif

View File

@ -1,81 +0,0 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_TRACYZONE_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_TRACYZONE_HPP
#ifdef TRACY_ENABLE
#include <tracy/TracyC.h>
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <stdexcept>
#endif
namespace AgcDriver {
class TracyZone {
public:
explicit TracyZone([[maybe_unused]] const char* name) {
#ifdef TRACY_ENABLE
context = ___tracy_emit_zone_begin(location(name), 1);
#endif
}
TracyZone(const TracyZone&) = delete;
TracyZone& operator=(const TracyZone&) = delete;
~TracyZone() {
End();
}
void End() {
#ifdef TRACY_ENABLE
if (context.active == 0) return;
___tracy_emit_zone_end(context);
context.active = 0;
#endif
}
static void MarkFrame() {
#ifdef TRACY_ENABLE
___tracy_emit_frame_mark(nullptr);
#endif
}
static void NameThread([[maybe_unused]] const char* name) {
#ifdef TRACY_ENABLE
___tracy_set_thread_name(name);
#endif
}
private:
#ifdef TRACY_ENABLE
static const ___tracy_source_location_data* location(const char* name) {
static constexpr std::size_t capacity = 256;
static std::array<std::atomic<const char*>, capacity> names{};
static std::array<___tracy_source_location_data, capacity> locations{};
static std::mutex mutex;
const auto start = static_cast<std::size_t>(reinterpret_cast<std::uintptr_t>(name) >> 4);
for (std::size_t probe = 0; probe < capacity; ++probe) {
const auto index = (start + probe) % capacity;
const auto* known = names[index].load(std::memory_order_acquire);
if (known == name) return &locations[index];
if (known != nullptr) continue;
std::lock_guard lock(mutex);
known = names[index].load(std::memory_order_relaxed);
if (known == name) return &locations[index];
if (known != nullptr) continue;
locations[index] = {name, name, "", 0, 0};
names[index].store(name, std::memory_order_release);
return &locations[index];
}
throw std::runtime_error("Tracy: too many zone names");
}
TracyCZoneCtx context;
#endif
};
}
#endif

View File

@ -7,11 +7,9 @@
#include "prx/libSceAgcDriver/Execution/include/Presentation.hpp"
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
#include "prx/libSceAgcDriver/Execution/include/Recipe.hpp"
#include "prx/libSceAgcDriver/Execution/include/ComputePipelineCapture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include <chrono>
#include <functional>
#include <memory>
#include <optional>
@ -33,19 +31,12 @@ struct RecordedDispatch;
class VulkanDevice {
public:
explicit VulkanDevice(const PresentationWindow* window = nullptr);
struct CompileOnly {};
explicit VulkanDevice(CompileOnly);
ComputePipelineReplayResult ReplayComputePipeline(const ComputePipelineCapture& capture, std::span<const std::byte> initialCache = {});
~VulkanDevice();
VulkanDevice(const VulkanDevice&) = delete;
VulkanDevice& operator=(const VulkanDevice&) = delete;
std::string DeviceName() const;
std::string DriverInfo() const;
bool CalibrateClock(double& gpuNs, std::chrono::steady_clock::time_point& cpu) const;
ShaderRecompiler::SpirvTarget Target() const;
std::optional<ShaderRecompiler::GeometryStageLimits> GeometryLimits() const;
const std::uint32_t* WorkGroupCountLimit() const;
std::uint32_t VendorId() const;
ShaderRecompiler::SpirvTarget ComputeTarget(std::uint32_t waveSize) const;
// Distinguishes this device from every earlier one in the process (a memo keyed by the device
// cannot rely on the pointer, which a replacement may reuse).
@ -64,7 +55,6 @@ 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);
// 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
@ -222,7 +212,7 @@ public:
// blit-submit times after the flip packet; noted in-place reads checked
// (APS5_FLIP_READ_CHECK=1) and found overwritten before the blit's fence.
struct PresentStatistics {
std::uint64_t presents, gpuUnread, batchesAheadOfBlit, batchesAfterFlip, readsChecked, readsOverwritten, acquireDrops;
std::uint64_t presents, gpuUnread, batchesAheadOfBlit, batchesAfterFlip, readsChecked, readsOverwritten;
double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs;
};
static PresentStatistics PresentCounts();
@ -302,11 +292,8 @@ public:
};
IndirectDrawSupport DrawIndirectSupport() const;
void EnqueueDraw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots = {});
bool PrebuildGraphicsLibraries(const Graphics::ShaderStages& stages, std::span<const Graphics::CompiledShader> shaders);
private:
VulkanDevice(const PresentationWindow* window, bool compileOnly);
friend struct VulkanDeviceTestAccess;
// The device's Graphics::Context: a copy of the one built at setup (its instance functions
// resolved then, its function table filled then), or with APS5_NO_CONTEXT_CACHE=1 built anew.
Graphics::Context graphicsContext() const;

View File

@ -1,265 +0,0 @@
#include "prx/libSceAgcDriver/Execution/include/ComputePipelineCapture.hpp"
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <limits>
#include <mutex>
#include <set>
#include <sstream>
#include <stdexcept>
namespace AgcDriver {
namespace {
void RequireCapture(bool condition, const char* message) {
if (!condition) throw std::runtime_error(std::string("compute pipeline capture: ") + message);
}
std::string Hash(std::span<const std::byte> bytes) {
std::uint64_t hash = 14695981039346656037ull;
for (const auto value : bytes) {
hash ^= std::to_integer<std::uint8_t>(value);
hash *= 1099511628211ull;
}
std::ostringstream result;
result << std::hex << std::setfill('0') << std::setw(16) << hash;
return result.str();
}
class Encoder {
public:
void Word(std::uint32_t value) {
for (unsigned shift = 0; shift < 32; shift += 8) bytes.push_back(static_cast<std::byte>((value >> shift) & 0xffu));
}
void String(const std::string& value) {
Word(static_cast<std::uint32_t>(value.size()));
for (const auto character : value) bytes.push_back(static_cast<std::byte>(static_cast<unsigned char>(character)));
}
std::vector<std::byte> bytes;
};
class Decoder {
public:
explicit Decoder(std::span<const std::byte> bytes) : bytes(bytes) {}
std::uint32_t Word() {
RequireCapture(bytes.size() - cursor >= 4, "truncated integer");
std::uint32_t value = 0;
for (unsigned shift = 0; shift < 32; shift += 8) value |= std::to_integer<std::uint32_t>(bytes[cursor++]) << shift;
return value;
}
std::uint32_t Count(std::uint32_t maximum, std::size_t minimumBytes) {
const auto count = Word();
RequireCapture(count <= maximum && count <= (bytes.size() - cursor) / minimumBytes, "count exceeds record bounds");
return count;
}
std::string String() {
const auto count = Count(VK_MAX_PHYSICAL_DEVICE_NAME_SIZE - 1, 1);
std::string result;
for (std::uint32_t index = 0; index < count; ++index) result.push_back(static_cast<char>(std::to_integer<unsigned char>(bytes[cursor++])));
return result;
}
bool Finished() const { return cursor == bytes.size(); }
private:
std::span<const std::byte> bytes;
std::size_t cursor = 0;
};
std::vector<std::byte> ReadBytes(const std::filesystem::path& path) {
const auto size = std::filesystem::file_size(path);
RequireCapture(size <= ComputePipelineCapture::MaxFileBytes, "file exceeds 36 MiB");
std::ifstream input(path, std::ios::binary);
RequireCapture(input.good(), "cannot open capture");
std::vector<std::byte> bytes(static_cast<std::size_t>(size));
input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
RequireCapture(input.good() && input.peek() == std::char_traits<char>::eof(), "capture changed while reading");
return bytes;
}
}
void ComputePipelineCapture::Validate() const {
RequireCapture(spirv.size() >= 5 && spirv.size() <= MaxSpirvWords, "SPIR-V word count out of bounds");
RequireCapture(spirv[0] == 0x07230203u && spirv[1] >= 0x00010000u && spirv[1] <= 0x00010600u && (spirv[1] & 0xffu) == 0 && spirv[3] != 0 && spirv[3] <= 0x003fffffu && spirv[4] == 0, "invalid SPIR-V header");
bool mainEntry = false;
for (std::size_t cursor = 5; cursor < spirv.size();) {
const auto count = spirv[cursor] >> 16u;
RequireCapture(count != 0 && count <= spirv.size() - cursor, "truncated SPIR-V instruction");
if ((spirv[cursor] & 0xffffu) == 15u) {
RequireCapture(count >= 5 && spirv[cursor + 1] == 5u && spirv[cursor + 2] != 0 && spirv[cursor + 2] < spirv[3] && spirv[cursor + 3] == 0x6e69616du && spirv[cursor + 4] == 0, "only a compute main entry point is supported");
RequireCapture(!mainEntry, "multiple entry points are unsupported");
mainEntry = true;
}
cursor += count;
}
RequireCapture(mainEntry, "compute main entry point missing");
RequireCapture(deviceName.size() < VK_MAX_PHYSICAL_DEVICE_NAME_SIZE && deviceName.find('\0') == std::string::npos, "invalid device name");
RequireCapture((flags & ~(VK_PIPELINE_CREATE_DISPATCH_BASE_BIT | VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR)) == 0, "unsupported pipeline flags");
RequireCapture(stageFlags == 0, "unsupported shader stage flags");
RequireCapture(requiredSubgroupSize == 0 || (requiredSubgroupSize <= 128 && (requiredSubgroupSize & (requiredSubgroupSize - 1u)) == 0), "invalid required subgroup size");
RequireCapture(sets.size() <= 8, "too many descriptor sets");
std::size_t totalBindings = 0;
for (const auto& layout : sets) {
RequireCapture((layout.flags & ~VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT) == 0, "unsupported descriptor layout flags");
RequireCapture(layout.bindings.size() <= 4096, "too many descriptor bindings");
totalBindings += layout.bindings.size();
std::set<std::uint32_t> used;
const auto highest = layout.bindings.empty() ? 0u : std::ranges::max(layout.bindings, {}, &ComputeDescriptorBinding::binding).binding;
for (const auto& binding : layout.bindings) {
RequireCapture(used.insert(binding.binding).second, "duplicate descriptor binding");
RequireCapture(binding.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || binding.type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE || binding.type == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE || binding.type == VK_DESCRIPTOR_TYPE_SAMPLER, "unsupported descriptor type");
RequireCapture(binding.count != 0 && binding.count <= 1048576, "invalid descriptor count");
RequireCapture(binding.stages == VK_SHADER_STAGE_COMPUTE_BIT, "unsupported descriptor stage flags");
constexpr VkDescriptorBindingFlags supported = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT;
RequireCapture((binding.flags & ~supported) == 0, "unsupported descriptor binding flags");
RequireCapture((binding.flags & VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT) == 0 || (binding.type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER && (layout.flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT) != 0), "unsupported update-after-bind descriptor");
RequireCapture((binding.flags & VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT) == 0 || binding.binding == highest, "variable descriptor count must use the highest binding");
}
}
RequireCapture(totalBindings <= 4096, "too many total descriptor bindings");
RequireCapture(pushConstants.size() <= 1, "too many compute push constant ranges");
for (const auto& range : pushConstants) RequireCapture(range.stageFlags == VK_SHADER_STAGE_COMPUTE_BIT && range.size != 0 && range.size <= 4096 && range.offset <= 4096 - range.size && (range.offset % 4) == 0 && (range.size % 4) == 0, "invalid push constant range");
RequireCapture(specialization.size() <= 4096, "too many specialization values");
std::set<std::uint32_t> specializationIds;
for (const auto& value : specialization) RequireCapture(specializationIds.insert(value.id).second, "duplicate specialization ID");
}
std::vector<std::byte> ComputePipelineCapture::Encode() const {
Validate();
Encoder output;
output.Word(0x35535041u);
output.Word(0x504d4f43u);
output.Word(Version);
output.Word(flags);
output.Word(stageFlags);
output.Word(requiredSubgroupSize);
output.Word(vendorId);
output.Word(deviceId);
output.Word(driverVersion);
output.Word(apiVersion);
for (const auto value : pipelineCacheUuid) output.Word(value);
output.String(deviceName);
output.Word(static_cast<std::uint32_t>(sets.size()));
for (const auto& layout : sets) {
output.Word(layout.flags);
output.Word(static_cast<std::uint32_t>(layout.bindings.size()));
for (const auto& binding : layout.bindings) {
output.Word(binding.binding);
output.Word(static_cast<std::uint32_t>(binding.type));
output.Word(binding.count);
output.Word(binding.stages);
output.Word(binding.flags);
}
}
output.Word(static_cast<std::uint32_t>(pushConstants.size()));
for (const auto& range : pushConstants) {
output.Word(range.stageFlags);
output.Word(range.offset);
output.Word(range.size);
}
output.Word(static_cast<std::uint32_t>(specialization.size()));
for (const auto& value : specialization) {
output.Word(value.id);
output.Word(value.value);
}
output.Word(static_cast<std::uint32_t>(spirv.size()));
for (const auto word : spirv) output.Word(word);
RequireCapture(output.bytes.size() <= MaxFileBytes, "encoded capture exceeds size bound");
return std::move(output.bytes);
}
ComputePipelineCapture ComputePipelineCapture::Decode(std::span<const std::byte> bytes) {
RequireCapture(bytes.size() <= MaxFileBytes, "record exceeds 36 MiB");
Decoder input(bytes);
RequireCapture(input.Word() == 0x35535041u && input.Word() == 0x504d4f43u, "invalid file signature");
RequireCapture(input.Word() == Version, "unsupported file version");
ComputePipelineCapture result;
result.flags = input.Word();
result.stageFlags = input.Word();
result.requiredSubgroupSize = input.Word();
result.vendorId = input.Word();
result.deviceId = input.Word();
result.driverVersion = input.Word();
result.apiVersion = input.Word();
for (auto& value : result.pipelineCacheUuid) {
const auto word = input.Word();
RequireCapture(word <= 255, "invalid cache UUID byte");
value = static_cast<std::uint8_t>(word);
}
result.deviceName = input.String();
result.sets.resize(input.Count(8, 8));
std::size_t totalBindings = 0;
for (auto& layout : result.sets) {
layout.flags = input.Word();
const auto count = input.Count(4096, 20);
totalBindings += count;
RequireCapture(totalBindings <= 4096, "too many total descriptor bindings");
layout.bindings.resize(count);
for (auto& binding : layout.bindings) {
binding.binding = input.Word();
binding.type = static_cast<VkDescriptorType>(input.Word());
binding.count = input.Word();
binding.stages = input.Word();
binding.flags = input.Word();
}
}
result.pushConstants.resize(input.Count(1, 12));
for (auto& range : result.pushConstants) {
range.stageFlags = input.Word();
range.offset = input.Word();
range.size = input.Word();
}
result.specialization.resize(input.Count(4096, 8));
for (auto& value : result.specialization) {
value.id = input.Word();
value.value = input.Word();
}
result.spirv.resize(input.Count(MaxSpirvWords, 4));
for (auto& word : result.spirv) word = input.Word();
RequireCapture(input.Finished(), "trailing bytes after capture");
result.Validate();
return result;
}
ComputePipelineCapture ComputePipelineCapture::Read(const std::filesystem::path& path) {
return Decode(ReadBytes(path));
}
std::string ComputePipelineCapture::Identity() const {
return Hash(Encode());
}
std::filesystem::path ComputePipelineCapture::Write(const std::filesystem::path& directory) const {
const auto bytes = Encode();
const auto recordDirectory = directory / ("compute-" + Hash(bytes));
const auto path = recordDirectory / "capture.aps5cp";
static std::mutex mutex;
std::lock_guard lock(mutex);
std::filesystem::create_directories(directory);
RequireCapture(!std::filesystem::is_symlink(std::filesystem::symlink_status(directory)), "capture directory is a symbolic link");
if (std::filesystem::exists(recordDirectory)) {
RequireCapture(std::filesystem::is_directory(recordDirectory) && !std::filesystem::is_symlink(std::filesystem::symlink_status(recordDirectory)) && !std::filesystem::is_symlink(std::filesystem::symlink_status(path)), "existing capture path is not a regular capture");
RequireCapture(ReadBytes(path) == bytes, "existing capture differs or is incomplete");
return path;
}
std::uintmax_t totalBytes = 0;
std::size_t count = 0;
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
RequireCapture(++count < 128, "capture directory reached 128 entries");
RequireCapture(entry.is_directory() && !entry.is_symlink(), "capture directory contains an unexpected entry");
const auto capture = entry.path() / "capture.aps5cp";
RequireCapture(std::filesystem::is_regular_file(capture) && !std::filesystem::is_symlink(std::filesystem::symlink_status(capture)), "capture directory contains an incomplete record");
const auto size = std::filesystem::file_size(capture);
RequireCapture(size <= MaxFileBytes, "existing capture exceeds size bound");
totalBytes += size;
}
RequireCapture(totalBytes + bytes.size() <= 256u * 1024u * 1024u, "capture directory exceeds 256 MiB");
RequireCapture(std::filesystem::create_directory(recordDirectory), "capture directory was concurrently created");
std::ofstream output(path, std::ios::binary);
RequireCapture(output.good(), "cannot create capture");
output.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
output.close();
RequireCapture(output.good(), "cannot finish capture");
return path;
}
}

View File

@ -1,6 +1,5 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
#include <cstdlib>
#include <stdexcept>
namespace AgcDriver::DriverDetail {
@ -10,11 +9,6 @@ double TraceMs() {
return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - origin).count();
}
std::uint64_t SkippedShader() {
static const std::uint64_t skipped = [] { const char* text = std::getenv("APS5_SKIP_SHADER"); return text ? std::strtoull(text, nullptr, 16) : 0ull; }();
return skipped;
}
void require(bool condition, const char* reason) {
if (!condition) {
throw std::runtime_error(std::string("AGC driver: ") + reason);

View File

@ -16,7 +16,6 @@ namespace AgcDriver::DriverDetail {
void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet, const Submission& submission, std::uint64_t indirectArguments) {
PerformanceTimer timing("Driver.Dispatch");
const auto address = (static_cast<std::uint64_t>(readRegister(queue.shader, 0x20c)) << 8u) | (static_cast<std::uint64_t>(readRegister(queue.shader, 0x20d) & 0xffu) << 40u);
if (address == SkippedShader() && address != 0) return;
auto registeredShader = RegisteredProgram(*submission.shaders, address, {0});
if (!registeredShader) registeredShader = ReadRawShader(address);
const auto& snapshot = *registeredShader;
@ -76,9 +75,6 @@ void Driver::dispatch(QueueState& queue, std::span<const std::uint32_t> packet,
if (!partialLastGroup && indirectArguments == 0 && matchesCopyKernel(std::span(snapshot.code).subspan(codeOffset), userData, compute)) return;
} else {
if (const auto htile = Graphics::HtileDepthClearAddress(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads); htile != 0) Graphics::NoteHtileDepthClear(htile);
if (indirectArguments == 0 && (packet[4] & 0x20u) == 0) {
if (const auto fill = Graphics::MatchMemoryValueFill(std::span(snapshot.code).subspan(codeOffset), userData, compute.numThreads, {packet[1], packet[2], packet[3]})) Graphics::NoteMemoryValueFill(*fill);
}
if (!partialLastGroup && fillBuffer(queue, submission.queue, packet, std::span(snapshot.code).subspan(codeOffset), userData, compute, localDevice)) {
pendingDispatchPhases().outcome = DispatchOutcome::FillHle;
return;

View File

@ -1,5 +1,4 @@
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
#include "ThreadOwned.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp"
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
@ -11,8 +10,8 @@
namespace AgcDriver::DriverDetail {
std::map<std::size_t, Driver::ResolvedDispatch>& Driver::resolvedAhead() {
static thread_local std::map<std::size_t, ResolvedDispatch>* resolved = nullptr;
return ShaderRecompiler::ThreadOwned(resolved);
static thread_local std::map<std::size_t, ResolvedDispatch> resolved;
return resolved;
}
Driver::GroupCaptureStats& Driver::groupCaptureStats() {
@ -179,7 +178,6 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l
const auto label = Pm4::DecodeLabelWrite(packet);
if (!label) break;
NoteLabel(labels, label->address, label->Bytes());
writes.emplace_back(label->address, label->address + label->Bytes().size());
} else if (opcode != 0x3f && opcode != 0x42 && opcode != 0x46 && opcode != 0x58) {
if (Pm4::AccessesMemory(header)) break;
try {

View File

@ -4,7 +4,6 @@
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
#include "Optimization/ResourceProgram.hpp"
#include <algorithm>
#include <cstdlib>
namespace AgcDriver::DriverDetail {
@ -93,9 +92,8 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
resolveDrawDecode(queue, submission, decode, registerKey, drawKey, profile);
const auto& graphics = decode->state;
auto pixel = decode->pixel;
const auto& pixel = decode->pixel;
std::vector<DrawProgram> programs = decode->programs;
if (const auto skipped = SkippedShader(); skipped != 0 && std::any_of(programs.begin(), programs.end(), [skipped](const DrawProgram& program) { return program.binary.codeAddress == skipped; })) return drawn();
const auto setMeshIndexBuffer = [&](const Pm4::DrawParameters& parameters) {
if (!graphics.stages.mesh) return;
auto& words = programs.front().userData;
@ -146,8 +144,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
const auto& program = programs[i];
if (program.binary.stage == Stage::Fragment || roles[i] == Role::GeometryBack) return;
decodeReads[i].clear();
vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i], false, 8u - program.firstUserSgpr);
if (vertexInfos[i]) vertexInfos[i]->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, readRegister(queue.context, 0x207)) | graphics.layerExports;
vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i]);
};
if (!registerKey) {
for (std::size_t i = 0; i < programs.size(); ++i) decodeVertexInfo(i);
@ -207,13 +204,6 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
memory.insert(memory.end(), matchedRegions[i].begin(), matchedRegions[i].end());
} else {
resultIndex[i] = results.size();
if (program.binary.stage == ShaderRecompiler::ShaderStage::Fragment) {
for (auto previous = i; previous-- > 0;) {
if (programResults[previous] == nullptr) continue;
pixel = Graphics::DefaultUnexportedInputs(decode->pixel, programResults[previous]->parameterExports);
break;
}
}
results.push_back(materializeDrawStage(i, pushCursorBytes, queue, submission, programs, graphics, pixel, vertexInfos, memory, linked, drawParameters, localDevice, shaderMemory, stageCaptures, recompiled, drawHit, matched, matchedRegions, profile, dumpTarget, dumpSlot1, captures, phaseTiming, phaseMs, rejected));
if (!rejected.empty()) return DrawVerdict::Rejected;
programResults[i] = &results.back();

View File

@ -1,7 +1,6 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp"
#include <algorithm>
#include <bit>
#include <cstdlib>
namespace AgcDriver::DriverDetail {
@ -57,36 +56,6 @@ void Driver::accountDrawVariant(const DispatchVariant& variant, bool added) {
}
}
DrawKeySightings::DrawKeySightings(std::size_t keyCount) {
Graphics::Require(keyCount >= Ways, "draw key sightings need at least one bucket");
const auto buckets = std::bit_ceil(keyCount / Ways);
keys.assign(buckets * Ways, 0);
filled.assign(buckets, 0);
next.assign(buckets, 0);
bucketMask = buckets - 1;
}
bool DrawKeySightings::SeenBefore(std::uint64_t key) {
auto mixed = key;
mixed = (mixed ^ (mixed >> 30u)) * 0xbf58476d1ce4e5b9ull;
mixed = (mixed ^ (mixed >> 27u)) * 0x94d049bb133111ebull;
mixed ^= mixed >> 31u;
const auto bucket = static_cast<std::size_t>(mixed) & bucketMask;
auto* ways = keys.data() + bucket * Ways;
if (std::find(ways, ways + filled[bucket], key) != ways + filled[bucket]) return true;
ways[next[bucket]] = key;
next[bucket] = static_cast<std::uint8_t>((next[bucket] + 1u) % Ways);
if (filled[bucket] < Ways) ++filled[bucket];
return false;
}
bool Driver::admitDrawKey(std::uint64_t key) {
std::lock_guard cacheLock(drawCacheMutex);
if (drawCache.contains(key)) return true;
if (!drawKeySightings) drawKeySightings.emplace(drawCacheEntries() * DrawKeySightings::Ways);
return drawKeySightings->SeenBefore(key);
}
void Driver::insertDrawEntry(std::uint64_t key, std::vector<std::shared_ptr<DispatchVariant>>& fresh, std::shared_ptr<const DrawDecode> decode) {
std::lock_guard cacheLock(drawCacheMutex);
auto& counters = drawEntryCounters;

View File

@ -20,7 +20,7 @@ ShaderRecompiler::RecompileResult Driver::materializeDrawStage(std::size_t i, st
program.binary,
{waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, vertexInfos[i], memory, RegisteredFloatMode(*program.snapshot)},
localDevice->Target(),
{0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : graphics.stages.tessellation && pushOffset < Graphics::PipelinePushSlotBytes ? ShaderRecompiler::TessellationLevelPushOffsetBytes : Graphics::PipelinePushSlotBytes) - pushOffset % Graphics::PipelinePushSlotBytes},
{0, 0, pushOffset, (graphics.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushSlotBytes) - pushOffset % Graphics::PipelinePushSlotBytes},
ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}}
};
const auto waitedBefore = traceCapSync() || profile ? Graphics::Recorder::ThreadWaitedMs() : 0.0;
@ -83,7 +83,7 @@ ShaderRecompiler::RecompileResult Driver::materializeDrawStage(std::size_t i, st
}
void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawParameters& drawParameters, const std::optional<Graphics::IndirectDrawPath>& indirectCpu, const std::vector<DrawProgram>& programs, const std::vector<StageCapture>& stageCaptures, const std::vector<std::optional<ShaderRecompiler::ShaderVertexStageInfo>>& vertexInfos, const std::vector<std::vector<Graphics::DecodeRead>>& decodeReads, bool verifyHit, const std::vector<std::shared_ptr<DispatchVariant>>& matched, std::vector<std::shared_ptr<DispatchVariant>>& fresh, std::uint64_t drawKey, bool registerKey, const std::shared_ptr<const DrawDecode>& decode, DrawPhaseTiming& phaseTiming) {
if (useDrawEntries && !drawHit && !(drawParameters.indirect && indirectCpu) && (verifyHit || admitDrawKey(drawKey))) {
if (useDrawEntries && !drawHit && !(drawParameters.indirect && indirectCpu)) {
phaseTiming.Phase(DrawRowVectors);
std::uint64_t unstable = 0, mismatches = 0;
for (std::size_t i = 0; i < programs.size(); ++i) {

View File

@ -81,7 +81,7 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
};
const auto& graphics = product->state;
if (graphics.stages.path == Graphics::ShaderPath::Tessellation) {
if (!staticAbi || programAddress(0x148) != 0) append(0x148, 5, Stage::Local, 0x10b, 0x10c, Role::Local);
append(0x148, 5, Stage::Local, 0x10b, 0x10c, Role::Local);
append(0x108, 7, Stage::TessellationControl, 0x10b, 0x10c, Role::Hull);
initializeMerged(programs.back(), 0x102, true);
append(0x0c8, 2, Stage::TessellationEvaluation, 0x08b, 0x08c, Role::Domain);
@ -109,14 +109,21 @@ void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const
std::shared_ptr<DrawDecode> Driver::decodeDraw(const QueueState& queue, const Submission& submission) {
auto product = std::make_shared<DrawDecode>();
product->state = Graphics::DecodeState(queue, VertexWorkgroupRequired(queue, *submission.shaders));
bool vertexWorkgroup = false;
const auto stages = queue.context.find(0x2d5);
const auto low = queue.shader.find(0xc8);
const auto high = queue.shader.find(0xc9);
if (stages != queue.context.end() && (stages->second & 0x02002024u) == 0x2000u && low != queue.shader.end() && high != queue.shader.end()) {
const auto address = (static_cast<std::uint64_t>(low->second) << 8u) | (static_cast<std::uint64_t>(high->second & 0xffu) << 40u);
const auto front = ProgramSnapshot(*submission.shaders, address, {2});
vertexWorkgroup = front->header.empty() && ProgramUsesWorkgroup(*front, static_cast<std::size_t>((address - front->codeAddress) / sizeof(std::uint32_t)));
}
product->state = Graphics::DecodeState(queue, vertexWorkgroup);
DecodeGraphicsPrograms(*product, queue, *submission.shaders, false, true);
product->pixel = Graphics::DecodePixelStageInfo(queue.context, Graphics::ExportMappings(product->state), Graphics::PixelProgramSkipped(queue));
product->pixel.targetExportPacking = Graphics::ExportPackings(product->state);
product->pixel.dualSourceBlend = product->state.dualSourceBlend;
product->pixel.alphaToCoverage = product->state.alphaToCoverage;
if (product->pixel.ancillary) product->pixel.rasterPrimitive = Graphics::RasterPrimitiveOf(product->state);
if (product->pixel.ancillary) product->pixel.layerExported = product->state.layerExports != 0;
return product;
}

View File

@ -26,7 +26,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
mix(graphics.stages.tessellation.has_value());
if (graphics.stages.tessellation) {
const auto& tess = *graphics.stages.tessellation;
for (const auto value : {tess.inputControlPoints, tess.outputControlPoints, tess.domain, tess.partitioning, tess.outputTopology, tess.patchesPerGroup}) mix(value);
for (const auto value : {tess.inputControlPoints, tess.outputControlPoints, tess.domain, tess.partitioning, tess.outputTopology}) mix(value);
}
mix(graphics.rectList);
mix(programs.size());
@ -48,7 +48,6 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
mix(vertex.fetchAttribReg);
mix(vertex.fetchBufferReg);
mix(vertex.fetchEmbedded);
mix(vertex.paClVsOutCntl);
for (std::uint32_t r = 0; r < vertex.resourcesNum; ++r) {
for (const auto field : vertex.resources[r].fields) mix(field);
const auto& destination = vertex.resourcesDst[r];
@ -65,13 +64,11 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
for (const bool flag : {pixel.wave32, pixel.hasPerspectiveCenterVgpr, pixel.perspectiveCentroid, pixel.posX, pixel.posY, pixel.posZ, pixel.posW, pixel.frontFace, pixel.ancillary, pixel.sampleShading, pixel.noPerspective, pixel.linearCentroid, pixel.pixelKillEnable, pixel.depthExportEnable, pixel.sampleMaskExportEnable, pixel.earlyZ, pixel.executeOnNoop}) mix(flag);
mix(static_cast<std::uint64_t>(pixel.conservativeZExport));
mix(pixel.orderedPixelShader);
mix(pixel.stencilRefExportEnable);
for (const auto value : pixel.targetOutputMode) mix(value);
for (const auto value : pixel.targetExportMapping) mix(value);
for (const auto value : pixel.targetExportPacking) mix(static_cast<std::uint64_t>(value));
mix(pixel.dualSourceBlend);
mix(pixel.alphaToCoverage);
mix(static_cast<std::uint64_t>(pixel.rasterPrimitive));
std::lock_guard cacheLock(drawCacheMutex);
++drawEntryCounters.lookups;
const auto found = drawCache.find(drawKey);

View File

@ -23,7 +23,6 @@ std::optional<DrawVerdict> Driver::precheckDraw(const QueueState& queue, const S
localDevice->ColorMetadataPass(*pass);
return DrawVerdict::Drawn;
}
if (Graphics::DepthResummarizeDrawsNothing(queue)) return DrawVerdict::Nothing;
rejected = Graphics::DepthMaintenanceRejection(queue);
if (!rejected.empty()) return DrawVerdict::Rejected;
static const bool traceIndirectEnabled = std::getenv("APS5_TRACE_INDIRECT_DRAWS") != nullptr;

View File

@ -1,5 +1,4 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawRegisterKey.hpp"
#include <algorithm>
#include <array>
#include <cstdlib>
@ -7,69 +6,36 @@
namespace AgcDriver::DriverDetail {
namespace {
bool runtimeRegister(std::uint32_t offset) {
return offset - 0x00cu < 32u || offset - 0x08cu < 32u || offset - 0x10cu < 32u || offset == 0x082u || offset == 0x083u || offset == 0x102u || offset == 0x103u;
}
const auto& registerMasks() {
static const auto masks = [] {
std::array<std::vector<std::uint64_t>, 3> bits;
for (const auto& range : Graphics::DrawKeyRegisters) {
auto& bank = bits[static_cast<std::size_t>(range.bank)];
bank.resize(std::max(bank.size(), (static_cast<std::size_t>(range.first) + range.count + 63) / 64));
for (auto offset = range.first; offset < range.first + range.count; ++offset) {
if (range.bank == Graphics::RegisterBank::Shader && runtimeRegister(offset)) continue;
bank[offset / 64] |= std::uint64_t{1} << (offset % 64);
}
}
std::array<std::shared_ptr<const Registers::Mask>, 3> result;
for (std::size_t i = 0; i < bits.size(); ++i) result[i] = std::make_shared<const Registers::Mask>(std::move(bits[i]));
return result;
}();
return masks;
}
}
std::uint64_t RegisterStateKey(const QueueState& queue, bool allUserWords, bool incremental) {
const auto& masks = registerMasks();
const auto fingerprint = [&](const Registers& registers, Graphics::RegisterBank bank) {
const auto& mask = masks[static_cast<std::size_t>(bank)];
return incremental ? registers.Fingerprint(mask) : registers.RecomputeFingerprint(*mask);
};
auto shape = (0xcbf29ce484222325ull ^ fingerprint(queue.context, Graphics::RegisterBank::Context)) * 0x100000001b3ull;
shape = (shape ^ fingerprint(queue.shader, Graphics::RegisterBank::Shader)) * 0x100000001b3ull;
shape = (shape ^ fingerprint(queue.userConfig, Graphics::RegisterBank::UserConfig)) * 0x100000001b3ull;
auto exact = shape;
const auto words = [&](std::uint32_t base, std::uint32_t count) {
for (auto it = queue.shader.lower_bound(base); it != queue.shader.end() && it->first < base + count; ++it) {
exact = (exact ^ it->first) * 0x100000001b3ull;
exact = (exact ^ it->second) * 0x100000001b3ull;
}
};
for (const auto base : {0x00cu, 0x08cu, 0x10cu}) {
const auto resources = queue.shader.find(base - 1);
const auto count = resources == queue.shader.end() ? 0u : ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u);
words(base, allUserWords ? 32u : std::min(count, 32u));
}
words(0x082u, 2);
words(0x102u, 2);
return exact;
}
std::uint64_t Driver::drawRegisterKey(const QueueState& queue, const ShaderRegistry& registry, std::uint64_t deviceSerial) {
static const bool allUserWords = std::getenv("APS5_DRAW_KEY_ALL_USER_WORDS") != nullptr;
static const bool verify = std::getenv("APS5_VERIFY_REGISTER_KEYS") != nullptr;
auto key = RegisterStateKey(queue, allUserWords);
if (verify && key != RegisterStateKey(queue, allUserWords, false)) throw std::runtime_error("incremental register fingerprint disagrees with full state");
std::uint64_t key = 0xcbf29ce484222325ull;
const auto mix = [&](std::uint64_t value) {
key ^= value;
key *= 0x100000001b3ull;
};
const auto userEnd = [&](std::uint32_t base) {
if (allUserWords) return base + 32u;
const auto resources = queue.shader.find(base - 1);
if (resources == queue.shader.end()) return base;
const auto count = ((resources->second >> 1u) & 0x1fu) | (((resources->second >> 27u) & 1u) << 5u);
return base + std::min(count, 32u);
};
const std::array<std::pair<std::uint32_t, std::uint32_t>, 3> users{{{0x00cu, userEnd(0x00cu)}, {0x08cu, userEnd(0x08cu)}, {0x10cu, userEnd(0x10cu)}}};
const auto unread = [&](std::uint32_t offset) {
return std::any_of(users.begin(), users.end(), [&](const auto& user) { return offset >= user.second && offset < user.first + 32u; });
};
mix(deviceSerial);
for (const auto& range : Graphics::DrawKeyRegisters) {
const auto& bank = range.bank == Graphics::RegisterBank::Context ? queue.context : range.bank == Graphics::RegisterBank::Shader ? queue.shader : queue.userConfig;
mix((static_cast<std::uint64_t>(range.bank) << 32u) | range.first);
const auto end = range.first + range.count;
const bool shader = range.bank == Graphics::RegisterBank::Shader;
for (auto it = bank.lower_bound(range.first); it != bank.end() && it->first < end; ++it) {
if (shader && unread(it->first)) continue;
mix(it->first);
mix(it->second);
}
}
for (const auto base : {0x008u, 0x088u, 0x0c8u, 0x108u, 0x148u}) {
const auto low = queue.shader.find(base);
const auto high = queue.shader.find(base + 1);
@ -91,7 +57,7 @@ std::uint64_t Driver::drawRegisterKey(const QueueState& queue, const ShaderRegis
}
bool Driver::sameVertexInfo(const ShaderRecompiler::ShaderVertexStageInfo& a, const ShaderRecompiler::ShaderVertexStageInfo& b) {
if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded || a.paClVsOutCntl != b.paClVsOutCntl) return false;
if (a.resourcesNum != b.resourcesNum || a.fetchAttribReg != b.fetchAttribReg || a.fetchBufferReg != b.fetchBufferReg || a.fetchEmbedded != b.fetchEmbedded) return false;
for (std::uint32_t i = 0; i < a.resourcesNum && i < a.resources.size(); ++i) {
if (a.resources[i].fields != b.resources[i].fields) return false;
const auto& x = a.resourcesDst[i];
@ -118,7 +84,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
const auto sameTess = [](const std::optional<ShaderRecompiler::TessellationConfiguration>& x, const std::optional<ShaderRecompiler::TessellationConfiguration>& y) {
if (x.has_value() != y.has_value()) return false;
if (!x) return true;
return x->inputControlPoints == y->inputControlPoints && x->outputControlPoints == y->outputControlPoints && x->domain == y->domain && x->partitioning == y->partitioning && x->outputTopology == y->outputTopology && x->patchesPerGroup == y->patchesPerGroup;
return x->inputControlPoints == y->inputControlPoints && x->outputControlPoints == y->outputControlPoints && x->domain == y->domain && x->partitioning == y->partitioning && x->outputTopology == y->outputTopology;
};
if (s.stages.path != t.stages.path || s.stages.registerValue != t.stages.registerValue || s.stages.vertexWaveSize != t.stages.vertexWaveSize || s.stages.fragmentWaveSize != t.stages.fragmentWaveSize || !sameMesh(s.stages.mesh, t.stages.mesh) || !sameTess(s.stages.tessellation, t.stages.tessellation)) return false;
if (!sameColor(s.color, t.color) || s.colors.size() != t.colors.size() || s.blends.size() != t.blends.size()) return false;
@ -132,14 +98,14 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
if (std::memcmp(&s.viewport, &t.viewport, sizeof(VkViewport)) != 0 || std::memcmp(&s.scissor, &t.scissor, sizeof(VkRect2D)) != 0) return false;
const auto& p = a.pixel;
const auto& q = b.pixel;
if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.orderedPixelShader != q.orderedPixelShader || p.stencilRefExportEnable != q.stencilRefExportEnable || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false;
if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.orderedPixelShader != q.orderedPixelShader || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false;
if (a.roles != b.roles || a.programs.size() != b.programs.size()) return false;
for (std::size_t i = 0; i < a.programs.size(); ++i) {
const auto& x = a.programs[i];
const auto& y = b.programs[i];
if (x.binary.stage != y.binary.stage || x.binary.codeAddress != y.binary.codeAddress || x.userDataBase != y.userDataBase || x.firstUserSgpr != y.firstUserSgpr || x.userData != y.userData || x.snapshot != y.snapshot || x.codeOffset != y.codeOffset) return false;
}
if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.alphaToCoverage != q.alphaToCoverage || p.rasterPrimitive != q.rasterPrimitive) return false;
if (p.targetExportPacking != q.targetExportPacking || p.dualSourceBlend != q.dualSourceBlend || p.alphaToCoverage != q.alphaToCoverage) return false;
return true;
}

View File

@ -123,19 +123,13 @@ extern "C" void AgcDriverReportFailure_nid_postfix(std::exception_ptr error) {
AgcDriver::ReportFailure(error);
}
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) try {
extern "C" void AgcDriverResolveShaderAbi_nid_postfix(const Shader* shader, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
AgcDriver::DriverDetail::Driver::Get().ResolveShaderAbi(shader, context, primitive);
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}
extern "C" void AgcDriverResolveGraphicsAbi_nid_postfix(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) try {
extern "C" void AgcDriverResolveGraphicsAbi_nid_postfix(const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) {
AgcDriver::DriverDetail::Driver::Get().ResolveGraphicsAbi(vertex, pixel, primitiveType);
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) try {
extern "C" void AgcDriverResolveGraphicsStagesAbi_nid_postfix(std::span<const Shader* const> stages, std::span<const ShaderRegister> context, std::span<const ShaderRegister> primitive) {
AgcDriver::DriverDetail::Driver::Get().ResolveGraphicsStagesAbi(stages, context, primitive);
} catch (const ProcessShutdown&) {
LibcAwaitExit_nid_postfix();
}

View File

@ -3,68 +3,13 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTrace.hpp"
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <system_error>
#include <thread>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <pthread.h>
#endif
namespace AgcDriver::DriverDetail {
namespace {
constexpr std::uintptr_t InlineImportStackBytes = 256 * 1024;
std::atomic<std::uint64_t> gpuMapImportThreads{0};
bool StackHoldsImport() {
#ifdef _WIN32
ULONG_PTR low = 0;
ULONG_PTR high = 0;
GetCurrentThreadStackLimits(&low, &high);
#else
struct Bounds {
std::uintptr_t low = 0;
std::uintptr_t high = 0;
Bounds() {
pthread_attr_t attributes;
if (pthread_getattr_np(pthread_self(), &attributes) != 0) return;
void* base = nullptr;
std::size_t size = 0;
if (pthread_attr_getstack(&attributes, &base, &size) == 0 && base != nullptr) {
low = reinterpret_cast<std::uintptr_t>(base);
high = low + size;
}
pthread_attr_destroy(&attributes);
}
};
thread_local const Bounds bounds;
const auto low = bounds.low;
const auto high = bounds.high;
#endif
const volatile unsigned char marker = 0;
const auto frame = reinterpret_cast<std::uintptr_t>(&marker);
return frame >= low && frame < high && frame - low >= InlineImportStackBytes;
}
}
std::uint64_t Driver::GpuMapImportThreads() {
return gpuMapImportThreads.load(std::memory_order_relaxed);
}
Driver& Driver::Get() {
static Driver driver;
return driver;
@ -105,13 +50,11 @@ void Driver::stop() {
device.Reset();
replacedDevices.clear();
Graphics::ShutdownGuestBufferWorkers();
PerformanceTrace::StopOutput();
StopProfileOutput_nid_no_patch();
stopped = true;
}
Driver::Driver() {
static_cast<void>(GuestMemory::TrackerGeneration());
ShaderMemory::SetWaitedMsProvider(&Graphics::Recorder::ThreadWaitedMs);
try {
LibcRegisterShutdown_nid_postfix([] { Driver::Get().Shutdown(); });
@ -120,29 +63,14 @@ Driver::Driver() {
throw;
}
GuestAllocations::GuestAllocationsSetGpuMapObserver_nid_postfix([](const GuestAllocations::Mapped& ranges, std::uint64_t generation) {
const auto import = [&] {
try {
const auto current = Driver::Get().device.Load();
if (current == nullptr || current->ImportGuestMemory(ranges, generation, false)) return;
std::lock_guard lock(GuestMemory::GpuMutex());
if (Driver::Get().device.Load() == current) current->ImportGuestMemory(ranges, generation, true);
} catch (const std::exception& error) {
std::fprintf(stderr, "[gpu] import of GPU memory at its mapping failed: %s\n", error.what());
}
};
if (GuestMemory::GpuMutex().HeldByThisThread() || StackHoldsImport()) {
import();
return;
}
std::thread host;
try {
host = std::thread(import);
} catch (const std::system_error& error) {
std::fprintf(stderr, "[gpu] import of GPU memory at its mapping failed: no host thread to run it on: %s\n", error.what());
return;
const auto current = Driver::Get().device.Load();
if (current == nullptr || current->ImportGuestMemory(ranges, generation, false)) return;
std::lock_guard lock(GuestMemory::GpuMutex());
if (Driver::Get().device.Load() == current) current->ImportGuestMemory(ranges, generation, true);
} catch (const std::exception& error) {
std::fprintf(stderr, "[gpu] import of GPU memory at its mapping failed: %s\n", error.what());
}
gpuMapImportThreads.fetch_add(1, std::memory_order_relaxed);
host.join();
});
}

View File

@ -6,7 +6,6 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include <algorithm>
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
#include <bit>
#include <cstdlib>
#include <optional>
@ -14,22 +13,13 @@
namespace AgcDriver::DriverDetail {
namespace {
bool matchesBufferPatternFillKernel(std::span<const std::uint32_t> code) {
static constexpr std::array<std::uint32_t, 12> kernel{0xd7460004u, 0x04010c08u, 0xf4280302u, 0xfa000000u, 0xbf8cc07fu, 0x7e00020cu, 0x7e02020du, 0x7e04020eu, 0x7e06020fu, 0xe01c2000u, 0x80000004u, 0xbf810000u};
return code.size() >= kernel.size() && std::equal(kernel.begin(), kernel.end(), code.begin());
}
}
bool Driver::matchesFillKernel(std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute) {
static const bool enabled = std::getenv("APS5_NO_FILL_HLE") == nullptr;
static const bool patternEnabled = std::getenv("APS5_NO_PATTERN_FILL_HLE") == nullptr;
if (!enabled || userData.size() < 8 || compute.numThreads[0] != 64 || compute.numThreads[1] != 1 || compute.numThreads[2] != 1) return false;
if (patternEnabled && MatchesPatternFillKernel(code, userData, compute)) return true;
static constexpr std::array<std::uint32_t, 9> fillKernel{0xd7460004u, 0x04010c08u, 0x7e000204u, 0x7e020205u, 0x7e040206u, 0x7e060207u, 0xe01c2000u, 0x80000004u, 0xbf810000u};
if (!matchesBufferPatternFillKernel(code) && (code.size() < fillKernel.size() || !std::equal(fillKernel.begin(), fillKernel.end(), code.begin()))) return false;
if (code.size() < fillKernel.size() || !std::equal(fillKernel.begin(), fillKernel.end(), code.begin())) return false;
const auto stride = (userData[1] >> 16u) & 0x3fffu;
const bool swizzled = ((userData[1] >> 31u) & 1u) != 0;
@ -58,9 +48,8 @@ std::optional<DwordPatternFill> MatchDwordPatternFill(std::span<const std::uint3
const bool swizzled = ((userData[1] >> 31u) & 1u) != 0;
const bool addTid = ((userData[3] >> 23u) & 1u) != 0;
const auto type = userData[3] >> 30u;
const auto format = (userData[3] >> 12u) & 0x7fu;
const auto period = userData[9];
if (type != 0 || stride != 4 || swizzled || addTid || format < 20u || format > 22u || (period != 1u && period != 2u && period != 4u) || (packet[4] & 0x20u) != 0 || packet[2] != 1 || packet[3] != 1) return std::nullopt;
if (type != 0 || stride != 4 || swizzled || addTid || (period != 1u && period != 2u && period != 4u) || (packet[4] & 0x20u) != 0 || packet[2] != 1 || packet[3] != 1) return std::nullopt;
const auto dwords = std::min<std::uint64_t>({static_cast<std::uint64_t>(packet[1]) * 64u, userData[8], userData[2]});
const auto base = userData[0] | (static_cast<std::uint64_t>(userData[1] & 0xffffu) << 32u);
if (dwords == 0 || dwords % 4u != 0 || base % 16u != 0) return std::nullopt;
@ -69,50 +58,6 @@ std::optional<DwordPatternFill> MatchDwordPatternFill(std::span<const std::uint3
return fill;
}
std::optional<DwordPatternFill> MatchZeroDwordFill(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute) {
static const bool enabled = std::getenv("APS5_NO_FILL_HLE") == nullptr;
if (!enabled || userData.size() != 4 || packet.size() < 5 || !compute.groupIdEnable[0] || compute.numThreads[0] != 64 || compute.numThreads[1] != 1 || compute.numThreads[2] != 1) return std::nullopt;
static constexpr std::array<std::uint32_t, 6> kernel{0xd7460000u, 0x04010c04u, 0x7e020280u, 0xe0102000u, 0x80000100u, 0xbf810000u};
if (code.size() < kernel.size() || !std::equal(kernel.begin(), kernel.end(), code.begin())) return std::nullopt;
const auto stride = (userData[1] >> 16u) & 0x3fffu;
const bool swizzled = ((userData[1] >> 31u) & 1u) != 0;
const bool addTid = ((userData[3] >> 23u) & 1u) != 0;
const auto outOfBounds = (userData[3] >> 28u) & 3u;
const auto format = (userData[3] >> 12u) & 0x7fu;
const auto type = userData[3] >> 30u;
if (type != 0 || stride != 4 || swizzled || addTid || outOfBounds != 0 || format < 20u || format > 22u || (packet[4] & 0x20u) != 0 || packet[2] != 1 || packet[3] != 1) return std::nullopt;
const auto dwords = std::min<std::uint64_t>(static_cast<std::uint64_t>(packet[1]) * 64u, userData[2]);
const auto base = userData[0] | (static_cast<std::uint64_t>(userData[1] & 0xffffu) << 32u);
if (dwords == 0 || dwords % 4u != 0 || base % 16u != 0) return std::nullopt;
return DwordPatternFill{base, static_cast<std::size_t>(dwords * 4u), {}};
}
std::optional<Graphics::MemoryValueFill> MatchMemoryValueBufferFill(std::span<const std::uint32_t> packet, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute) {
static const bool enabled = std::getenv("APS5_NO_FILL_HLE") == nullptr;
if (!enabled || userData.size() != 8 || packet.size() < 5 || (packet[4] & 0x20u) != 0 || !compute.groupIdEnable[0]) return std::nullopt;
const auto fill = Graphics::MatchMemoryValueFill(code, userData, compute.numThreads, {packet[1], packet[2], packet[3]});
if (!fill) return std::nullopt;
if (fill->address % 16u != 0 || fill->bytes % 16u != 0) return std::nullopt;
return fill;
}
bool IsByteFill(const std::array<std::uint32_t, 4>& pattern) {
return std::all_of(pattern.begin(), pattern.end(), [&](std::uint32_t word) { return word == (pattern[0] & 0xffu) * 0x01010101u; });
}
std::optional<Graphics::DccKeys> KeysFillMemo(const std::array<std::uint32_t, 4>& pattern, bool coversStorageKeys, bool memoryValueFill) {
if (!IsByteFill(pattern) || !(coversStorageKeys || memoryValueFill)) return std::nullopt;
switch (pattern[0] & 0xffu) {
case 0x00: return Graphics::DccKeys::Clear0000;
case 0x40: return Graphics::DccKeys::Clear0001;
case 0x80: return Graphics::DccKeys::Clear1110;
case 0xc0: return Graphics::DccKeys::Clear1111;
case 0x20: return Graphics::DccKeys::ClearRegister;
case 0xff: return Graphics::DccKeys::Uncompressed;
default: return std::nullopt;
}
}
bool Driver::fillClearEnabled() {
static const bool enabled = std::getenv("APS5_NO_FILL_CLEAR") == nullptr;
return enabled;
@ -178,11 +123,8 @@ void Driver::fillClearCount(const Graphics::StorageTexture::FillCoverage& covera
}
bool Driver::fillBuffer(QueueState& queue, std::uint32_t queueId, std::span<const std::uint32_t> packet, std::span<const std::uint32_t> code, const std::vector<std::uint32_t>& userData, const ShaderRecompiler::ShaderComputeStageInfo& compute, const std::shared_ptr<VulkanDevice>& localDevice) {
auto dwordFill = MatchDwordPatternFill(packet, code, userData, compute);
if (!dwordFill) dwordFill = MatchZeroDwordFill(packet, code, userData, compute);
std::optional<Graphics::MemoryValueFill> memoryFill;
if (!dwordFill) memoryFill = MatchMemoryValueBufferFill(packet, code, userData, compute);
if (!dwordFill && !memoryFill && !matchesFillKernel(code, userData, compute)) return false;
const auto dwordFill = MatchDwordPatternFill(packet, code, userData, compute);
if (!dwordFill && !matchesFillKernel(code, userData, compute)) return false;
std::uint64_t base;
std::size_t bytes;
std::array<std::uint32_t, 4> pattern;
@ -191,15 +133,6 @@ bool Driver::fillBuffer(QueueState& queue, std::uint32_t queueId, std::span<cons
base = dwordFill->base;
bytes = dwordFill->bytes;
pattern = dwordFill->pattern;
} else if (memoryFill) {
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Fill);
inputLock.lock();
recordLabelsForPacket(localDevice.get(), queueId);
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(memoryFill->valueAddress), 4)) return false;
GuestMemory::Read(memoryFill->valueAddress, std::as_writable_bytes(std::span(pattern).first(1)), 4);
std::fill(pattern.begin() + 1, pattern.end(), pattern[0]);
base = memoryFill->address;
bytes = memoryFill->bytes;
} else if (MatchesPatternFillKernel(code, userData, compute)) {
const auto range = DecodePatternFillRange(userData, packet);
if (!range) return false;
@ -231,16 +164,7 @@ bool Driver::fillBuffer(QueueState& queue, std::uint32_t queueId, std::span<cons
const auto records = std::min<std::uint64_t>(static_cast<std::uint64_t>(groups[0]) * 64u, numRecords);
base = userData[0] | (static_cast<std::uint64_t>(userData[1] & 0xffffu) << 32u);
bytes = static_cast<std::size_t>(records * 16u);
if (matchesBufferPatternFillKernel(code)) {
const auto source = userData[4] | (static_cast<std::uint64_t>(userData[5] & 0xffffu) << 32u);
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Fill);
inputLock.lock();
recordLabelsForPacket(localDevice.get(), queueId);
if (!GuestMemory::Accessible(reinterpret_cast<const void*>(source), sizeof(pattern))) return false;
GuestMemory::Read(source, std::as_writable_bytes(std::span(pattern)), 4);
} else {
pattern = {userData[4], userData[5], userData[6], userData[7]};
}
pattern = {userData[4], userData[5], userData[6], userData[7]};
}
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
static std::atomic<std::uint64_t> fills{0}, filledBytes{0}, cpuFills{0};
@ -277,7 +201,7 @@ bool Driver::fillBuffer(QueueState& queue, std::uint32_t queueId, std::span<cons
static const bool traceKeys = std::getenv("APS5_TRACE_DCC_KEYS") != nullptr;
if (traceKeys && coverage.cover == Graphics::StorageTexture::FillCover::Keys) std::fprintf(stderr, "[dcc-keys] title fills keys 0x%llx+0x%zx with %08x %08x %08x %08x (queue 0x%x)\n", static_cast<unsigned long long>(base), bytes, pattern[0], pattern[1], pattern[2], pattern[3], queueId);
const bool uniformKeysFill = coverage.cover == Graphics::StorageTexture::FillCover::Keys && IsByteFill(pattern);
const bool uniformKeysFill = coverage.cover == Graphics::StorageTexture::FillCover::Keys && std::all_of(pattern.begin(), pattern.end(), [&](std::uint32_t word) { return word == (pattern[0] & 0xffu) * 0x01010101u; });
if (uniformKeysFill) {
Graphics::StorageTexture::NoteKeysFill(base, bytes, static_cast<std::uint8_t>(pattern[0]));
Graphics::StorageTexture::ClearByKeysFill(base, bytes, static_cast<std::uint8_t>(pattern[0]));
@ -310,8 +234,18 @@ bool Driver::fillBuffer(QueueState& queue, std::uint32_t queueId, std::span<cons
phase(FillFlush);
const bool stored = cleared || localDevice->FillBuffer(base, bytes, pattern);
phase(FillDevice);
if (stored && !cleared) {
if (const auto filled = KeysFillMemo(pattern, uniformKeysFill, memoryFill.has_value())) Graphics::NoteKeysFillOnGpu(base, bytes, *filled);
if (uniformKeysFill && stored && !cleared) {
Graphics::DccKeys filled = Graphics::DccKeys::Mixed;
switch (pattern[0] & 0xffu) {
case 0x00: filled = Graphics::DccKeys::Clear0000; break;
case 0x40: filled = Graphics::DccKeys::Clear0001; break;
case 0x80: filled = Graphics::DccKeys::Clear1110; break;
case 0xc0: filled = Graphics::DccKeys::Clear1111; break;
case 0x20: filled = Graphics::DccKeys::ClearRegister; break;
case 0xff: filled = Graphics::DccKeys::Uncompressed; break;
default: break;
}
if (filled != Graphics::DccKeys::Mixed) Graphics::NoteKeysFillOnGpu(base, bytes, filled);
}
if (profile && stored && !cleared) {
const bool uniform = pattern[0] == pattern[1] && pattern[1] == pattern[2] && pattern[2] == pattern[3];
@ -331,10 +265,7 @@ bool Driver::fillBuffer(QueueState& queue, std::uint32_t queueId, std::span<cons
for (std::size_t done = 0; done < bytes; done += chunk) GuestMemory::Write(base + done, std::span<const std::byte>(block).first(std::min(chunk, bytes - done)), 16);
phase(FillCpu);
}
if (pattern[0] == pattern[1] && pattern[1] == pattern[2] && pattern[2] == pattern[3]) {
Graphics::NoteDepthMetadataFill(base, bytes, pattern[0]);
Graphics::NoteColorMetadataFill(base, bytes, pattern[0]);
}
if (pattern[0] == pattern[1] && pattern[1] == pattern[2] && pattern[2] == pattern[3]) Graphics::NoteDepthMetadataFill(base, bytes, pattern[0]);
if (profile) {
const auto now = std::chrono::steady_clock::now();
const auto cover = static_cast<std::size_t>(coverage.cover);

View File

@ -205,7 +205,6 @@ void Driver::execute(const Submission& submission) {
} else if (header == FlipPacketHeader) {
CheckFailure();
std::uint64_t batchesAtFlip = 0, unsignaledAtFlip = 0;
bool flipInterruptDeferred = false;
if (!drains) {
GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush);
@ -213,13 +212,8 @@ void Driver::execute(const Submission& submission) {
const auto localDevice = device.Load();
if (!recordLabelsForPacket(localDevice.get(), submission.queue) && localDevice != nullptr) localDevice->SubmitRecorded(submission.queue == 0);
if (localDevice != nullptr) {
localDevice->FlipBatches(batchesAtFlip, unsignaledAtFlip);
const auto queueId = submission.queue;
flipInterruptDeferred = localDevice->AfterRecordedWork([queueId] { AgcDriverDeliverEopInterrupt(queueId); }, false);
}
if (localDevice != nullptr) localDevice->FlipBatches(batchesAtFlip, unsignaledAtFlip);
}
if (!flipInterruptDeferred) AgcDriverDeliverEopInterrupt(submission.queue);
++flipsCounted;
if (batchesAtFlip != 0) flipSerial = batchesAtFlip;
flipBatchesUnsignaled += unsignaledAtFlip;

View File

@ -8,7 +8,6 @@
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
#include <cstdlib>
#include <cstring>
#include <limits>
@ -61,12 +60,7 @@ bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue
}
}
if (opcode == 0x50 && packet.size() >= 7) {
if (Pm4::DmaSource(packet) == 0 || Pm4::DmaSource(packet) == 3) Graphics::RequireNoMultisampleTarget(packet[2] | (static_cast<std::uint64_t>(packet[3]) << 32u), packet[6] & 0x3ffffffu, "a DMA copy");
if (Pm4::DmaSource(packet) == 2 && Pm4::DmaDestination(packet) != 1) {
const auto address = packet[4] | (static_cast<std::uint64_t>(packet[5]) << 32u);
Graphics::NoteDepthMetadataFill(address, packet[6] & 0x3ffffffu, packet[2]);
Graphics::NoteColorMetadataFill(address, packet[6] & 0x3ffffffu, packet[2]);
}
if (Pm4::DmaSource(packet) == 2 && Pm4::DmaDestination(packet) != 1) Graphics::NoteDepthMetadataFill(packet[4] | (static_cast<std::uint64_t>(packet[5]) << 32u), packet[6] & 0x3ffffffu, packet[2]);
}
if (!drainAll && !endOfPipeInterrupt && (opcode == 0x49 || opcode == 0x37)) {
if (const auto label = Pm4::DecodeLabelWrite(packet)) {

View File

@ -4,7 +4,6 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/WorkerAffinity.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libSceAgcDriver/Execution/include/TracyZone.hpp"
#include <cstdlib>
namespace AgcDriver::DriverDetail {
@ -38,7 +37,7 @@ void Driver::AttachWindow(const PresentationWindow& window) {
}
void Driver::Present(const PresentationWindow& window, const DisplayBuffer* buffer, bool opaque, void (*gpuReady)(void*), void* context) {
if (PerformanceTrace::Configured()) PerformanceTrace::Present(window.width, window.height, device.Load().get());
PerformanceContext timingContext(window.timing.get());
PerformanceTimer timing("Driver.Present");
static thread_local bool pinned = false;
@ -128,7 +127,6 @@ void Driver::Present(const PresentationWindow& window, const DisplayBuffer* buff
}
gpuReady(context);
timing.Mark("release_and_callback");
TracyZone::MarkFrame();
if (profile) reportPresents(waitedMs, inFlight);
CheckFailure();
} catch (const ProcessShutdown&) {
@ -154,12 +152,10 @@ void Driver::reportPresents(double waitedMs, std::size_t inFlight) {
const auto counts = VulkanDevice::PresentCounts();
const auto presents = counts.presents - previous.presents;
const double perPresent = presents != 0 ? 1.0 / static_cast<double>(presents) : 0.0;
const auto dropped = counts.acquireDrops - previous.acquireDrops;
const double perFrame = presents + dropped != 0 ? 1.0 / static_cast<double>(presents + dropped) : 0.0;
const auto flips = flipsCounted.load() - previousFlips;
const double perFlip = flips != 0 ? 1.0 / static_cast<double>(flips) : 0.0;
const auto serial = flipSerial.load();
AgcDriver::ProfilePrint_nid_no_patch( "[present] %llu presents over 10 s (%llu dropped: no free swapchain image; %zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per frame, presented or dropped: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked\n", static_cast<unsigned long long>(presents), static_cast<unsigned long long>(dropped), inFlight, inFlight != 0 ? "inflight_wait" : "render_fence_wait", percentile(0.5), percentile(0.9), waits.empty() ? 0.0 : waits.back(), waits.size(), (counts.gpuBusyMs - previous.gpuBusyMs) * perFrame, (counts.gpuGapMs - previous.gpuGapMs) * perFrame, static_cast<unsigned long long>(counts.gpuUnread - previous.gpuUnread), static_cast<double>(serial - windowSerial) * perFlip, static_cast<double>(flipBatchesUnsignaled.load() - previousUnsignaled) * perFlip, static_cast<double>(counts.batchesAheadOfBlit - previous.batchesAheadOfBlit) * perPresent, static_cast<double>(counts.batchesAfterFlip - previous.batchesAfterFlip) * perPresent, (counts.lastSubmitAfterFlipMs - previous.lastSubmitAfterFlipMs) * perPresent, (counts.blitSubmitAfterFlipMs - previous.blitSubmitAfterFlipMs) * perPresent, static_cast<unsigned long long>(counts.readsOverwritten - previous.readsOverwritten), static_cast<unsigned long long>(counts.readsChecked - previous.readsChecked));
AgcDriver::ProfilePrint_nid_no_patch( "[present] %llu presents over 10 s (%zu may trail on the GPU); %s p50 %.2f ms, p90 %.2f ms, max %.2f ms over %zu; per present: GPU busy %.1f ms, idle gaps %.1f ms (%llu batches without a completion record); per flip: %.1f batches recorded (%.1f unsignaled at the flip); per present: %.1f batches ahead of the blit, %.1f after it; after the flip packet: last submit %.1f ms, blit submit %.1f ms; in-place reads overwritten before execution: %llu of %llu checked\n", static_cast<unsigned long long>(presents), inFlight, inFlight != 0 ? "inflight_wait" : "render_fence_wait", percentile(0.5), percentile(0.9), waits.empty() ? 0.0 : waits.back(), waits.size(), (counts.gpuBusyMs - previous.gpuBusyMs) * perPresent, (counts.gpuGapMs - previous.gpuGapMs) * perPresent, static_cast<unsigned long long>(counts.gpuUnread - previous.gpuUnread), static_cast<double>(serial - windowSerial) * perFlip, static_cast<double>(flipBatchesUnsignaled.load() - previousUnsignaled) * perFlip, static_cast<double>(counts.batchesAheadOfBlit - previous.batchesAheadOfBlit) * perPresent, static_cast<double>(counts.batchesAfterFlip - previous.batchesAfterFlip) * perPresent, (counts.lastSubmitAfterFlipMs - previous.lastSubmitAfterFlipMs) * perPresent, (counts.blitSubmitAfterFlipMs - previous.blitSubmitAfterFlipMs) * perPresent, static_cast<unsigned long long>(counts.readsOverwritten - previous.readsOverwritten), static_cast<unsigned long long>(counts.readsChecked - previous.readsChecked));
previous = counts;
previousFlips = flipsCounted.load();
previousUnsignaled = flipBatchesUnsignaled.load();

View File

@ -6,18 +6,10 @@
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include <algorithm>
#include <chrono>
#include <cstdlib>
namespace AgcDriver::DriverDetail {
namespace {
constexpr auto IdleResultsQuietPeriod = std::chrono::milliseconds(50);
}
FrameTiming* Driver::frameTiming() {
if (!APS5_ENABLE_TIMING_LOG) return nullptr;
if (pendingFrameTiming == nullptr) pendingFrameTiming = std::make_shared<FrameTiming>(++frameSerial, true);
@ -74,9 +66,11 @@ void Driver::reapCompletionLabels() {
void Driver::run(std::uint32_t id) noexcept {
onWorkerThread() = true;
char role[32];
std::snprintf(role, sizeof role, "queue worker 0x%x", id);
PinWorkerThread(role);
{
char role[32];
std::snprintf(role, sizeof role, "queue worker 0x%x", id);
PinWorkerThread(role);
}
GuestMemory::TagGpuLockThread(id);
if (id == 0) StartWorkerSampler();
@ -84,27 +78,8 @@ void Driver::run(std::uint32_t id) noexcept {
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
auto& costs = submissionCosts(id);
try {
if (PerformanceTrace::Configured()) PerformanceTrace::BindThread(role);
for (;;) {
submission = Submission{};
struct Running {
std::atomic<std::uint32_t>& count;
std::atomic<std::uint64_t>& idleEpoch;
std::condition_variable& changed;
bool active = false;
Running(std::atomic<std::uint32_t>& count, std::atomic<std::uint64_t>& idleEpoch, std::condition_variable& changed)
: count(count), idleEpoch(idleEpoch), changed(changed) {}
void Start() {
count.fetch_add(1, std::memory_order_acq_rel);
active = true;
}
~Running() {
if (active && count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
idleEpoch.fetch_add(1, std::memory_order_release);
changed.notify_all();
}
}
} running{runningWorkers, idleEpoch, changed};
static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr;
{
PerformanceContext timingContext(id == 0 ? frameTiming() : FrameTiming::Async());
@ -116,13 +91,6 @@ void Driver::run(std::uint32_t id) noexcept {
workerQueued() = &worker.queued;
if (traceGpu && pending.empty()) std::fprintf(stderr, "[gpu] %.1f idle queue=0x%x\n", TraceMs(), id);
const auto ready = [&] { return failure || stopping || !pending.empty(); };
const auto allQueuesIdle = [&] {
return runningWorkers.load(std::memory_order_acquire) == 0
&& std::all_of(workers.begin(), workers.end(), [](const auto& item) { return item.second.pending.empty(); });
};
const auto idleResultsPending = [&] {
return id == 0 && Graphics::StorageTexture::AnyPending() && allQueuesIdle() && !completionsPending();
};
const auto poll = [&](const auto& done) {
#ifdef _WIN32
lock.unlock();
@ -136,35 +104,9 @@ void Driver::run(std::uint32_t id) noexcept {
while (!ready()) {
if (!completionsPending() || (id != 0 && Graphics::Recorder::PendingCompletionLabels() == 0)) {
const auto idleEpochAtCheck = idleEpoch.load(std::memory_order_acquire);
const bool storeIdleResults = idleResultsPending();
if (storeIdleResults) {
queue0Dormant.store(true, std::memory_order_relaxed);
const auto quietUntil = std::chrono::steady_clock::now() + IdleResultsQuietPeriod;
const auto idleEpochAtWait = idleEpoch.load(std::memory_order_acquire);
changed.wait_until(lock, quietUntil, [&] {
return ready() || idleEpoch.load(std::memory_order_acquire) != idleEpochAtWait;
});
queue0Dormant.store(false, std::memory_order_relaxed);
if (ready()) break;
if (idleEpoch.load(std::memory_order_acquire) != idleEpochAtWait) {
continue;
}
if (!idleResultsPending()) continue;
}
lock.unlock();
if (storeIdleResults) {
if (const auto localDevice = device.Load()) localDevice->StoreIdleResults();
}
lock.lock();
if (ready()) break;
if (id == 0 && allQueuesIdle() && !completionsPending()
&& (!storeIdleResults || idleEpoch.load(std::memory_order_acquire) != idleEpochAtCheck)) continue;
if (id == 0) queue0Dormant.store(true, std::memory_order_relaxed);
const auto idleEpochAtWait = idleEpoch.load(std::memory_order_acquire);
changed.wait(lock, [&] { return ready() || (id == 0 && idleEpoch.load(std::memory_order_acquire) != idleEpochAtWait); });
changed.wait(lock, ready);
if (id == 0) queue0Dormant.store(false, std::memory_order_relaxed);
if (id == 0 && !ready() && idleEpoch.load(std::memory_order_acquire) != idleEpochAtWait) continue;
break;
}
if (poll(ready)) break;
@ -191,10 +133,16 @@ void Driver::run(std::uint32_t id) noexcept {
if (APS5_ENABLE_TIMING_LOG) submission.orderedAt = std::chrono::steady_clock::now();
timing.Mark("dequeue_order");
worker.queued.fetch_sub(1, std::memory_order_acq_rel);
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());
}
execute(submission);
{
struct Running {
std::atomic<std::uint32_t>& count;
explicit Running(std::atomic<std::uint32_t>& count) : count(count) { count.fetch_add(1, std::memory_order_acq_rel); }
~Running() { count.fetch_sub(1, std::memory_order_acq_rel); }
} running{runningWorkers};
execute(submission);
}
if (traceGpu) std::fprintf(stderr, "[gpu] %.1f done serial=%llu queue=0x%x\n", TraceMs(), static_cast<unsigned long long>(submission.serial), id);
const auto completeStart = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
PerformanceContext completionContext(id == 0 ? frameTiming() : FrameTiming::Async());

View File

@ -332,12 +332,8 @@ bool Driver::orderReleased(std::uint32_t queue, std::uint64_t received) const {
}
void Driver::noteWaitBlocked(std::uint32_t queue, std::uint64_t awaited, bool blocked) {
if (blocked) {
if (runningWorkers.fetch_sub(1, std::memory_order_acq_rel) == 1) {
idleEpoch.fetch_add(1, std::memory_order_release);
changed.notify_all();
}
} else runningWorkers.fetch_add(1, std::memory_order_acq_rel);
if (blocked) runningWorkers.fetch_sub(1, std::memory_order_acq_rel);
else runningWorkers.fetch_add(1, std::memory_order_acq_rel);
if (queue == 0) queue0Awaited.store(blocked ? awaited : 0, std::memory_order_release);
if (queue == 0 && !blocked) queue0AwaitsTitle.store(false, std::memory_order_release);
if (blocked && orderHolders.load(std::memory_order_acquire) != 0) {

View File

@ -1,6 +1,5 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
#include "prx/libSceAgcDriver/Execution/include/Driver/Queues/WorkerAffinity.hpp"
#include "prx/libSceAgcDriver/Execution/include/TracyZone.hpp"
#include "prx/libc/include/CpuTopology.hpp"
#include <cstdlib>
@ -21,7 +20,6 @@ std::uint64_t WorkerAffinityMask() {
}
void PinWorkerThread(const char* role) {
TracyZone::NameThread(role);
const auto mask = WorkerAffinityMask();
if (mask == 0) return;
static std::once_flag summary;

View File

@ -19,12 +19,9 @@
#include <cstdlib>
#include <cstring>
#include <deque>
#include <exception>
#include <functional>
#include <list>
#include <atomic>
#include <pthread.h>
#include <sstream>
#include <stdexcept>
#include <thread>
#include "RdnaDecoder/RdnaInstructionDecoder.hpp"
@ -32,25 +29,13 @@
namespace AgcDriver::DriverDetail {
std::shared_ptr<const ShaderRecompiler::SourceHandle> PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation) {
std::exception_ptr failure;
std::string dump;
try {
auto handle = ShaderRecompiler::PrepareShader(request, preparation);
return handle;
return ShaderRecompiler::PrepareShader(request, preparation);
} catch (...) {
failure = std::current_exception();
try {
if (std::getenv("APS5_DUMP_SHADERS") != nullptr) dump = Driver::dumpRequest(request.shader.codeAddress, request);
if (std::getenv("APS5_DUMP_SHADERS") != nullptr) static_cast<void>(Driver::dumpRequest(request.shader.codeAddress, request));
} catch (...) {}
}
try {
std::rethrow_exception(failure);
} catch (const std::exception& error) {
std::ostringstream where;
where << "shader 0x" << std::hex << request.shader.codeAddress << " stage " << static_cast<unsigned>(request.shader.stage);
if (!dump.empty()) where << " (" << dump << ")";
where << ": ";
throw std::runtime_error(where.str() + error.what());
throw;
}
}
@ -141,17 +126,10 @@ bool RawGraphicsDrawn() {
namespace {
void AwaitRegisteredPreparation(PreparedShaders& prepared, std::unique_lock<std::mutex>& lock) {
if (prepared.pending) PrioritizeRegistrationPreparation(prepared);
prepared.settled.wait(lock, [&] { return !prepared.pending; });
if (prepared.failure != nullptr) std::rethrow_exception(prepared.failure);
}
bool CommittedAbi(const ShaderSnapshot& snapshot, std::span<const std::uint64_t> key) {
std::unique_lock lock(snapshot.prepared->mutex);
AwaitRegisteredPreparation(*snapshot.prepared, lock);
return std::ranges::any_of(snapshot.prepared->registeredAbis, [&](const auto& abi) { return std::ranges::equal(abi, key); });
}
}
bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset) {
@ -168,39 +146,6 @@ bool ProgramUsesWorkgroup(const ShaderSnapshot& snapshot, std::size_t codeOffset
return used;
}
std::uint32_t PositionExportLayout(const ShaderSnapshot* snapshot, std::size_t codeOffset, std::span<const std::uint32_t> code, std::uint32_t paClVsOutCntl) {
constexpr std::uint8_t Unknown = 0, Unused = 1, Used = 2;
const auto bits = paClVsOutCntl & Graphics::PositionExportLayoutBits;
if (bits == 0) return 0;
const bool cached = snapshot != nullptr && codeOffset == 0;
if (cached) {
const auto use = snapshot->positionVectorUse->load(std::memory_order_acquire);
if (use != Unknown) return use == Used ? bits : 0;
}
const auto decoded = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code);
const bool used = std::any_of(decoded.instructions.begin(), decoded.instructions.end(), [](const auto& instruction) {
return instruction.family == ShaderRecompiler::RdnaInstructionFamily::EXP && instruction.exportTarget >= 0x0du && instruction.exportTarget <= 0x0fu && instruction.exportEnableMask != 0;
});
if (cached) snapshot->positionVectorUse->store(used ? Used : Unused, std::memory_order_release);
return used ? bits : 0;
}
bool VertexWorkgroupRequired(const QueueState& queue, const ShaderRegistry& registry) {
Graphics::NoteRegisterRead(Graphics::RegisterBank::Context, 0x2d5);
const auto routing = queue.context.find(0x2d5);
if (routing == queue.context.end() || (routing->second & 0x02002024u) != 0x2000u) return false;
Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, 0xc8);
Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, 0xc9);
const auto low = queue.shader.find(0xc8);
const auto high = queue.shader.find(0xc9);
if (low == queue.shader.end() || high == queue.shader.end()) return false;
require((high->second & ~0xffu) == 0, "reserved graphics program address bits are set");
const auto address = (static_cast<std::uint64_t>(low->second) << 8u) | (static_cast<std::uint64_t>(high->second) << 40u);
const auto snapshot = ProgramSnapshot(registry, address, {2});
require((address - snapshot->codeAddress) % sizeof(std::uint32_t) == 0, "graphics entry point is not dword aligned");
return (snapshot->header.empty() || snapshot->type == 2) && ProgramUsesWorkgroup(*snapshot, (address - snapshot->codeAddress) / sizeof(std::uint32_t));
}
struct ShaderPreparationTransaction::State {
struct Change {
@ -212,13 +157,9 @@ struct ShaderPreparationTransaction::State {
};
static std::mutex writers;
static thread_local State* active;
std::unique_lock<std::mutex> lock{writers, std::defer_lock};
std::unique_lock<std::mutex> lock{writers};
std::map<PreparedShaders*, Change> changes;
bool failed = false;
void Hold() {
if (!lock.owns_lock()) lock.lock();
}
};
std::mutex ShaderPreparationTransaction::State::writers;
@ -239,7 +180,6 @@ ShaderPreparationTransaction::~ShaderPreparationTransaction() {
PreparedShaderState& ShaderPreparationTransaction::Edit(const ShaderSnapshot& snapshot) {
require(!committed, "shader preparation transaction is already committed");
root->Hold();
auto& changes = root->changes;
const auto found = changes.find(snapshot.prepared.get());
if (found != changes.end()) {
@ -253,7 +193,6 @@ PreparedShaderState& ShaderPreparationTransaction::Edit(const ShaderSnapshot& sn
const PreparedShaderState& ShaderPreparationTransaction::Read(const ShaderSnapshot& snapshot) const {
require(!committed, "shader preparation transaction is already committed");
root->Hold();
auto& changes = root->changes;
const auto found = changes.find(snapshot.prepared.get());
if (found != changes.end()) return found->second.prepared;
@ -262,11 +201,6 @@ const PreparedShaderState& ShaderPreparationTransaction::Read(const ShaderSnapsh
return changes.emplace(snapshot.prepared.get(), State::Change{snapshot.prepared, *snapshot.prepared, false, snapshot.prepared->entries.size(), snapshot.prepared->registeredAbis.size()}).first->second.prepared;
}
void ShaderPreparationTransaction::Hold() {
require(!committed, "shader preparation transaction is already committed");
root->Hold();
}
void ShaderPreparationTransaction::Commit() {
require(!committed && !root->failed, "shader preparation transaction was aborted");
if (state != nullptr) {
@ -296,34 +230,6 @@ void ShaderPreparationTransaction::Commit() {
committed = true;
}
bool FusedHullHeader(const ShaderSnapshot& snapshot, Shader used, const Shader& registered) {
if (used.type != 3u || registered.type != 7u) return false;
const auto* bound = used.sh_registers;
used.type = registered.type;
used.sh_registers = registered.sh_registers;
if (std::memcmp(&used, &registered, offsetof(Shader, num_sh_registers) + sizeof(Shader::num_sh_registers)) != 0) return false;
const auto address = reinterpret_cast<std::uintptr_t>(registered.sh_registers);
const std::size_t bytes = registered.num_sh_registers * sizeof(ShaderRegister);
if (bytes == 0 || bound == nullptr || address < snapshot.headerAddress || address - snapshot.headerAddress > snapshot.header.size() || snapshot.header.size() - (address - snapshot.headerAddress) < bytes) return false;
GuestMemory::CheckRange(bound, bytes, alignof(ShaderRegister));
for (std::size_t index = 0; index < registered.num_sh_registers; ++index) {
ShaderRegister expected;
std::memcpy(&expected, snapshot.header.data() + (address - snapshot.headerAddress) + index * sizeof(ShaderRegister), sizeof(ShaderRegister));
const auto reg = bound[index];
if (reg.offset != expected.offset) return false;
const bool fused = reg.offset == 0x100u || reg.offset == 0x10au || reg.offset == 0x10bu || reg.offset == 0x148u || reg.offset == 0x149u;
if (!fused && reg.value != expected.value) return false;
}
return true;
}
static Shader GuestShaderFields(const Shader* shader) {
GuestMemory::CheckRange(shader, sizeof(Shader), 1);
Shader fields;
std::memcpy(&fields, static_cast<const void*>(shader), sizeof(Shader));
return fields;
}
bool SameHeader(const ShaderSnapshot& snapshot, const Shader* shader) {
if (snapshot.headerAddress == reinterpret_cast<std::uintptr_t>(shader)) return true;
if (snapshot.header.size() < sizeof(Shader)) return false;
@ -333,11 +239,11 @@ bool SameHeader(const ShaderSnapshot& snapshot, const Shader* shader) {
std::memcpy(&used, static_cast<const void*>(shader), sizeof(Shader));
std::memcpy(&registered, snapshot.header.data(), sizeof(Shader));
used.user_data = registered.user_data;
return std::memcmp(&used, &registered, offsetof(Shader, num_sh_registers) + sizeof(Shader::num_sh_registers)) == 0 || FusedHullHeader(snapshot, used, registered);
return std::memcmp(&used, &registered, offsetof(Shader, num_sh_registers) + sizeof(Shader::num_sh_registers)) == 0;
}
std::shared_ptr<const ShaderSnapshot> RegisteredHeader(const ShaderRegistry& registry, const Shader* shader) {
const auto found = registry.find(reinterpret_cast<std::uintptr_t>(const_cast<const void*>(GuestShaderFields(shader).code)));
const auto found = registry.find(reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code)));
if (found == registry.end()) return {};
for (const auto& snapshot : found->second) {
if (snapshot->headerAddress == reinterpret_cast<std::uintptr_t>(shader)) return snapshot;
@ -367,7 +273,6 @@ std::shared_ptr<const ShaderSnapshot> RegisteredProgram(const ShaderRegistry& re
void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const std::shared_ptr<const ShaderSnapshot>& snapshot) {
ShaderPreparationTransaction transaction;
transaction.Hold();
auto published = snapshot;
if (registry != nullptr) {
const auto found = registry->find(snapshot->codeAddress);
@ -399,10 +304,6 @@ void ReportPreparedAtUse(const ShaderSnapshot& snapshot, const ShaderRecompiler:
APS5_LOG_ERR("The null pixel program has no wave%u artifact for this draw; preparing it at draw", request.context.waveSize);
return;
}
if (request.context.vertex && (request.context.vertex->paClVsOutCntl & (1u << 18u)) != 0) {
APS5_LOG_OUT_DEBUG("Shader 0x%llx is drawn into a layered target; preparing its layered variant at draw", static_cast<unsigned long long>(snapshot.codeAddress));
return;
}
if (!snapshot.header.empty()) {
if (!snapshot.prepared->deferred) APS5_LOG_ERR("Shader 0x%llx stage %u has no artifact prepared at registration for the state of this draw or dispatch; preparing it at use", static_cast<unsigned long long>(request.shader.codeAddress), static_cast<std::uint32_t>(request.shader.stage));
return;
@ -573,8 +474,9 @@ Shader ReadHeader(const ShaderSnapshot& snapshot) {
return header;
}
RegisteredShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot, RegisteredShaderState state = {{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}}) {
RegisteredShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot) {
const auto header = ReadHeader(snapshot);
RegisteredShaderState state{{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}};
for (const auto reg : ReadHeaderArray(snapshot, header.sh_registers, header.num_sh_registers)) {
state.shader.insert_or_assign(reg.offset, reg.value);
}
@ -603,7 +505,7 @@ void BuildRegisteredAbiKey(const QueueState& state, const VulkanDevice& device,
key.insert(key.end(), {device.Serial(), ShaderRecompiler::DebugProbeActive(), ShaderRecompiler::RayTracingStrict(), ShaderRecompiler::RayTracingMiss()});
for (const auto* registers : {&state.shader, &state.context, &state.userConfig}) {
key.push_back(registers->size());
registers->ForEach([&](std::uint32_t offset, std::uint32_t value) { key.push_back((static_cast<std::uint64_t>(offset) << 32u) | value); });
for (const auto& [offset, value] : *registers) key.push_back((static_cast<std::uint64_t>(offset) << 32u) | value);
}
}
@ -615,7 +517,6 @@ struct RegisteredPreparation {
std::array<ShaderRecompiler::MemoryRegion, 2> memory{};
std::optional<ShaderRecompiler::RecompileRequest> request;
std::vector<std::uint64_t> abiKey;
std::shared_ptr<VulkanDevice> prebuildDevice;
};
std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snapshot, const VulkanDevice& device, const QueueState& state, bool registration, bool* deferred = nullptr) {
@ -630,7 +531,7 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
case 1: stage = Stage::Fragment; programRegister = 0x008; resourceRegister = 0x00b; break;
case 2: stage = Stage::Vertex; programRegister = 0x0c8; resourceRegister = 0x08b; firstUser = 8; break;
case 4: stage = Stage::Mesh; programRegister = 0x0c8; resourceRegister = 0x08b; break;
case 5: return {};
case 5: stage = Stage::Local; programRegister = 0x148; resourceRegister = 0x10b; firstUser = 8; break;
case 6: stage = Stage::Mesh; programRegister = 0x088; resourceRegister = 0x08b; break;
case 7: stage = Stage::TessellationControl; programRegister = 0x108; resourceRegister = 0x10b; break;
default:
@ -685,9 +586,8 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
} else {
const auto routing = RegisterValue(state.context, 0x2d5);
wave = (routing & 0x00400000u) != 0 ? 32u : 64u;
if (registration && stage == Stage::Vertex && (routing & 0x02002024u) == 0x2000u && ProgramUsesWorkgroup(snapshot, codeOffset)) return nullptr;
if ((routing & 0x20u) != 0 || stage == Stage::Mesh || (routing & 4u) != 0 || ((routing >> 3u) & 3u) == 1u) {
if (registration) return {};
if ((routing & 0x20u) != 0 || stage == Stage::Mesh || (routing & 4u) != 0) {
if (registration) return nullptr;
auto stageState = state;
stageState.context[0x1b6] = 0;
const auto stages = Graphics::DecodeShaderStages(stageState);
@ -698,11 +598,7 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
if (firstUser == 0) userCount += 8;
}
plan->userData.resize(userCount);
if (stage != Stage::Compute && stage != Stage::Fragment && snapshot.type != 6) {
vertex = Graphics::DecodeVertexStageInfo(snapshot.header, snapshot.headerAddress, plan->userData, nullptr, true, 8u - firstUser);
const auto outputs = state.context.find(0x207u);
if (vertex && outputs != state.context.end()) vertex->paClVsOutCntl = PositionExportLayout(&snapshot, codeOffset, std::span(snapshot.code).subspan(codeOffset), outputs->second);
}
if (stage != Stage::Compute && stage != Stage::Fragment && snapshot.type != 6) vertex = Graphics::DecodeVertexStageInfo(snapshot.header, snapshot.headerAddress, plan->userData, nullptr, true);
plan->swappc = {vertex.has_value(), firstUser, userCount};
if (deferred != nullptr) {
if (const auto counter = ShaderRecompiler::UnresolvableSwappcTarget(plan->decoded, &plan->swappc)) {
@ -714,23 +610,10 @@ std::unique_ptr<RegisteredPreparation> PlanRegistered(const ShaderSnapshot& snap
plan->memory = {{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}, {snapshot.headerAddress, snapshot.header}}};
auto& request = plan->request.emplace(ShaderRecompiler::RecompileRequest{{stage, address, code, snapshot.headerAddress, snapshot.header}, {wave, firstUser, plan->userData, compute, pixel, vertex, plan->memory, RegisteredFloatMode(snapshot)}, stage == Stage::Compute ? device.ComputeTarget(wave) : device.Target(), {0, 0, 0, 128}, graphics});
if (graphics && graphics->mesh) request.layout.pushConstantSizeBytes = ShaderRecompiler::MeshDrawPushOffsetBytes;
if (graphics && graphics->tessellation) request.layout.pushConstantSizeBytes = ShaderRecompiler::TessellationLevelPushOffsetBytes;
if (pixel && device.GraphicsPipelineLibraries()) request.layout.pushConstantOffsetBytes = Graphics::FixedPushOffset(Stage::Fragment);
return plan;
}
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) {
if (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}}};
try {
static_cast<void>(device->PrebuildGraphicsLibraries(stages, shaders));
} catch (const std::exception& error) {
APS5_LOG_OUT("shader library prebuild skipped: %s", error.what());
}
});
}
std::vector<PreparedShaders::Entry> PrepareRegistered(RegisteredPreparation& plan) {
auto& request = *plan.request;
std::vector<PreparedShaders::Entry> entries;
@ -740,13 +623,6 @@ std::vector<PreparedShaders::Entry> PrepareRegistered(RegisteredPreparation& pla
entries.push_back({plan.codeOffset, PrepareShaderWithDiagnostics(request, &preparation)});
};
append();
if (plan.prebuildDevice != nullptr && request.target.registrationModules && request.shader.stage == ShaderRecompiler::ShaderStage::Fragment) {
try {
PrebuildStageLibrary(plan.prebuildDevice, {Graphics::ShaderPath::Vertex, 0u, 32u, request.context.waveSize, {}, {}}, request.shader.stage, ShaderRecompiler::RegistrationResult(request, *entries.front().handle), request.layout.pushConstantOffsetBytes);
} catch (const std::exception& error) {
APS5_LOG_OUT("shader library prebuild skipped: %s", error.what());
}
}
if (request.context.compute) {
request.context.compute->partialThreads = {1, 1, 1};
append();
@ -765,51 +641,15 @@ std::vector<PreparedShaders::Entry> PrepareRegistered(const ShaderSnapshot& snap
class RegistrationPreparePool {
public:
static RegistrationPreparePool& Instance() {
static auto* pool = [] {
auto* created = new RegistrationPreparePool(std::max(2u, std::thread::hardware_concurrency() / 2u));
PreparationSlot().store(created);
return created;
}();
static auto* pool = new RegistrationPreparePool();
return *pool;
}
static RegistrationPreparePool* Existing() {
return PreparationSlot().load();
}
static RegistrationPreparePool& Prebuild() {
static auto* pool = [] {
const char* text = std::getenv("APS5_PREBUILD_THREADS");
auto* created = new RegistrationPreparePool(text != nullptr ? std::max(1u, static_cast<unsigned>(std::strtoul(text, nullptr, 10))) : 1u);
PrebuildSlot().store(created);
return created;
}();
return *pool;
}
static RegistrationPreparePool* ExistingPrebuild() {
return PrebuildSlot().load();
}
private:
static std::atomic<RegistrationPreparePool*>& PreparationSlot() {
static std::atomic<RegistrationPreparePool*> slot{nullptr};
return slot;
}
static std::atomic<RegistrationPreparePool*>& PrebuildSlot() {
static std::atomic<RegistrationPreparePool*> slot{nullptr};
return slot;
}
public:
void Submit(std::function<void()> task, const PreparedShaders* prepared = nullptr) {
void Submit(std::function<void()> task) {
{
std::unique_lock lock(mutex);
if (threads != 0 && !closed) {
if (prepared != nullptr && prepared->demanded.load()) tasks.push_front({std::move(task), prepared});
else tasks.push_back({std::move(task), prepared});
tasks.push_back(std::move(task));
lock.unlock();
ready.notify_one();
return;
@ -818,14 +658,6 @@ public:
task();
}
void Prioritize(const PreparedShaders& prepared) {
std::lock_guard lock(mutex);
const auto found = std::ranges::find_if(tasks, [&](const auto& task) { return task.prepared == &prepared; });
if (found == tasks.end()) return;
if (std::getenv("APS5_TRACE_PREPARATION_WAIT") != nullptr) std::fprintf(stderr, "[shader-prepare] demanded queued task at position %zu of %zu\n", static_cast<std::size_t>(std::distance(tasks.begin(), found)), tasks.size());
std::rotate(tasks.begin(), found, std::next(found));
}
void Close() {
std::unique_lock lock(mutex);
closed = true;
@ -833,7 +665,8 @@ public:
}
private:
explicit RegistrationPreparePool(unsigned count) {
RegistrationPreparePool() {
const auto count = std::max(2u, std::thread::hardware_concurrency() / 2u);
pthread_attr_t attributes;
pthread_attr_init(&attributes);
pthread_attr_setstacksize(&attributes, 64u << 20u);
@ -849,9 +682,8 @@ private:
static void* Main(void* argument) {
auto& pool = *static_cast<RegistrationPreparePool*>(argument);
if (PerformanceTrace::Configured()) PerformanceTrace::BindThread("shader prepare", TraceThread::DefaultCapacity / 8);
for (;;) {
Task task;
std::function<void()> task;
{
std::unique_lock lock(pool.mutex);
pool.ready.wait(lock, [&] { return !pool.tasks.empty(); });
@ -859,8 +691,8 @@ private:
pool.tasks.pop_front();
++pool.running;
}
task.function();
task = {};
task();
task = nullptr;
{
std::lock_guard lock(pool.mutex);
--pool.running;
@ -870,15 +702,10 @@ private:
return nullptr;
}
struct Task {
std::function<void()> function;
const PreparedShaders* prepared = nullptr;
};
std::mutex mutex;
std::condition_variable ready;
std::condition_variable idle;
std::deque<Task> tasks;
std::deque<std::function<void()>> tasks;
unsigned threads = 0;
unsigned running = 0;
bool closed = false;
@ -890,7 +717,6 @@ bool AsyncRegistrationPrepare() {
}
void PrepareRegisteredInBackground(std::shared_ptr<const ShaderSnapshot> snapshot, std::shared_ptr<RegisteredPreparation> plan) {
const auto* preparedKey = snapshot->prepared.get();
SubmitRegistrationPreparation([snapshot = std::move(snapshot), plan = std::move(plan)] {
PerformanceContext timingContext(FrameTiming::Preparation());
std::vector<PreparedShaders::Entry> entries;
@ -912,27 +738,17 @@ void PrepareRegisteredInBackground(std::shared_ptr<const ShaderSnapshot> snapsho
prepared.pending = false;
}
prepared.settled.notify_all();
}, preparedKey);
});
}
}
void SubmitRegistrationPreparation(std::function<void()> task, const PreparedShaders* prepared) {
RegistrationPreparePool::Instance().Submit(std::move(task), prepared);
}
void PrioritizeRegistrationPreparation(PreparedShaders& prepared) {
prepared.demanded.store(true);
if (auto* pool = RegistrationPreparePool::Existing()) pool->Prioritize(prepared);
}
void SubmitLibraryPrebuild(std::function<void()> task) {
RegistrationPreparePool::Prebuild().Submit(std::move(task));
void SubmitRegistrationPreparation(std::function<void()> task) {
RegistrationPreparePool::Instance().Submit(std::move(task));
}
void CloseRegistrationPreparation() {
RegistrationPreparePool::Instance().Close();
if (auto* prebuild = RegistrationPreparePool::ExistingPrebuild()) prebuild->Close();
}
std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target) {
@ -940,10 +756,8 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
require(decoded.programs.size() == decoded.roles.size(), "graphics ABI program roles are incomplete");
for (const auto& program : decoded.programs) require(program.snapshot != nullptr, "graphics ABI has no registered shader snapshot");
std::vector<ShaderRecompiler::ProgramRole> expected;
if (decoded.state.stages.path == Graphics::ShaderPath::Tessellation) {
if (!decoded.roles.empty() && decoded.roles.front() == ShaderRecompiler::ProgramRole::Local) expected.push_back(ShaderRecompiler::ProgramRole::Local);
expected.insert(expected.end(), {ShaderRecompiler::ProgramRole::Hull, ShaderRecompiler::ProgramRole::Domain});
} else {
if (decoded.state.stages.path == Graphics::ShaderPath::Tessellation) expected = {ShaderRecompiler::ProgramRole::Local, ShaderRecompiler::ProgramRole::Hull, ShaderRecompiler::ProgramRole::Domain};
else {
expected.push_back(ShaderRecompiler::ProgramRole::Main);
if (decoded.state.stages.path == Graphics::ShaderPath::Geometry && !decoded.programs.empty() && decoded.programs.front().snapshot->type == 4) expected.push_back(ShaderRecompiler::ProgramRole::GeometryBack);
}
@ -962,15 +776,14 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
std::vector<PreparedGraphicsStage> prepared;
std::uint32_t pushOffset = 0;
const bool fixedSlots = target.fixedPushSlots;
const auto capacity = decoded.state.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : decoded.state.stages.tessellation ? ShaderRecompiler::TessellationLevelPushOffsetBytes : Graphics::PipelinePushSlotBytes;
const auto capacity = decoded.state.stages.mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : Graphics::PipelinePushSlotBytes;
for (std::size_t index = 0; index < decoded.programs.size(); ++index) {
if (decoded.roles[index] == ShaderRecompiler::ProgramRole::GeometryBack) continue;
const auto& program = decoded.programs[index];
const bool fragment = program.binary.stage == ShaderRecompiler::ShaderStage::Fragment;
const auto wave = fragment ? decoded.state.stages.fragmentWaveSize : decoded.state.stages.vertexWaveSize;
std::optional<ShaderRecompiler::ShaderVertexStageInfo> vertex;
if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true, 8u - program.firstUserSgpr);
if (vertex) vertex->paClVsOutCntl = PositionExportLayout(program.snapshot.get(), program.codeOffset, program.binary.code, decoded.state.paClVsOutCntl) | decoded.state.layerExports;
if (!fragment) vertex = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, nullptr, true);
pushOffset = Graphics::StagePushOffset(pushOffset, program.binary.stage, fixedSlots);
const auto offset = pushOffset;
const auto room = (fixedSlots && fragment ? Graphics::PipelinePushSlotBytes : capacity) - pushOffset % Graphics::PipelinePushSlotBytes;
@ -990,15 +803,7 @@ std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decod
}
const auto bytes = handle->artifact->bindings.pushConstantSizeBytes;
require(bytes <= room, "prepared graphics stages exceed the push constant block");
std::shared_ptr<const ShaderRecompiler::RecompileResult> registration;
if (target.registrationModules) {
try {
registration = ShaderRecompiler::RegistrationResult(request, *handle);
} catch (const std::exception& error) {
APS5_LOG_OUT("shader library prebuild skipped: %s", error.what());
}
}
prepared.push_back({program.snapshot, {program.codeOffset, handle}, program.binary.stage, offset, std::move(registration)});
prepared.push_back({program.snapshot, {program.codeOffset, handle}});
pushOffset += bytes;
}
transaction.Commit();
@ -1018,23 +823,18 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
std::lock_guard lock(mutex);
registry = shaders;
for (const auto* shader : stages) {
const auto fields = GuestShaderFields(shader);
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(fields.code));
GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader));
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
require(registry != nullptr && registry->contains(address), "graphics ABI refers to an unregistered shader");
const auto selected = RegisteredHeader(*registry, shader);
require(selected != nullptr, "graphics ABI refers to a replaced shader header");
if (owner == nullptr) owner = selected;
const auto& snapshot = *selected;
require(snapshot.registeredState != nullptr, "registered shader state is missing");
const auto registered = DecodeRegisteredState(snapshot, {});
registered.shader.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.shader.insert_or_assign(offset, value); });
registered.context.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.context.insert_or_assign(offset, value); });
registered.userConfig.ForEach([&](std::uint32_t offset, std::uint32_t value) { state.userConfig.insert_or_assign(offset, value); });
if (fields.type != snapshot.type) {
const std::size_t count = fields.num_sh_registers;
GuestMemory::CheckRange(fields.sh_registers, count * sizeof(ShaderRegister), alignof(ShaderRegister));
for (std::size_t index = 0; index < count; ++index) state.shader.insert_or_assign(fields.sh_registers[index].offset, fields.sh_registers[index].value);
}
const auto& registered = *snapshot.registeredState;
for (const auto& [offset, value] : registered.shader) state.shader.insert_or_assign(offset, value);
for (const auto& [offset, value] : registered.context) state.context.insert_or_assign(offset, value);
for (const auto& [offset, value] : registered.userConfig) state.userConfig.insert_or_assign(offset, value);
}
}
for (const auto reg : context) state.context.insert_or_assign(reg.offset, reg.value);
@ -1056,7 +856,7 @@ void Driver::ResolveGraphicsStagesAbi(std::span<const Shader* const> stages, std
return;
}
DrawDecode decoded{};
decoded.state.stages = Graphics::DecodeShaderStages(state, VertexWorkgroupRequired(state, *registry));
decoded.state.stages = Graphics::DecodeShaderStages(state);
DecodeGraphicsPrograms(decoded, state, *registry, true, false);
auto prepared = PrepareGraphicsStages(decoded, localDevice->Target());
for (auto& stage : prepared) {
@ -1068,11 +868,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());
}
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;
@ -1086,8 +881,8 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegist
PerformanceTimer timing("Shader.ResolveAbi");
ShaderPreparationTransaction transaction;
CheckFailure();
const auto fields = GuestShaderFields(shader);
if (fields.type != 0 && fields.type != 1) {
GuestMemory::CheckRange(shader, sizeof(Shader), alignof(Shader));
if (shader->type != 0 && shader->type != 1) {
const std::array<const Shader*, 1> stages{shader};
ResolveGraphicsStagesAbi(stages, context, primitive);
transaction.Commit();
@ -1096,7 +891,7 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegist
std::shared_ptr<const ShaderSnapshot> snapshot;
{
std::lock_guard lock(mutex);
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(fields.code));
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
require(shaders != nullptr && shaders->contains(address), "static ABI refers to an unregistered shader");
snapshot = RegisteredHeader(*shaders, shader);
require(snapshot != nullptr, "static ABI refers to a replaced shader header");
@ -1113,20 +908,12 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::span<const ShaderRegist
struct RegisteredAbiKeyStorage {};
auto& key = HostThreadLocal<std::vector<std::uint64_t>, RegisteredAbiKeyStorage>();
BuildRegisteredAbiKey(state, *localDevice, key);
const auto prepare = [&] {
const auto plan = PlanRegistered(*snapshot, *localDevice, state, false);
if (plan == nullptr) return std::vector<PreparedShaders::Entry>{};
plan->prebuildDevice = localDevice;
return PrepareRegistered(*plan);
};
const bool committedAbi = CommittedAbi(*snapshot, key);
auto entries = committedAbi ? std::vector<PreparedShaders::Entry>{} : prepare();
const auto& registeredAbis = transaction.Read(*snapshot).registeredAbis;
if (std::ranges::find(registeredAbis, key) != registeredAbis.end()) {
transaction.Commit();
return;
}
if (committedAbi) entries = prepare();
auto entries = PrepareRegistered(*snapshot, *localDevice, state, false);
auto& prepared = transaction.Edit(*snapshot);
for (auto& entry : entries) {
const auto duplicate = std::ranges::any_of(prepared.entries, [&](const auto& existing) { return existing.handle->artifact == entry.handle->artifact; });
@ -1163,8 +950,10 @@ void Driver::ResolveGraphicsAbi(const Shader* vertex, const Shader* pixel, std::
std::shared_ptr<const ShaderSnapshot> fragment;
{
std::lock_guard lock(mutex);
GuestMemory::CheckRange(vertex, sizeof(Shader), alignof(Shader));
GuestMemory::CheckRange(pixel, sizeof(Shader), alignof(Shader));
const auto lookup = [&](const Shader* shader) {
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(GuestShaderFields(shader).code));
const auto address = reinterpret_cast<std::uintptr_t>(const_cast<const void*>(shader->code));
require(shaders != nullptr && shaders->contains(address), "rectangle ABI refers to an unregistered shader");
const auto snapshot = RegisteredHeader(*shaders, shader);
require(snapshot != nullptr, "rectangle ABI refers to a replaced shader header");
@ -1278,7 +1067,6 @@ void Driver::RegisterShader(const Shader* shader) {
snapshot.header.resize(fields.header_size);
std::memcpy(snapshot.header.data(), static_cast<const void*>(shader), fields.header_size);
transaction.Hold();
{
std::lock_guard lock(mutex);
rethrowFailure();
@ -1311,7 +1099,6 @@ void Driver::RegisterShader(const Shader* shader) {
std::shared_ptr<RegisteredPreparation> preparation;
if (AsyncRegistrationPrepare()) {
preparation = PlanRegistered(snapshot, *localDevice, registered, true, &snapshot.prepared->deferred);
if (preparation != nullptr) preparation->prebuildDevice = localDevice;
if (preparation != nullptr && (snapshot.type == 0 || snapshot.type == 1)) BuildRegisteredAbiKey(registered, *localDevice, preparation->abiKey);
snapshot.prepared->pending = preparation != nullptr;
} else {

View File

@ -5,7 +5,6 @@
#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include <cstdlib>
#include <mutex>
#include <set>
@ -55,8 +54,8 @@ bool Driver::traceLateLabels() {
}
std::vector<Driver::PendingWait>& Driver::pendingWaits() {
struct WaitsTag {};
return HostThreadLocal<std::vector<PendingWait>, WaitsTag>();
static thread_local std::vector<PendingWait> waits;
return waits;
}
void Driver::notePendingWait(std::span<const std::uint32_t> packet, std::uint32_t queue, std::uint64_t received) {

Some files were not shown because too many files have changed in this diff Show More