Skip to content

fix(agc): keep guest bytes when a stale depth plane is flushed - #16373

Open
NFSS10 wants to merge 1 commit into
boykopovar:mainfrom
NFSS10:fix/stale-depth-plane-flush
Open

NFSS10 wants to merge 1 commit into
boykopovar:mainfrom
NFSS10:fix/stale-depth-plane-flush

Conversation

@NFSS10

@NFSS10 NFSS10 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

What

A depth surface keeps its mirror registered after the guest releases the memory it covers, so a flush driven by later GPU work over that range stores pending results into live guest data. A pending fast clear made it silent: the storing synchronisation returned before the guest-write check, so the clear value (zero) was written over whatever the guest now keeps there. On PPSA27616 the fault read through a TLSF bin whose node sat inside a 64 KiB depth plane (plane+0x7ff8) while TLSF control pointed at the guest heap.

  • Each plane unit records the backingSerial of the allocation registered over it at every synchronisation, and a flush drops a unit whose allocation changed instead of storing into it. Per unit, because the rest of the plane can still be the guest's.
  • A storing synchronisation (SyncFromMemory(resource, storing)) no longer lets a pending clear skip the guest-write check: it drops the cleared units and forgets the clear for the surface.
  • retireStoringStale, which retires a depth plane after it was sampled and overwritten, applies the same unit rule.
  • The draw path is unchanged: storing=false keeps the previous early return.
  • The technical debt entry records the residual hole (reuse inside one unchanged registration with no write to the range since the last synchronisation) and that the stencil plane keeps its own rule, which fix(agc): keep guest writes over a stale stencil plane #14969 changes.

Related: #14969 covers the stencil half of the same bug and #15621 touches SyncFromMemory and the plane member block; this change does not include the stencil plane on purpose.

Tested

  • Windows 11 Pro 22631, MinGW build.
  • PPSA27616 (Bendy and the Ink Machine), a recorded pad session replayed through APS5_PAD_INPUT_FILE.
  • Result: the crash no longer reproduces. On main all 11 runs crashed; with this change none of the 17 did.
  • main (e3a14a86e4 and d23df92b38): 11 runs, all fatal, 0xc0000005 on UnityGfxDeviceWorker, rip 0x1417f2bfb, reading through a TLSF bin whose node is inside a depth plane.
  • This commit: 5 runs, no fatal, the last one after the rebase onto e8b1bd3d7f. Counting the two intermediate builds that carry the same depth rule: 17 runs, no fatal.
  • In 9 of those 17 runs the new guard logs the condition it exists for ([gpu] depth plane 0x257810000+0x40000 was re-registered with GPU results pending; they are dropped) and the session carries on.
  • ctest -R 'depth|storage|write_tracking|guest_memory': 124/124 on this branch. The same suite passes on main and on this branch with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (that comparison ran on the immediately preceding main, d23df92b38, whose DepthSurface.cpp is identical); neither log contains was not found but was requested by env var VK_INSTANCE_LAYERS and neither contains a VUID- line.

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 7900 GRE, driver 32.0.21013.1000, Windows 11 Pro 22631
  • AI-assisted: yes

A depth surface keeps mirroring guest memory after the guest has released
and reused it, so a flush triggered by later GPU work over that range stores
its pending results into live guest data. A pending fast clear made it worse:
the storing synchronisation returned before the guest-write check, so the
clear value was written over whatever the guest now keeps there.

Record, per plane unit, the backing serial of the allocation registered over
it at every synchronisation, and drop a unit whose allocation changed before
storing. A storing synchronisation no longer skips the guest-write check when
a clear is pending: the units the guest wrote are dropped and the clear is
forgotten for the surface. The sampling path that retires a stale plane
applies the same unit rule.
@NFSS10
NFSS10 force-pushed the fix/stale-depth-plane-flush branch from ad72b96 to 5de1309 Compare November 6, 2026 13:20
@github-actions github-actions Bot added docs agc libSceAgc* labels Nov 6, 2026
@JJenkx

JJenkx commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Windows check of head 5de1309fd0, merged onto main 1ff0fe98aa (dea78930cd, clean merge).

Setup: Windows 11 Enterprise 22621, WinLibs GCC 15.2 UCRT, Release, Radeon RX 6700 XT, AMD driver 26.10.1. Run through the Windows test queue with ANYPS5_REQUIRE_VULKAN=1 and Vulkan validation layers 1.4.363. The hosted Windows check does not run these GPU tests on pull requests.

This PR changes DepthSurface.cpp and adds no test, so I ran the existing depth-plane GPU tests, with all six of their executables rebuilt from this merge (agc_driver_depth_plane_{buffer_memory,fill,fill_discard,guest_layers,partial_store,sampling}_tests):

  • All 16 agc_driver_depth_plane_* tests pass in 3 of 3 runs on the real device, none skipped.
  • The Vulkan device was created in each run, and there were no VUID- errors.

AI-assisted: yes.

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

Read it against DepthSurface.cpp on main. The unit rule and the storing/non-storing split look right to me. One cost point and one coverage note.

  1. The refresh runs on every sync, including the draw path. SyncFromMemory now refreshes unitBacking with one GuestAllocationsLookup_nid_postfix per 64 KiB unit, before the early return. That's one registry lock and one shared_ptr copy per unit. It's reached from the depth-target bind (if (mirrorable(target)) surface->SyncFromMemory(...)) and the sampled path, not only from flushes. A 3840x2160 D32 plane is about 500 units, so about 500 locked lookups per bind. Two existing APIs cover it:
    • GuestAllocationsLookupEach_nid_postfix does the whole plane under one lock.
    • GuestAllocationsGeneration_nid_postfix changes on every mapping mutation, so a surface that stores the generation it filled unitBacking at can skip the refresh when nothing was mapped or unmapped since.
      On a draw-heavy title the second one makes this free.
  2. Reuse inside one registration. The debt entry already says it isn't covered. For what it's worth, STEINS;GATE RE:BOOT (PPSA36591, #15350) hits that case with storage images: the game's GPU Heap mspace reuses blocks inside one mapped direct-memory range, so backingSerial stays the same there. That's a separate fix, not something this PR should take on.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

Runs of this PR on two NVIDIA machines, ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1, ^agc_driver_depth_(storage_reuse|plane_partial_store|storage|plane_sampling|fast_clear_submit):

Machine Tree Result
Linux, RTX 4070 Ti, NVIDIA 615.71.09, GCC 15.2, Khronos layer 1.4.341 main 9a4bcc3018 13 of 13 pass, 11 devices created, no VUIDs
merge 94ae49bb46 13 of 13 pass, 11 devices created, no VUIDs
Windows 11 VM, GT 1030, NVIDIA 582.78, WinLibs GCC 15.2 merge-base e8b1bd3d7f 12 of 12 pass in each of 3 runs
head 5de1309fd0 12 of 12 pass in each of 3 runs

On the VM the pattern also matched agc_driver_depth_storage_dispatch, whose executable I hadn't put in the build list, so a stale binary ran there on both sides (0xc0000139); it isn't counted. This PR also merges cleanly together with #16368, which edits retireStoringStale too.

AI-assisted: yes (Claude Code)

@blawesom

blawesom commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Linux/RADV validation-layer run of head 5de1309fd0 merged onto main 9fb6149d12d (clean), adding the Linux arm to your Windows verification.

Setup. CachyOS x86-64, kernel 7.3.1, AMD Radeon RX 9070 XT (RADV GFX1201), Mesa 26.2.5, Vulkan 1.4.363 loader with Khronos validation layer 1.4.363, GCC 16.2.1, Ninja, Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON. Both arms ran your focused pattern (depth|storage|write_tracking|guest_memory) sequentially on the same GPU with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 and VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation.

Results.

  • Main arm: 124 of 124 pass. PR arm: 124 of 124 pass, identical outcomes per test.
  • The runs exercise the new guard's log path ([gpu] storage image ...: memory no longer registered; pending ranges dropped), so the unit-drop rule is taken on this vendor too.
  • Neither log contains a VUID- line or was not found but was requested by env var VK_INSTANCE_LAYERS.

The PPSA27616 crash itself remains Windows-verified by your replay runs; this arm covers the depth/storage regression suite on a second vendor with no validation regressions.

AI-assisted: yes

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 5de1309 merged onto main 9fb6149 · tested 2026-11-06 18:51 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,941 passed · 4 failed (0 new, 3 also fail on main, 1 unstable on main) · 54 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,999 of 4,235 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 (990 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s, ⚠️ agc_shader_sampler_limits 33.5 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 1 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 (9 IDs in 594 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (594 tests): agc_driver, agc_driver_address_draw_space_reuse, agc_driver_address_draw_space_reuse_verify, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, and 586 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 (54)
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_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_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_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: DisplayW
Reproduce
git fetch origin 5de1309fd0f8fbbc562d7205b91fa2722d806b6d 9fb6149d12d51e8dc769943c828f2f23635acb4d
git checkout 9fb6149d12d51e8dc769943c828f2f23635acb4d && git merge --no-ff 5de1309fd0f8fbbc562d7205b91fa2722d806b6d
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.

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.

5 participants