-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
WebGPU support #5027
WebGPU support #5027
Conversation
This adds some boilerplate:
|
Can we test this in CI yet? It looks like WebGPU support still isn't general access, so that may be hard 🤔 |
You can check if your browser supports WebGPU here: https://wgpu.rs/examples-gpu/?example=hello-triangle. I was able to run Bevy examples in Chromium 105.0.5114.0, but not on Firefox Nightly 103.0a1. It seems Firefox doesn't support the new WGSL syntax yet. As for running in CI, Bevy assumes for now that |
@@ -763,12 +763,12 @@ impl App { | |||
/// # | |||
/// App::new().add_plugin(bevy_log::LogPlugin::default()); | |||
/// ``` | |||
pub fn add_plugin<T>(&mut self, plugin: T) -> &mut Self | |||
pub async fn add_plugin<T>(&mut self, plugin: T) -> &mut Self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think adding plugins should ever be async. IMO they should just register things, not do any actual work until the startup systems run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with this, but that ties into the broader discussion in #1255.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current add_plugin
does all the work of building the plugin immediately.
We could (and probably should) switch to a register_plugin
that would actually build the plugin just before running, then only the last step would need to be async. That would open a lot of solutions for plugin ordering and dependencies
renderer::initialize_renderer(&instance, &options, &request_adapter_options), | ||
); | ||
let (device, queue, adapter_info) = | ||
renderer::initialize_renderer(&instance, &options, &request_adapter_options).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#4913 moves this into a closure called once winit says that the app has resumed rather than immediatly running it when building the plugin. This is necessary for android and would solve all the threading through of async plugin building.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not enough in this case. I tried using #4913 and it crashed here:
bevy/crates/bevy_render/src/lib.rs
Lines 144 to 146 in ab72c83
let (device, queue, adapter_info) = futures_lite::future::block_on( | |
renderer::initialize_renderer(&instance, &options, &request_adapter_options), | |
); |
The issue with wasm/WebGPU is not to do it later, it's to be able to wait for it without blocking.
I suppose the WebGPU context takes just long enough that it blocks. Some people reported a similar issue when using WebGL2 not in release mode, if initialising the renderer takes too long it doesn't work in wasm to block for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WIth #4913 it would be possible to turn just the render initialize functions into async functions (hidden from the user) rather than forcing all plugin builders to be async (right in the face of every user).
I suppose the WebGPU context takes just long enough that it blocks.
Getting a value out of a javascript promise requires either calling .then()
on it, or await
on it. Using a mutex on the main thread like futures_lite does isn't possible in javascript. Only workers are allowed to block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a mutex on the main thread like futures_lite does isn't possible in javascript
This is what Bevy is currently doing, and it works... if it's fast enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it works on systems where blocking is allowed (non-web platforms). Or when the future is already resolved (webgl on web as webgl isn't async).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So that code can't work for WebGPU, even if its execution is delayed. If I understand correctly its asyncness needs to be kept, so at some point we need to expose something async to the user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be possible to call the initialize_renderer
function and then use wasm_bindgen_futures
to run the rest of the render initialization after the promise resolves. This can be handled entirely inside bevy itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With #4913 the runner is essentially given responsibility to iterate over a vector of callbacks to handle initializing the render state.
One possibility is that those callbacks could be broken up relatively easily if there's some requirement to not block for too long with Web GPU for something. e.g. the runner could potentially run a single callback at a time and return to the browser mainloop between each callback if that would work to keep it happy.
Or, maybe as an alternative to making add_plugin() async then maybe the render_init callbacks could be made async for this. It would probably be fairly simple for the runner to continue running them as blocking functions via pollster::block_on
in the general case but then there could be some extra care taken for webgpu to let them complete asynchronously.
Maybe the need to make the render_init functions async could be hidden for the most part from most plugins that don't need async render initialization by having separate APIs for .add_render_init
and .add_async_render_init
so only the few things dealing with webgpu would utilize async render init callbacks?
# Objective - Support WebGPU - alternative to #5027 that doesn't need any async / await - fixes #8315 - Surprise fix #7318 ## Solution ### For async renderer initialisation - Update the plugin lifecycle: - app builds the plugin - calls `plugin.build` - registers the plugin - app starts the event loop - event loop waits for `ready` of all registered plugins in the same order - returns `true` by default - then call all `finish` then all `cleanup` in the same order as registered - then execute the schedule In the case of the renderer, to avoid anything async: - building the renderer plugin creates a detached task that will send back the initialised renderer through a mutex in a resource - `ready` will wait for the renderer to be present in the resource - `finish` will take that renderer and place it in the expected resources by other plugins - other plugins (that expect the renderer to be available) `finish` are called and they are able to set up their pipelines - `cleanup` is called, only custom one is still for pipeline rendering ### For WebGPU support - update the `build-wasm-example` script to support passing `--api webgpu` that will build the example with WebGPU support - feature for webgl2 was always enabled when building for wasm. it's now in the default feature list and enabled on all platforms, so check for this feature must also check that the target_arch is `wasm32` --- ## Migration Guide - `Plugin::setup` has been renamed `Plugin::cleanup` - `Plugin::finish` has been added, and plugins adding pipelines should do it in this function instead of `Plugin::build` ```rust // Before impl Plugin for MyPlugin { fn build(&self, app: &mut App) { app.insert_resource::<MyResource> .add_systems(Update, my_system); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, }; render_app .init_resource::<RenderResourceNeedingDevice>() .init_resource::<OtherRenderResource>(); } } // After impl Plugin for MyPlugin { fn build(&self, app: &mut App) { app.insert_resource::<MyResource> .add_systems(Update, my_system); let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, }; render_app .init_resource::<OtherRenderResource>(); } fn finish(&self, app: &mut App) { let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, }; render_app .init_resource::<RenderResourceNeedingDevice>(); } } ```
Objective
Solution
#[bevy_main]
handlesasync fn main
details per commit:
Those 3 commits are only partially related to webgpu, and I feel could be pushed in other PRs if needed to reduce this one
missing wasm flag
spirv as a feature
fix skinning shader syntax
Those 4 commits are most of the changes:
async plugin v0
webgpu feature and how to build
async bevy_main v0
#[bevy_main]
handle async main function in the correct manner for each archautomatically update shader syntax
The last two commits are updating examples and tests
update many examples
fix tests
to build an example for WebGPU:
Cargo.toml
and remove the automaticwebgl
feature added forwasm32
cargo run -p build-wasm-example -- lighting --webgpu
Migration Guide
TODO