Skip to content

Texture pool tables hold a view of a texture read through another layered type, so cube maps read by handle are no longer zero and PRAGMATA's Cabin shows his face in the Shelter - #2534

Open
computerguy526 wants to merge 1 commit into
seedds:mainfrom
computerguy526:pool-typebridge

Conversation

@computerguy526

@computerguy526 computerguy526 commented Nov 12, 2026 •

Copy link
Copy Markdown

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

In PRAGMATA's Shelter, Cabin's monitor stays black; on console it shows an animated cyan face: rounded eyes, a smile, ^ ^ when greeting, the CABIN SYSTEM logo, the stamp grid. Every cube map the game reads by handle samples zero, which removes environment reflections and light everywhere. No [RED] line points at either.

Both come from the texture pool tables (pool_gpu). There is one table per element type a shader declares, and pool_entry / pool_seen_scan gave a table only textures of its own Metal type. To Maxwell 2D, 2D-array, cube and cube-array textures are all layers, and each texture instruction carries its own dimension. The host keeps cubes and cube arrays as 2D arrays and makes cube views only for slot binds (tex_fit). So:

  • a cube read through a cube table got the dummy (Metal type 2D array, not cube);
  • Cabin's material, Character_CabinDisplay (fragment shader ecd01ea9b68f5390), samples its five sprite slots through a texture2d_array table. The sprites (Ch06000FaceMonitorUserData's fms*.tex, 256x256 BC7) are 2D textures, so the face read zero. The game sets the sprites and their animated parameters every frame; the draw itself is fine.

Reach (pool element types in the translated libraries, counted from their reflection):

Game Shaders Read a cube table Read a 2D-array table Read a 3D table
PRAGMATA 36,649 3,808 (2,831 fragment, 977 compute) 319 3,442
The Legend of Zelda: Ocarina of Time 42,848 0 2,626 (2,557 also read 2D tables) 0
Orbitals 23,432 0 11 0
Donkey Kong Bananza 22,852 no texture pool reads

What changes (nvn_mtl.m, for every game)

  • A read bitmap section per table type. The bitmap the shaders mark (dm_pool_mark) has one section per table type, and a table's head names its own section (pool_seen_gpu_t). 2D tables keep the bitmap's start, so a game that reads only 2D tables marks exactly as before.
    • The host folds each section separately (g_seen_typed, pool_seen_t), so it knows through which type a shader read an ID.
    • A shader reads each table through handles of its instruction's type, so a mark in a section is a real read of that type.
  • pool_fit: a table of another layered type holds a view of the ID's texture when a shader read the ID through that type. The view uses the layers fit_layers gives, made from the root texture as tex_fit does for a slot:
    • a 2D texture as a one-layer 2D array, and a 2D array's layer 0 as 2D;
    • a cube as a cube, and as six layers;
    • a square 2D array of 6n layers as cubes, and a cube array as layers or cubes.
  • View cache and lifetime: the view is cached per ID (palt), apart from the slot binds' alt, so the two do not take turns. It goes with the ID's views (treg_drop_views) and is rebuilt when the ID's texture changes. An ID first read through a second type is written into the existing tables in place (pool_seen_scan), as a first read is.
  • What no view serves keeps the dummy: a 2D or 3D texture read as a cube, or a non-square array read as a cube. This is the zero texture tex_fit binds for a slot; GL and Vulkan leave such a read undefined. Each such ID read is reported once: [RED] [pool-type] texture ID … is Metal type X, read through a type Y texture pool table: left out (dummy), at most 256 a run.
  • No new switch.

Tests

  • t_pool_typebridge (new, Metal validation, encoder thread off and on, 30 checks):
    • each pair, read on the GPU through the table at a given layer or face (texture2d_array, texture2d, texturecube, texturecube_array handles);
    • an ID read only through its own type gets no view in another table;
    • an ID read later through a second type goes into that table in place;
    • the view is kept and goes when the ID is emptied, with its marks;
    • the unserved reads (3D, 2D, non-square as cube) hold the dummy and are reported once.
  • Fail-without: with main's rule (no table holds another type), 8 of its checks fail: the 2D sprite, the cube, the 6-layer cube and the cube array read the dummy, and the GPU reads zero.
  • t_pool_standin (3 modes), t_pool_notes, t_pool_seen_replace, t_pool_retire, t_cube_placeholder and t_tex_fit_root pass. They use 2D tables, whose bitmap section is the one they write.
  • Reference entry: Pool tables that held only their own texture type. The PRAGMATA note links it, and the symptom index is regenerated.
  • Local CI (ci_local.sh --full, this commit merged into main 8d44aaf): host tests, t_tex, the gfx tests build, CPU-only gfx tests, TSan, ASan/UBSan, thermal, gl-imports, Metal lock, dmhost for the three profiles, the iOS compile check and build smoke all pass. The lint-c and tool-test failures are already on main 8d44aaf: lint-c's findings are in files this change does not touch, and test_tree_tar fails there too. lint-python is skipped because no Python file changed.

Regression

  • regress.py impact HEAD~1..HEAD lists The Legend of Zelda: Breath of the Wild, Pokémon Scarlet and The Legend of Zelda: Tears of the Kingdom as affected, through any change to nvn_mtl.m. Hades is broad (present path). None was run.
  • Only texture pool tables change, and only for IDs a shader reads through a type other than their texture's:
    • a game that reads only 2D tables gets the same entries;
    • a game that reads cube or 2D-array tables gets views where it read the dummy;
    • a read through 3D, MSAA or buffer types is as before.
  • Cost: one 128 KB bitmap section per table type a game reads, folded at each submission like the existing one, and one view per bridged ID.
  • In game: PRAGMATA on our branch with this change, M4 Pro, the Shelter (Cabin's menu, then free play). Cabin's face, its expressions and the reflections and lighting match the console footage, confirmed by eye against a PS5 frame of the same menu. No GPU faults. Static reach is in the table above.
  • Not run: Orbitals (2D-array reach; hosts with the change are built); the iPhone.

Co-authored with UncraftableXD.

…nother layered type, so cube maps read by handle are no longer zero and PRAGMATA's Cabin has his face

A texture pool table gave an ID only a texture of the table's Metal type. The host keeps cubes and
cube arrays as 2D arrays, so every cube read through a cube table sampled the dummy, and PRAGMATA's
Character_CabinDisplay material, which samples 2D sprites through a texture2d_array table, drew
Cabin's monitor black. The read bitmap gets one section per table type, so the host knows through
which type a shader read an ID; a table of another layered type then holds the view fit_layers
gives (2D <-> 2D array, cubes <-> 6n layers), cached per ID and dropped with its views. Reads no view
serves keep the dummy and are reported once per ID as [RED] [pool-type]. Test: t_pool_typebridge.

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 12, 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