Skip to content

fix(agc): apply the raw OOB_SELECT range rules to zero-stride vertex buffers - #1769

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
TideOPL:pr/stride0-raw-range
Oct 10, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
TideOPL:pr/stride0-raw-range

Conversation

@TideOPL

@TideOPL TideOPL commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

What

A vertex attribute whose V# has stride 0 and fewer NUM_RECORDS bytes than one element made the draw throw ("vertex fetch exceeds descriptor byte range"), so the draw was skipped.

With stride 0 the range check depends on OOB_SELECT (V# word 3, bits 28-29). The GFX10 table in Mesa ac_descriptors.c gives:

  • OOB_SELECT 2: out of range only when NUM_RECORDS == 0.
  • OOB_SELECT 3 (swizzle off): out of range when offset >= NUM_RECORDS.

This change:

  • VertexFetchOutOfRange (VertexInput.hpp) is true for a stride-0 OOB_SELECT 2 or 3 V# with no records. Draw binds a zeroed buffer of one element for such an attribute instead of planning a guest copy.
  • ZeroStrideAvailableBytes makes VertexBufferReadSize and VertexBufferExtent (the indirect-draw path) read the whole element for a stride-0 OOB_SELECT 2 V# with nonzero records. This goes through the normal guest copy, so pending GPU writes are flushed and the render-target alias check runs.
  • OOB_SELECT 3 with a fetch that only partly fits still throws, because the source does not say whether each dword is checked separately. OOB_SELECT 0 and 1 keep the current structured checks.
  • The rules are recorded in TechnicalDebt as taken from Mesa, not measured on hardware.

Why it matters: Unreal Engine binds its per-draw PrimitiveId vertex stream as a stride-0, one-record, OOB_SELECT 2 V# (V# xxxxxxxx 0000001e 00000001 20014204 in PPSA16064). The vertex shader uses the id to fetch the primitive's transform. With the draw skipped, or with the id read as zero, every mesh is placed with primitive 0's transform.

Relation to #1426: #1426 handles the same short zero-stride V#s. Its first version kept only the whole dwords below NUM_RECORDS (records & ~3), so a one-record PrimitiveId stream read as 0. A downstream branch with that rule drew every terrain chunk of PPSA16064's title scene on top of the first one: the depth prepass covered about 13% of the frame and the base pass showed see-through blocks. Reading the whole element, as OOB_SELECT 2 defines, brought depth coverage to about 37% of the 2848x1600 target. Since 7a8926c, #1426 also reads the whole element for OOB_SELECT 2, so both PRs give the same result for that case and for zero records. They differ only where no source settles the behaviour: a stride-0 OOB_SELECT 3 fetch that only partly fits, and short OOB_SELECT 0 and 1 buffers. #1426 keeps the whole dwords below NUM_RECORDS and zero-fills the rest, recorded as unmeasured in its TechnicalDebt entry; this PR leaves those cases throwing. Both PRs change the same lines of Draw.cpp, tests/Graphics.cpp and TechnicalDebt.md, so whichever merges second needs rework.

Tested

Windows 11, MinGW-w64 GCC 15.2.0, Release, BUILD_TESTING=ON, NVIDIA RTX 5070.

  • New execution test agc_driver_zero_stride_vertex_fetch. It draws through a fetch-shader vertex attribute and stores the fetched registers. Cases: OOB_SELECT 2 with one record reads the whole element; OOB_SELECT 2 with none reads zeros; OOB_SELECT 3 with a whole element in range reads it; OOB_SELECT 3 with none reads zeros; OOB_SELECT 0 with a whole element in range is unchanged. It fails on main with "vertex fetch exceeds descriptor byte range" and passes with this change.
  • agc_driver_graphics has new unit checks for VertexBufferReadSize, VertexBufferExtent and VertexFetchOutOfRange across the OOB_SELECT modes.
  • All agc_driver tests: 196/196 pass.
  • PPSA16064 (Minecraft Dungeons II) title scene: verified on a downstream branch with an equivalent change (the PrimitiveId case reads the full 4-byte element), comparing prepass depth coverage before and after. This exact branch was not run in the title.
  • Not tested on Linux or on an AMD GPU.

Checklist

…buffers

With stride 0, OOB_SELECT 2 puts a fetch out of range only when NUM_RECORDS
is 0, and OOB_SELECT 3 only when the offset is not below NUM_RECORDS (GFX10
table in Mesa ac_descriptors.c). A one-record descriptor therefore returns
its whole element: Unreal's per-draw PrimitiveId stream is such a descriptor,
and refusing it skipped the draw. A wholly out-of-range fetch reads a zero
buffer.
@github-actions github-actions Bot added docs agc libSceAgc* labels Oct 8, 2026
@fisherjoey

Copy link
Copy Markdown
Contributor

Linux results, since this was tested on Windows only. Everything passes here, including agc_driver_zero_stride_vertex_fetch.

Nobara 44 (Linux 7.2), GCC 16.2.1, Release, BUILD_TESTING=ON. PR head merged onto main 29e533a.

ctest -R agc_driver -j1
RTX 5070 Ti, driver 595.91.07 198 / 198
RADV, Ryzen 5 7600X iGPU (gfx1036), Mesa 26.2.1 198 / 198

CPU tests (-E agc_driver): 284 / 285. The one failure is guest_dynamic_loader (subprocess aborted), which fails the same way on main 29e533a.

@johnmaia

johnmaia commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Linux run on AMD, since the description lists it as not tested: Ubuntu 24.04.5 (kernel 6.8), GCC 13.3, Release, AMD Radeon RX 7600 (RADV NAVI33, Mesa 25.2.8). This PR's head c17c5ebd, merged onto main f1451396. Full ctest: 495/495 pass, with the GPU tests running on the RX 7600 (none skipped). That includes the new agc_driver_zero_stride_vertex_fetch and agc_driver_graphics with this PR's cases.

@Angel21PC

Angel21PC commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Tested this PR's commit (c17c5eb) in Poppy Playtime: Chapter 4 (PPSA25063, Unreal Engine 5), applied with the other open PRs that title needs. Windows 11, RTX 4070. It binds the same kind of V#: stride 0, NUM_RECORDS 1, a 4-byte element, OOB_SELECT 2. Without a fix its scene draws are skipped thousands of times a minute with vertex fetch exceeds descriptor byte range. With this PR none fail that check and the scene renders. I had an equivalent change and won't open it.

@github-actions

github-actions Bot commented Oct 9, 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
#1426 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp L886-888, L890-891, L903-904
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp L2658
docs/dev/TechnicalDebt.md L113
#1969 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp L886, L891, L903-904
#2253 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp L903-904
#2545 core/libs/prx/libSceAgcDriver/tests/execution/ZeroStrideVertexFetch.cpp

oneandonlydean added a commit to oneandonlydean/AnyPS5 that referenced this pull request Oct 10, 2026
oneandonlydean added a commit to oneandonlydean/AnyPS5 that referenced this pull request Oct 10, 2026
@oneandonlydean
oneandonlydean merged commit 5b6c778 into boykopovar:main Oct 10, 2026
7 checks passed
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
fix(agc): apply the raw OOB_SELECT range rules to zero-stride vertex buffers
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