Skip to content

fix(agc): bind guest buffer views that start off a DWORD boundary - #1270

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
Celegans12:fix/agc-unaligned-buffer-views
Oct 8, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
Celegans12:fix/agc-unaligned-buffer-views

Conversation

@Celegans12

@Celegans12 Celegans12 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What

A V# whose base is not 4-byte aligned within its GPU owner can't be bound: GuestBufferMemory::Descriptor throws guest buffer view off the storage buffer offset alignment is not DWORD aligned, as the shader adds the offset below the view only in whole dwords.

  • Descriptor binds from the offset alignment below the view (at least 4), passes the whole remainder and ranges over the dwords around it (ViewBytes); lone copies bind the same way. An owner off a DWORD boundary throws, as does a view whose dwords cross its owner's end.
  • The buffer specialization (cache key, disk cache format 8) records the V# base's low two bits. Through such a V#, raw loads join two dwords (glc dwordx2 too), stores merge into both by compare-exchange, bounds checks include the second dword and s_buffer_load drops the two bits, as RDNA2 (gfx1035) does.
  • Formatted loads and stores through it throw when the base breaks the element's alignment (min(element bytes, 4)), where gfx1035 faults the wave; atomics throw. Other buffers emit the same SPIR-V.
  • PPSA26344 v1.008 skips about 18 DRAW_INDEX_2 per present into the G-buffer with this error; its 146 programs with such views only make 16_FLOAT (base +2) and 8-bit formatted loads through them.
  • perf(agc): baseline shader recompilation on registration #639 (draft) replaces this path with runtime V#s; perf(agc): reuse immutable buffer copies and batch uploads #1364 and perf(agc): group resource metadata allocations by lifetime #1366 also edit GuestBufferMemory. perf(agc): pipelined draws on by default #1202 adds a copy path next to the snapshot size this rounds with ViewBytes; the merge should keep the rounded size.

Tested

  • Windows 11, MinGW-w64 GCC 15.2.0, based on main c5e1018: configure -DBUILD_TESTING=ON, full build, --target libs, ctest:
  • 396/396 tests pass (ctest --output-on-failure)
  • Same build on lavapipe (Mesa 26.2.4 llvmpipe, 8-wide subgroups, VK_DRIVER_FILES): 395/395 pass; agc_driver_flat_store is left out there because it takes several minutes on Windows llvmpipe
  • agc_driver_buffer_unaligned_base: raw and 8-bit formatted loads and stores, scalar loads, and 16_UINT formatted ones at even addresses through views 1-3 bytes off; 16_UINT accesses at odd addresses and 32_UINT and 16_16_UINT loads off 4 bytes throw. agc_driver_graphics: the offsets and ranges. Both fail on main with that error.
  • PPSA26344 v1.008 with the null-SRT change: two 2,700 s runs have no not DWORD aligned skips, where a 900 s run without this change skipped 17 DRAW_INDEX_2 per present (4,314).
  • Not tested: Linux (CI), console behaviour.

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 in TechnicalDebt
  • 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
  • AI-assisted: yes

@fisherjoey

Copy link
Copy Markdown
Contributor

Linux results, since this was tested on Windows only. agc_driver_buffer_unaligned_base and agc_driver_graphics pass on both GPUs here.

Nobara 44 (Linux 7.2), GCC 16.2.1, Release, BUILD_TESTING=ON. This PR merged onto main df16c4c, plus #1329. Without #1329 the NVIDIA proprietary driver fails 100 GPU tests on main from the dma-buf probe.

result
RTX 5070 Ti, driver 595.91.07, ctest -R agc_driver -j1 167 / 167
RADV, Ryzen 5 7600X iGPU (gfx1036), Mesa 26.2.1, same 166 / 167. The failure is agc_driver_shader_clock, which is #985: it fails intermittently on main on this iGPU too
CPU tests (-E agc_driver) 240 / 242: guest_force_tls_destructor (fails on main too) and guest_pthread_attr (fails intermittently under load on main too, passes alone)

@Celegans12

Copy link
Copy Markdown
Contributor Author

Thanks, that's the Linux and RADV coverage this one was missing. Good to know the shader_clock failure is #985 and not this.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Measured on RDNA2 (gfx1035, HW oracle) with V# bases 0-3 bytes off a dword boundary:

  • Raw buffer_load_dword/dwordx2 (with and without glc), ushort and ubyte read the exact bytes at base + offset. The range check works per dword: a dword that is partly past num_records returns 0. Raw stores (dword, short, byte, dwordx2, tbuffer 8_UINT) write the exact bytes and drop a dword that is partly out of range. s_buffer_load_dword reads (base & ~3) + (offset & ~3). All of this matches what the PR emits and what BufferUnalignedBase expects.
  • Formatted accesses fault when the address isn't aligned to the element. tbuffer_load_format_x 16_UINT at an odd address, 32_UINT or 16_16_UINT at an address that isn't a multiple of 4, and tbuffer_store_format_x 16_UINT at an odd address all fault the wave (the kernel hangs). The same instructions work once the address is aligned (16_UINT base +1 offset 1, 32_UINT base +1 offset 3, 8_UINT at any address). With views +1 and +3, tbuffer_load_format_x 16_UINT ... offset:2 lands on odd addresses, so the hardware faults there. The PR returns data instead, and BufferUnalignedBase.cpp:108 pins that data. Under "exact or throw", formatted loads and stores through a V# whose baseMisalignment isn't a multiple of the element's alignment (min(element bytes, 4)) should throw at recompile time, and the test should check that they throw. The 8_UINT store row can stay.
  • docs/dev/TechnicalDebt.md:305: the raw behaviour is now measured, so the line can say so (gfx1035 on Linux, whose alignment mode is UNALIGNED; the console's mode is still unknown). Keep the part about vector offsets that aren't a multiple of 4: the hardware reads the exact bytes there, while the recompiler counts dwords from the V# base. That comes from main, not from this PR.

Build, ctest (435/435) and the negative run (both agc_driver_graphics and agc_driver_buffer_unaligned_base fail without the change) pass here. The conflicts with #1364, #1366 and #1202 are the usual ones between open PRs: whichever lands second rebases.

A V# whose base is not a multiple of 4 bytes from the start of its GPU
owner threw in GuestBufferMemory::Descriptor, because the shader added
the offset below the view only in whole dwords.

Descriptor now binds from the offset alignment below the view and
passes the whole remainder. Such a view's range covers the dwords
around it, and its descriptor region starts and ends on dword
boundaries, so a lone view copied to its own buffer binds the same way.

The buffer specialization records the V# base's low two bits. Raw
vector loads through such a V# join two dwords, glc dwordx2 loads
included, stores merge their bytes into both with compare-exchange
loops, and bounds checks include the second dword. s_buffer_load
ignores the low two bits, as SrtEvaluator does. Formatted accesses
whose element alignment (min(element bytes, 4)) the base breaks throw,
as they fault the wave on RDNA2, and so do atomics.
@Celegans12
Celegans12 force-pushed the fix/agc-unaligned-buffer-views branch from 246e0c8 to 38e89df Compare October 8, 2026 12:11
@Celegans12

Copy link
Copy Markdown
Contributor Author

Thanks for running these on real hardware. The formatted-access faults were the one part I had no way to check, and you're right that the old test pinned data the GPU never returns.

I've pushed 38e89df. Formatted loads and stores through a V# whose base misalignment isn't a multiple of the element's alignment, min(element bytes, 4), now throw at recompile time. BufferUnalignedBase checks the throw for 16_UINT loads and stores through views +1 and +3, and for 32_UINT and 16_16_UINT loads through +1, +2 and +3. It also checks that 16_UINT through +2 and the 8-bit formats still work. I moved the old 16_UINT slot to 8_UINT so the shared program runs at every offset, and the 8_UINT store row is unchanged. On the previous head, all ten throw cases recompile without an error. The TechnicalDebt entry now says the raw and s_buffer_load behaviour is what you measured on gfx1035 (Linux, alignment mode UNALIGNED, console mode unknown). The part about offsets that aren't a multiple of 4 is kept as its own line, since that's main's behaviour.

I also checked what Yotei does with these views. Its 146 programs that bind one only read them with formatted 16_FLOAT loads at base +2 and 8-bit formats, so none of them reaches the new throw.

Could you take another look when you have time?

Details
  • Yotei (PPSA26344 v1.008): I recompiled the 2,669 captured requests (request corpus plus the drv-8b dumps) with a probe on every buffer access through a V# whose low two base bits aren't zero. The 146 programs are 144 vertex shaders and 2 LS shaders. All 173 access sites are buffer_load_format_x with the V#'s own format, idxen with a dynamic index (strides 8 to 40), a dynamic soffset and immediate offset 0. The formats are 16_FLOAT at base +2 (129 sites), 8_UINT at +2 (41), and 8_UNORM at +2 (1) and +3 (2). None of them is raw, a store or s_buffer_load.
  • The same 2,669 requests recompiled with this change: the 146 programs all build, and the only failures are the same 17 requests that fail without it (unrelated SRT and image-table errors).
  • Throw cases (all through tbuffer_*_format_x, offen with lane * 4): 16_UINT load at offset 2 and store at offset 0, views +1 and +3; 32_UINT and 16_16_UINT loads at offset 0, views +1, +2 and +3. The message is "is a formatted buffer access through a V# whose base is not aligned to its element".
  • Passes on NVIDIA and lavapipe, and on the merge with current main.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Checked the formatted-access throw added in 38e89df against the earlier gfx1035 measurements: load and store paths both reject a V# base that breaks the element's alignment, the test pins the throws plus the 16_UINT +2 and 8-bit passes, and TechnicalDebt carries the measured raw behaviour. Build and ctest (451/451) pass, and agc_driver_graphics / agc_driver_buffer_unaligned_base fail without the change. Merging once CI is green.

@adriadam10
adriadam10 merged commit cba8d86 into boykopovar:main Oct 8, 2026
4 of 5 checks passed
@Celegans12
Celegans12 deleted the fix/agc-unaligned-buffer-views branch October 8, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants