Repository navigation
Conversation
|
Retargeted to |
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 program tables also tell which vertex and pixel programs form a shader program, and 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, booting a Switch 2 game scans its RomFS 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 list is cached beside the pipeline cache as native_programs.bin, 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>
… 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, for the render setups and vertex attribute types the title's pipeline cache recorded, and drop it again. The driver keeps what it compiled, so the game's own pipelines are ready when first drawn. Built keys are listed in precompiled.bin, so later boots only build what is new. Assisted-by: Claude Opus 5.5 (investigation and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
Build the game's compute programs with the workgroup and shared memory sizes from their control sections as well. They need no recorded state, so this also works on a first boot. A program that uses shared memory is only built with a dispatch that gives it some, as SPIR-V emission aborts otherwise. Assisted-by: Claude Opus 5.5 (investigation and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
failaip12
force-pushed
the
video_core/shader-precompile
branch
from
November 9, 2026 23:21
c0c19b4 to
c5be508
Compare
…lt it Graphics and compute pipelines catch a failed vkCreate*Pipelines call and still return their object, marked built without a Vulkan pipeline. Precompilation counted that object as success and saved it, so a failed build was skipped on every later boot. Only count builds that created the Vulkan pipeline. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
precompiled.bin only checked the cache version, so a new driver, another GPU or a deleted vulkan_pipelines.bin left every pair marked done while the pipelines themselves were gone. Store a hash of the device's pipeline cache UUID, driver id and driver version, and drop the records when it changes, when vulkan_pipelines.bin held nothing at boot, or when neither Eden nor the driver keeps a cache. Also bound the record count on read. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
The loading screen waited for the RomFS scan to finish even after the user cancelled loading, which on a first boot takes several seconds. Forward the loading stop token to the scan thread while waiting for it. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
The RomFS scan merged programs by their code alone, so a program that reuses another's code with a different header (outputs, attributes) was dropped, and its pairs pointed at the wrong header. Merge by header and code, and bump the program list version so old lists are rescanned. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
The scan read the loader's base RomFS, so an update or a LayeredFS mod that replaces shader archives was never seen, and the program list was reused as long as the base image kept its size. Open the RomFS through the process's RomFS controller, as the game does, and key the list by the names and sizes of the files in it. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
failaip12
force-pushed
the
video_core/shader-precompile
branch
from
November 9, 2026 23:48
8a5c8c4 to
6575f29
Compare
A recompiler change can alter the SPIR-V a program gives while its lowered code, and so its pipeline key, stays the same. precompiled.bin then kept every key marked done and the new pipelines were never built, though the setting says an emulator update redoes them. Add the build's commit to the identity the records are tied to. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
The scan read every file it did not skip by name whole, so a multi-GB pack in another title meant one allocation that size, and an exception there escaped the scan thread and ended the process. Skip files over 512 MB and catch scan failures, which then leave the list empty. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
Pipelines are precompiled from LoadDiskResources, which the frontends skip when the disk shader cache is off. The RomFS scan still ran then, during play, and its list was never used. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
Every precompiled pixel program assumed its vertex program writes all the inputs it reads. That is only needed where a stand-in program fills the other stage; for pairs from the game's program tables the vertex program is the one drawn with, so give its outputs as at runtime. A pixel program reading an input its vertex program skips then gets the same SPIR-V as at runtime. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
native_programs.bin was trusted as read: counts and sizes were not bounded, and pair and dispatch indices not checked against the program count, so a corrupt file could allocate without bound or index past the programs. Reject a list outside those bounds; it is then scanned again. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
The pair and dispatch scans walked every word from each container header to the end of the file, so their cost grew with containers times file size. Bounding each walk at the next container would lose entries: Mario Kart World's Drive archive keeps the stage entries of its BNSH containers past later containers (37 of its 375 dispatches). Find the words that hold the 0xd8 tag once, and match each container only against those. Results are unchanged; the table scans of Mario Kart World's main containers take 0.06 s instead of 0.22 s. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
Every program with a type 1 header was built as a vertex program, but tessellation and geometry programs use that type too (Mario Kart World has 13 tessellation programs), which gave wasted or failing builds. Read the shader type from the header and leave the other stages out. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
The program list (Mario Kart World: 11,261 programs), the recorded render setups and attribute layouts and the done keys stayed in memory for the whole session, though nothing reads them after precompiling. Release them when RunPrecompile returns. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
After loading was cancelled, the final line counted the jobs that never ran as precompiled, and devices with broken compute logged an error for each compute job. Count the builds that finished, and leave compute out on those devices. Assisted-by: Claude Opus 5.5 (code review and implementation) Signed-off-by: failaip12 <86018517+failaip12@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 and i am unsure of the stability of this right now.
Type of change
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
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
Screenshots / logs
Claude Opus 5.5 was used for the investigation and the implementation.
Assisted-by: Claude Opus 5.5 (investigation and implementation)