Skip to content

video_core: Implement shader precompilation - #814

Draft
failaip12 wants to merge 5 commits into
Eglantine-Community:nx2-experimentalfrom
cira0100:video_core/shader-precompile
Draft

failaip12 wants to merge 5 commits into
Eglantine-Community:nx2-experimentalfrom
cira0100:video_core/shader-precompile

Conversation

@failaip12

@failaip12 failaip12 commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a experimental toggle to precompile shaders.
It currently works in a weird way due to it needing a recorded pipeline cache. It needs the user to toggle the option first, run the game which will precompile the compute shaders, play at least one track, restart the game, and only then will the full recompilation process start.
This can be fixed by bundling the game specific render setups(recorded pipeline cache) which should be fine legally but is kinda close to bundling precompiled shaders so this is not done here.
This is a draft as windows and intel configurations are not tested, only MKW was tested and i am unsure of the stability of this right now.

Type of change

  • Bug fix
  • Game compatibility / game-support
  • Performance
  • New feature
  • Refactor / cleanup
  • CI / build
  • Documentation

Related issues

Works best with #812 (cache folder for Mario Kart World).
On RADV, precompiled pipelines are currently kept in Eglantine's vulkan_pipelines.bin; with #714 they would live in Mesa's shader cache instead.
May help with first-play shader stutter reported in #121.

Testing

  • Tested on Windows
  • Tested on Linux
  • Tested on macOS
  • Tested on NVIDIA
  • Tested on AMD
  • Tested on Intel

Games tested:

Tested on MKW 1.8.0 on Linux wiith nvidia and linux with amd gpu.
Nvidia shaders took much longer to build than AMD ones.

Checklist

  • I built it locally and it compiles without new warnings
  • I did not add any copyrighted game data, keys or firmware
  • Docs updated if needed
  • I added AI Disclosure if one was used

Screenshots / logs

Claude Opus 5.5 was used for the investigation and the implementation.

Assisted-by: Claude Opus 5.5 (investigation and implementation)

@github-actions
github-actions Bot changed the base branch from nx2 to nx2-experimental November 9, 2026 20:50
@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

Retargeted to nx2-experimental for pre-build testing. It will be promoted to nx2 after validation.

@failaip12
failaip12 force-pushed the video_core/shader-precompile branch from c0c19b4 to c5be508 Compare November 9, 2026 23:21
@failaip12 failaip12 changed the title Video core/shader precompile video_core: Implement shader precompilation Nov 9, 2026
@failaip12
failaip12 force-pushed the video_core/shader-precompile branch from 8a5c8c4 to 6575f29 Compare November 9, 2026 23:48
Switch 2 games ship their native Ampere programs verbatim in shader
containers (BNSH, agl BAHS, effect and
layout archives). Each program ends with a branch to itself, and a
graphics program starts 0x80 bytes into a 256-byte block that holds its
program header. The scanner finds the programs from those markers and
checks each one with the same capture the runtime uses, so it reads the
code the runtime would read.

BNSH and agl program tables also tell which vertex and pixel programs
form a shader program, and BNSH tables lead to the NVN shader control
section that holds a compute program's workgroup and shared memory
sizes. Nothing uses the scanner yet.

Assisted-by: Claude Opus 5.5 (investigation and implementation)
Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
Move the part of LowerNativeCode that builds the cached code (program
header, lowered instructions with scheduling words, native address
map) into MakeLoweredNativeCode, so programs read from the game's files
get the same code and shader hash as the ones captured at bind. No
functional change.

Assisted-by: Claude Opus 5.5 (investigation and implementation)
Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
ScannedEnvironment translates a native program before the game binds
it. Translation reads GPU state the program only sees once bound, so it
answers with what nearly all programs read: 2D textures with float
formats, mapped storage pointers and zero for constant buffer values.
Compute programs take their workgroup and shared memory sizes from the
caller.

Assisted-by: Claude Opus 5.5 (investigation and implementation)
Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
Add the per-game "Precompile shaders (Switch 2)" setting, off by
default. When it is on and the disk shader cache is too, booting a
Switch 2 game scans the RomFS the game reads (update and mods applied)
for native programs on a background thread while the pipeline cache
loads, and hands the programs, their vertex and pixel pairs and the
compute dispatch sizes to the rasterizer. The RomFS is opened only when
the rasterizer will use the programs. The scan bounds its reads and
skips media files.

The list is cached beside the pipeline cache as native_programs.bin,
checked when read back and kept while the RomFS keeps the same file
names and sizes, so later boots skip the scan. No rasterizer uses it
yet.

Assisted-by: Claude Opus 5.5 (investigation and implementation)
Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
…ng screen

Switch 2 games stutter for a long time the first time new tracks and
effects are drawn, because each new pipeline is compiled then. At the
end of the loading screen, build a pipeline for each vertex and pixel
pair of the game's files, and drop it again. The driver keeps what it
compiled, so the game's own pipelines are ready when first drawn.

Graphics pipelines copy the fixed state of the title's recorded
pipelines: the render setups enough of them use, each with the shader
state most of its pipelines have, and the vertex attribute types they
record, with the combinations that differ (integer bone indices) for
the vertex programs that read them. Compute programs are built with the
workgroup and shared memory sizes from their control sections; they
need no recorded state, so they are built on a first boot too.

Built and rejected pipelines are listed in precompiled.bin, written
after the Vulkan cache file holds them and at checkpoints spaced by
that file's size, so later boots build only what is new. What the
first pass finds in each program is kept in precompile_programs.bin.
Both name the cache version, the emulator build, the device, the driver
and the dynamic state, and are redone when any changes. Every step is
logged in precompile_running.bin before and after it runs: a step that
a boot did not finish, or that the driver failed to build, is tried
once more and then skipped, so a crashing driver does not crash every
boot. Without the Vulkan pipeline cache and without a driver that keeps
pipelines, nothing is precompiled.

The precompile lives in vk_pipeline_precompile.cpp. Its builds run on
as many threads as runtime builds and stay out of the slow-build log.

Assisted-by: Claude Opus 5.5 (investigation and implementation)
Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
@failaip12
failaip12 force-pushed the video_core/shader-precompile branch from 92f47fe to 748d713 Compare November 10, 2026 17:32
@kraibse kraibse self-assigned this Nov 11, 2026
@kraibse

kraibse commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

Good timing as that's something I am working on right now. Will have to do an A/B test to see how many compute shaders your approach detects versus my current one (which should also resolve #714 and #121).

Found approach for compiling all the shaders seems a little unintuitive but will need testing to see what we can do about that.

@kraibse

kraibse commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

Included in #825 , still have to check if anything crucial is missing from here before merging/closing the PR.

@failaip12

Copy link
Copy Markdown
Contributor Author

Included in #825 , still have to check if anything crucial is missing from here before merging/closing the PR.

Considering that should i stop changing this pr then, cause i was planning to do some windows testing and try some other games and fix any issues i find.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants