Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose API to precompile shaders for unspawned entities #13354

Open
jnhyatt opened this issue May 13, 2024 · 1 comment
Open

Expose API to precompile shaders for unspawned entities #13354

jnhyatt opened this issue May 13, 2024 · 1 comment
Labels
A-Rendering Drawing game state to the screen C-Enhancement A new feature C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Design This issue requires design work to think about how it would best be accomplished

Comments

@jnhyatt
Copy link
Contributor

jnhyatt commented May 13, 2024

What problem does this solve or what need does it fill?

Before async shader pipeline compilation, you could spawn a loading screen, wait for all your assets to finish loading, then spawn some graphical elements in using all those asset handles. The app would freeze up while all the shaders were compiled, and your loading screen wouldn't despawn until that was done. Admittedly, that was just an artifact of shader compilation blocking render execution, so not really desirable. However, now that shader compilation is asynchronous, even once all your assets are loaded, things will still pop in as their pipelines are compiled during the first few frames of gameplay. The solution to this seems to be to spawn everything in and wait for the render world to finish compiling the appropriate shaders. This has a couple of downsides: users don't necessarily want to have to spawn in entities with graphical bundles to get their shaders to load. For one thing, this will start drawing them. This can be fixed by placing them out of the camera's field of view. However, this likely means you'll have to move them back to their starting positions once shaders are done, and it also means graphics and gameplay logic have to be inserted into the world separately, or gameplay logic disabled until shaders are done. This introduces a new "phase" into a game's loading workflow. This could all potentially be solved by entity disabling (provided disabled entities still have their shaders loaded -- not necessarily the case!), but all solutions explored so far are better described as workarounds than solutions.

As a side note, grabbing the shader compilation state from the render world is not trivial, as inter-world communication is mainly designed around the "extract" workflow -- flowing data back to the main world is poorly documented and used far less often than the reverse.

What solution would you like?

My preferred solution is to add a render API that allows to determine and pre-load (and await) shaders for a given combination of mesh/material. If I understand correctly, the mesh and material aren't the only things that go into generating the pipeline key, but the hypothetical API would require all information needed to generate it, then that key would be returned to the user. The user could then ask the renderer to load that shader. The renderer could return a future/one-shot receiver that would fire when the shader finished loading. Alternatively, we could use the current API to determine when the shaders are loaded, and the burden of reacting to the event would stay on the user. This isn't necessarily the worst thing, since an async shader load event API wouldn't be hard to do in third-party code.

What alternative(s) have you considered?

Shader Pre-load Marker

We could add a component that says "don't render this, just load the shader":

#[derive(Component)]
struct LoadShader { loaded: bool }

Users could then wait until all entities with this component have loaded shaders, then remove the component from all of them and insert gameplay logic or transition to a gameplay state. This could potentially be a simpler API, but it makes some gameplay logic patterns harder/requires some extra boilerplate.

Additional context

Part of my reasoning for preferring the first option is that I'm doing my loading logic using async:

let assets = [
    load("some_scene.glb#Scene0"),
    ...
];
futures::join_all(assets).await;
let pipeline_keys = determine_required_pipelines(...);
futures::join_all(
    pipeline_keys.map(|key| load_pipeline(key))
).await;

On the other hand, this might be the less common use case since Bevy's (and Rust's, for that matter) async ecosystem is not particularly mature or developed. For that reason, the pre-load marker system might be the better, more discoverable option, and more ECS-y.

@jnhyatt jnhyatt added C-Enhancement A new feature S-Needs-Triage This issue needs to be labelled labels May 13, 2024
@JMS55
Copy link
Contributor

JMS55 commented May 13, 2024

See also #10871.

This is also an area unreal is currently trying to tackle, we should look into how other engines do it.

Note that there are many different things that influence shader compilation (from bevy's perspective):

  • Mesh
  • Material
  • View
  • Supported wgpu features for the current GPU/platform
  • Misc enabled effects that require extra pipelines

@alice-i-cecile alice-i-cecile added A-Rendering Drawing game state to the screen C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Design This issue requires design work to think about how it would best be accomplished and removed S-Needs-Triage This issue needs to be labelled labels May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Enhancement A new feature C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Design This issue requires design work to think about how it would best be accomplished
Projects
None yet
Development

No branches or pull requests

3 participants