Skip to content

A 1D array texture uploads one row per layer, not its layer count of rows - #2399

Open
computerguy526 wants to merge 1 commit into
seedds:mainfrom
computerguy526:tex1d-array-rows
Open

computerguy526 wants to merge 1 commit into
seedds:mainfrom
computerguy526:tex1d-array-rows

Conversation

@computerguy526

Copy link
Copy Markdown

Related to the PRAGMATA bring-up, ccd834e (@seedds).

A 1D array (NVN target 3) is created as a one-row 2D array whose layer count is the NVN height (tex_desc). The upload paths took that same NVN height as each layer's row count. So every layer went up as height rows into a one-row slice. Metal's API validation refuses these copies (destinationOrigin.y + sourceSize.height must be <= height). Without validation the GPU writes past each slice, into the next layers.

Found on PRAGMATA: its light-profile lookup table is a 512-wide R16F 1D array of 64 layers, read by the deferred lighting. It went up as 64 copies of 64 rows. The junk rows made a light flash across a wall when the camera crossed a distance band. That was one of two causes of the flicker on my branch. The other was a host experiment of mine that main does not have. With both fixed, the flicker was gone by eye on the route where it showed (macOS 26, M4 Pro).

What changes

  • tex_lh gives a level's row count as the Metal texture holds it: 1 for a 1D or 1D array texture, the NVN height otherwise.
  • The upload paths use it: pkg_upload_'s byte count, its GPU deswizzle and CPU deswizzle loops, and the sparse texture's tile grid and tile uploads.
  • Nothing else changes. 1D textures (target 0) already had height 1. Region-based operations on 1D arrays (WriteTexels, texture copies, clears) are not part of this PR.

Tests

  • t_partial_upload gets two 1D array cases, R16F 1D array (512 x 64 layers, PRAGMATA's shape) and RGBA8 1D array with levels, with and without the direct (GPU) upload. Run under MTL_DEBUG_LAYER=1 as before. On main the first 1D array case aborts in validation: (destinationOrigin.y + sourceSize.height)(64) must be <= height(1). With the fix, all 4,551 checks pass.

Regression

  • regress.py impact picks The Legend of Zelda: Breath of the Wild, Pokémon Scarlet and The Legend of Zelda: Tears of the Kingdom (the profiles with a usage manifest, through nvn_mtl.m). They were not run.
  • Reach: only textures with NVN target 3 take another path. Every other target gets the same row count as before.

…rows

A 1D array (NVN target 3) is made as a one-row 2D array whose layer count is
the NVN height (tex_desc), but the upload paths took that height as each
layer's row count: every layer went up as `height` rows into a one-row slice.
Metal's validation refuses the copy ((destinationOrigin.y + sourceSize.height)
(64) must be <= height(1)); without it the GPU writes past each slice into the
next layers. PRAGMATA's light-profile lookup table, a 512-wide R16F 1D array
of 64 layers read by the deferred lighting, went up as 64 copies of 64 rows,
and the junk rows flashed a light across a wall.

tex_lh gives a level's rows as the Metal texture holds them (1 for 1D and 1D
array textures); pkg_upload_'s byte count and both deswizzle loops, and the
sparse texture's tile grid and tile uploads, use it. Other targets keep their
row count.

t_partial_upload: two 1D array cases (R16F 512 x 64 layers, and RGBA8 with
levels), direct and CPU uploads. Under MTL_DEBUG_LAYER=1, main aborts on the
copy above; with the change 4,551 checks pass.

Co-authored-by: uncraftablexd <352372032+uncraftablexd@users.noreply.github.com>
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation game: 0400C76024710000 A game profile labels Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0400C76024710000 A game profile nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant