Shader census: the agl archives' graphics and mesh programs, and the libraries' resource archives #727

Closed
hckrman101 wants to merge 1 commit from shader-census-agl-lib into main
hckrman101 commented 2026-11-08 18:52:09 +00:00 (Migrated from github.com)

Shader census: the agl archives' graphics and mesh programs, and the libraries' resource archives

On a fresh first start, every shader program still translated in play on a short Kokiri Forest walk came from shaders the census never read:

  • the graphics and mesh stages of the agl shader archives (ApplicationPackage's AglShader.sharcb and FoliageShader.sharcb): only their compute stages were read;
  • the libraries' resource archives in romfs/Lib/ (agl_resource: agl_common, agl_technique_pfx, agl_technique_shdw; gsys_resource; tera_resource): not walked at all. These hold agl's post effects and shadows.

Change

  • sharc_programs(): in a .sharcb, each stage has a record: u32 code offset (from the code region at header +0x1c), u32 0, u32 control-section size (0xe0 or 0xf8), s32 control-section offset relative to the record. Control sections shared by several stages are stored once, so a record may point a long way back. A program's records follow one another in stage order (vertex then fragment; task, mesh, fragment; compute alone), and the census groups them that way. sharc_computes() stays, for the compute stages it already found.
  • archives() also walks Lib/*/.
  • tests/test_shader_census.py: synthetic records (a shared control section, a vertex-only program, task/mesh/fragment, compute) and the Lib folder.

This record format is inferred from the game's archives, not documented. A record is taken only when the control section has NVN's magic, its sizes are sane, and its code lies inside the archive.

Effect (RTX 3090, Linux, fresh first start: no program, pipeline or driver caches)

  • Shader list: 31,985 → 38,741 programs (488 → 538 MB).
  • On main, a fresh run met 30 stages in play that were in no shader list. All 30 are in the new one, as are the 36 met in an earlier run.
  • After the change, one fresh run had 0 unseeded misses in play (main: 13 graphics + 5 compute programs from stages not in the list). It also built 52 graphics pipelines in play, against 81–89 in comparable main runs.
  • Cost: first-start shader preparation takes about 15–20 s longer (78 s vs 55–68 s here), on the first start of a build only. Peak memory at play is unchanged (~9.3 GB).

These are early numbers from few runs. Most runs on current main were cut short by an unrelated NVIDIA device loss (kernel Xid 31 MMU faults), which also happens without this change. More runs will follow once that is resolved.

Game-free checks: tests/run_all.py --strict passes (84 passed, 4 skipped for game files or a Windows device). The census step make_game runs gives the same list on the real romfs.

## Shader census: the agl archives' graphics and mesh programs, and the libraries' resource archives On a fresh first start, every shader program still translated in play on a short Kokiri Forest walk came from shaders the census never read: - **the graphics and mesh stages of the agl shader archives** (ApplicationPackage's `AglShader.sharcb` and `FoliageShader.sharcb`): only their compute stages were read; - **the libraries' resource archives in `romfs/Lib/`** (`agl_resource`: `agl_common`, `agl_technique_pfx`, `agl_technique_shdw`; `gsys_resource`; `tera_resource`): not walked at all. These hold agl's post effects and shadows. ### Change - `sharc_programs()`: in a `.sharcb`, each stage has a record: u32 code offset (from the code region at header +0x1c), u32 0, u32 control-section size (0xe0 or 0xf8), s32 control-section offset relative to the record. Control sections shared by several stages are stored once, so a record may point a long way back. A program's records follow one another in stage order (vertex then fragment; task, mesh, fragment; compute alone), and the census groups them that way. `sharc_computes()` stays, for the compute stages it already found. - `archives()` also walks `Lib/*/`. - `tests/test_shader_census.py`: synthetic records (a shared control section, a vertex-only program, task/mesh/fragment, compute) and the `Lib` folder. This record format is inferred from the game's archives, not documented. A record is taken only when the control section has NVN's magic, its sizes are sane, and its code lies inside the archive. ### Effect (RTX 3090, Linux, fresh first start: no program, pipeline or driver caches) - Shader list: 31,985 → 38,741 programs (488 → 538 MB). - On main, a fresh run met 30 stages in play that were in no shader list. All 30 are in the new one, as are the 36 met in an earlier run. - After the change, one fresh run had 0 unseeded misses in play (main: 13 graphics + 5 compute programs from stages not in the list). It also built 52 graphics pipelines in play, against 81–89 in comparable main runs. - Cost: first-start shader preparation takes about 15–20 s longer (78 s vs 55–68 s here), on the first start of a build only. Peak memory at play is unchanged (~9.3 GB). These are early numbers from few runs. Most runs on current main were cut short by an unrelated NVIDIA device loss (kernel Xid 31 MMU faults), which also happens without this change. More runs will follow once that is resolved. Game-free checks: `tests/run_all.py --strict` passes (84 passed, 4 skipped for game files or a Windows device). The census step `make_game` runs gives the same list on the real romfs.
hckrman101 commented 2026-11-08 18:58:19 +00:00 (Migrated from github.com)

BlueHeisenberg on Discord says they are working on compute shaders as well, awaiting his PR to see overlap

BlueHeisenberg on Discord says they are working on compute shaders as well, awaiting his PR to see overlap
ENPFakeDeveloper commented 2026-11-08 19:36:06 +00:00 (Migrated from github.com)

Not a review, just pointers in case they help. The Fritchii fork (Frizti/Fritchii, a Lichii fork, GPL-3.0) has two things related to shader preparation and build stalls:

  • Frizti/Fritchii#71 (commit f8f29b5): when a draw or dispatch waits on a pipeline that is still queued, it now builds that pipeline inline instead of waiting behind the whole queue. It also removes two quadratic walks in shader translation (one compute shader went from 1.6 s to 0.8 s). Same kind of hitch as the "entering new areas" lag in #711.
  • Frizti/Fritchii#60 (commit 51c850a): read-only tools in tools/nx2/ that extract shaders from game archives (shader_archive.py reads SARC and BNSH, shader_containers.py reads BND4 and others). Might be useful to cross-check the .sharcb record format this PR infers. I have not compared the formats.

I'm on an RX 6650 XT and the latest Fritchii build feels a bit better for shader building than what I had before. This is my impression, not a measurement. I did not test any of this inside Hookshot, so it may not apply directly. Happy to test a build of this PR on AMD if useful.

Analysis created with Claude; it may contain errors.

Not a review, just pointers in case they help. The Fritchii fork (Frizti/Fritchii, a Lichii fork, GPL-3.0) has two things related to shader preparation and build stalls: - Frizti/Fritchii#71 (commit f8f29b5): when a draw or dispatch waits on a pipeline that is still queued, it now builds that pipeline inline instead of waiting behind the whole queue. It also removes two quadratic walks in shader translation (one compute shader went from 1.6 s to 0.8 s). Same kind of hitch as the "entering new areas" lag in #711. - Frizti/Fritchii#60 (commit 51c850a): read-only tools in `tools/nx2/` that extract shaders from game archives (`shader_archive.py` reads SARC and BNSH, `shader_containers.py` reads BND4 and others). Might be useful to cross-check the `.sharcb` record format this PR infers. I have not compared the formats. I'm on an RX 6650 XT and the latest Fritchii build feels a bit better for shader building than what I had before. This is my impression, not a measurement. I did not test any of this inside Hookshot, so it may not apply directly. Happy to test a build of this PR on AMD if useful. _Analysis created with Claude; it may contain errors._
BlueHeisenberg commented 2026-11-08 21:00:46 +00:00 (Migrated from github.com)

My pr is ready: #772

My pr is ready: https://git.magicbean.dev/MagicBeanDev/Hookshot/pulls/772
hckrman101 commented 2026-11-08 21:02:51 +00:00 (Migrated from github.com)

Closing in favour of #772, which covers the same agl archives and Lib/ through their real stage list and program table, also reads the networks and the movie upscaler, and fixes the compute layout bug in prepared programs. For what it's worth, measured here with the census part alone (RTX 3090, Linux, fresh first start on a Kokiri route): 0 unseeded misses in play after the change, and 52 graphics pipelines built in play against 81–89 on main.

Closing in favour of #772, which covers the same agl archives and `Lib/` through their real stage list and program table, also reads the networks and the movie upscaler, and fixes the compute layout bug in prepared programs. For what it's worth, measured here with the census part alone (RTX 3090, Linux, fresh first start on a Kokiri route): 0 unseeded misses in play after the change, and 52 graphics pipelines built in play against 81–89 on main.
Commenting is not possible because the repository is archived.
No description provided.