Skip to content

feat(vk): observe completed GPU write-back differences - #2131

Merged
hckrman101 merged 4 commits into
NotRexed:mainfrom
Arkex-ai:feat/wb-diff-observations
Nov 11, 2026
Merged

hckrman101 merged 4 commits into
NotRexed:mainfrom
Arkex-ai:feat/wb-diff-observations

Conversation

@Arkex-ai

@Arkex-ai Arkex-ai commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

There is no measurement of how much copied GPU data actually changed, so it is hard to judge whether skipping copies would be safe. This adds optional observations of completed write-backs. It changes no copy decisions and claims no performance improvement.

Coordinated with blessedplummer in #2099; their dirty-origin, urgent/bulk and sparse-mirror work remains separate. Dev-work claim.

Technical changes

  • Export one actual-difference flag per chunk from the existing shader with RX_VK_WB_OBSERVE=1; regenerate SPIR-V with the pinned glslang 16.6.0.
  • Tag mapped results with their submission and read them only after completion, before reuse. Invalid metadata, flush/invalidation or optional allocation failure leaves observations unknown and preserves ordinary write-back. No extra queue wait, submission or thread.
  • Report bounded aggregate diagnostics. Changed-chunk volume is not exact changed bytes, and a clean observation cannot justify skipping a later write.

Validation

Current tested head da2747e1, rebased onto main 940eb9ef. The tested-configurations conflict preserves the new main evidence rows and this PR’s earlier benchmark. Production/test blobs are unchanged from fb8d0c55; focused checks pass again on both systems. Review follow-up uses the shared env_on rule, so on and true enable observations and off spellings disable them. Production lifetime checks pass for ten unset/off/on forms; all five causal mutations are rejected. Shared C/C++/Python switch and menu-switch checks pass on Windows and Linux. The worklist entry is in Fixed, waiting for a release. Shader source and generated SPIR-V are identical to the GPU-tested 7ee371f8; no new game/runtime or overhead result is claimed. Fresh hosted checks are separate exact-head evidence.

Earlier validation evidence and limits

At 7ee371f8 on 6c8c1bfb, four CPU/page checks, lifetime/four mutations and all five actual RTX shader scenarios passed; the main shader passed off parity and rejected missing feedback. Both tested-configuration rows were retained during rebase.

Head 6d53c2a7 on main 4a53c897 passed the same focused Windows CPU/lifetime and actual RTX shader checks before this documentation-only rebase.

Earlier tested head bb9944f2, rebased onto main 6df80a8d. Both production/test patches are unchanged by range-diff; the documentation conflict preserves both evidence rows.

  • Current-head Windows production-helper checks pass for CPU writes, chunk splitting, stale shadows and page tracking. Observation lifetime passes with the option off/on; all four causal mutations are rejected (premature completion, repeated consumption, missing flags and ignored invalidation error).
  • Actual current-head shader execution passes on RTX 5070 Ti: off mode, mixed chunks, reused clean results, zero count and the 65535-wide 2D grid boundary, checking guest/shadow parity and output guards. The main shader passes off-mode parity then fails exactly the missing-feedback assertion.
  • Fresh hosted checks run on this head. No current-head Linux execution, complete game build, gameplay or overhead comparison is claimed.

Earlier authored shader execution passed on Linux llvmpipe; lifetime tests and four mutations passed on both platforms. At earlier head 2fe9e4c0, full Linux was 352 / 0 failed / 23 skipped, Windows 341 / 4 failed / 30 skipped: three failures reproduced on the exact base, and one unchanged compiler fixture timed out then passed isolated. Fourteen release-script cases subsequently passed with jq; the aggregate's earlier skipped internal cases are not counted as executed.

Six earlier native processes used the fab8cb37 host with candidate edits, clean/off/on/on/off/clean, Kokiri balcony at 4K, FG off and cap 144. Mean source FPS: clean 75.25, off 75.52, on 74.24. On was 1.70% below off; two windows per mode do not establish equivalence or precise overhead. Reports found differences in about 15% of chunks, covering about 13% of compared volume, with zero unavailable chunks. All six exited normally and preserved the checkpoint. These are earlier-host results, not validation of the current rebase.

No AMD hardware, physical noncoherent-memory or campaign coverage is claimed. No game-derived files are included.

Current-head workflow Result
Game-free source regressions Queued
Linux AppImage Queued
PR title Queued
Windows builder Queued

@Arkex-ai

Copy link
Copy Markdown
Contributor Author

All current-head checks are green at d67505a4. The follow-up fixes the Windows fixture's CRT declarations, and the rebase includes main's shared release-fixture race fix (#2130). The GPU/host production patch is unchanged; full previous-head local results and the earlier-host six-run comparison remain labeled separately in the description.

@Arkex-ai
Arkex-ai force-pushed the feat/wb-diff-observations branch 4 times, most recently from bb9944f to 6d53c2a Compare November 11, 2026 19:41

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is careful work, and it stays inside what it claims: with RX_VK_WB_OBSERVE unset, the only changes on the hot path are a uniform branch in the shader, an 8-byte-larger push constant and an early return in wbd_table_get. I checked the parts that could hurt players:

  • Shader (gpu/vk/shaders/wb_diff.comp): observe comes from a push constant, and the early return depends only on the workgroup ID. Both barrier() calls are therefore in uniform control flow. Every invocation reaches the final barrier, because both loops run to their bounds. glslang emits them as OpControlBarrier Workgroup Workgroup AcquireRelease|WorkgroupMemory, so the shared atomicOr results are visible to invocation 0. On the vector path, the flag follows differs per vector, which matches what the vote writes.
  • Generated header: I regenerated wb_diff_comp_spv.h with glslang 16.6.0 using the command in the shader, and it is identical to the committed one.
  • Lifetime (gpu/vk/vk_memory.cpp): results are read only in wbd_table_get once t.value <= mem_completed, which is the same gate the ring already uses for reuse. They are read before the slot can grow, be freed or be overwritten. The compute→host barrier plus invalidate cover non-coherent memory. The 0xff sentinel makes a missing store invalidate the whole batch instead of reading it as clean. The growth-failure fallback keeps the old behaviour: retry at the ordinary size, or return nullptr and do whole copies.
  • Tests: on the PR's head, test_wb_*, test_env_on and test_menu_env_on pass. CI is green.

minor gpu/vk/vk_memory.cpp:3422: RX_VK_WB_OBSERVE is read as atoi(s) != 0. Since #2042, on/off switches go through env_on (this file already includes hookshot_env.h and uses it for RX_VK_LOG_ADDR, RX_VK_TABLE_NOW and others), so as written RX_VK_WB_OBSERVE=on or =true would quietly do nothing. static const bool on = env_on("RX_VK_WB_OBSERVE"); fixes it.

minor docs/worklist.md:36: the row is added to Open with you as owner, which fits "put your name in Owner in the same PR that starts the work". But this PR also finishes that work, and the worklist says a merged PR's row moves to Fixed, waiting for a release. Put it there with this PR's link, or nobody will clear it.

minor: the branch conflicts with main in docs/tested-configurations.md (rows added on top). The description could also open with a sentence a player could follow, per CONTRIBUTING (for example, "Nobody knows how much of each write-back actually changed, so we can't tell whether skipping some would be safe"). The current summary gets there, but in its second sentence and in renderer terms.

@Arkex-ai
Arkex-ai force-pushed the feat/wb-diff-observations branch 2 times, most recently from 7ee371f to fb8d0c5 Compare November 11, 2026 20:34
@Arkex-ai

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. Addressed in fb8d0c55 on main bebb0c64: RX_VK_WB_OBSERVE uses env_on, the worklist row is under Fixed, and the summary opens with the measurement problem in plain language. The tested-configuration conflict is resolved with both rows kept.

The lifetime fixture covers ten off/on forms and five rejected mutations, including restoring the numeric-only parser; shared switch and menu-switch checks pass on Windows and Linux. Shader source and SPIR-V are unchanged from the RTX-tested 7ee371f8. Fresh CI is queued; no new gameplay or overhead result is claimed.

Add opt-in actual-difference feedback tied to retired chunk-table submissions, with bounded diagnostics and ordinary write-back fallback. Exercise authored GPU buffers and production host lifetime/failure paths; document measured native controls and limits.

Assisted-by: Codex
Keep strict compiler diagnostics and lifecycle assertions while matching the Windows CRT declaration configuration used by the other production-function fixtures.

Assisted-by: Codex
@Arkex-ai
Arkex-ai force-pushed the feat/wb-diff-observations branch from fb8d0c5 to da2747e Compare November 11, 2026 20:48

@Joey-Magnus Joey-Magnus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three earlier points are fixed in the head: env_on("RX_VK_WB_OBSERVE") at vk_memory.cpp:3428, the worklist row moved to Fixed, and the description opens with why. With the switch off, the hot path only gains a uniform branch, 8 bytes of push constant and an early return. Good once the queued CI is green.

@hckrman101
hckrman101 merged commit 8b9e549 into NotRexed:main Nov 11, 2026
1 check passed
blessedplummer pushed a commit to blessedplummer/Hookshot-optimi that referenced this pull request Nov 11, 2026
NotRexed#2131's check (test_wb_observation_lifetime) measures the production
observation lifetime by cutting vk_memory.cpp from `struct WbdChunk {` to
`void memory_end_submission(` and compiling the span alone. The trials'
definitions sat inside that span, so the cut carried them and their
symbols went nowhere (texture_frame_no, present_time_total_ns, Pool,
dgpu_trial, dgpu_pool_at all undeclared in it). dgpu_trials and dgpu_pick
move to just before WbdChunk: after everything they call (mirror_free,
the skip list, the window counters), before everything that calls them,
and outside the span the check measures - the span is the write-back
machinery's own again, and the check passes untouched.

Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
blessedplummer added a commit to blessedplummer/Hookshot-optimi that referenced this pull request Nov 11, 2026
A dGPU has its own fast memory and a slower bus to system RAM; stock mirrors
every guest pool into VRAM and keeps both copies coherent with captures,
shadows and write-backs. At 4K that coherence is CPU-bound: the write-back
diff compares ~3.1 GB per 600 submits and mirror syncs move ~137k dirty
pages, for 35.6 fps where 1080p runs 60 (issue NotRexed#2099).

Under RX_VK_DGPU=1 a pool's first binding makes no mirror (MIRROR_REFUSED,
the state iGPU runs use today): reads take the lock-free TLS path and GPU
reads/writes land in the imported guest memory, coherent by construction -
no mirror sync, no write-back. Every 600-submit window the heaviest
guest-memory-read pool with >=1 MB of reads and not write-dominated runs a
measured trial (120 frames direct -> 12 settle, held until the mirror is
really on -> 120 mirrored, on the recordings' own timestamps); the mirror
stays only when the win is both >10% (RX_VK_DGPU_GAIN) and >0.25 ms
(RX_VK_DGPU_FLOOR) on present-to-present frame time, so the verdict sees
the mirror's CPU cost and its PCIe benefit alike. Trials whose windows did
not measure the same workload (zero binds, a >2x bind shift, a mirror that
never came) abort with no verdict and no skip line; a pool that aborts
three times sits out the session, and iGPUs run no trials. A lost verdict
persists to hookshot_mirror_skip.txt beside the game's own state files,
keyed to the device and driver (a foreign or header-less file is ignored
and re-trialled). RX_VK_DGPU_TRIALS=0 is pure direct. Trials need serial
replay (documented); under RX_VK_PARALLEL the policy runs trial-less.
Unset = stock exactly: the added counters and logging are gated on the
switch.

Refused pools read uniform buffers as submitted (NotRexed#1795's per-pool form:
the snapshot engages for mirror-less pools at the read, trial-won pools
keep in-place mirror binds), so the artifact class the issue documents on
mirror-less GPUs is covered under the policy too.

Measured on RX 9070 XT / RADV, 4K, Uncapped frame cap, warp-normalized
adjacent pairs (Hyrule Field): 70.2->78.0, 71.0->77.0, 59.6->76.6 fps
(+9.7% median), draw parity 705 vs 709, GPU 9.2 ms of a ~14 ms frame.
Mirror-sync dirty pages 136,872 -> 0; write-back diff 3.06-3.13 GB ->
330 MB or less per 600 submits.

Review round 2 (external): RX_VK_DGPU_TRIALS=0's predicate was inverted
and now matches the docs (=0 is pure direct); the window's read counter
moved off a shared map into the pool (dgpu_read, both direct paths);
Settle holds until the mirror is actually live; zero-bind baselines and
>2x bind drift between the direct and mirrored windows abort the trial
un-blacklisted; keep_line refuses signed and trailing-garbage lines.

Review round 3 (hckrman101, Joey-Magnus): refused pools read UBs as
submitted (NotRexed#1795 per-pool, live-smoked at 1.26M copies fault-free);
verdicts decide on present-to-present frame time instead of GPU-only
time (the mirror's CPU and upload-buffer cost is what the frame pays);
the policy runs trial-less under RX_VK_PARALLEL and its fast-path
counters are parallel-safe; no trials on iGPUs and a 3-abort cap; the
skip list carries a device+driver header and ignores foreign files; the
test driver uses {cxx}. Rebased onto main's env_on switch rule (NotRexed#2042) and the split-submission
fence work (NotRexed#2419); the trials' functions sit outside the write-back
observation's compile-span check (NotRexed#2131) and the GCN mirror-less default
(NotRexed#2343) composes unchanged (on GCN the switch runs trial-less direct plus
the UB snapshot).

Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
blessedplummer added a commit to blessedplummer/Hookshot-optimi that referenced this pull request Nov 11, 2026
A dGPU has its own fast memory and a slower bus to system RAM; stock mirrors
every guest pool into VRAM and keeps both copies coherent with captures,
shadows and write-backs. At 4K that coherence is CPU-bound: the write-back
diff compares ~3.1 GB per 600 submits and mirror syncs move ~137k dirty
pages, for 35.6 fps where 1080p runs 60 (issue NotRexed#2099).

Under RX_VK_DGPU=1 a pool's first binding makes no mirror (MIRROR_REFUSED,
the state iGPU runs use today): reads take the lock-free TLS path and GPU
reads/writes land in the imported guest memory, coherent by construction -
no mirror sync, no write-back. Every 600-submit window the heaviest
guest-memory-read pool with >=1 MB of reads and not write-dominated runs a
measured trial (120 frames direct -> 12 settle, held until the mirror is
really on -> 120 mirrored, on the recordings' own timestamps); the mirror
stays only when the win is both >10% (RX_VK_DGPU_GAIN) and >0.25 ms
(RX_VK_DGPU_FLOOR) on present-to-present frame time, so the verdict sees
the mirror's CPU cost and its PCIe benefit alike. Trials whose windows did
not measure the same workload (zero binds, a >2x bind shift, a mirror that
never came) abort with no verdict and no skip line; a pool that aborts
three times sits out the session, and iGPUs run no trials. A lost verdict
persists to hookshot_mirror_skip.txt beside the game's own state files,
keyed to the device and driver (a foreign or header-less file is ignored
and re-trialled). RX_VK_DGPU_TRIALS=0 is pure direct. Trials need serial
replay (documented); under RX_VK_PARALLEL the policy runs trial-less.
Unset = stock exactly: the added counters and logging are gated on the
switch.

Refused pools read uniform buffers as submitted (NotRexed#1795's per-pool form:
the snapshot engages for mirror-less pools at the read, trial-won pools
keep in-place mirror binds), so the artifact class the issue documents on
mirror-less GPUs is covered under the policy too.

Measured on RX 9070 XT / RADV, 4K, Uncapped frame cap, warp-normalized
adjacent pairs (Hyrule Field): 70.2->78.0, 71.0->77.0, 59.6->76.6 fps
(+9.7% median), draw parity 705 vs 709, GPU 9.2 ms of a ~14 ms frame.
Mirror-sync dirty pages 136,872 -> 0; write-back diff 3.06-3.13 GB ->
330 MB or less per 600 submits.

Review round 2 (external): RX_VK_DGPU_TRIALS=0's predicate was inverted
and now matches the docs (=0 is pure direct); the window's read counter
moved off a shared map into the pool (dgpu_read, both direct paths);
Settle holds until the mirror is actually live; zero-bind baselines and
>2x bind drift between the direct and mirrored windows abort the trial
un-blacklisted; keep_line refuses signed and trailing-garbage lines.

Review round 3 (hckrman101, Joey-Magnus): refused pools read UBs as
submitted (NotRexed#1795 per-pool, live-smoked at 1.26M copies fault-free);
verdicts decide on present-to-present frame time instead of GPU-only
time (the mirror's CPU and upload-buffer cost is what the frame pays);
the policy runs trial-less under RX_VK_PARALLEL and its fast-path
counters are parallel-safe; no trials on iGPUs and a 3-abort cap; the
skip list carries a device+driver header and ignores foreign files; the
test driver uses {cxx}. Follow-up fix: the drift check counted served
volume, so a working mirror's read collapse voided 40+ trials a run and
trials never converged; binds now count once at the serving path and the
drift rule compares the scene's pool set, with a three-no-verdict cap. Rebased onto main's env_on switch rule (NotRexed#2042) and the split-submission
fence work (NotRexed#2419); the trials' functions sit outside the write-back
observation's compile-span check (NotRexed#2131) and the GCN mirror-less default
(NotRexed#2343) composes unchanged (on GCN the switch runs trial-less direct plus
the UB snapshot).

Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
blessedplummer pushed a commit to blessedplummer/Hookshot-optimi that referenced this pull request Nov 11, 2026
NotRexed#2131's check (test_wb_observation_lifetime) measures the production
observation lifetime by cutting vk_memory.cpp from `struct WbdChunk {` to
`void memory_end_submission(` and compiling the span alone. The trials'
definitions sat inside that span, so the cut carried them and their
symbols went nowhere (texture_frame_no, present_time_total_ns, Pool,
dgpu_trial, dgpu_pool_at all undeclared in it). dgpu_trials and dgpu_pick
move to just before WbdChunk: after everything they call (mirror_free,
the skip list, the window counters), before everything that calls them,
and outside the span the check measures - the span is the write-back
machinery's own again, and the check passes untouched.

Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
blessedplummer added a commit to blessedplummer/Hookshot-optimi that referenced this pull request Nov 11, 2026
A dGPU has its own fast memory and a slower bus to system RAM; stock mirrors
every guest pool into VRAM and keeps both copies coherent with captures,
shadows and write-backs. At 4K that coherence is CPU-bound: the write-back
diff compares ~3.1 GB per 600 submits and mirror syncs move ~137k dirty
pages, for 35.6 fps where 1080p runs 60 (issue NotRexed#2099).

Under RX_VK_DGPU=1 a pool's first binding makes no mirror (MIRROR_REFUSED,
the state iGPU runs use today): reads take the lock-free TLS path and GPU
reads/writes land in the imported guest memory, coherent by construction -
no mirror sync, no write-back. Every 600-submit window the heaviest
guest-memory-read pool with >=1 MB of reads and not write-dominated runs a
measured trial (120 frames direct -> 12 settle, held until the mirror is
really on -> 120 mirrored, on the recordings' own timestamps); the mirror
stays only when the win is both >10% (RX_VK_DGPU_GAIN) and >0.25 ms
(RX_VK_DGPU_FLOOR) on present-to-present frame time, so the verdict sees
the mirror's CPU cost and its PCIe benefit alike. Trials whose windows did
not measure the same workload (zero binds, a >2x bind shift, a mirror that
never came) abort with no verdict and no skip line; a pool that aborts
three times sits out the session, and iGPUs run no trials. A lost verdict
persists to hookshot_mirror_skip.txt beside the game's own state files,
keyed to the device and driver (a foreign or header-less file is ignored
and re-trialled). RX_VK_DGPU_TRIALS=0 is pure direct. Trials need serial
replay (documented); under RX_VK_PARALLEL the policy runs trial-less.
Unset = stock exactly: the added counters and logging are gated on the
switch.

Refused pools read uniform buffers as submitted (NotRexed#1795's per-pool form:
the snapshot engages for mirror-less pools at the read, trial-won pools
keep in-place mirror binds), so the artifact class the issue documents on
mirror-less GPUs is covered under the policy too.

Measured on RX 9070 XT / RADV, 4K, Uncapped frame cap, warp-normalized
adjacent pairs (Hyrule Field): 70.2->78.0, 71.0->77.0, 59.6->76.6 fps
(+9.7% median), draw parity 705 vs 709, GPU 9.2 ms of a ~14 ms frame.
Mirror-sync dirty pages 136,872 -> 0; write-back diff 3.06-3.13 GB ->
330 MB or less per 600 submits.

Review round 2 (external): RX_VK_DGPU_TRIALS=0's predicate was inverted
and now matches the docs (=0 is pure direct); the window's read counter
moved off a shared map into the pool (dgpu_read, both direct paths);
Settle holds until the mirror is actually live; zero-bind baselines and
>2x bind drift between the direct and mirrored windows abort the trial
un-blacklisted; keep_line refuses signed and trailing-garbage lines.

Review round 3 (hckrman101, Joey-Magnus): refused pools read UBs as
submitted (NotRexed#1795 per-pool, live-smoked at 1.26M copies fault-free);
verdicts decide on present-to-present frame time instead of GPU-only
time (the mirror's CPU and upload-buffer cost is what the frame pays);
the policy runs trial-less under RX_VK_PARALLEL and its fast-path
counters are parallel-safe; no trials on iGPUs and a 3-abort cap; the
skip list carries a device+driver header and ignores foreign files; the
test driver uses {cxx}. Follow-up fix: the drift check counted served
volume, so a working mirror's read collapse voided 40+ trials a run and
trials never converged; binds now count once at the serving path and the
drift rule compares the scene's pool set, with a three-no-verdict cap. Rebased onto main's env_on switch rule (NotRexed#2042) and the split-submission
fence work (NotRexed#2419); the trials' functions sit outside the write-back
observation's compile-span check (NotRexed#2131) and the GCN mirror-less default
(NotRexed#2343) composes unchanged (on GCN the switch runs trial-less direct plus
the UB snapshot). After the presenter-thread refactor (NotRexed#2325) the
present-to-present tick lives in present_finish, which every made present
passes exactly once in either threading mode, with a headless twin so
offscreen runs stay measurable.

Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
blessedplummer added a commit to blessedplummer/Hookshot-optimi that referenced this pull request Nov 12, 2026
A dGPU has its own fast memory and a slower bus to system RAM; stock mirrors
every guest pool into VRAM and keeps both copies coherent with captures,
shadows and write-backs. At 4K that coherence is CPU-bound: the write-back
diff compares ~3.1 GB per 600 submits and mirror syncs move ~137k dirty
pages, for 35.6 fps where 1080p runs 60 (issue NotRexed#2099).

Under RX_VK_DGPU=1 a pool's first binding makes no mirror (MIRROR_REFUSED,
the state iGPU runs use today): reads take the lock-free TLS path and GPU
reads/writes land in the imported guest memory, coherent by construction -
no mirror sync, no write-back. Every 600-submit window the heaviest
guest-memory-read pool with >=1 MB of reads and not write-dominated runs a
measured trial (120 frames direct -> 12 settle, held until the mirror is
really on -> 120 mirrored, on the recordings' own timestamps); the mirror
stays only when the win is both >10% (RX_VK_DGPU_GAIN) and >0.25 ms
(RX_VK_DGPU_FLOOR) on present-to-present frame time, so the verdict sees
the mirror's CPU cost and its PCIe benefit alike. Trials whose windows did
not measure the same workload (zero binds, a >2x bind shift, a mirror that
never came) abort with no verdict and no skip line; a pool that aborts
three times sits out the session, and iGPUs run no trials. A lost verdict
persists to hookshot_mirror_skip.txt beside the game's own state files,
keyed to the device and driver (a foreign or header-less file is ignored
and re-trialled). RX_VK_DGPU_TRIALS=0 is pure direct. Trials need serial
replay (documented); under RX_VK_PARALLEL the policy runs trial-less.
Unset = stock exactly: the added counters and logging are gated on the
switch.

Refused pools read uniform buffers as submitted (NotRexed#1795's per-pool form:
the snapshot engages for mirror-less pools at the read, trial-won pools
keep in-place mirror binds), so the artifact class the issue documents on
mirror-less GPUs is covered under the policy too.

Measured on RX 9070 XT / RADV, 4K, Uncapped frame cap, warp-normalized
adjacent pairs (Hyrule Field): 70.2->78.0, 71.0->77.0, 59.6->76.6 fps
(+9.7% median), draw parity 705 vs 709, GPU 9.2 ms of a ~14 ms frame.
Mirror-sync dirty pages 136,872 -> 0; write-back diff 3.06-3.13 GB ->
330 MB or less per 600 submits.

Review round 2 (external): RX_VK_DGPU_TRIALS=0's predicate was inverted
and now matches the docs (=0 is pure direct); the window's read counter
moved off a shared map into the pool (dgpu_read, both direct paths);
Settle holds until the mirror is actually live; zero-bind baselines and
>2x bind drift between the direct and mirrored windows abort the trial
un-blacklisted; keep_line refuses signed and trailing-garbage lines.

Review round 3 (hckrman101, Joey-Magnus): refused pools read UBs as
submitted (NotRexed#1795 per-pool, live-smoked at 1.26M copies fault-free);
verdicts decide on present-to-present frame time instead of GPU-only
time (the mirror's CPU and upload-buffer cost is what the frame pays);
the policy runs trial-less under RX_VK_PARALLEL and its fast-path
counters are parallel-safe; no trials on iGPUs and a 3-abort cap; the
skip list carries a device+driver header and ignores foreign files; the
test driver uses {cxx}. Follow-up fix: the drift check counted served
volume, so a working mirror's read collapse voided 40+ trials a run and
trials never converged; binds now count once at the serving path and the
drift rule compares the scene's pool set, with a three-no-verdict cap.
The set rule still voided every trial in a streaming scene, so anchors
now match by guest range (a recycled pool is the same anchor), anchor at
4x the floor, void only when a substantial fraction of the scene moves,
and the candidate floor rose to 256 MB of reads a window (the verdict
floor is unreachable below it - a trial there is churn by construction).
Rebased onto main's env_on switch rule (NotRexed#2042) and the split-submission
fence work (NotRexed#2419); the trials' functions sit outside the write-back
observation's compile-span check (NotRexed#2131) and the GCN mirror-less default
(NotRexed#2343) composes unchanged (on GCN the switch runs trial-less direct plus
the UB snapshot). After the presenter-thread refactor (NotRexed#2325) the
present-to-present tick lives in present_finish, which every made present
passes exactly once in either threading mode, with a headless twin so
offscreen runs stay measurable.

Co-authored-by: GLM-5.3-Flash <glm-5.3-flash@opencode.noreply.github.com>
dazemc added a commit to dazemc/Hookshot-rs that referenced this pull request Nov 12, 2026
…ifetime and refused imports

Port upstream 8b9e549 (NotRexed#2131): RX_VK_WB_OBSERVE=1 exports one
actual-difference flag per write-back chunk from shaders/wb_diff.comp,
appended to the existing mapped table. Results are read only once the
submission that wrote them is done, before the slot is reused; missing,
out-of-bounds or invalid results reject the whole batch and keep the
ordinary write-back. The 600-submission report adds the completed
batches, chunks, changed chunks and changed-chunk bytes, with no extra
submission, wait or thread. The SPIR-V header is regenerated from the
shader with the pinned glslang 16.6.0 (exact match checked).

Port upstream f1d0a67 (NotRexed#2767): a pool whose write-back was worth a
256 KB shadow keeps Pool::shadow_wanted after VRAM pressure takes it,
and shadows_check_room makes wanted shadows again (up to 8 a check)
once the pressure rule says there is room. A pool whose write-backs stay
under the make size for good after a scene change no longer stays a
whole copy for the session.

Port upstream c2e9ed5 (NotRexed#2795): a retired pool whose mirror was refused
is given back like a MIRROR_NONE pool. It has no mirror either, and its
import otherwise stayed pinned for the rest of the session.

Game-free fixtures, run by builder/rust/tests/vk_memory_fixtures.rs and
directly with a C++ compiler: page ranges and part matching
(wb_pages.h), fence/timeline ordering (rec_value.h part map), shadow
lifetime (wb_shadow_policy.h), retired refused-mirror give-back, and
completed observation lifetime (wb_observe.h). tests/wb_diff_observations_gpu.cpp
executes the generated shader on a real Vulkan 1.3 device (NVIDIA RTX
4070 Ti here: off/mixed/reused/zero/65535-wide cases pass).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants