Skip to content

fix(agc): keep guest writes over a stale stencil plane - #14969

Open
JJenkx wants to merge 5 commits into
boykopovar:mainfrom
JJenkx:windows/agc-stale-stencil-keeps-guest-writes
Open

JJenkx wants to merge 5 commits into
boykopovar:mainfrom
JJenkx:windows/agc-stale-stencil-keeps-guest-writes

Conversation

@JJenkx

@JJenkx JJenkx commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

What

A depth surface's stencil plane reaches guest memory only when something needs it: it is marked stale when a draw writes stencil, and stored later. One later trigger is a GPU write that retires the surface (RetireDepthSurfaces, RetireCoveredDepthSurfaces); another is a read of the plane (FlushDepthPlanes).

That store wrote the stencil over the plane even when the guest had written the plane after the draw, typically because the title had freed the memory and reused it for something else.

  • The mirror upload inside StoreStencilPlane takes the guest bytes at store time, so the guest's later write is already in the "original" bytes. The storage write-back therefore cannot tell that write apart from stale stencil data.

  • On Windows the driver stops write-watching memory it uses as a surface, so the tracker does not see that write either.

Space Adventure Cobra: The Awakening (PPSA17337) crashes this way. Unity's graphics-thread allocator keeps stencil and depth targets in its heap. After the title frees one, the allocator's free-list links in the block are overwritten by the old stencil plane, and the next allocation dereferences them. In a diagnostic build that logged every guest write, the last write over the corrupted links before each crash was a write-back with reason stencil plane store.

How it works now:

  • The comparison point is the plane's last store, or the last stencil-writing draw or stencil plane fill. HTILE clears and rebinds that mark the plane stale again do not move it, so a guest write before them still counts. On an unwatched plane the guest bytes are copied at that point; a later stencil draw takes a new copy and so supersedes an intervening CPU write.

  • Before storing a stale stencil plane, both store paths check whether the guest has written the plane since that point. On watched memory this uses CollectWritesUncached and WrittenSince against the generation of the last sync or store; otherwise the bytes are compared with the copy.

  • If the guest wrote the plane, it keeps its bytes: the store is dropped and the stale state cleared. Otherwise the plane is stored as before.

#14475 also changes DepthSurface.cpp, but only for depth-plane clears superseded by a storage write; it touches neither stencil store path.

Tested

A title it unblocks, reported by @milescoviello (comment). STEINS;GATE RE:BOOT (PPSA36591), Ubuntu 26.04, RTX 4070 Ti (NVIDIA 615.71), new game with fast text skip, 900 s runs. On main the title's GPU Heap is corrupted ([SceLibc] A heap error is detected, then a SIGSEGV in its allocator) in 5 of 5 runs on e322066f82 (333 to 413 s) and on 9a4bcc3018 (329 s). With this PR merged onto 9a4bcc3018 (6096b477de), two runs reached the 900 s limit with no heap error. In a traced main run, the last two write-backs over the corrupted chunk were stencil plane stores of two depth targets at that address: the reused-memory case this PR handles.

Linux re-check of the current head by @hugodearaujo (comment). Arch Linux 7.2.11, RX 9070 XT (RADV GFX1201, Mesa 26.2.6), Release, main 1c8c0efc8c + #8932 with this PR's four commits up to c1fe457b32. Hollow Knight: Silksong's first-boot route (calibration and profile screens, 420 s, same save and inputs), which crashed 3 of 3 on 811f2d92e4, crashes 0 of 3 on c1fe457b32; all three runs reached the limit at 600 presents per 10 s. A shared-memfd fixture of theirs that runs the write-watched branch passes next to CheckGuestWriteBeforeRepeatedClears with the validation layer inserted and no VUID- line.

Windows follow-up for the re-marking report (2026-11-04). @hugodearaujo found on Linux, in Hollow Knight: Silksong (PPSA12544), that an HTILE clear or bind re-marks a plane every frame, and that the previous comparison point (the last stale mark) moved with it. In 1,478 of the stores they traced, the guest had written the plane after its last sync but before its last mark, and the store overwrote it; their first-boot route crashed 3 of 3 with the previous head. Head c1fe457b32 (one commit on 811f2d92e4) compares with the last store or stencil write instead. It also covers the unwatched path, where the re-mark re-took the snapshot after the guest write.

agc_driver_stencil_plane_memory gets a new case: a draw writes stencil 0x18 and a read stores it, the guest writes 0x77, two HTILE fills over the surface re-mark the plane, and a read follows. The plane must still hold 0x77. Windows 11 Enterprise 10.0.22621, AMD Radeon RX 6700 XT, AMD proprietary driver 26.10.1, WinLibs GCC 15.2.0 UCRT r7, Release, through the shared GPU queue with ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1 and the Khronos validation layer 1.4.363 inserted. Each arm is merged onto main b11c7bf84f:

Arm Result VUIDs
main b11c7bf84f (main's own fixture) 3 of 3 pass none
previous head 811f2d92e4 with the new fixture fails: "stale stencil planes re-marked by HTILE clears were stored over the guest's write after their last store" none
this head's code with the new fixture 3 of 3 pass, including the consumed-clear cases and the guest-write-then-newer-draw case none

No test was skipped. As before on Windows, the fixture's watched-only section reports "guest memory is not write-watched here" in both the main and the fix arm, so the Windows run covers the snapshot path; the new case does not depend on write-watching. The watched path follows the reviewer's variant (last sync), which ran 0 of 6 first-boot crashes and 0 of 8 gameplay crashes in their Linux runs; I have not reproduced the title route. The Windows run is a single FIFO job, f878bf6e29d3483887c1799b4d02579c.

Earlier Linux refresh (2026-11-03). Head 811f2d92e488fb8b335be520ef9bc74d87074d68 is three commits on main 04a9eb4fa2877eb6e40bf2c83adf972e40d89b24 with no merge commits. The rebase preserves main's unmapped-plane serial/invalidation handling from merged #15466 and adds the guest-write predicate to both stale stencil store paths. The second-draw snapshot-refresh commit is unchanged apart from its parent; the final commit documents partial-write behavior and comparison cost and removes the stray test blank line.

On the same Ryzen 9 7950X / Radeon RX 6650 XT (RADV NAVI23) Linux host, main and the change each pass all eight focused stencil/depth cases with ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, and the real VK_LAYER_KHRONOS_validation inserted: zero VUIDs, zero skipped tests, unchanged sources/libraries throughout each arm. Exact selection and commands are in the evidence archive. The fixture reports that these planes are not write-watched, so this is snapshot-path coverage; the watched branch remains unverified on this host.

The negative control uses current main's production code with this PR's updated StencilPlaneMemory.cpp fixture. See the linked result for the expected stale-store corruption assertion. It stops at the first failing assertion, so it does not independently prove that every later new case fails on main. No new title, Windows, or frame-time result is claimed in this refresh.

Current Linux commands, logs, patches, source pins and negative control (SHA-256 b03befd0d94a1cc1608146652b63eeb7e62641d2e60d576f94e6bedf6ce8f204).

Earlier Windows and title measurements follow with their original revisions.

Windows 11 Enterprise 10.0.22621, AMD Ryzen 9 5950X, AMD Radeon RX 6700 XT, AMD proprietary driver 26.10.1, WinLibs GCC 15.2.0 UCRT r7, Release. All runs went through the shared GPU queue with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1 and ANYPS5_REQUIRE_VULKAN=1.

agc_driver_stencil_plane_memory gets a new case: a draw writes stencil 0x3c, the guest then writes 0x77 over the stencil plane, and a GPU write over the depth plane retires the surface. The plane must still hold 0x77, and a later read must store nothing. The case runs whether or not the plane is write-watched.

  • main 93dbd0f82a with only this test added: fails with "retiring the surface stored its stale stencil 0x3c over the guest's later write of 0x77".

  • This change: passes 3/3 with no VUIDs. The test's existing guest-write-over-a-stored-plane section still runs only where the plane is write-watched, and is not exercised on Windows.

Full GPU suite at ac9b10b80a (this PR's commit), with the validation layer inserted 1084 times: 3234/3282 pass and 19 skip.

  • All 27 stencil and depth tests pass, including agc_driver_stencil_plane_memory, agc_driver_stencil_replay, agc_driver_depth_surface_from_storage and the agc_driver_depth_plane_* set.

  • The VUIDs are the same three main reports.

  • main 0cc3899f69, run the same way, fails the same 28 tests: 26 video_out_flip_*, which return VK_ERROR_SURFACE_LOST because this machine's display was off during both runs, plus agc_driver_sample_order_grid_reference and ngs2_pitchshift.

  • The only difference was agc_driver_pipeline_journal, which segfaulted once in a validation-layer worker thread on this change. It then passed 3/3 and 20/20 on this change and 3/3 and 20/20 on main, and this change does not touch that code.

Space Adventure Cobra: The Awakening (PPSA17337). Each boot started without a save and ran 120 s with scripted cross presses.

  • A diagnostic build of main that skipped every stale stencil store ran 5/5 without a crash. That build is not part of this PR.

  • Boots of unchanged main crashed on the allocator corruption at 20–90 s. That includes 2 of 5 on 0580e5c951 and 2 of 3 with APS5_NO_HOST_IMPORT=1.

Matched boots, five each, in one session through the shared FIFO (all five fix boots ran first, then the five main boots):

| Libraries | Boots | Crashed |

|---|---:|---|

| this change (da1cee2240, same content as the PR commit) | 5 | none (120 s each) |

| main 93dbd0f82a | 5 | 1, at 21.1 s, the allocator fault at app.exe+0x17f7e33 |

Five boots per side is a small sample, but it is consistent with the diagnostic build and the test above. Longer main boots crashed every time (89 s and 212 s). Two 300 s boots of head e84452d263 both ran to the cap without a crash (follow-up).

Cost. On memory that is not write-watched, each store and each stencil-writing bind or stencil plane fill copies the complete plane, and each store compares it. These operations hold the graphics locks. At 3840x2160 each full-plane comparison reads about 8 MiB. The title timings below do not isolate or establish the cost of a stencil-heavy workload.

Partial-write limitation. A CPU write after the last draw drops the entire pending stencil store. Untouched bytes keep their earlier guest contents instead of the drawn stencil; on watched memory, a write anywhere on an overlapping tracked page can trigger that drop. Bytewise merging is not implemented; this is recorded in docs/dev/debt/libSceAgcDriver.md.

Goat Simulator 3 (PPSA04158) at 3840x2160, with APS5_TRACE_SECONDS=600:4 only and the log not redirected: this change gave p50 181.9 ms (22 presents) and main 93dbd0f82a gave p50 178.7 ms (23 presents). These are one capture each; three more arms were lost, two to early title crashes before the window and the rest to the display being off. That is within this scene's run-to-run spread (178.9–185.5 ms on main), so it is not a measured cost claim. On head e84452d263, two more arms of this change (p50 177.5 and 180.4 ms) straddle one valid main arm (178.3 ms); the other main arm crashed before the window (follow-up). Still not a cost claim either way.

A diagnostic Goat Simulator 3 run that logged every storage-image write-back recorded none, so the Goat numbers show only that nothing else regressed. They may not exercise the copy. The copy is exercised in Cobra, which was not traced.

Pins. The tests and boots ran on 93dbd0f82a, as da1cee2240, then as ac9b10b80a with the same content. The PR head is that patch rebased onto main 4569fe654a without conflicts (git range-diff: identical), and CI builds it. e84452d263 then adds a refresh of the snapshot at each stencil draw, after review; its tests are in the review reply. The current head 191a4291bb is both commits rebased onto main 4f41f4c375, where main had moved this block into DepthSurface::MarkStencilStale(). The logic now lives there, and the 7 stencil and depth-plane GPU tests pass 3 of 3 (see the rebase comment).

Independent report: @TechLevelZero's NVIDIA CI (RTX 2000 Ada, driver 595.99.02) on head e84452d263 merged onto main 88e804b: 2,494 passed, 0 new failures.

Not run: Linux and macOS, where the planes are write-watched and the existing generation check applies; other titles.

Checklist

  • 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

  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file

  • 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: none

  • Fixes: none

  • Unblocks: none

  • GPUs tested: AMD Radeon RX 6650 XT / RADV NAVI23 on Linux for the current refresh; AMD Radeon RX 6700 XT, AMD proprietary driver 26.10.1, Windows 11 10.0.22621 for the historical runs below

  • AI-assisted: yes

🤖 Generated with Claude Code

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, @JJenkx. Could you cover a second draw on the same unwatched stencil plane after the guest write, before retiring it?

At 53965a49c240ab2e18035b77a6f4819433886bf7, DepthSurface.cpp:2381-2390 keeps the original snapshot while stencilStale is true. On an unwatched plane, SyncStencilFromMemory at lines 992-999 does not clear that state: GuestMemory::WrittenSince returns false when the tracker does not cover the range (GuestMemory.cpp:2277-2281). The new comparison at DepthSurface.cpp:2095-2108 can therefore drop the newer draw's result too.

For example, use the existing full-plane ALWAYS/REPLACE draw: draw 0x3c, wait, fill guest bytes with 0x77, draw 0x55 to the same surface, wait, then retire via the depth plane. The latest draw should leave 0x55; comparing against the first snapshot instead leaves 0x77 and skips the store. A source-derived decision model with the exact snapshot/predicate/retirement branches shows this; I have not run this sequence on Vulkan hardware. The pre-change unconditional store preserves the later 0x55 in that model, while retaining the original single-draw corruption your patch fixes.

Please handle the new draw's ownership of an unwatched plane and add this sequence alongside the existing draw → guest write → retire case. Keeping both cases green would distinguish a guest write after the final draw from a guest write superseded by a later draw. Please also exercise the read-flush path, since it uses the same snapshot predicate.

@github-actions github-actions Bot added the agc libSceAgc* label Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 15067 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp L2544-2548

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1368 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6808 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9672 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9895 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 9950 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 10193 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 10249 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 10256 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 11444 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 11998 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 12120 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 13863 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 13920 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 13928 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 13929 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 14005 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 14344 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 15059 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp

@JJenkx

JJenkx commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean Thanks, you're right. Fixed in e84452d263.

Change. Every draw that marks the stencil stale now refreshes the unwatched-plane snapshot when the guest bytes no longer match it (DepthSurface.cpp, the StencilMirrorable branch of the draw). A write the guest made before the latest draw is then superseded by that draw, and only a write after the last draw keeps its bytes. The watched path is unchanged: it already re-records the tracker generation on each draw. While the snapshot still matches, the refresh costs one compare and no copy.

Tests. agc_driver_stencil_plane_memory now has three new cases, next to the existing draw → guest write → retire:

  • draw 0x3c, wait, guest 0x44, draw 0x55, wait, retire: the plane holds 0x55;
  • draw 0x3c, wait, guest 0x77, read (FlushDepthPlanes): nothing is stored and the plane keeps 0x77;
  • draw 0x3c, wait, guest 0x66, draw 0x55, wait, read: the read stores and the plane holds 0x55.

Results (Windows, RX 6700 XT, AMD 26.10.1, validation layer on, through the shared GPU queue):

  • previous head 53965a49c2 with only the new test fails your case: "retiring the surface kept the guest's 0x44 over the stencil 0x55 a later draw wrote". An earlier run of the same control also failed the read-path case: "a read of the stencil plane a draw wrote after the guest's write stored nothing".
  • e84452d263: passes 3/3 with no VUIDs. The 27 stencil and depth tests (stencil|depth_plane|depth_surface|depth_storage|sample_mask_depth|multisample_target) pass with no VUIDs. That set ran on the same source before a test-only change of one fill value from 0x77 to 0x44, so that the retire case's guest write differs from the plane's previous contents.

🤖 Generated with Claude Code

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head e84452d merged onto main 88e804b · tested 2026-11-02 13:26 UTC · 19 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,494 passed · 7 failed (0 new, 3 also fail on main, 4 unstable on main) · 45 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,546 of 3,234 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (970 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.2 s, ⚠️ agc_driver_graphics_maps_fallback 40.5 s, ⚠️ agc_driver_graphics 35.9 s
Unstable on main (4, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_job_manager_compute_capacity: failed in 1 of the last 10 main runs
agc_driver_pipeline_journal: failed in 2 of the last 10 main runs
agc_submit_progress: failed in 8 of the last 10 main runs
agc_submit_progress_no_timeline: failed in 8 of the last 10 main runs
Failing on main too (3, not counted against this PR)
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
Validation IDs already present on main (4 IDs in 495 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (495 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 487 more
VUID-vkCmdCopyImage-srcImage-01548 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkShaderModuleCreateInfo-pCode-08737 (1 test): agc_driver_image_sample_derivatives
Skipped (45)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip_in_order: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_dcc: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_deferred: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_main: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_fps_limit: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_o
Reproduce
git fetch origin e84452d263fcd625d2ce472ac24d75ccb68d4dc8 88e804b0a7df1f1ac373a9e2f72fb378d925fdd4
git checkout 88e804b0a7df1f1ac373a9e2f72fb378d925fdd4 && git merge --no-ff e84452d263fcd625d2ce472ac24d75ccb68d4dc8
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@JJenkx

JJenkx commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the runs the description said were blocked: long Cobra boots and the Goat cost pair. All are on the current head e84452d263, including the snapshot refresh from review. Same machine: RX 6700 XT, AMD driver 26.10.1, Windows 11 22621. Game launches went through the shared FIFO.

Space Adventure Cobra: The Awakening (PPSA17337), 300 s boots with scripted cross presses. The loaded libSceAgcDriver.prx hash matches this head's build (c7d97084…).

Libraries Boots Crashed
this head e84452d263 2 none: both ran to the 300 s cap (300.7 s and 300.6 s), presses until 271 s, no crash report
main (earlier long boots, in the description) 2 2, at 89 s and 212 s

Goat Simulator 3 (PPSA04158) cost, 3840x2160, APS5_TRACE_SECONDS=600:4 only, log not redirected, arms alternating:

Order Libraries Result p50 p90
1 this head ran to the cap 177.5 ms 181.0 ms
2 main 4569fe654a crashed at 180.7 s (0xc0000005) before the window, no trace — —
3 this head ran to the cap 180.4 ms 186.4 ms
4 main 4569fe654a ran to the cap 178.3 ms 182.0 ms

All arms with a trace have 23 presents. The two arms of this change straddle main's one valid arm, and the gaps are within this scene's run-to-run spread, so this shows no measurable cost. It is not a cost claim either way.

main's crash at 180.7 s is the Goat instability I see on recent main builds, at 148–189 s and independent of this change. That arm is plain main without this PR. I am bisecting it separately.

The trace of this change's arms names commit 53965a49c240 (dirty) because the build tree's revision stamp predates the review commit. The loaded libSceAgcDriver.prx hash matches the e84452d263 build, as for the Cobra boots.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Nov 3, 2026
@JJenkx
JJenkx force-pushed the windows/agc-stale-stencil-keeps-guest-writes branch from e84452d to 191a429 Compare November 3, 2026 11:35
@JJenkx

JJenkx commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main 4f41f4c375. The head is now 191a4291bb, and the two commits keep their original messages.

Conflict: main's 6625bb5 ("retain pending stencil clears across sampling") moved the stale-stencil block this PR changed into DepthSurface::MarkStencilStale(). It also calls that from NoteHtileClear and NotePlaneFill, and the bind path now calls it only if (stencilWritten || created || stencilRebound). I kept main's call sites and moved this PR's logic into MarkStencilStale():

  • the snapshot of the guest's stencil bytes, taken when the plane goes stale and refreshed when a later draw follows a guest write;
  • stencilStaleGeneration.

So a stale plane marked by an HTILE clear or plane fill now also gets the snapshot that keeps later guest writes.

Tested on Windows: 11 Enterprise 22621, Radeon RX 6700 XT, AMD 26.10.1, MinGW-w64 GCC 15.2.0 UCRT r7, Release, ANYPS5_REQUIRE_VULKAN=1, VK_LAYER_KHRONOS_validation 1.4.363, 3 repeats. These 7 tests pass 3 of 3, with 0 VUID- lines:

  • agc_driver_stencil_plane_memory;
  • agc_driver_stencil_known_replace;
  • agc_driver_stencil_null_feedback;
  • agc_driver_sample_mask_depth_stencil;
  • the three agc_driver_depth_plane_partial_store*.

This PR's cases in StencilPlaneMemory.cpp (the 0x77, 0x44/0x55 and 0x66/0x55 sequences) run before the write-watch check, so they ran here. Only the older watched-memory case after them prints that it is not exercised on Windows. check_conventions.py --base origin/main and git diff --check pass.

AI-assisted: yes.

@XairRrR XairRrR 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.

Reviewed — approved

The stale-stencil store now leaves alone a plane the guest wrote after the last stencil draw. The new cases pass on the merged tree and fail with main's DepthSurface.cpp, and nothing I found blocks the merge.

What I ran

Head 191a4291bbefac61f96624ee13c025c4d7b1ce9b, main 4f41f4c375f5abb487dbd184fbc150f31994d762, merged tree 41e494af0edcc143b7b1cdd470c58cedd6b55b73. Debian 13 VM, 32 vCPU Xeon Gold 6136, GCC 14.2.0, Vulkan through lavapipe (Mesa 26.1.6, LLVM 19), no GPU.

git diff --check 4f41f4c375f5abb487dbd184fbc150f31994d762...191a4291bbefac61f96624ee13c025c4d7b1ce9b
python3 tools/check_conventions.py --base 4f41f4c375f5abb487dbd184fbc150f31994d762
env VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ANYPS5_NO_SHADER_CACHE=1 ANYPS5_REQUIRE_VULKAN=1 VK_LOADER_DEBUG=error,warn,layer \
    ctest --test-dir build -V -R "agc_driver_(stencil_plane_memory|native_depth_uniform_planes)$"
# negative control
git show 4f41f4c375f5abb487dbd184fbc150f31994d762:core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp > core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
ninja -C build -j32 -l32 agc_driver_native_multisample_depth_tests agc_driver_stencil_plane_memory_tests
env VK_INSTANCE_LAYERS=... ctest --test-dir build -V -R "agc_driver_(stencil_plane_memory|native_depth_uniform_planes)$"
git show 41e494af0edcc143b7b1cdd470c58cedd6b55b73:core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp > core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
git status --porcelain
ninja -C build -j32 -l32 agc_driver_native_multisample_depth_tests agc_driver_stencil_plane_memory_tests libs
ctest --test-dir build -j8 --test-load 32 --output-on-failure -R "stencil|depth_plane|depth_surface|native_depth"

Results

Check Result
git diff --check clean
check_conventions.py All conventions checks passed
Configure, build, libs (script) ok
Selected tests (script): 2162 run 0 new failures, 0 timeouts; 7 known failures that also fail on main
agc_driver_stencil_plane_memory, validation layer on Passed, 0 VUID- lines. It printed "guest memory is not write-watched here", so this machine runs the unwatched (snapshot) path the PR targets
Validation new_vuid in agc_driver_native_depth_uniform_planes (script: "test not run on main") Not caused by this PR: with main's DepthSurface.cpp the test reports the same 4 VUID-vkCmdDispatch-groupCountX-00386 errors
35 tests matching stencil|depth_plane|depth_surface|native_depth on the merged tree 35/35 pass (1 skipped: agc_driver_native_depth_bda_clear)
Validation run on the merged tree
1/2 Test #1504: agc_driver_stencil_plane_memory ..........   Passed    0.36 sec
1504: guest memory is not write-watched here: a guest write into the stencil plane is not exercised
1504: stencil plane memory tests passed
1852: Validation Error: [ VUID-vkCmdDispatch-groupCountX-00386 ] | MessageID = 0x8e927036   (x4, same count with main's DepthSurface.cpp)
2/2 Test #1852: agc_driver_native_depth_uniform_planes ...   Passed    0.37 sec

Negative control

I restored only core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp from main and rebuilt. agc_driver_stencil_plane_memory then fails on the first new case:

1504: AGC graphics: retiring the surface stored its stale stencil 0x3c over the guest's later write of 0x77

After that I restored the merged file and rebuilt. git status --porcelain printed nothing, and the 35 tests above ran on the restored build.

The negative control stops at the first failing Require, so it does not show on its own that the later cases (second draw after the guest write, read path) fail without the fix. The author reports running that check on the previous head in their reply to the review comment.

Findings

Blocking

None.

Non-blocking

  1. Every stencil-writing bind now compares the whole plane, while the global locks are held, and the description's cost note is out of date.

    • Where: DepthSurface.cpp:736. The call is from the bind path at DepthSurface.cpp:2657, under GpuMutex and surfacesMutex.
    • What changed: once the plane is already stale, every later MarkStencilStale() on an unwatched plane runs EqualsCommittedUnsynced over the full plane. At 3840x2160 that is 8 MiB per stencil-writing draw.
    • Why it matters: the description says "the copy is taken once each time a surface's stencil goes from stored to stale". That no longer describes the cost since the refresh commit, and since the rebase added the NoteHtileClear and NotePlaneFill callers. The Goat Simulator 3 numbers do not exercise this path, as the description itself says. A title with many stencil draws per frame on Windows would pay one plane-sized memcmp per draw.
    • Suggested fix: update the cost paragraph. Ideally, measure a stencil-heavy scene, or make the compare cheaper, for example by checking only when the plane may have changed since the last check.
  2. A partial guest write drops the whole stencil store.

    • Where: DepthSurface.cpp:2365-2379.
    • What happens: if the guest writes any byte of the plane (or, on watched memory, any 4 KiB page that overlaps it), the stencil is not stored anywhere in the plane.
    • On the console, the bytes the guest did not touch would hold the drawn stencil. Here they keep the bytes from before the draw.
    • For the freed-and-reused case this does not matter. For a live plane that the CPU patches in part, it is a wrong result.
    • Suggested fix: on the unwatched path, store the stencil only where the guest bytes still equal the snapshot. Otherwise, record the approximation in docs/dev/debt/libSceAgcDriver.md.
  3. The watched branch of stencilWrittenSinceStale is not tested.

    • Where: DepthSurface.cpp:2366.
    • This changes behaviour on watched memory too. Before, the store always ran. Now it is dropped when UnchangedSinceCollected reports a collected CPU store since stencilStaleGeneration.
    • Neither the author's Windows runs nor this Linux/lavapipe machine (the plane was not watched here) cover that branch, and the PR's new cases do not force it. The NVIDIA CI run passing is the only indirect evidence.
  4. Stray blank line. The PR adds an empty line before the closing brace of the watched-memory else block in StencilPlaneMemory.cpp:211. It is unrelated to the change and can be removed.

On the earlier review comment: the second-draw case it asked for is now in the test, together with both read-path cases, and those cases pass on the merged tree here.

Not verified

  • The watched-memory path (Linux or macOS with the plane write-watched). On this machine the test reports that the plane is not watched.
  • Hardware GPUs: only lavapipe here.
  • The title results (PPSA17337 crash, PPSA04158 timings): no titles are run in review.
  • Whether each later new case fails on its own without the fix: the negative control stops at the first failing Require.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ AMD GPU test: passed

PR head 191a429 merged onto main 4f41f4c · tested 2026-11-03 12:02 UTC · 18 min

GPU AMD Radeon RX 6300 (RADV NAVI24), driver Mesa 25.2.8-0ubuntu0.24.04.4, Vulkan 1.4.318
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-42-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,671 passed · 10 failed (0 new, 7 also fail on main, 3 unstable on main) · 58 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,739 of 3,811 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (923 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s, ⚠️ public_headers 55.7 s, ⚠️ agc_driver_image_sample_unnormalized 49.9 s, ⚠️ agc_driver_depth_clear_hle 36.0 s, ⚠️ agc_driver_sample_multisample_target 35.3 s
Unstable on main (3, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_depth_fast_clear_submit: failed in 2 of the last 9 main runs
agc_driver_pipeline_journal: failed in 7 of the last 9 main runs
agc_driver_store_over_untracked: failed in 1 of the last 9 main runs
Failing on main too (7, not counted against this PR)
agc_driver_image_table
agc_driver_native_depth_bda_clear
agc_driver_native_multisample_depth
agc_driver_submit_progress
agc_submit_progress
agc_submit_progress_no_timeline
guest_socket_options_multicast
Validated but not compared (1, not in the main baseline's validation pass)
guest_job_manager_worker_spin_scale_0.1
Validation IDs already present on main (4 IDs in 517 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (517 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 509 more
VUID-vkCmdCopyImage-srcImage-01548 (8 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth, agc_driver_stencil_null_feedback, agc_driver_stencil_plane_memory
VUID-vkCmdCopyImage-srcImage-01551 (8 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth, agc_driver_stencil_null_feedback, agc_driver_stencil_plane_memory
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_srgb8_color_target
Skipped (58)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_copied_writer_label_order: skipped, the device reports a minStorageBufferOffsetAlignment of 4, so the dispatch binds its record at +0x1004 in place and nothing is written back; the test needs an alignment above 4
agc_driver_float16_unused_nan_payloads: skipped, the device has no native f16 conversions with RTE rounding and kept f16 denormals
agc_driver_image_table_vouched_lookups: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a watched imported table cannot be vouched for
agc_driver_linear_target_direct_write_completion: skipped, the device does not keep host imports write-watched, so no completion stamp over the target is tracked
agc_driver_ngg_vertex_wave_reduction: skipped, s3 describes a 32-lane wave only on 32-lane subgroups, and the device has 64 lanes
agc_driver_scattered_runs: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_sparse_dma_buf_import: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_driver_unit_shadow_budget: skipped, the device does not import watched host memory for unit shadows
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_idle_publish_wait: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a forced watch of the import reads its submissions as CPU stores
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_shadowed_key_stores: skipped, the device imports no watched host memory under a unit shadow
agc_small_tiled_shadow_label: skipped, host imports are unavailable: the test block is not imported and watched
agc_storage_clear_hand_over: skipped, the device imports no watched host memory for the clear-key hand-over
agc_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayW
Reproduce
git fetch origin 191a4291bbefac61f96624ee13c025c4d7b1ce9b 4f41f4c375f5abb487dbd184fbc150f31994d762
git checkout 4f41f4c375f5abb487dbd184fbc150f31994d762 && git merge --no-ff 191a4291bbefac61f96624ee13c025c4d7b1ce9b
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 12 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 16 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's AMD machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Nov 3, 2026
@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 191a429 merged onto main 4f41f4c · tested 2026-11-03 12:41 UTC · 19 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,686 passed · 5 failed (0 new, 2 also fail on main, 3 unstable on main) · 48 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,739 of 3,811 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (922 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.3 s
Unstable on main (3, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 4 of the last 10 main runs
agc_driver_submit_progress: failed in 5 of the last 10 main runs
agc_submit_progress: failed in 6 of the last 10 main runs
Failing on main too (2, not counted against this PR)
agc_driver_image_table
guest_socket_options_multicast
Validation IDs already present on main (4 IDs in 517 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (517 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 509 more
VUID-vkCmdCopyImage-srcImage-01548 (9 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth, agc_driver_stencil_null_feedback, and 1 more
VUID-vkCmdCopyImage-srcImage-01551 (9 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth, agc_driver_stencil_null_feedback, and 1 more
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_srgb8_color_target
Skipped (48)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_driver_scattered_runs: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_sparse_dma_buf_import: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_stencil_ref_export: skipped, the device lacks VK_EXT_shader_stencil_export
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip_in_order: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_dcc: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_deferred: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_main: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not co
Reproduce
git fetch origin 191a4291bbefac61f96624ee13c025c4d7b1ce9b 4f41f4c375f5abb487dbd184fbc150f31994d762
git checkout 4f41f4c375f5abb487dbd184fbc150f31994d762 && git merge --no-ff 191a4291bbefac61f96624ee13c025c4d7b1ce9b
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Nov 3, 2026
JJenkx and others added 3 commits November 3, 2026 16:15
A draw that writes stencil marks the surface's stencil plane stale; the
plane reaches guest memory later, when a GPU write retires the surface
or something reads the plane. That store wrote the stencil over the
plane even when the guest had written it after the draw, typically
after freeing the memory and reusing it. The mirror upload inside
StoreStencilPlane takes the guest bytes at store time, so the guest's
write looked like original data, and on Windows the driver no longer
write-watches memory it uses as a surface.

When the stencil goes stale the driver now records the tracker
generation and, for a plane that is not write-watched, a copy of its
guest bytes. Both store paths skip the store, and clear the stale
state, when the guest wrote the plane since: UnchangedSinceCollected
on watched memory, a compare with the copy otherwise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s a guest write

On an unwatched plane the snapshot was taken only when the stencil first
went stale, so a guest write followed by another stencil draw made the
store compare against the older bytes and keep the guest write over the
later draw. Each stencil draw now refreshes the snapshot when the plane's
bytes no longer match it.

The test covers a second draw after the guest write through retirement and
through a read, and a guest write after the last draw through a read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JJenkx
JJenkx force-pushed the windows/agc-stale-stencil-keeps-guest-writes branch from 191a429 to 811f2d9 Compare November 3, 2026 21:20
@JJenkx

JJenkx commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

Rebased without merge commits onto current main 04a9eb4fa28; head is now 811f2d92e488fb8b335be520ef9bc74d87074d68. The conflict resolution retains merged #15466's unmap serial/invalidation checks and adds this PR's CPU-write guard to both stencil store paths.

Also addressed the non-blocking review notes: corrected the description to state that subsequent stale marks compare the entire unwatched plane while graphics locks are held; recorded whole-plane dropping after a partial CPU write in docs/dev/debt/libSceAgcDriver.md; removed the stray test blank line. This is not a bytewise-merge implementation or a new performance result.

Same host/GPU main and change: 8/8 each, real Vulkan validation layer loaded, zero VUIDs and zero skips. With current main production code plus the new fixture, the negative control fails at the expected assertion: retiring the surface stored its stale stencil 0x3c over the guest's later write of 0x77. The fixture reports that the plane is not write-watched here, so the watched branch remains unverified on this Linux host.

Commands, logs, source patches, range-diff and negative control — SHA-256 b03befd0d94a1cc1608146652b63eeb7e62641d2e60d576f94e6bedf6ce8f204. Conventions and git diff --check pass. Prior approvals/CI/title runs refer to their recorded older heads; no new Windows or title result is claimed. AI-assisted: yes.

@github-actions github-actions Bot added the docs label Nov 3, 2026

@XairRrR XairRrR 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.

Re-checked — approved

What changed: a rebase onto main 04a9eb4fa2 that combines #15466's unmappedSince check with this PR's guest-write check in both stale-stencil store paths, a new debt entry for partial writes and comparison cost, an updated cost paragraph and the removal of the stray blank line. Checks and tests pass, and the negative controls fail as expected.

What changed since my last review

  • Rebase. The new base is main 04a9eb4fa2877eb6e40bf2c83adf972e40d89b24. Both store paths now drop the store when unmappedSince(...) || stencilWrittenSinceStale(...):

    • storeStaleStencil at DepthSurface.cpp:2846 (this goes through retireStencil);
    • the read path in FlushDepthPlanes at DepthSurface.cpp:3240-3242.

    MarkStencilStale() (DepthSurface.cpp:858-872) has the same logic as in 191a4291bb.

  • Commit 811f2d92e4.

    • Adds a Functional entry to docs/dev/debt/libSceAgcDriver.md:125. It covers dropping the whole store after a partial CPU write, the page-granular drop on watched memory, and the full-plane compare under the graphics locks.
    • Removes the stray blank line from StencilPlaneMemory.cpp.
  • Description. The cost paragraph now says that each later stale mark compares the whole plane. A partial-write paragraph was added.

What I ran

Head 811f2d92e488fb8b335be520ef9bc74d87074d68, main 04a9eb4fa2877eb6e40bf2c83adf972e40d89b24, merged tree cab8c66aeb43838dda1d4067e7d9b0850437e8cd. Debian 13 VM, Xeon Gold 6136, GCC 14.2.0, lavapipe (Mesa 26.1.6), no GPU.

git diff --check 04a9eb4fa2877eb6e40bf2c83adf972e40d89b24...811f2d92e488fb8b335be520ef9bc74d87074d68
python3 tools/check_conventions.py --base 04a9eb4fa2877eb6e40bf2c83adf972e40d89b24
# negative control 1: main's production file
git show 04a9eb4fa2877eb6e40bf2c83adf972e40d89b24:core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp > core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
ninja -C build -j32 -l32 agc_driver_stencil_plane_memory_tests
env VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ANYPS5_NO_SHADER_CACHE=1 ANYPS5_REQUIRE_VULKAN=1 VK_LOADER_DEBUG=error,warn,layer ctest --test-dir build -V -R "^agc_driver_stencil_plane_memory$"
# negative control 2: the first commit's DepthSurface.cpp, without the snapshot refresh
git show 15ef08b3edd9:core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp > core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
ninja -C build -j32 -l32 agc_driver_stencil_plane_memory_tests
ANYPS5_NO_SHADER_CACHE=1 ANYPS5_REQUIRE_VULKAN=1 ctest --test-dir build -V -R "^agc_driver_stencil_plane_memory$"
# restore
git show cab8c66aeb43838dda1d4067e7d9b0850437e8cd:core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp > core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
git status --porcelain
ninja -C build -j32 -l32
env VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ANYPS5_NO_SHADER_CACHE=1 ANYPS5_REQUIRE_VULKAN=1 VK_LOADER_DEBUG=error,warn,layer ctest --test-dir build -V -R "^agc_driver_stencil_plane_memory$"
ctest --test-dir build -j8 --test-load 32 --output-on-failure -R "stencil|depth_plane|depth_surface|native_depth|image_gather_volume"
# script-flagged VUIDs: merged tree vs main's DepthSurface.cpp
env VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ANYPS5_NO_SHADER_CACHE=1 ANYPS5_REQUIRE_VULKAN=1 ctest --test-dir build -V -R "^agc_driver_image_gather_volume$"
Check Result
git diff --check clean
check_conventions.py All conventions checks passed
Configure, build, libs (script) ok
Selected tests (script): 2173 run 0 new failures, 0 timeouts. The 7 known failures also fail on main
agc_driver_stencil_plane_memory, validation layer loaded Passed, 0 VUID- lines. The test reports that the plane is not write-watched, so the snapshot path runs
41 tests matching stencil|depth_plane|depth_surface|native_depth|image_gather_volume 40 pass, 1 skipped (agc_driver_native_depth_bda_clear)
Script new_vuid in agc_driver_image_gather_volume (VUID-VkSamplerCreateInfo-addressMode*, "test not run on main") Not from this PR: main's DepthSurface.cpp gives the same 8 lines (2 each of addressModeU-01079 and of addressModeU/V/W-parameter). These are sampler-creation errors, unrelated to stencil stores

Negative control

  • main's DepthSurface.cpp: agc_driver_stencil_plane_memory fails with retiring the surface stored its stale stencil 0x3c over the guest's later write of 0x77.
  • New this time: DepthSurface.cpp from the first commit 15ef08b3ed, without the snapshot refresh: the test fails with retiring the surface kept the guest's 0x44 over the stencil 0x55 a later draw wrote. This shows on its own that the second-draw case needs the refresh commit. My previous review could not show this.

After both runs I restored the merged file and rebuilt. git status --porcelain printed nothing.

Previous findings

  1. Whole-plane compare per stencil-writing bind; cost note out of date. Resolved as a documentation finding.
    • The description now states that every later stale mark compares the complete plane under the graphics locks, about 8 MiB at 3840x2160. The debt entry at libSceAgcDriver.md:125 records it.
    • The cost itself is unchanged and unmeasured on a stencil-heavy scene. The description says so plainly.
  2. A partial guest write drops the whole stencil store. Resolved by recording it as debt in the same entry. Bytewise merging is listed as not implemented.
  3. The watched branch of stencilWrittenSinceStale is untested. Still open, non-blocking.
    • DepthSurface.cpp:2837 still runs only where the plane is watched.
    • The author's Linux/RADV refresh and this machine both report that the plane is not watched. The author states this in the description.
  4. Stray blank line in StencilPlaneMemory.cpp. Resolved. The watched-memory else block at StencilPlaneMemory.cpp:205-210 now closes without it.

New findings

Blocking

None.

Non-blocking

None. The rebase resolution looks correct to me:

  • In storeStaleStencil, a guest-written plane now goes through retireStencil. That clears stencilStale and calls forgetStaleStencil, as the earlier explicit branch did.
  • The read path clears stencilStale in the same branch that main uses for an unmapped plane.

Not verified

  • The watched-memory path (previous finding 3).
  • Hardware GPUs: this machine has only lavapipe. The author's Linux RX 6650 XT run and the archive with its logs are author-reported. I did not download the archive.
  • Title results (PPSA17337, PPSA04158): reviews do not run titles, and the description claims no new title result for this head.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Nov 3, 2026
@JJenkx

JJenkx commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Windows title check on the current head, plus an update that weakens the title evidence for this PR on current main.

Space Adventure Cobra (PPSA17337) is the title whose heap crash led to this change. Same 300 s boots as my earlier comparison, Windows / RX 6700 XT, plain library sets with only the build changed:

Build Runs Crashed
main 086c0686c8 (includes #15466) 4 2, at 83.3 s and 45.3 s, both app.exe+0x17f7ac6, the same site as before this PR
this PR's head 811f2d92e4 (on 04a9eb4fa28) 4 0
main 1c8c0efc8c (contains 04a9eb4fa28 and #13021; without this PR) 8 0

So on current main, Cobra no longer demonstrates that this PR is needed. The case this PR fixes, guest writes over a stale stencil plane being stored over, is still pinned by its own StencilPlaneMemory checks from the Linux validation above. On Windows that test's watched branch does not run, because surface memory is unwatched here, so I cannot add a Windows negative control for it. I'm leaving it to reviewers whether the change should still go in on that basis.

AI-assisted: yes.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ AMD GPU test: passed

PR head 811f2d9 merged onto main 1c8c0ef · tested 2026-11-04 00:33 UTC · 13 min

GPU AMD Radeon RX 6300 (RADV NAVI24), driver Mesa 25.2.8-0ubuntu0.24.04.4, Vulkan 1.4.318
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-42-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,697 passed · 8 failed (0 new, 7 also fail on main, 1 unstable on main) · 60 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,765 of 3,842 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (944 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.4 s, ⚠️ public_headers 47.0 s, ⚠️ agc_driver_image_sample_unnormalized 46.5 s, ⚠️ agc_shader_sampler_limits 39.5 s, ⚠️ agc_driver_lazy_resolved_abi 36.1 s
Unstable on main (1, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 8 of the last 10 main runs
Failing on main too (7, not counted against this PR)
agc_driver_image_table
agc_driver_native_depth_bda_clear
agc_driver_native_multisample_depth
agc_submit_progress
agc_submit_progress_no_timeline
guest_mmap_executable
guest_socket_options_multicast
Validation IDs already present on main (8 IDs in 531 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (531 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 523 more
VUID-vkCmdCopyImage-srcImage-01548 (4 tests): agc_driver_depth_clear_hle, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (4 tests): agc_driver_depth_clear_hle, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_srgb8_color_target
VUID-VkSamplerCreateInfo-addressModeU-01079 (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeU-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeV-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeW-parameter (1 test): agc_driver_image_gather_volume
Skipped (60)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_copied_writer_label_order: skipped, the device reports a minStorageBufferOffsetAlignment of 4, so the dispatch binds its record at +0x1004 in place and nothing is written back; the test needs an alignment above 4
agc_driver_float16_unused_nan_payloads: skipped, the device has no native f16 conversions with RTE rounding and kept f16 denormals
agc_driver_gcn_display_present: skipped, no display: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
agc_driver_image_table_vouched_lookups: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a watched imported table cannot be vouched for
agc_driver_linear_target_direct_write_completion: skipped, the device does not keep host imports write-watched, so no completion stamp over the target is tracked
agc_driver_ngg_vertex_wave_reduction: skipped, s3 describes a 32-lane wave only on 32-lane subgroups, and the device has 64 lanes
agc_driver_scattered_runs: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_sparse_dma_buf_import: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_driver_unit_shadow_budget: skipped, the device does not import watched host memory for unit shadows
agc_driver_vertex_elected_atomic_max: skipped, the device runs vertex programs in subgroups, so they keep their cross-lane reductions
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_idle_publish_wait: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a forced watch of the import reads its submissions as CPU stores
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_shadowed_key_stores: skipped, the device imports no watched host memory under a unit shadow
agc_small_tiled_shadow_label: skipped, host imports are unavailable: the test block is not imported and watched
agc_storage_clear_hand_over: skipped, the device imports no watched host memory for the clear-key hand-over
agc_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SD
Reproduce
git fetch origin 811f2d92e488fb8b335be520ef9bc74d87074d68 1c8c0efc8c41314402e6966da5bef6ea3d639d10
git checkout 1c8c0efc8c41314402e6966da5bef6ea3d639d10 && git merge --no-ff 811f2d92e488fb8b335be520ef9bc74d87074d68
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 12 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 16 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's AMD machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 811f2d9 merged onto main 1c8c0ef · tested 2026-11-04 00:49 UTC · 20 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,710 passed · 5 failed (0 new, 3 also fail on main, 2 unstable on main) · 50 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,765 of 3,842 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (944 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s
Unstable on main (2, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 4 of the last 10 main runs
agc_submit_progress: failed in 5 of the last 10 main runs
Failing on main too (3, not counted against this PR)
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
Validation IDs already present on main (8 IDs in 531 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (531 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 523 more
VUID-vkCmdCopyImage-srcImage-01548 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_srgb8_color_target
VUID-VkSamplerCreateInfo-addressModeU-01079 (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeU-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeV-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeW-parameter (1 test): agc_driver_image_gather_volume
Skipped (50)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_gcn_display_present: skipped, no display: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_driver_scattered_runs: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_sparse_dma_buf_import: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_stencil_ref_export: skipped, the device lacks VK_EXT_shader_stencil_export
agc_driver_vertex_elected_atomic_max: skipped, the device runs vertex programs in subgroups, so they keep their cross-lane reductions
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip_in_order: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_dcc: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_deferred: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
vide
Reproduce
git fetch origin 811f2d92e488fb8b335be520ef9bc74d87074d68 1c8c0efc8c41314402e6966da5bef6ea3d639d10
git checkout 1c8c0efc8c41314402e6966da5bef6ea3d639d10 && git merge --no-ff 811f2d92e488fb8b335be520ef9bc74d87074d68
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@hugodearaujo

Copy link
Copy Markdown
Contributor

@JJenkx some data for the Linux, write-watched side, which the description lists as not run. The title is Hollow Knight: Silksong (PPSA12544).

Setup. Arch Linux 7.2.11, RX 9070 XT (RADV GFX1201, Mesa 26.2.6), Release. Base: main 1c8c0ef + #8932, which is closed but fixes an earlier stop at 217 s in this title. The "#14969" arm adds this PR's three commits; their patch IDs match the current head 811f2d92e4. Every boot uses the same save and the same scripted route.

The stop. It's the stale stencil store you describe. With dynamic resolution, depth targets from 1748x982 to 1920x1080 share stencil addresses in the title's 16 MiB heaps. An HTILE fill on the current target marks the older surfaces' planes stale. Their planes (0x1c0000-0x280000 bytes) are then stored over memory the allocator has reused. The title faults in its allocator (app.elf+0x1a4771d / +0x1a47ad7), or the driver stops on AGC graphics: unaligned vertex buffer. In all four crashes I traced, the fault address is inside a plane stored as stencil plane store just before.

route, 420 s base #14969 local variant (below)
gameplay 3 of 5 crash (51, 51, 303 s) 0 of 5 0 of 8
first boot (calibration and profile screens) 3 of 3 crash (243 s) 3 of 3 crash (243, 256, 281 s) 0 of 6

Why the first-boot route still crashes. On watched memory the check compares against the generation of the last stale mark, and an HTILE clear or bind re-arms that mark every frame. I ran a trace build on this PR's code. In 1,478 of the stale-plane stores it logged, the plane's memory had been written since the plane was last synced from memory, but not since the last stale mark (cpuSinceStale=0 anySinceStale=0 anySinceSync=1). Those stores overwrite the guest's bytes, and the crash follows one of them.

What worked here. A local variant compares against the plane's last sync instead: SyncStencilFromMemory at bind, or a completed store. Concretely: CollectWritesUncached over the plane, then WrittenSince(plane, stencilGeneration). When the memory is newer, both store paths drop the plane, as for an unmapped plane. That is the "local variant" column above.

Test coverage. I also made a fixture for agc_driver_stencil_plane_memory that backs the planes with shared memfd memory. It's imported as a dma-buf and is write-watched, so it runs the watched branch on Linux. It passes on your head, and on the variant. It doesn't reproduce the first-boot failure, which depends on the title's per-frame re-marking.

I'm not opening a competing PR: the fix belongs in this one. If the last-sync threshold works for you, I can post the diff or push the variant to a branch, whichever is easier.

Not judged: other titles, Windows, frame time (the screens matched between arms at the same points of the route).

@JJenkx

JJenkx commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the paired route evidence and the distinction between the last stale mark and the last sync. Please post the variant diff or a branch link, including the shared-memfd fixture. I will review it in this PR and add a focused regression that writes the plane after its last sync, re-marks it stale, and then attempts a store. The existing fixture passing on both versions does not cover that ordering.

I have not reproduced the first-boot route locally yet, so I am treating the title results above as your measurements, rather than claiming that this head fixes that route.

…last mark

A stale stencil plane is dropped instead of stored when the guest wrote its
memory. The comparison was against the memory at the plane's latest stale
mark, and HTILE clears and rebinds re-mark a plane without writing it, so a
guest write before the latest mark was taken as the plane's own bytes and
the stale plane was stored over it.

The baseline is now the last store, or a mark that comes from a stencil
write or a stencil plane fill. On write-watched memory the store checks
WrittenSince against the generation of the last sync or store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JJenkx

JJenkx commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

@hugodearaujo thanks for the trace, which showed exactly where it went wrong. Pushed c1fe457b32 (one commit on 811f2d92e4).

The change.

  • The comparison point is now the plane's last store, or the last stencil-writing draw or stencil plane fill. HTILE clears and rebinds no longer move it.
  • On watched memory both store paths use your variant: CollectWritesUncached over the plane, then WrittenSince(plane, stencilGeneration).
  • The unwatched path had the same problem in another form: a re-mark after a guest write took a new snapshot of the guest's bytes. The snapshot is now taken after each store and each stencil-writing bind or fill, and not on other marks.
  • An HTILE stencil clear stays a mark, not a write. A guest write followed by an HTILE clear therefore keeps the guest's bytes, although a console decompress would write the clear over them. That limit is now in the debt entry.

Test. The new case in agc_driver_stencil_plane_memory runs these steps on one surface:

  1. A draw writes stencil 0x18, and a read stores it.
  2. The guest writes 0x77.
  3. Two HTILE fills re-mark the plane.
  4. A read follows.

The plane must still hold 0x77. On Windows (RX 6700 XT, Khronos validation layer, required Vulkan, shader cache off), with each arm merged onto main b11c7bf84f:

  • The previous head with this fixture fails on that case.
  • This change passes 3 of 3, including the consumed-clear and guest-write-then-newer-draw cases.
  • main's own fixture passes 3 of 3.
  • There are no VUIDs on any arm, and no test was skipped.

Limits.

  • On Windows these planes are not write-watched, so this run covers the snapshot path only. The watched branch is your variant as written, and I have not run it on Linux.
  • I have not reproduced the first-boot route. Your 0 of 6 and 0 of 8 runs used your local variant, not this head.
  • A run of this head on your shared-memfd fixture, and the first-boot route, would show whether the watched side behaves the same.

AI-assisted: yes.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ AMD GPU test: passed

PR head c1fe457 merged onto main b11c7bf · tested 2026-11-04 20:54 UTC · 28 min

GPU AMD Radeon RX 6300 (RADV NAVI24), driver Mesa 25.2.8-0ubuntu0.24.04.4, Vulkan 1.4.318
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-42-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,756 passed · 8 failed (0 new, 7 also fail on main, 1 unstable on main) · 58 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,822 of 3,894 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (1033 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.3 s, ⚠️ public_headers 79.0 s, ⚠️ check_skips 52.4 s, ⚠️ agc_driver_image_sample_unnormalized 44.4 s, ⚠️ conventions_pr_template 42.7 s
Unstable on main (1, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 8 of the last 10 main runs
Failing on main too (7, not counted against this PR)
agc_driver_image_table
agc_driver_job_manager_compute_capacity
agc_driver_native_multisample_depth
agc_submit_progress
agc_submit_progress_no_timeline
guest_mmap_executable
guest_socket_options_multicast
Validated but not compared (56, not in the main baseline's validation pass)
guest_job_manager_worker_spin
public_headers
public_headers_compile_0
public_headers_compile_10
public_headers_compile_12
public_headers_compile_14
public_headers_compile_16
public_headers_compile_18
public_headers_compile_2
public_headers_compile_20
public_headers_compile_21
public_headers_compile_22
public_headers_compile_23
public_headers_compile_24
public_headers_compile_25
public_headers_compile_26
public_headers_compile_27
public_headers_compile_28
public_headers_compile_29
public_headers_compile_30
public_headers_compile_31
public_headers_compile_32
public_headers_compile_33
public_headers_compile_34
public_headers_compile_35
public_headers_compile_36
public_headers_compile_37
public_headers_compile_38
public_headers_compile_39
public_headers_compile_4
public_headers_compile_40
public_headers_compile_41
public_headers_compile_42
public_headers_compile_43
public_headers_compile_44
public_headers_compile_45
public_headers_compile_46
public_headers_compile_47
public_headers_compile_48
public_headers_compile_49
public_headers_compile_50
public_headers_compile_51
public_headers_compile_52
public_headers_compile_53
public_headers_compile_54
public_headers_compile_55
public_headers_compile_56
public_headers_compile_57
public_headers_compile_58
public_headers_compile_59
public_headers_compile_6
public_headers_compile_60
public_headers_compile_61
public_headers_compile_62
public_headers_compile_63
public_headers_compile_8
Validation IDs already present on main (8 IDs in 538 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (538 tests): agc_driver, agc_driver_address_draw_space_reuse, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, and 530 more
VUID-vkCmdCopyImage-srcImage-01548 (4 tests): agc_driver_depth_clear_hle, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (4 tests): agc_driver_depth_clear_hle, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_prepared_exports
VUID-VkSamplerCreateInfo-addressModeU-01079 (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeU-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeV-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeW-parameter (1 test): agc_driver_image_gather_volume
Skipped (58)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_copied_writer_label_order: skipped, the device reports a minStorageBufferOffsetAlignment of 4, so the dispatch binds its record at +0x1004 in place and nothing is written back; the test needs an alignment above 4
agc_driver_float16_unused_nan_payloads: skipped, the device has no native f16 conversions with RTE rounding and kept f16 denormals
agc_driver_gcn_display_present: skipped, no display: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
agc_driver_image_table_vouched_lookups: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a watched imported table cannot be vouched for
agc_driver_linear_target_direct_write_completion: skipped, the device does not keep host imports write-watched, so no completion stamp over the target is tracked
agc_driver_ngg_vertex_wave_reduction: skipped, s3 describes a 32-lane wave only on 32-lane subgroups, and the device has 64 lanes
agc_driver_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_driver_unit_shadow_budget: skipped, the device does not import watched host memory for unit shadows
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_idle_publish_wait: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a forced watch of the import reads its submissions as CPU stores
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_shadowed_key_stores: skipped, the device imports no watched host memory under a unit shadow
agc_small_tiled_shadow_label: skipped, host imports are unavailable: the test block is not imported and watched
agc_storage_clear_hand_over: skipped, the device imports no watched host memory for the clear-key hand-over
agc_storage_pending_key_refresh: skipped, refreshes over a pending key store are not testable here
agc_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
vid
Reproduce
git fetch origin c1fe457b32fbd69c5dea801bad46743028d6979f b11c7bf84f5d2f636ff60166cc3371bd071e24d5
git checkout b11c7bf84f5d2f636ff60166cc3371bd071e24d5 && git merge --no-ff c1fe457b32fbd69c5dea801bad46743028d6979f
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 12 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 16 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's AMD machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head c1fe457 merged onto main 9ab6a28 · tested 2026-11-04 20:54 UTC · 25 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,772 passed · 2 failed (0 new, 1 also fail on main, 1 unstable on main) · 48 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,822 of 3,909 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (969 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s, ⚠️ agc_driver_gpu_buffer_format 33.7 s
Unstable on main (1, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 3 of the last 10 main runs
Failing on main too (1, not counted against this PR)
agc_driver_image_table
Validation IDs already present on main (13 IDs in 548 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (548 tests): agc_driver, agc_driver_address_draw_space_reuse, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, and 540 more
VUID-VkShaderModuleCreateInfo-pCode-08737 (8 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_float16_fma_rounding, agc_driver_float16_unused_nan_payloads, agc_driver_packed_alu, agc_driver_packed_f16_denormal_modes, agc_driver_packed_float16_native
VUID-VkShaderModuleCreateInfo-pCode-08739 (8 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_float16_fma_rounding, agc_driver_float16_unused_nan_payloads, agc_driver_packed_alu, agc_driver_packed_f16_denormal_modes, agc_driver_packed_float16_native
VUID-VkShaderModuleCreateInfo-pCode-08741 (8 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_float16_fma_rounding, agc_driver_float16_unused_nan_payloads, agc_driver_packed_alu, agc_driver_packed_f16_denormal_modes, agc_driver_packed_float16_native
VUID-vkCmdCopyImage-srcImage-01548 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (2 tests): agc_driver_prepared_exports, agc_driver_srgb8_color_target
VUID-VkPipelineShaderStageCreateInfo-module-parameter (1 test): agc_driver_float16_unused_nan_payloads
VUID-VkPipelineShaderStageCreateInfo-pSpecializationInfo-06849 (1 test): agc_driver_float16_unused_nan_payloads
VUID-VkSamplerCreateInfo-addressModeU-01079 (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeU-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeV-parameter (1 test): agc_driver_image_gather_volume
VUID-VkSamplerCreateInfo-addressModeW-parameter (1 test): agc_driver_image_gather_volume
Skipped (48)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_gcn_display_present: skipped, no display: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_driver_scattered_runs: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_sparse_dma_buf_import: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_stencil_ref_export: skipped, the device lacks VK_EXT_shader_stencil_export
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip_in_order: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_dcc: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_deferred: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or pl
Reproduce
git fetch origin c1fe457b32fbd69c5dea801bad46743028d6979f 9ab6a281ea61c9e62a7eb07eb4cef8f19ede1c5c
git checkout 9ab6a281ea61c9e62a7eb07eb4cef8f19ede1c5c && git merge --no-ff c1fe457b32fbd69c5dea801bad46743028d6979f
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@JJenkx

JJenkx commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Windows recheck of head c1fe457b32 on current main d23df92b38 (365d5bca32, clean merge; DepthSurface.cpp and the AGC debt note auto-merged).

Setup: Windows 11 Enterprise 22621, WinLibs GCC 15.2 UCRT, Release, Radeon RX 6700 XT, AMD driver 26.10.1, run with ANYPS5_REQUIRE_VULKAN=1 and Vulkan validation layers 1.4.363 enabled.

  • agc_driver_stencil_plane_memory passes in 3 of 3 runs, none skipped, with no VUID- errors.

AI-assisted: yes.

@hugodearaujo

Copy link
Copy Markdown
Contributor

@JJenkx thanks for folding it in. I ran both things you asked for on Linux, with your current head.

Setup. Arch Linux 7.2.11, RX 9070 XT (RADV GFX1201, Mesa 26.2.6), Release. The base is the same as in my first comment: main 1c8c0ef + #8932. The four commits of this PR are applied up to c1fe457b32; the three earlier ones match 811f2d92e4 and the fourth is c1fe457b32 itself.

First-boot route. This is the Silksong route that crashed 3 of 3 times on 811f2d92e4: calibration and profile screens, 420 s, the same save and scripted inputs. On this head, 0 of 3 crash. All three runs reached the time limit at 600 presents per 10 s, and the SIGSEGV logger recorded no signal 11.

head first-boot route, 420 s
811f2d92e4 (earlier runs) 3 of 3 crash (243, 256, 281 s)
c1fe457b32 0 of 3 crash

Watched fixture. I added the shared-memfd fixture (CheckReusedStencilPlane, planes imported as a dma-buf, so they're write-watched) to agc_driver_stencil_plane_memory next to your new CheckGuestWriteBeforeRepeatedClears. On this head both pass, with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ANYPS5_NO_SHADER_CACHE=1 ANYPS5_REQUIRE_VULKAN=1, with no VUID- line, and the layer inserted. So the watched branch, your new ordering case and the record-preservation cases all hold on Linux.

If you want the fixture in this PR, I can post its diff (about 100 lines in tests/execution/StencilPlaneMemory.cpp: a memfd allocator, a shared-backing resolver and the test function, plus one call in main()). It sits next to your functions with no other changes. I haven't re-run the gameplay route on this head; on 811f2d92e4 it was already 0 of 5.

@JJenkx

JJenkx commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

@hugodearaujo Thank you, that is the result I needed: the first-boot route that crashed 3 of 3 on 811f2d92e4 runs 0 of 3 on c1fe457b32, and the watched branch holds under validation on Linux.

Yes, please post the fixture's diff here. It covers the write-watched branch on Linux, which none of my cases reach. I'll add it as its own commit with you as co-author, re-run agc_driver_stencil_plane_memory with and without the validation layer, and note it in the description with your two results.

AI-assisted: yes

@hugodearaujo

Copy link
Copy Markdown
Contributor

@JJenkx here it is. It applies cleanly to c1fe457b32 (git apply), and only touches tests/execution/StencilPlaneMemory.cpp: +99 lines.

  • AllocateSharedBlocks (Linux) maps the two planes from a sealed memfd. It registers that range with GuestArenaSetSharedBacking, the write watch and GuestAllocations, so the planes are imported as a dma-buf and stay write-watched. The existing private-memory fixture is a host-pointer import and is never watched. On other hosts it falls back to AllocateBlocks.
  • CheckReusedStencilPlane runs on those planes:
    1. A draw writes 0x66, then the guest fills the plane with 0x77. A read of the plane must store nothing and leave 0x77.
    2. A draw writes 0x2b, then the guest writes three 0x30-byte records. Retiring the surface over its depth plane must keep them, and the plane must not stay stale.
    3. The same with a whole read of both planes (DepthPlaneScope::Whole).
    4. A draw after the guest's records must still be stored (0x18 everywhere).
  • It's called at the end of main(), after your cases.

On your head, with the validation layer, it passes on Linux with no VUID- line, and all four cases run. Where the planes aren't write-watched (the fallback on other hosts), it only checks the first draw's store. It then prints the shared stencil plane is not write-watched here: a guest reusing the plane's memory is not exercised and returns. I forced that fallback on Linux to check: it passes, with the reuse cases skipped. So on Windows it should pass but cover nothing new.

For the trailer: Co-authored-by: hugodearaujo <hugoaraujocamara@gmail.com>

diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StencilPlaneMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StencilPlaneMemory.cpp
index b83c2ad95f..9af5921e84 100644
--- a/core/libs/prx/libSceAgcDriver/tests/execution/StencilPlaneMemory.cpp
+++ b/core/libs/prx/libSceAgcDriver/tests/execution/StencilPlaneMemory.cpp
@@ -13,7 +13,9 @@
 #endif
 #include <windows.h>
 #else
+#include <fcntl.h>
 #include <sys/mman.h>
+#include <unistd.h>
 #endif
 #include <algorithm>
 #include <array>
@@ -147,6 +149,101 @@ void CheckGuestWriteBeforeRepeatedClears(AgcDriver::VulkanDevice& device, const
     Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a stencil plane the guest wrote after its last store stayed stale");
 }
 
+#ifdef __linux__
+struct SharedPlanes {
+    int file = -1;
+    std::uintptr_t begin = 0;
+    std::size_t bytes = 0;
+    std::uint64_t offset = 0;
+};
+SharedPlanes sharedPlanes;
+
+bool ResolveSharedPlanes(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset) {
+    if (sharedPlanes.file < 0 || bytes == 0 || address < sharedPlanes.begin || address - sharedPlanes.begin >= sharedPlanes.bytes || bytes > sharedPlanes.bytes - (address - sharedPlanes.begin)) return false;
+    *file = fcntl(sharedPlanes.file, F_DUPFD_CLOEXEC, 0);
+    *offset = sharedPlanes.offset + (address - sharedPlanes.begin);
+    return *file >= 0;
+}
+#endif
+
+void* AllocateSharedBlocks(std::size_t bytes) {
+#ifdef __linux__
+    const int file = memfd_create("stencil-plane-memory", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+    Require(file >= 0, "cannot create the shared depth and stencil planes");
+    Require(ftruncate(file, static_cast<off_t>(bytes + Block)) == 0 && fcntl(file, F_ADD_SEALS, F_SEAL_SHRINK) == 0, "cannot size the shared depth and stencil planes");
+    void* raw = mmap(nullptr, bytes + Block, PROT_READ | PROT_WRITE, MAP_SHARED, file, 0);
+    Require(raw != MAP_FAILED, "cannot map the shared depth and stencil planes");
+    const auto begin = reinterpret_cast<std::uintptr_t>(raw);
+    const auto aligned = (begin + Block - 1) & ~static_cast<std::uintptr_t>(Block - 1);
+    if (aligned != begin) munmap(raw, aligned - begin);
+    if (aligned + bytes != begin + bytes + Block) munmap(reinterpret_cast<void*>(aligned + bytes), begin + Block - aligned);
+    sharedPlanes = {file, aligned, bytes, aligned - begin};
+    GuestArena::GuestArenaSetSharedBacking_nid_postfix(ResolveSharedPlanes);
+    void* block = reinterpret_cast<void*>(aligned);
+    GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(block, bytes);
+    GuestAllocations::Mutation().Add(block, bytes, true, true, true);
+    return block;
+#else
+    return AllocateBlocks(bytes);
+#endif
+}
+
+void CheckReusedStencilPlane(AgcDriver::VulkanDevice& device, const DepthFastClearHarness::Shaders& shaders, std::uint32_t swizzle) {
+    auto* blocks = static_cast<std::byte*>(AllocateSharedBlocks(DepthBytes + StencilBytes));
+    auto* depth = reinterpret_cast<float*>(blocks);
+    auto* stencil = reinterpret_cast<std::uint8_t*>(blocks + DepthBytes);
+    std::fill(depth, depth + DepthBytes / sizeof(float), 1.0f);
+    Fill(stencil, 0u);
+    const auto stencilBase = reinterpret_cast<std::uintptr_t>(stencil);
+    const Surface surface{reinterpret_cast<std::uintptr_t>(depth), stencilBase, 0, false, VK_FORMAT_D32_SFLOAT_S8_UINT, 0, 0, swizzle};
+    const auto draw = [&](std::uint8_t reference) {
+        Require(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, reference)}, PlaneExtent, Colors) == PlaneTexels, "a draw writing stencil over the reused plane did not cover it");
+    };
+    draw(0x18u);
+    Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the shared stencil plane after a draw stored nothing");
+    device.WaitIdle();
+    Require(Holding(stencil, 0x18u) == StencilBytes, "the stencil drawn into the shared plane did not reach guest memory");
+    if (!AgcDriver::GuestMemory::Watched(stencilBase, StencilBytes)) {
+        std::puts("the shared stencil plane is not write-watched here: a guest reusing the plane's memory is not exercised");
+        return;
+    }
+    constexpr std::size_t RecordBytes = 0x30;
+    constexpr std::array<std::size_t, 3> RecordOffsets{0x1000, 0x7dd0, StencilBytes - RecordBytes};
+    const auto writeRecords = [&](std::uint8_t value) {
+        for (const auto offset : RecordOffsets) std::fill(stencil + offset, stencil + offset + RecordBytes, value);
+        AgcDriver::GuestMemory::BumpCollectEpoch();
+    };
+    const auto recordsHold = [&](std::uint8_t value) {
+        return std::all_of(RecordOffsets.begin(), RecordOffsets.end(), [&](std::size_t offset) { return std::all_of(stencil + offset, stencil + offset + RecordBytes, [&](std::uint8_t byte) { return byte == value; }); });
+    };
+
+    draw(0x66u);
+    Fill(stencil, 0x77u);
+    const bool storedOverFill = !AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty();
+    device.WaitIdle();
+    Require(Holding(stencil, 0x77u) == StencilBytes, "a read of a stencil plane the guest wrote over after a draw stored the stencil drawn before the write over the guest's bytes");
+    Require(!storedOverFill, "a read of a stencil plane the guest wrote over after a draw stored the stencil drawn before the write");
+
+    draw(0x2bu);
+    writeRecords(0x7au);
+    AgcDriver::Graphics::RetireDepthSurfaces(device.graphicsContext().device, surface.depth, DepthBytes);
+    device.WaitIdle();
+    Require(recordsHold(0x7au), "retiring the surface stored the stencil drawn before the guest wrote records into the plane over them");
+    Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane whose retired surface's stencil was dropped stored it");
+
+    draw(0x4du);
+    writeRecords(0x3cu);
+    static_cast<void>(AgcDriver::Graphics::FlushDepthPlanes(surface.depth, DepthBytes + StencilBytes, AgcDriver::Graphics::DepthPlaneScope::Whole));
+    device.WaitIdle();
+    Require(recordsHold(0x3cu), "a whole read of the planes stored the stencil drawn before the guest wrote records into the plane over them");
+
+    writeRecords(0x5eu);
+    draw(0x18u);
+    Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane drawn after the guest's records stored nothing");
+    device.WaitIdle();
+    Require(Holding(stencil, 0x18u) == StencilBytes, "the stencil drawn after the guest wrote records did not reach guest memory over them");
+}
+
 }
 
 int main() {
@@ -255,6 +352,8 @@ int main() {
         Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a read of the stencil plane drawn after the unmap stored nothing");
         device->WaitIdle();
         Require(Holding(stencilPlane, 0x18u) == StencilBytes, "the stencil drawn after the unmap did not reach guest memory");
+
+        CheckReusedStencilPlane(*device, shaders, swizzle);
         std::puts("stencil plane memory tests passed");
         return 0;
     } catch (const std::exception& error) {

Back the planes with a shared memfd, imported as a dma-buf and write-watched,
so the watched branch runs on Linux.

Co-authored-by: hugodearaujo <175634979+hugodearaujo@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JJenkx

JJenkx commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Added @hugodearaujo's fixture as ed4ae4f33a ("test(agc): cover a reused, write-watched stencil plane on Linux", co-authored), unchanged from the diff above.

Arch Linux 7.2.11, RX 6650 XT (RADV Mesa 26.2.6), Release: agc_driver_stencil_plane_memory passes, plain and with VK_LAYER_PATH + VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (VK_LOADER_DEBUG=layer shows the layer inserted), with no VUID- messages. The new case's skip line ("the shared stencil plane is not write-watched here") does not appear, so its watched branch ran. tools/check_conventions.py passes.

AI-assisted: yes

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head ed4ae4f merged onto main 5299939 · tested 2026-11-06 16:36 UTC · 13 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,917 passed · 4 failed (0 new, 4 also fail on main) · 52 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,973 of 4,210 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (979 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 113.3 s
Failing on main too (4, not counted against this PR)
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
web_library_assets
Validation IDs already present on main (9 IDs in 587 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (587 tests): agc_driver, agc_driver_address_draw_space_reuse, agc_driver_address_passes, agc_driver_address_passes_targets_split, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, and 579 more
VUID-VkShaderModuleCreateInfo-pCode-08737 (14 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_f16_fma_denormal_modes, agc_driver_f16_fma_invalid_nan, agc_driver_float16_fma_rounding, agc_driver_float16_modifiers, agc_driver_float16_native, and 6 more
VUID-VkShaderModuleCreateInfo-pCode-08739 (14 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_f16_fma_denormal_modes, agc_driver_f16_fma_invalid_nan, agc_driver_float16_fma_rounding, agc_driver_float16_modifiers, agc_driver_float16_native, and 6 more
VUID-VkShaderModuleCreateInfo-pCode-08741 (14 tests): agc_driver_dpp16_operands, agc_driver_dpp8_pk_fmac, agc_driver_dx10_clamp, agc_driver_f16_fma_denormal_modes, agc_driver_f16_fma_invalid_nan, agc_driver_float16_fma_rounding, agc_driver_float16_modifiers, agc_driver_float16_native, and 6 more
VUID-vkCmdCopyImage-srcImage-01548 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_prepared_exports
VUID-VkPipelineShaderStageCreateInfo-module-parameter (1 test): agc_driver_float16_unused_nan_payloads
VUID-VkPipelineShaderStageCreateInfo-pSpecializationInfo-06849 (1 test): agc_driver_float16_unused_nan_payloads
Skipped (52)
agc_driver_gcn_display_present: skipped, no display: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_driver_scattered_runs: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_vertex_elected_atomic_max: skipped, the device runs vertex programs in subgroups, so they keep their cross-lane reductions
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_net_ctl_disconnected: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_device_capability: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_disabled: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_four_k: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_height_only: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_limit: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_malformed: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_host_resolution_width_only: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip_in_order: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_dcc: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not a
Reproduce
git fetch origin ed4ae4f33aca3c6775f7bfefab00b8269450210f 5299939ecfa9e186e1eb457c6f4bd133bdb087c7
git checkout 5299939ecfa9e186e1eb457c6f4bd133bdb087c7 && git merge --no-ff ed4ae4f33aca3c6775f7bfefab00b8269450210f
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@milescoviello

Copy link
Copy Markdown
Contributor

Title data point for this PR: STEINS;GATE RE:BOOT (PPSA36591) on Linux (Ubuntu 26.04, RTX 4070 Ti, NVIDIA 615.71), the GPU Heap corruption I reported on #15350. Repro: a new game, Cross every ~0.12 s through APS5_PAD_INPUT_FILE (fast text skip), 900 s runs.

Tree Result
main 9a4bcc3018 SIGSEGV at 329 s in the title's allocator, inside its GPU Heap mspace
this PR merged onto that main (6096b477de, clean merge), run 1 ran to the 900 s timeout, no heap error
the same, run 2 ran to the 900 s timeout, no heap error

Earlier main builds (e322066f82) ended the same way in 5 of 5 runs, between 333 and 413 s, with [SceLibc] A heap error is detected / GPU Heap ( 3). In a traced run on such a build, the last two write-backs over the block whose chunk the allocator then reported were stencil plane store write-backs of two depth targets (416x516 and 402x390) at that address. That's the case this PR handles: freed memory reused by the title's heap, then the stale stencil stored over it. So this PR also unblocks Steins;Gate's fast skip on Linux. I'm now running a long playthrough on it toward an ending.

AI-assisted: yes (Claude Code)

@JJenkx

JJenkx commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

@milescoviello thank you, that's a useful confirmation that the reused-memory case occurs in a real title, with the stencil plane stores traced to the corrupted chunk. I've added your Steins;Gate runs to the description's Tested section, linking your comment.

AI-assisted: yes

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants