Shader pipeline log for automatic compilation in loading screens
What problem does this solve or what need does it fill?
Pipelines take a while to compile. Ideally we compile all that ahead of time in a loading screen, rather than either stutter or pop in during gameplay.
What solution would you like?
Some kind of log to record created pipelines during runtime, and save it to a file. Then, on startup, read the file and queue all the pipelines.
We'll need to handle things like TAA that don't use the Material API, and therefore unconditionally create the pipeline without caching it and deduplicating with existing pipelines in the pipeline cache (which doesn't deduplicate anything in its own).
@JMS55 does the edited title make sense? Trying to make sure this is easy to search :)
I'm on mobile quickly jotting down my thoughts, but yeah mostly makes sense. I would remove the on disk caching, we can't (more complicated issue) cache the pipelines themselves on disk, but we can keep a log of the pipeline descriptors, and then queue their creation on startup every app run.
This would be very welcome, I couldn't find an easy way to accomplish this.
Some kind of log to record created pipelines during runtime, and save it to a file. Then, on startup, read the file and queue all the pipelines.
would this work for a game with multiple maps each with different materials? Wouldn't we want an ergonomic way to programmatically preload a pipeline and check its state?
The way I envision this working is via events. You'd send a ShaderPipelineLogEvent::StartRecording { asset_path: String } event, and then later ShaderPipelineLogEvent::StopRecording. Then there would be a PipelineCache method to queue pipelines from a log of descriptors.
Then we could make a default plugin/cargo "dev pipeline log" feature that does a simple setup where it reads an existing log on startup, sends the StartRecording event, and then on app close sends StopRecording and replaces the old log with the new one.
More complicated games with multiple "maps/levels" would have to manually manage the logs themselves, and clearing the cache and loading new logs on level change. You'd probably also want to hide it behind a loading screen, same as loading your mesh and material assets.
Something else that was brought up: Even with a precompiled pipeline log, users still need some bevy events to be fired to know when pipeline compilation, asset uploading, etc are all finished so that they can remove the loading screen.
it's possible to get the count of ready pipelines, which you can compare with how many you expect
https://crates.io/crates/bevy_pipelines_ready is doing that trick
https://www.unrealengine.com/en-US/tech-blog/game-engines-and-shader-stuttering-unreal-engines-solution-to-the-problem
Unreal posted some very useful insights recently. Looks like they're moving away from logging pipeline descriptor during gameplay and playing it back, to trying to guess all the possible pipeline states upon load.
This is actually a really useful idea that we could adapt in Bevy! Add a system that scans over new materials added to Assets<T>, and tries to guess what views + meshes are in the scene, and then builds pipelines for them (and then deletes them to save memory). They'll still be cached in the driver layer when they need to be used - not sure how expensive the intermediate naga/naga_oil steps are.
We could then have users add all their materials during level load, and then query if there are any pipelines still compiling to know if things are ready.
The alternative is of course we try and precompile everything on app startup based on a static subset of pipeline key combinations the user selects, but that's a lot more complicated for users.
Small note if I end up redoing PipelineCache: waiting_pipelines() should probably return impl ExactSizeIterator.
One thing Anvil engine does that's cool is they measure pipeline compile times, and any that are too slow get logged to the console so that you know why the game stuttered.