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

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

Conversation

@computerguy526

@computerguy526 computerguy526 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

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
seedds added a commit that referenced this pull request Nov 13, 2026
…nother layered type, so cube maps read by handle are no longer zero (PR #2534)

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 table type and dropped with the
ID's views. One view per type: a table stands over submissions while nothing is registered, so a
texture read through two other types (a cube as 2D and as a cube, a cube array as a cube and as a
cube array) keeps the view each table holds. Reads no view serves keep the dummy and are reported
once per ID as [RED] [pool-type]. Test: t_pool_typebridge (10 of 32 checks fail without the bridge,
2 with one view per ID).

Co-authored-by: computerguy526 <355510486+computerguy526@users.noreply.github.com>
Co-authored-by: uncraftablexd <352372032+uncraftablexd@users.noreply.github.com>
@seedds

seedds commented Nov 13, 2026

Copy link
Copy Markdown
Owner

Landed on main as 3852186, with you and uncraftablexd as co-authors. Thanks for the diagnosis, the reach table and the test.

Changes on top of yours:

  • One view per table type. palt held a single view per ID. A texture read through two types other than its own (a cube as 2D and as a cube, a cube array as a cube and as a cube array) is in tables of both at once, and pool_gpu keeps a table over submissions while nothing is registered. So building the second table freed the view the first table still named. palt, palt_of and palt_rid are now arrays indexed by pool_lk (2D, 2D array, cube, cube array). t_pool_typebridge has two new checks for this, which fail with one view per ID; 10 of its 32 checks fail without the bridge.
  • Merged with main's buffer-texture check (tbuf_lead, RED_ONCE(167)) in pool_entry, and with the t_vpool_first_map Makefile entry from nvn: inherit aliased virtual pool flags and bound sampler pool IDs #2519.
  • Warning key: main took RED_ONCE key 168 meanwhile, so the view-creation line is now 169 (g_red_once[170]).

Not run: a regression run (no game had libraries for the current shader chain), and PRAGMATA in game with the per-type views. A retest of Cabin's face and the reflections on main would be welcome.

@seedds seedds closed this Nov 13, 2026
@computerguy526

Copy link
Copy Markdown
Contributor Author

Retested on main with your per-type views. Cabin's face and the reflections are right.

Setup: M4 Pro, macOS 26.6.2. The host is main 9ccbd6b (3852186, 529cf68 and 529c5fa all included) plus our PRAGMATA branch's local patches: the open #2509 and #2477, our sparse/residency safety and the pipeline-archive fixes. The shader library was rebuilt for the new chain from the DLC-inclusive split: 36,779 shaders, all ok. Played on the Mac by the user.

  • Cabin's face: shows in the Shelter, with the same expressions as the PS5 footage of the Stamp Club menu.
  • Reflections and lighting: the cube-map reads come back. No black reflections, and nothing looks wrong against the console.
  • Opening run: about 8 minutes from a new game, through the cockpit cutscene. No GPU faults, no crash, no shader load failures.
  • Played save: Shelter and the first areas. No faults, the pipeline prepare is 0.34 s with a warm archive, and residency is about 6.4 GB.
  • [pool-type] … left out lines: 11 IDs in the played-save session.
    • Ten are one 1x1 2D texture (ser 5, format 0x25) read through 3D tables.
    • One is a 32x32 3D texture read through a 2D table.
    • Neither has a faithful view, so they keep the dummy as designed. I saw no visible effect.

Co-authored with UncraftableXD.

@computerguy526

Copy link
Copy Markdown
Contributor Author

Also checked Ocarina of Time (Switch 2) on main with the per-type views.

Setup: M4 Pro, macOS 26.6.2. Host is main 9ccbd6b plus our local OoT patches, library rebuilt for that chain. Walk/camera routes on three saves (Kakariko, the Kokiri Forest mist and foliage path, Hyrule Field), timed runs interleaved against our previous play setup, 15 runs in all.

  • Reach: 2,626 OoT shaders read the 2D-array table, all of them 2D textures read through it.
  • Visible change: none we could find. Foliage, haze, shadows and the Kakariko props match the old setup in burst frames from each route. Nothing new missing, no new artifacts.
  • Faults: none (no PageFault, watchdog, loop cap or gpuEvent).
  • Numbers: 59-60 fps, p50 16.66 ms on every route. GPU p50 is 0.6 to 1.7 ms lower than our old setup. One Hyrule Field run had 8 frames over 50 ms on the new build; its repeat had none and the old setup had 5 in its own second run, so we read it as noise. (One run per cell, and the new build ran with our residency/wired limits set, so treat the timing as rough.)
  • [RED] [pool-type] lines: 20 to 250 per run, all "texture … is Metal type 7, read through a type 2 texture pool table: left out (dummy)", small 128x72 3D textures. No visible effect.

Co-authored with UncraftableXD.

@computerguy526

Copy link
Copy Markdown
Contributor Author

A follow-up to the OoT note above: the 20 to 250 [pool-type] lines per run were not real reads. They were read marks left from the texture an ID held before; OoT re-registers its IDs with transient targets. e5fb155 (@seedds) clears an ID's typed marks when its texture changes type, which is the same cause we had traced. On Kakariko, a host with the same idea took them from 70 to 0, with the frames unchanged.

Co-authored with UncraftableXD.

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.

2 participants