Skip to content

fix(agc): bound sampled array views to guest allocations - #9318

Open
MarcZX10 wants to merge 3 commits into
boykopovar:mainfrom
MarcZX10:codex/sample-view-allocation-bound
Open

MarcZX10 wants to merge 3 commits into
boykopovar:mainfrom
MarcZX10:codex/sample-view-allocation-bound

Conversation

@MarcZX10

Copy link
Copy Markdown
Contributor

What

Issue #9295 reports sampled 2D-array descriptors naming 6,145 slices when only 90 complete slices fit in the contiguous mapped guest range. Building the full sampled image can request more than 100 GiB of host image memory and abort with std::bad_alloc.

Cap sampled 1D-array, 2D-array and cube views to complete slices in the mapped range before creating the cached image. If the requested starting slice is outside the range, use slice 0 alone when at least one complete slice fits; throw when none fit. Storage views are unchanged. Regression cases cover the 90-of-6,145 boundary, an in-range starting slice and the fallback/error cases.

This addresses only the oversized sampled-view allocation described in #9295. It does not establish what the console does with out-of-range descriptors and does not resolve the issue's other stops or black frames. Related open work #6481 validates image-table entries; #6977 adds GCN tiling. Neither implements allocation-bounded sampled array views.

Tested

Linux x86_64, GCC 12.2, Release. ninja -C build agc_driver_graphics_tests passed after rebasing on main 6b6c701636. The focused RunTextureTilingTests suite passed in a standalone runner linked from the project-built objects.

The registered agc_driver_graphics_maps_fallback CTest could not reach its test body: startup rejected this host's Intel Xeon E5-1620 v2 because it lacks LZCNT, BMI1, BMI2, MOVBE, AVX2 and FMA. No Vulkan or GPU test is claimed. python3 tools/check_conventions.py --base upstream/main passed.

Checklist

  • Based on current main; no other open PR implements the same behavior
  • One topic per PR; follow-ups go in a new pull request
  • No comments in code except technical debt
  • Unimplemented paths throw; silent stubs are listed in technical debt
  • No notes, investigation files or images added to the repository
  • New third-party code is a submodule built from source (none added)
  • Generic behavior, not specific to one title
  • Depends on: none
  • Fixes: none (related to The Last of Us Part I (PPSA03396): runs past loading on Linux/RADV but presents only black frames #9295; the issue has other unresolved stops and black frames)
  • Unblocks: none demonstrated
  • GPUs tested: none; the test runner could not start on this host CPU
  • AI-assisted: yes

@simonbbby

Copy link
Copy Markdown
Contributor

Independent native Linux check at exact head b21d32e41d8488b7557a0556bd9141de3f8fae3e: the registered tests reach their bodies on this CPU, and a bounded-array execution diagnostic passes on NVIDIA, RADV and lavapipe.

  • Built agc_driver_graphics_tests; both registered tests, agc_driver_graphics and agc_driver_graphics_maps_fallback, pass. This runs the new BoundArrayWindow assertions in the normal executable, beyond the standalone runner reported above. These are CPU/mock-Vulkan tests; their pass is not a GPU claim. The unrelated heap-mirror cases explicitly report unavailable/unwatched memory.
  • Built the existing agc_driver_image_array_window_past_layer_limit_tests; its stock form passes on all three ICDs.
  • For a separate full-path check, I temporarily adapted that fixture to a linear 1D-array descriptor with baseArray=0, lastArray=6144 (6,145 declared slices), over host-mapped memory registered through GuestAllocations::Mutation::Add for only 90 × 256 = 23,040 bytes. Width is 8, format 56. The descriptor still describes all 6,145 slices; no call to BoundArrayWindow was added to the test. Logs show layers=90 in the texture actually created by the sampled-resource path.
  • It dispatches the existing shader twice per process, checking both image_sample_lz and image_load results for 32 lanes against exact RGBA texels. The load inputs include slice 89; sample inputs include 89, 89.6 and 90, checking the final registered slice and the sampler's clamp. The adapted fixture passes 3/3 fresh processes on RTX 3090, 3/3 on gfx1036/RADV and 3/3 on lavapipe.

Integration negative control: keeping that diagnostic and the PR's helper/tests, but replacing only Graphics/src/ShaderResources.cpp with merge-base 6b6c7016365271245e8b81c1b734cac2d65f664d, makes all three ICDs exit 1 with an image view of more array slices than maxImageArrayLayers is not implemented. Restoring the PR's file makes all three pass again. This checks that bounding is connected to the sampled-resource path; it does not reproduce the title's >100-GiB allocation attempt.

All temporary edits were then removed, the stock graphics and array-window targets rebuilt, both registered graphics tests rerun successfully, and the stock GPU fixture rerun successfully on all three ICDs. Checkout is clean at the tested head.

Ubuntu 24.04.3, GCC 13.3, Release; RTX 3090 / NVIDIA 595.99.02, gfx1036 (1002:13c0) / RADV Mesa 25.2.8, lavapipe Mesa 25.2.8 / LLVM 20.1.2. Each GPU process pins its ICD and uses ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1. No Khronos validation layer is installed here. The GPU diagnostic covers the shared bounding path through a 1D array, not a 2D-array/cube title render, and does not establish the console's out-of-range behavior or resolve #9295's black frames.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

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 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 6146 docs/dev/debt/libSceAgcDriver.md
PR 6185 docs/dev/debt/libSceAgcDriver.md
PR 6186 docs/dev/debt/libSceAgcDriver.md
PR 6192 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6194 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6223 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6412 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6743 docs/dev/debt/libSceAgcDriver.md
PR 6804 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6926 docs/dev/debt/libSceAgcDriver.md
PR 6970 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6973 docs/dev/debt/libSceAgcDriver.md
PR 6987 docs/dev/debt/libSceAgcDriver.md
PR 6989 docs/dev/debt/libSceAgcDriver.md
PR 6995 docs/dev/debt/libSceAgcDriver.md
PR 7002 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7085 core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7668 docs/dev/debt/libSceAgcDriver.md
PR 7978 docs/dev/debt/libSceAgcDriver.md
PR 8212 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 8592 docs/dev/debt/libSceAgcDriver.md
PR 8707 docs/dev/debt/libSceAgcDriver.md
PR 8963 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9012 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9020 docs/dev/debt/libSceAgcDriver.md
PR 9335 docs/dev/debt/libSceAgcDriver.md
PR 9356 docs/dev/debt/libSceAgcDriver.md
PR 9387 docs/dev/debt/libSceAgcDriver.md
PR 9508 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9592 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9672 docs/dev/debt/libSceAgcDriver.md
PR 9673 docs/dev/debt/libSceAgcDriver.md
PR 9674 core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9675 docs/dev/debt/libSceAgcDriver.md
PR 9700 docs/dev/debt/libSceAgcDriver.md
PR 9712 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9742 docs/dev/debt/libSceAgcDriver.md
PR 9748 docs/dev/debt/libSceAgcDriver.md
PR 9852 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9884 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9891 core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9892 docs/dev/debt/libSceAgcDriver.md
PR 9893 docs/dev/debt/libSceAgcDriver.md
PR 9895 docs/dev/debt/libSceAgcDriver.md
PR 9897 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9898 docs/dev/debt/libSceAgcDriver.md
PR 9915 docs/dev/debt/libSceAgcDriver.md
PR 9950 docs/dev/debt/libSceAgcDriver.md
PR 9999 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 10000 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 10049 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10064 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10193 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10233 docs/dev/debt/libSceAgcDriver.md
PR 10256 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10351 docs/dev/debt/libSceAgcDriver.md
PR 10359 docs/dev/debt/libSceAgcDriver.md
PR 10442 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 10469 docs/dev/debt/libSceAgcDriver.md
PR 10472 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10489 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 10492 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10497 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10556 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 10743 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10779 docs/dev/debt/libSceAgcDriver.md
PR 10894 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 11104 docs/dev/debt/libSceAgcDriver.md
PR 11346 docs/dev/debt/libSceAgcDriver.md
PR 11444 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 11551 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 11557 docs/dev/debt/libSceAgcDriver.md
PR 11587 docs/dev/debt/libSceAgcDriver.md
PR 11825 docs/dev/debt/libSceAgcDriver.md
PR 11901 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 11995 docs/dev/debt/libSceAgcDriver.md
PR 11998 docs/dev/debt/libSceAgcDriver.md
PR 12120 docs/dev/debt/libSceAgcDriver.md
PR 12452 docs/dev/debt/libSceAgcDriver.md
PR 12565 docs/dev/debt/libSceAgcDriver.md
PR 12567 docs/dev/debt/libSceAgcDriver.md
PR 12568 docs/dev/debt/libSceAgcDriver.md
PR 12575 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 12662 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 12745 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 12764 docs/dev/debt/libSceAgcDriver.md
PR 12792 docs/dev/debt/libSceAgcDriver.md
PR 12840 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 12855 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 12904 docs/dev/debt/libSceAgcDriver.md
PR 12921 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 12944 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 12980 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 13021 docs/dev/debt/libSceAgcDriver.md
PR 13023 docs/dev/debt/libSceAgcDriver.md
PR 13146 docs/dev/debt/libSceAgcDriver.md
PR 13147 docs/dev/debt/libSceAgcDriver.md
PR 13151 docs/dev/debt/libSceAgcDriver.md
PR 13248 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 13474 docs/dev/debt/libSceAgcDriver.md
PR 13793 docs/dev/debt/libSceAgcDriver.md
PR 13796 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 13809 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 13858 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 13863 docs/dev/debt/libSceAgcDriver.md
PR 13920 docs/dev/debt/libSceAgcDriver.md
PR 13928 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 13929 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 13963 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 13991 docs/dev/debt/libSceAgcDriver.md
PR 13994 docs/dev/debt/libSceAgcDriver.md
PR 14005 docs/dev/debt/libSceAgcDriver.md
PR 14018 docs/dev/debt/libSceAgcDriver.md
PR 14032 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 14085 docs/dev/debt/libSceAgcDriver.md
PR 14212 docs/dev/debt/libSceAgcDriver.md
PR 14219 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 14220 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 14243 docs/dev/debt/libSceAgcDriver.md
PR 14248 core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp
docs/dev/debt/libSceAgcDriver.md
PR 14262 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 14265 docs/dev/debt/libSceAgcDriver.md
PR 14296 docs/dev/debt/libSceAgcDriver.md
PR 14302 docs/dev/debt/libSceAgcDriver.md
PR 14311 docs/dev/debt/libSceAgcDriver.md
PR 14344 docs/dev/debt/libSceAgcDriver.md
PR 14561 docs/dev/debt/libSceAgcDriver.md
PR 14621 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 14628 docs/dev/debt/libSceAgcDriver.md
PR 14815 docs/dev/debt/libSceAgcDriver.md
PR 14838 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 14868 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 14871 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp
PR 14880 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 14891 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 14909 docs/dev/debt/libSceAgcDriver.md
PR 14943 docs/dev/debt/libSceAgcDriver.md
PR 14944 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
docs/dev/debt/libSceAgcDriver.md
PR 14950 docs/dev/debt/libSceAgcDriver.md
PR 14963 docs/dev/debt/libSceAgcDriver.md
PR 14964 docs/dev/debt/libSceAgcDriver.md
PR 14976 docs/dev/debt/libSceAgcDriver.md
PR 14982 docs/dev/debt/libSceAgcDriver.md
PR 15058 docs/dev/debt/libSceAgcDriver.md
PR 15059 core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp
core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp

@wachi35676

Copy link
Copy Markdown
Contributor

Windows run of the full sampled path on NVIDIA and AMD, plus one problem in the cube fallback. Head b21d32e merged onto main a6dcea927 (clean merge, 17c1e949b).

Host: Windows 11 (10.0.26300), Ryzen 5 9600X, WinLibs GCC 15.2.0, Release. GPUs: NVIDIA RTX 5070 Ti (617.42) and AMD Radeon RDNA2 iGPU 1002:13c0 (proprietary 32.0.21042.62, ANYPS5_GPU=AMD). ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1.

Registered tests. The 81-test related set (agc_driver_graphics*, image_*array*/*cube*, array_layer_target*, layered draws and others) matches main on both GPUs. ImageArrayWindowPastLayerLimit.cpp does not compile under MinGW on main (alignas(65536): "requested alignment exceeds object file maximum 8192"), so it cannot run here as committed.

Local GPU probe (not for the PR). I adapted that fixture to build here: alignas(256), linear 1D array, width 8, format 56, descriptor slices 0..6144, and GuestAllocations::Mutation::Add registers only 90 × 256 bytes. image_sample_lz and image_load run on 32 lanes. Same results on both GPUs:

  • base 0: texture created with layers=90; all 32 lanes return the slice clamped to 0..89 (sample layers 0, 1, 45, 88.4, 89, 89.6, 90, 3000).
  • base 6: layers=84; sample layer 0→slice 6, 1→7, 45→51, 88.4→89; load layer 0→slice 6. Lanes stay inside the registered range.
  • base 100 (past the range): layers=1; every lane returns slice 0. This is the documented fallback, not the requested slices.
  • Negative control: with ShaderResources.cpp from main, NVIDIA throws an image view of more array slices than maxImageArrayLayers is not implemented for all three bases. AMD does not throw: it creates layers=6145/6139/6045 textures and reads past the registered range. So on this AMD driver the PR changes behaviour from "whole descriptor" to "bounded".

Cube fallback. BoundArrayWindow treats kCube like an array of single faces. I added a unit probe to RunTextureTilingTests (16×16, format 56, 64 KiB standard tiling):

  • cube faces 6..11 with 6 faces mapped → baseArray=0 lastArray=0, a 1-face view.
  • faces 0..11 with 4 faces mapped → lastArray=3, 4 faces.

The texture path then requires at least 6 slices for a cube (Texture.cpp, "guest cube texture view holds fewer than 6 array slices"). So a cube whose base cube lies past the range, or whose range holds fewer than 6 faces from the base, now throws. On main it builds the full view. For kCube, rounding the available faces down to whole cubes and falling back to faces 0..5 would avoid that. I did not run a cube draw on the GPU.

AI-assisted: yes (Claude Code); results above were run on this machine.

@github-actions github-actions Bot added docs agc libSceAgc* labels Oct 22, 2026
@milescoviello

Copy link
Copy Markdown
Contributor

@MarcZX10 a related case from Ghost of Tsushima Director's Cut (PPSA02225). This PR doesn't cover it, by design, but you may want to know about it.

On @EagleBearerYS's test/ghost-9742-9717 stack with #8115, #8121, #8213 and #8608, the title binds this sampled view about 9 minutes in, during its opening:

[gpu] sampled view 0x1564050000 (320x1280 format 71, dimension 4, tile 7, slices 0..899) covers 4781 MiB
[gpu] view 0x1564050000 of 4781 MiB lies in the guest allocation 0x1500000000..0x1598600000 (2438 MiB, ...): 157 of its 900 slices fit in the range
  • Host heap grows by about 3 GB within 2 s while it is built, and the run ends at the 20 GiB cap. That happened twice, at 572 s and at 563 s.
  • I merged this PR's b21d32e onto that stack, and the view is still built at full size. dimension 4 is k3D, and BoundArrayWindow only bounds 1D-array, 2D-array and cube views, so a 3D texture whose depth runs past its allocation isn't capped.
  • I don't think the same bound fits a 3D view. The depth coordinate is normalized, so trimming the depth to 157 slices would move every sample. Array layers are indexed, which is why your bound keeps them right.
  • One option would be keeping the full extent and filling only the depth slices inside the allocation. That avoids the host copy of guest memory past the allocation, but still allocates the full image.

I have no evidence of what the console reads past the allocation, or of which depth range the shader samples. I'll look at the shader's sampling before suggesting anything concrete.

Release libs, Linux, RTX 4070 Ti, NVIDIA 615.71.09.

AI-assisted: yes (Claude Code)

@milescoviello

milescoviello commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

@MarcZX10 a follow-up on the 3D view from Ghost of Tsushima I mentioned above, in case it helps decide whether BoundArrayWindow should cover k3D.

I tried a local cut, not for a PR, on the same stack with this PR merged: a sampled k3D view whose depth runs past its guest allocation keeps only the depth slices that fit. The 320x1280x900 R64KBX view became 157 slices, the same count as above.

  • Without it, every run was killed at 563 to 572 s when that view's upload pushed the heap up by 3 GB in 2 s.
  • With it, the run went past that point and on to the intro cinematic after the brightness screen. It was then killed by the same 20 GiB memory cap at 1622 s, this time while building the opening's pipelines. There was no refusal or crash in between.

The catch is that a shorter depth changes what a normalized z samples, so cutting the depth alone isn't a correct fix. It only shows that the oversized volume is the one thing stopping the title there. I can test a proper k3D handling on this title if you want to try one.

AI-assisted: yes (Claude Code)

@EagleBearerYS

Copy link
Copy Markdown
Contributor

@milescoviello thanks for the depth-cut run and the offer to test. Here's proper k3D handling for Ghost of Tsushima Director's Cut (PPSA02225): test/ghost-9742-9717-3d (bc32434). It's the test/ghost-9742-9717 base you ran plus one commit, b3ee668.

  • What it changes: the view keeps all 900 slices, so r sampling is unchanged. Only the slabs with committed memory go through the snapshot and upload. The rest are filled with zeros, which is what main already reads for uncommitted pages. If only the 157 slices are committed, the host side of this view drops from about 9.3 GiB to about 1.6 GiB.
  • How to tell it ran: a new line next to covers … MiB says how many slabs were uploaded and how many were zero-filled. If it doesn't appear, the view took main's path. That path is kept for DCC, depth-compare, sRGB-expanded and storage-image sources, and knowing that would help too.
  • Platform: Linux only for now. It passes its tests on Linux (run). On Windows, that base fails to build an older test that main has since fixed.

If it gets past 572 s, the next stop's log would be great. The 1622 s cap while the opening's pipelines build looks like a separate question, so a log of it would help as well.

@MarcZX10 this sits in the 3D snapshot and upload path, not in #9318's view bound. It doesn't touch BoundArrayWindow, so the two compose.

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS thanks, that's the right shape: keep all 900 slices and fill only what's committed. The run is queued for after the current ones, in a couple of hours.

It uses the same stack as my earlier runs (837cf70ec0: your 3ec696f7fe with #8115, #8121, #8213 and #8608), with bc32434751 and #9318 merged in and my depth cut left out. The only extra is #9991, which shares registered shader state for memory and doesn't touch the 3D path. Settings are as before: 1500 s, journal replay off, cross pressed throughout, 20 GiB cap. I'll post the new slab line, whether it gets past 572 s, the next stop with its log, and a log of the pipeline-build phase if the cap ends it.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS a first, inconclusive run of bc32434751: it never got to the view. This was the stack I described, with this branch and #9318 merged and a cold shader cache. The 20 GiB cap killed it at 982 s, before the opening bound the 320x1280x900 view, so neither your slab line nor the dimension 4 line appeared. From about 500 s it was building the opening's pipelines with no presents, and the heap grew by roughly 14 MB/s from 4.2 GB to 9.6 GB. That growth is the same wall my earlier runs died at; this cold run just hit it before the view.

The cache is warm now, and warm runs reach that point sooner with more room left, so I'll rerun it the same way and post again.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS the warm rerun didn't reach the view either. It was killed at 807 s by the same cap during the opening's pipeline builds, so bc32434751 is still untested on this title. Neither run printed a dimension 4 view or your slab line.

The growth there is now traced. A jemalloc profile in that phase puts about 1 GiB live under Driver::tagVariantRuns: the draw cache's copies of what each stage read, which went from 0.4 MiB to over 150 MiB by variantBytes within a few minutes. Your #9129 bounds exactly that, so my next build has #9129 merged. If it gets far enough to bind the 3D view, I'll build the same with your bc32434751 in place of my depth cut and report the slab line.

AI-assisted: yes (Claude Code)

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@MarcZX10 Please preserve complete-cube bounds in TextureTiling.cpp:508–526 before Texture.cpp:346–354 creates a cube-compatible image. Five mapped faces currently produce a cube window with fewer than six layers, and an out-of-range base can produce a one-layer fallback; both violate the six-layer minimum for VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT (VUID-VkImageCreateInfo-flags-00949). Round down to complete cubes and reject when no complete cube fits, or leave cubes outside this array-only change. Add regressions for fewer than six faces, an out-of-allocation base cube with six faces mapped, and a partial trailing cube, and verify that none creates an invalid cube-compatible image.

@EagleBearerYS

Copy link
Copy Markdown
Contributor

@milescoviello an update to the 3D branch, for when a build with #9129 gets Ghost of Tsushima Director's Cut (PPSA02225) to the 320x1280x900 view: test/ghost-9742-9717-3d-v2 (fd1b755) replaces bc32434751. It's the same base with #10064's current commits on it. Besides the slab path, a cached slab view is now rebuilt when a page inside a kept slab is decommitted, so it reads zeros there instead of the old texels. It also has two test fixes from main, so it builds on Windows too.

The slab line next to covers … MiB is the same as before, so it still shows whether the view took the slab path. On GitHub's runners (run), the 20 agc_driver_partially_ tests pass on Linux, 3 runs; on Windows they build and skip without a Vulkan device.

@MarcZX10
MarcZX10 force-pushed the codex/sample-view-allocation-bound branch from b21d32e to 8ed6ddb Compare October 27, 2026 07:40
@MarcZX10

Copy link
Copy Markdown
Contributor Author

Fixed on 8ed6ddb (rebased onto current main). BoundArrayWindow now handles cube views separately: the available layers are rounded down to complete cubes, a view with no complete cube in its allocation is rejected (a cube view has no complete cube in its registered guest allocation), and a base that lies past the allocation, or leaves fewer than six layers, falls back to the first cube (layers 0 to 5) instead of one layer, so the bounded window never creates a cube-compatible image with fewer than six layers. The new checks in TextureTiling.cpp cover five mapped faces (rejected), a partial trailing cube (cut to 6), a base cube past the allocation with six faces mapped (first cube), a view inside its allocation (unchanged, also when a partial cube follows), and check that every bounded cube window holds a multiple of six layers. agc_driver_graphics passes on Linux (lavapipe, host CPU gate disabled in a throwaway copy) and fails with the previous TextureTiling.cpp; conventions pass. I did not run VK_LAYER_KHRONOS_validation on these cases and did not run Windows or a GPU.

@milescoviello

Copy link
Copy Markdown
Contributor

@EagleBearerYS thanks, and sorry for the late reply. I've noted fd1b755145 (test/ghost-9742-9717-3d-v2) in place of bc32434751. Ghost of Tsushima still doesn't reach the 320x1280x900 view here. My last runs with #9129 in the stack stopped at the opening fleet scene on the 19–20 GiB cap: 372 s on a warm cache and 1258 s on a cold one, with no refusal. I'll run your branch as soon as a build gets past that point, and I'll post whether the slab line shows up.

@MarcZX10 for the new cube cases, I've queued agc_driver_graphics at 8ed6ddb407 and at its base 951e5963a4 on a GPU (RTX 4070 Ti, NVIDIA 615.71.09, Linux). Both sides run with VK_LAYER_KHRONOS_validation, and I'll post the results and any VUIDs that are new on the head here.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

@MarcZX10 the run I offered gives less than I expected. On Linux (GCC 15.2, Release), agc_driver_graphics passes at 8ed6ddb407 and at its base 951e5963a4, with your new cube checks from TextureTiling.cpp in the head build.

The Khronos validation half turned out to be empty, though. agc_driver_graphics_tests doesn't link libvulkan, and strace shows it never opens a Vulkan driver here. Its [gpu] lines and the vkAllocateMemory failure it prints come from the fixture's own device. So VK_LAYER_KHRONOS_validation never loaded, and "no VUIDs on either side" says nothing about the six-layer rule. Checking that on a real device would need a GPU test that creates one of these bounded cube views. If one exists or you add one, I'll run it on the RTX 4070 Ti with the layer.

AI-assisted: yes (Claude Code)

@MarcZX10

Copy link
Copy Markdown
Contributor Author

@milescoviello thank you for running it and for checking whether the layer was actually loaded. You are right that it was not, and that is why the earlier note said I had not run VK_LAYER_KHRONOS_validation on these cases: the new cube checks cover BoundArrayWindow's descriptor arithmetic (a bounded cube view holds a multiple of six layers, none is rejected, a base past the allocation falls back to the first cube), not an image creation. There is no GPU test in this PR that creates a bounded cube view, so a validation run cannot say anything about the six-layer rule yet. Building one needs a sampled cube view through ShaderResources against a registered allocation on a real device; I have not written it and cannot promise it here, and I will say in this thread if I add one. Until then the VUID-00949 point is argued from the arithmetic, not measured.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@MarcZX10 the complete-cube rounding reads right now. One question about where the bound comes from. cachedTexture (ShaderResources.cpp:714-716) caps the view at the end of the single GuestAllocations range that holds its base. Ranges aren't merged, though: a range carries its own r/w/gpu flags, so a GuestAllocationsProtect over part of a mapping, or two mappings placed back to back, leave adjacent ranges with no gap between them. A texture pool built that way (or one whose tail was later reprotected) gets its arrays truncated at the first range end. The slices past it then sample the last kept slice, while main reads them correctly. The debt line also says "the contiguous registered guest range containing their base", which is the behaviour I'd expect. Could you extend availableBytes across following ranges that start exactly where the previous one ends (for example, keep calling GuestAllocationsLookup_nid_postfix(end, false) while it returns a range that starts at end), and add a BoundArrayWindow/cachedTexture case with two adjacent ranges?

@Pacuka

Pacuka commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

@MarcZX10 thanks for this, I tested it on The Last of Us Part I (PPSA03396), the game from #9295. Linux, RX 6650 XT (8 GB), 32 GB RAM. I cherry-picked the five commits onto an older main (base adca8cfa2f), so this is not your exact head.

What got better: the 2560x1440 arrays with 40-142 layers (500-600 MiB each) that we mirrored before are gone from our big-texture log. I could also drop the local 512 MiB array clamp I had added for #9295.

One problem: after about 6.5 minutes in the menu, the game ended at the new Require in BoundArrayWindow:

[gpu] driver failure: compute shader 0x12b65a1f00: AGC graphics: an array view has no complete slice in its registered guest allocation
[videoout] presentation failed: compute shader 0x12b65a1f00: AGC graphics: an array view has no complete slice in its registered guest allocation
terminate called after throwing an instance of 'std::runtime_error'

So a sampled array view whose base is inside a registered range, but with less than one slice left in that range, ends the whole game. Before this PR the same view was just mirrored. Would it be better to keep the view unbounded (or bind it absent) in that case instead of throwing? Locally I catch it in cachedTexture and keep the old view, but that run ran out of memory before it reached the same point, so I can't confirm it yet.

This may be the same thing @oneandonlydean asked about: if the texture continues into a neighbouring range, the first range alone holds less than one slice.

@MarcZX10

MarcZX10 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Bug found — sampled array bounds truncate adjacent allocations

The current head can stop at the first registered range and can throw when fewer than one full slice remains, matching the two newly reported failure modes.

Findings

Blocking

  • cachedTexture derives its byte limit from only the range containing the base, so directly adjacent registered ranges are not included. BoundArrayWindow also throws when that limit contains no complete slice, which can terminate the title instead of handling the view safely. The relevant code is pinned at ShaderResources.cpp and TextureTiling.cpp. The minimal correction is to extend the bound across directly adjacent readable ranges, stop at a gap, and provide a non-throwing path when no complete slice can be represented.

Non-blocking

  • The reported agc_driver_graphics test did not create a real Vulkan image, so it does not verify the cube-compatible image-layer VUID on a GPU.

Not verified

I did not run a new build or test for these findings in this response. Pacuka's report was from a cherry-pick onto older base adca8cfa2f, not the current PR head.

To get this to a good state

I need to update the bound and fallback behavior, add regressions for adjacent ranges and incomplete slices, and rerun the focused tests on the new head before treating these findings as resolved.

@TechLevelZero

TechLevelZero commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 8ed6ddb merged onto main e6f5730 · tested 2026-11-01 17:26 UTC · 23 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,432 passed · 5 failed (0 new, 3 also fail on main, 2 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,482 of 3,148 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 (931 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s, ⚠️ agc_driver_graphics_maps_fallback 33.4 s, ⚠️ agc_driver_graphics 30.6 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 1 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 (5 IDs in 484 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (484 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 476 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-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 8ed6ddb40781f559cf60b855a99c1e794147b42d e6f57307fa7f47165d7fc1bee945abdb2f6816e7
git checkout e6f57307fa7f47165d7fc1bee945abdb2f6816e7 && git merge --no-ff 8ed6ddb40781f559cf60b855a99c1e794147b42d
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.

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

Correction (2026-11-01): the title-ID/log demand below is not a CONTRIBUTING requirement; Unblocks: none is allowed. The technical points below are unaffected.

This is an approximation of what the hardware does with an out-of-range descriptor, and the PR says so itself ("does not establish what the console does with out-of-range descriptors"). Under the exact-or-throw rule that is the part that should not merge as written.

  1. BoundArrayWindow invents a view. When the requested range runs past the registered allocation it silently shrinks depthOrLastArray to the last whole slice, and when baseArray is past the end it rewrites the view to slice 0 (or cube 0). A guest that really samples slice 5000 of a 90-slice allocation gets slice 0 back with no log and no throw. Nothing in the description says the console returns slice 0, or that the descriptor's slice count is meant to be clamped by the allocation size at all. If the general behaviour is unknown, the choices are to throw (as the availableLayers == 0 case already does) and record the gap in docs/dev/debt/libSceAgcDriver.md, not to pick a substitute view. The 6,145 versus 90 case in #9295 is a case where the descriptor probably does not mean what the code assumes; a throw naming the descriptor fields would show that, a clamp hides it.

  2. The clamp is keyed to registered allocations, not to what is mapped. GuestAllocationsLookup_nid_postfix(baseAddress, false) decides the limit. A texture whose layers span two adjacent registered allocations, or memory mapped outside the registered list, gets truncated at the first allocation's end, which would produce a wrong image for a valid descriptor rather than an out-of-range one. Is the lookup guaranteed to return the whole contiguous mapping? The PR text says "contiguous mapped guest range", the code uses the registration.

  3. Per-view cost. The lookup and DescribeSurface now run on every sampled 1D/2D array and cube view built, not only on the oversized ones, and guestBytes = 0 is set whenever the window changes. Please state how often this path runs per draw in a real scene, or gate the lookup behind the existing size check (layers * layerBytes against a cheap bound) so the common case does not pay for it.

  4. Evidence. No title ID and log line showing a title reaches this on main; the justification is issue #9295 only. The regression cases test the clamp's arithmetic, but nothing checks that an in-range descriptor on an allocation that is not the first registered one is left alone.

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reviewed — four review points checked, clamp reaches every array view

Three of CnRJay's four technical points are confirmed by the code at head 8ed6ddb, and the fourth is only partly checked.

What I ran

Head 8ed6ddb40781f559cf60b855a99c1e794147b42d (live, OPEN). Read-only: gh pr view, gh api GET on comments and reviews, git grep and git show at the head. Nothing was built or run.

Results

Check Result Note
Hosted checks 4 success, none failing NVIDIA run by TechLevelZero passed on this head
Code reads (points 1-3) Confirmed Read at head, lines below
Test coverage (point 4) Partly checked Arithmetic tests found, placement not checked

Findings

  • Point 1, the window invents a view: confirmed. TextureTiling.cpp:523-526 resets baseArray to 0 when the start is past the available layers, and :528 shrinks depthOrLastArray to the last whole slice. No log or throw on those paths. The only throw is for zero layers (:521).
  • Point 2, registration-based limit: confirmed. ShaderResources.cpp:714 calls GuestAllocationsLookup_nid_postfix(resource.baseAddress, false) and clamps to range->bytes - offset (:716). The limit is the registered range, not the mapping.
  • Point 3, per-view cost: confirmed. The lookup at ShaderResources.cpp:714 runs for every 1D or 2D array or cube view, with no size check first. :720 sets guestBytes = 0 when the window changes. How often this runs per draw in a real scene is not measured.
  • Point 4, evidence: tests/TextureTiling.cpp:535-564 cover the clamp arithmetic on a 90-layer allocation and cube cases. I did not confirm whether any test puts an in-range view on a non-first registered allocation. Not verified.

Sources

  • GuestTextureResource BoundArrayWindow(const GuestTextureResource& viewed, std::uint64_t availableBytes) {
    const bool layered = viewed.dimension == TextureDimension::k2DArray || viewed.dimension == TextureDimension::k1DArray || viewed.dimension == TextureDimension::kCube;
    if (!layered) return viewed;
    Require(viewed.baseArray <= viewed.depthOrLastArray, "an image view's first array slice lies past its last");
    const auto geometry = DescribeSurface(viewed);
    Require(!geometry.thick && geometry.layerBytes != 0, "an array window must use thin slices");
    auto availableLayers = availableBytes / geometry.layerBytes;
    if (availableLayers > viewed.depthOrLastArray) return viewed;
    const bool cube = viewed.dimension == TextureDimension::kCube;
    if (cube) availableLayers -= availableLayers % 6u;
    Require(availableLayers != 0, cube ? "a cube view has no complete cube in its registered guest allocation" : "an array view has no complete slice in its registered guest allocation");
    auto bounded = viewed;
    if (viewed.baseArray >= availableLayers || (cube && viewed.baseArray + 6u > availableLayers)) {
    bounded.baseArray = 0;
    bounded.depthOrLastArray = cube ? 5u : 0u;
    return bounded;
    }
    bounded.depthOrLastArray = static_cast<std::uint32_t>(availableLayers - 1u);
    return bounded;
    }
  • std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const std::uint32_t> words, const GuestTextureResource& requested, VkComponentMapping components, std::uint64_t guestBytes = 0, bool depthCompare = false, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D) {
    auto resource = requested;
    if (resource.dimension == TextureDimension::k2DArray || resource.dimension == TextureDimension::k1DArray || resource.dimension == TextureDimension::kCube) {
    if (const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(resource.baseAddress, false)) {
    const auto offset = resource.baseAddress - range->address;
    if (offset < range->bytes) resource = BoundArrayWindow(resource, range->bytes - offset);
    }
    }
    resource = ArrayLayerWindow(resource, context.limits.maxImageArrayLayers);
    if (resource.baseAddress != requested.baseAddress || resource.baseArray != requested.baseArray || resource.depthOrLastArray != requested.depthOrLastArray) guestBytes = 0;
  • const auto bounded = BoundArrayWindow(outsideAllocation, geometry.layerBytes * 90u);
    Require(bounded.baseAddress == outsideAllocation.baseAddress && bounded.baseArray == 0 && bounded.depthOrLastArray == 89, "a sampled array view must stop at the registered allocation boundary");
    auto boundedWindow = outsideAllocation;
    boundedWindow.baseArray = 6;
    Require(BoundArrayWindow(boundedWindow, geometry.layerBytes * 90u).depthOrLastArray == 89, "a sampled array window must preserve its in-range starting slice when bounded");
    boundedWindow.baseArray = 90;
    const auto firstSlice = BoundArrayWindow(boundedWindow, geometry.layerBytes * 90u);
    Require(firstSlice.baseArray == 0 && firstSlice.depthOrLastArray == 0, "a sampled array window starting past its allocation must fall back to its first slice");
    reject([&] { static_cast<void>(BoundArrayWindow(boundedWindow, geometry.layerBytes - 1u)); }, "no complete slice");
    auto cubes = outsideAllocation;
    cubes.dimension = TextureDimension::kCube;
    cubes.baseArray = 0;
    cubes.depthOrLastArray = 11;
    const auto cubeLayers = [](const GuestTextureResource& window) { return window.depthOrLastArray - window.baseArray + 1u; };
    reject([&] { static_cast<void>(BoundArrayWindow(cubes, geometry.layerBytes * 5u)); }, "no complete cube");
    const auto trailing = BoundArrayWindow(cubes, geometry.layerBytes * 8u);
    Require(trailing.baseArray == 0 && trailing.depthOrLastArray == 5 && cubeLayers(trailing) % 6u == 0, "a cube view must be cut to the complete cubes of its allocation");
    auto cubesPastAllocation = cubes;
    cubesPastAllocation.baseArray = 12;
    cubesPastAllocation.depthOrLastArray = 17;
    const auto firstCube = BoundArrayWindow(cubesPastAllocation, geometry.layerBytes * 6u);
    Require(firstCube.baseArray == 0 && firstCube.depthOrLastArray == 5, "a cube view starting past its allocation must fall back to its first complete cube");
    auto cubesNearEnd = cubes;
    cubesNearEnd.baseArray = 6;
    const auto nearEnd = BoundArrayWindow(cubesNearEnd, geometry.layerBytes * 8u);
    Require(cubeLayers(nearEnd) >= 6u && cubeLayers(nearEnd) % 6u == 0, "a cube view whose last cube is incomplete must still hold complete cubes");
    const auto fittingCubes = BoundArrayWindow(cubes, geometry.layerBytes * 12u);
    Require(fittingCubes.baseArray == 0 && fittingCubes.depthOrLastArray == 11, "a cube view within its allocation keeps its descriptor");
    const auto cubeAbove = BoundArrayWindow(cubes, geometry.layerBytes * 13u);

Not verified

  • Console behavior for out-of-range descriptors (no hardware).
  • Whether the lookup always returns the full contiguous mapping (point 2 asks this; the code only shows the registration is used).
  • The #9295 descriptor values and any title log.
  • Per-draw frequency of the lookup (point 3).

MarcZX10 and others added 3 commits November 5, 2026 21:19
A cube-compatible image needs at least six layers, so a cube view cut at its allocation boundary is rounded down to complete cubes, rejected when none fits, and falls back to its first cube instead of one layer when it starts past the allocation.
Fewer than six mapped faces is rejected, a partial trailing cube is cut off, a base past the allocation falls back to the first cube, and a view inside its allocation keeps its descriptor.
@MarcZX10
MarcZX10 force-pushed the codex/sample-view-allocation-bound branch from 8ed6ddb to 99a6c08 Compare November 5, 2026 21:26
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Nov 5, 2026
@TechLevelZero

Copy link
Copy Markdown
Contributor

❌ NVIDIA GPU test: failed

PR head 99a6c08 merged onto main 55064c8 · tested 2026-11-05 22:53 UTC · 34 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,899 passed · 6 failed (1 new: 1 not compared, 3 also fail on main, 2 unstable on main) · 51 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,956 of 4,105 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 (976 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s

Failing tests

This PR changes test files, so each failing test was also built and run on main with only the PR's test changes applied. ✅ there means the failure comes from the PR's other changes; ❌ means the test fails on main too, so this run does not show the PR caused it.

Test With this PR Same test on main 55064c8 (main + this PR's test changes only)
agc_driver_submit_progress ❌ failed not compared: the PR's tests do not build without its other changes
agc_driver_submit_progress: Failed (also failed when re-run alone), assertion and diagnostics
ctest: Failed

[write-watch] host imports resolve write protection: no (0 of 4 scratch pages written after a GPU read, 0 after a GPU write, 4 after the import); imported ranges stay watched
[write-watch] host imports resolve write protection: no (0 of 4 scratch pages written after a GPU read, 0 after a GPU write, 4 after the import); imported ranges stay watched
submission lost its real GPU completion store
[Shaders/ShaderRegistry.cpp:901 ReportPreparedAtUse] Compute shader 0x585464b61100 was not registered; preparing its artifact at dispatch
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 1 of the last 10 main runs
agc_submit_progress_no_timeline: 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
Passed on retry (1, failed in the parallel run, passed when re-run with a 360 s timeout; not counted against this PR)
agc_submit_progress: Failed in the parallel run; also passed only on retry in 2 other runs in the last 7 days
Validation IDs already present on main (9 IDs in 592 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (592 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 584 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 (51)
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_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 available in current SDL video driver (offscreen) or platform
video_out_flip_deferred: skipped, no
Reproduce
git fetch origin 99a6c086009dcb968bc28b1f0d4eb7718d8da604 55064c81fb67060082e3e9f6f4262ba348d95a0a
git checkout 55064c81fb67060082e3e9f6f4262ba348d95a0a && git merge --no-ff 99a6c086009dcb968bc28b1f0d4eb7718d8da604
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
# the failing tests, on this merge and on main:
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc_driver_submit_progress)$' --parallel 1 -V

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.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Hi @MarcZX10, thanks for the rework at 99a6c08600. I think the rebase lost the call site, though. At this head BoundArrayWindow is declared and tested, but nothing in the driver calls it. cachedTexture in ShaderResources.cpp still builds the view with ArrayLayerWindow alone (lines 843-844, same as main), so a 6,145-slice descriptor over 90 mapped slices would still request the full image. Before the rebase, the call sat just before ArrayLayerWindow and looked up the registered guest range.

The two earlier points need that call site to exist before they can be checked:

  • The bound should cover adjacent registered ranges, not just the single one GuestAllocationsLookup_nid_postfix returns, so an array laid out across back-to-back ranges isn't cut at the first range end.
  • When no complete slice or cube fits, BoundArrayWindow still throws (TextureTiling.cpp:794). That was the in-game abort reported on 28 Oct, and you mentioned a non-throwing path then.

Could you restore the caller with those two changes?

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.

9 participants