Skip to content

feat(shader): implement the D16 format buffer loads - #329

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
adriadam10:feat/format-d16
Oct 4, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
adriadam10:feat/format-d16

Conversation

@adriadam10

Copy link
Copy Markdown
Collaborator

What

Nine D16 format loads were rejected by the decoder:

  • MUBUF: buffer_load_format_d16_{x,xy,xyz,xyzw} (0x80-0x83) and buffer_load_format_d16_hi_x (0x26).
  • MTBUF: tbuffer_load_format_d16_{x,xy,xyz,xyzw} (0x08-0x0b).

The ISA sweep only covered MUBUF opcodes below 0x80, which is why it missed the first four. They load a formatted element like the 32-bit forms, convert each component to 16 bits, and pack two per VGPR.

Semantics measured on an RDNA2 GPU (ROCm HSA, raw kernels; 3344 vectors per format, all matched by a Python model):

Component type 16-bit result
UNORM / SNORM f16 of the exact value v/(2^n−1), round to nearest even (SNORM clamps to −1)
USCALED / SSCALED f16 round to nearest even, overflow saturates to ±65504
32-bit FLOAT f16 round toward zero, overflow to ±65504, inf stays inf, NaN keeps its top payload bits (at least 1)
16-bit FLOAT passthrough
UINT / SINT 8/16-bit: low 16 bits; 32-bit: saturated to u16 / i16

Packing:

  • _x writes the low half and keeps the high half. _hi_x writes the high half and keeps the low half.
  • _xy and _xyzw overwrite their registers.
  • _xyz keeps the upper half of its second register.
  • Components the format lacks are 0. A descriptor-swizzle constant one is f16 1.0 (0x3c00) for non-integer formats and 1 for UINT/SINT.

Implementation:

  • MemoryInfo::d16 marks the load.
  • The backend's formatted-load path calls EmitD16FormatComponent instead of NormalizeFormatComponent. It uses integer ops only (q = (v << s) / M with the remainder for UNORM/SNORM, mantissa truncation for RTZ), so results don't depend on the driver's f32→f16 rounding.
  • Going through an f32 division and then a f16 conversion would double-round 4 of the 16-bit UNORM/SNORM values (e.g. 0xffaf). The test includes them.
  • Translation packs the halves. _x/_hi_x reuse the d16Half destination selector from feat(shader): implement the D16 byte/short loads and high-half stores #319.

D16 format stores are not part of this PR. The existing formatted stores don't convert to the format yet, and that will be a separate change.

Test agc_driver_format_d16: 16 results × 32 lanes captured on the RDNA2 GPU. It covers every component type above, the double-rounding cases, f32 specials (±inf, NaN payloads, 65520, overflow and subnormal edges), every packing form including the preserved halves, and a descriptor built in the shader with swizzle x,0,1,1.

Tested

Linux, full ctest (100) passes.

  • RADV (Radeon 660M) and lavapipe: the test passes.
  • Without the D16 conversion: 8unorm is 0xdead0000, expected 0xdead3c00. Without the f16 one constant: xyzw_hi is 0x3f800000, expected 0x3c003c00.

Checklist

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Progress report (d3ec1f1 → 5c8c5d2)

📈 GPU shader instructions: 74.96% (+3.95%, +46 implemented instructions)

✅ 46 implemented
Encoding Name
DS DS_READ_I8_D16
DS DS_READ_I8_D16_HI
DS DS_READ_U8_D16
DS DS_READ_U8_D16_HI
DS DS_WRITE_B8_D16_HI
FLAT FLAT_LOAD_SBYTE_D16
FLAT FLAT_LOAD_SBYTE_D16_HI
FLAT FLAT_LOAD_SHORT_D16
FLAT FLAT_LOAD_SHORT_D16_HI
FLAT FLAT_LOAD_UBYTE_D16
FLAT FLAT_LOAD_UBYTE_D16_HI
FLAT FLAT_STORE_BYTE_D16_HI
FLAT FLAT_STORE_SHORT_D16_HI
GLOBAL GLOBAL_LOAD_SBYTE_D16
GLOBAL GLOBAL_LOAD_SBYTE_D16_HI
GLOBAL GLOBAL_LOAD_SHORT_D16
GLOBAL GLOBAL_LOAD_SHORT_D16_HI
GLOBAL GLOBAL_LOAD_UBYTE_D16
GLOBAL GLOBAL_LOAD_UBYTE_D16_HI
GLOBAL GLOBAL_STORE_BYTE_D16_HI
GLOBAL GLOBAL_STORE_SHORT_D16_HI
MTBUF TBUFFER_LOAD_FORMAT_D16_X
MTBUF TBUFFER_LOAD_FORMAT_D16_XY
MTBUF TBUFFER_LOAD_FORMAT_D16_XYZ
MTBUF TBUFFER_LOAD_FORMAT_D16_XYZW
MUBUF BUFFER_LOAD_FORMAT_D16_HI_X
MUBUF BUFFER_LOAD_FORMAT_D16_X
MUBUF BUFFER_LOAD_FORMAT_D16_XY
MUBUF BUFFER_LOAD_FORMAT_D16_XYZ
MUBUF BUFFER_LOAD_FORMAT_D16_XYZW
MUBUF BUFFER_LOAD_SBYTE_D16
MUBUF BUFFER_LOAD_SBYTE_D16_HI
MUBUF BUFFER_LOAD_SHORT_D16
MUBUF BUFFER_LOAD_SHORT_D16_HI
MUBUF BUFFER_LOAD_UBYTE_D16
MUBUF BUFFER_LOAD_UBYTE_D16_HI
MUBUF BUFFER_STORE_BYTE_D16_HI
MUBUF BUFFER_STORE_SHORT_D16_HI
SCRATCH SCRATCH_LOAD_SBYTE_D16
SCRATCH SCRATCH_LOAD_SBYTE_D16_HI
SCRATCH SCRATCH_LOAD_SHORT_D16
SCRATCH SCRATCH_LOAD_SHORT_D16_HI
SCRATCH SCRATCH_LOAD_UBYTE_D16
SCRATCH SCRATCH_LOAD_UBYTE_D16_HI
SCRATCH SCRATCH_STORE_BYTE_D16_HI
SCRATCH SCRATCH_STORE_SHORT_D16_HI

This was referenced Oct 2, 2026
@github-actions github-actions Bot added build shader core/shader agc libSceAgc* labels Oct 2, 2026
@github-actions github-actions Bot added ci .github needs rebase Conflicts with main, rebase needed labels Oct 2, 2026
adriadam10 and others added 2 commits October 3, 2026 11:37
buffer/flat/global/scratch_load_{ubyte,sbyte,short}_d16(_hi),
buffer/flat/global/scratch_store_{byte,short}_d16_hi and
ds_read_{u8,i8}_d16(_hi), ds_write_b8_d16_hi threw in the decoder. They use
the same 16-bit destination/source select as the existing ds_read_u16_d16
and ds_write_b16_d16_hi: a load writes one half and keeps the other, a
_d16_hi store takes its data from the high half.

Co-Authored-By: Claude <noreply@anthropic.com>
buffer_load_format_d16_{x,xy,xyz,xyzw}, buffer_load_format_d16_hi_x and
tbuffer_load_format_d16_{x,xy,xyz,xyzw} convert each component to 16
bits and pack two per VGPR. The conversions follow the hardware:
unorm/snorm round the exact value to nearest even, scaled integers
round to nearest even and saturate at 65504, 32-bit floats truncate
toward zero (saturating at 65504), and 32-bit integers saturate to 16
bits. All of it is done with integer ops so it does not depend on the
driver's f16 rounding. _x and _hi_x keep the other half, xyz keeps the
upper half of its second register, and a descriptor constant one is
f16 1.0 for non-integer formats.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 3, 2026
@oneandonlydean
oneandonlydean merged commit 5988332 into boykopovar:main Oct 4, 2026
3 checks passed
@adriadam10
adriadam10 deleted the feat/format-d16 branch October 5, 2026 06:47
SP4C3B4R-8 pushed a commit to SP4C3B4R-8/AnyPS5 that referenced this pull request Oct 6, 2026
Takes the 13 gate-batch PRs that merged upstream (boykopovar#319 boykopovar#329 boykopovar#330 boykopovar#331 boykopovar#325
boykopovar#435 boykopovar#471 boykopovar#294 boykopovar#376 boykopovar#404 boykopovar#394 boykopovar#401 boykopovar#446) in place of astrobot's local
versions, as integ/gate-batch cab0eab had them: boykopovar#294 and boykopovar#376 in
astrobot's depth model (DepthTarget, state.depth), boykopovar#394 with the thick mip
tails, boykopovar#401 over astrobot's slab pool, boykopovar#471 keeping the settled single-lane
path. Also takes the rest of main since a818bee (boykopovar#532 boykopovar#340 boykopovar#486 boykopovar#484 boykopovar#316
boykopovar#243 boykopovar#334 boykopovar#434 boykopovar#437 boykopovar#470 boykopovar#373 boykopovar#452 boykopovar#455 and our upstreamed PRs, where
astrobot keeps its later revisions and env switches).

Resolution notes: relinker converter/matcher/tests are upstream's (boykopovar#432
decoder covers VEX RIP-relative followers); RecompileResult gains boykopovar#316's
hostSubgroupSize (set from request.target.subgroupSize); shader disk cache
format 9; boykopovar#452's scissor message; upstream's later live-stack and
memory-copy decode tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mgp3oZvembQziVNE2xARQK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build ci .github shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants