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

Webgpu support #8336

Merged
merged 20 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ jobs:
target: wasm32-unknown-unknown
- name: Check wasm
run: cargo check --target wasm32-unknown-unknown
env:
RUSTFLAGS: --cfg=web_sys_unstable_apis

markdownlint:
runs-on: ubuntu-latest
Expand Down
9 changes: 4 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ default = [
"android_shared_stdcxx",
"tonemapping_luts",
"default_font",
"webgl2",
]

# Force dynamic linking, which improves iterative compile times
Expand Down Expand Up @@ -235,15 +236,13 @@ shader_format_glsl = ["bevy_internal/shader_format_glsl"]
# Enable support for shaders in SPIR-V
shader_format_spirv = ["bevy_internal/shader_format_spirv"]

# Enable some limitations to be able to use WebGL2. If not enabled, it will default to WebGPU in Wasm
webgl2 = ["bevy_internal/webgl"]

[dependencies]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.11.0-dev", default-features = false, optional = true }
bevy_internal = { path = "crates/bevy_internal", version = "0.11.0-dev", default-features = false }

[target.'cfg(target_arch = "wasm32")'.dependencies]
bevy_internal = { path = "crates/bevy_internal", version = "0.11.0-dev", default-features = false, features = [
"webgl",
] }

[dev-dependencies]
anyhow = "1.0.4"
rand = "0.8.0"
Expand Down
46 changes: 39 additions & 7 deletions crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ impl Default for App {
}
}

// Dummy plugin used to temporary hold the place in the plugin registry
struct PlaceholderPlugin;
impl Plugin for PlaceholderPlugin {
fn build(&self, _app: &mut App) {}
}

impl App {
/// Creates a new [`App`] with some default structure to enable core engine features.
/// This is the preferred constructor for most use cases.
Expand Down Expand Up @@ -288,19 +294,40 @@ impl App {
panic!("App::run() was called from within Plugin::build(), which is not allowed.");
}

Self::setup(&mut app);

let runner = std::mem::replace(&mut app.runner, Box::new(run_once));
(runner)(app);
}

/// Run [`Plugin::setup`] for each plugin. This is usually called by [`App::run`], but can
/// be useful for situations where you want to use [`App::update`].
pub fn setup(&mut self) {
/// Check that [`Plugin::ready`] of all plugins returns true. This is usually called by the
/// event loop, but can be useful for situations where you want to use [`App::update`]
pub fn ready(&self) -> bool {
for plugin in &self.plugin_registry {
if !plugin.ready(self) {
return false;
}
}
true
}

/// Run [`Plugin::finish`] for each plugin. This is usually called by the event loop once all
/// plugins are [`App::ready`], but can be useful for situations where you want to use
/// [`App::update`].
pub fn finish(&mut self) {
// temporarily remove the plugin registry to run each plugin's setup function on app.
let plugin_registry = std::mem::take(&mut self.plugin_registry);
for plugin in &plugin_registry {
plugin.finish(self);
}
self.plugin_registry = plugin_registry;
}

/// Run [`Plugin::cleanup`] for each plugin. This is usually called by the event loop after
/// [`App::finish`], but can be useful for situations where you want to use [`App::update`].
pub fn cleanup(&mut self) {
// temporarily remove the plugin registry to run each plugin's setup function on app.
let plugin_registry = std::mem::take(&mut self.plugin_registry);
for plugin in &plugin_registry {
plugin.setup(self);
plugin.cleanup(self);
}
self.plugin_registry = plugin_registry;
}
Expand Down Expand Up @@ -685,13 +712,18 @@ impl App {
plugin_name: plugin.name().to_string(),
})?;
}

// Reserve that position in the plugin registry. if a plugin adds plugins, they will be correctly ordered
let plugin_position_in_registry = self.plugin_registry.len();
self.plugin_registry.push(Box::new(PlaceholderPlugin));

self.building_plugin_depth += 1;
let result = catch_unwind(AssertUnwindSafe(|| plugin.build(self)));
self.building_plugin_depth -= 1;
if let Err(payload) = result {
resume_unwind(payload);
}
self.plugin_registry.push(plugin);
self.plugin_registry[plugin_position_in_registry] = plugin;
Ok(self)
}

Expand Down
25 changes: 23 additions & 2 deletions crates/bevy_app/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,35 @@ use std::any::Any;
/// should be overridden to return `false`. Plugins are considered duplicate if they have the same
/// [`name()`](Self::name). The default `name()` implementation returns the type name, which means
/// generic plugins with different type parameters will not be considered duplicates.
///
/// ## Lifecycle of a plugin
///
/// When adding a plugin to an [`App`]:
/// * the app calls [`Plugin::build`] immediately, and register the plugin
/// * once the app started, it will wait for all registered [`Plugin::ready`] to return `true`
/// * it will then call all registered [`Plugin::finish`]
/// * and call all registered [`Plugin::cleanup`]
pub trait Plugin: Downcast + Any + Send + Sync {
/// Configures the [`App`] to which this plugin is added.
fn build(&self, app: &mut App);

/// Runs after all plugins are built, but before the app runner is called.
/// Has the plugin finished it's setup? This can be useful for plugins that needs something
/// asynchronous to happen before they can finish their setup, like renderer initialization.
/// Once the plugin is ready, [`finish`](Plugin::finish) should be called.
fn ready(&self, _app: &App) -> bool {
true
}

/// Finish adding this plugin to the [`App`], once all plugins registered are ready. This can
/// be useful for plugins that depends on another plugin asynchronous setup, like the renderer.
fn finish(&self, _app: &mut App) {
// do nothing
}

/// Runs after all plugins are built and finished, but before the app schedule is executed.
/// This can be useful if you have some resource that other plugins need during their build step,
/// but after build you want to remove it and send it to another thread.
fn setup(&self, _app: &mut App) {
fn cleanup(&self, _app: &mut App) {
mockersf marked this conversation as resolved.
Show resolved Hide resolved
// do nothing
}

Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_core_pipeline/src/blit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ pub struct BlitPlugin;
impl Plugin for BlitPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(app, BLIT_SHADER_HANDLE, "blit.wgsl", Shader::from_wgsl);
}

fn finish(&self, app: &mut App) {
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return
};
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_core_pipeline/src/bloom/bloom.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ fn downsample_first(@location(0) output_uv: vec2<f32>) -> @location(0) vec4<f32>
// Lower bound of 0.0001 is to avoid propagating multiplying by 0.0 through the
// downscaling and upscaling which would result in black boxes.
// The upper bound is to prevent NaNs.
sample = clamp(sample, vec3<f32>(0.0001), vec3<f32>(3.40282347E+38));
// with f32::MAX (E+38) Chrome fails with ":value 340282346999999984391321947108527833088.0 cannot be represented as 'f32'"
sample = clamp(sample, vec3<f32>(0.0001), vec3<f32>(3.40282347E+37));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to know what value should be used. But I guess this will do.


#ifdef USE_THRESHOLD
sample = soft_threshold(sample);
Expand Down
13 changes: 11 additions & 2 deletions crates/bevy_core_pipeline/src/bloom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ impl Plugin for BloomPlugin {
};

render_app
.init_resource::<BloomDownsamplingPipeline>()
.init_resource::<BloomUpsamplingPipeline>()
.init_resource::<SpecializedRenderPipelines<BloomDownsamplingPipeline>>()
.init_resource::<SpecializedRenderPipelines<BloomUpsamplingPipeline>>()
.add_systems(
Expand Down Expand Up @@ -95,6 +93,17 @@ impl Plugin for BloomPlugin {
],
);
}

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::<BloomDownsamplingPipeline>()
.init_resource::<BloomUpsamplingPipeline>();
}
}

pub struct BloomNode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ impl Plugin for CASPlugin {
Err(_) => return,
};
render_app
.init_resource::<CASPipeline>()
.init_resource::<SpecializedRenderPipelines<CASPipeline>>()
.add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare));

Expand Down Expand Up @@ -149,6 +148,14 @@ impl Plugin for CASPlugin {
);
}
}

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::<CASPipeline>();
}
}

#[derive(Resource)]
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_core_pipeline/src/core_2d/main_pass_2d_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl Node for MainPass2dNode {

// WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't
// reset for the next render pass so add an empty render pass without a custom viewport
#[cfg(feature = "webgl")]
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
if camera.viewport.is_some() {
#[cfg(feature = "trace")]
let _reset_viewport_pass_2d = info_span!("reset_viewport_pass_2d").entered();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ impl Node for MainTransparentPass3dNode {

// WebGL2 quirk: if ending with a render pass with a custom viewport, the viewport isn't
// reset for the next render pass so add an empty render pass without a custom viewport
#[cfg(feature = "webgl")]
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
if camera.viewport.is_some() {
#[cfg(feature = "trace")]
let _reset_viewport_pass_3d = info_span!("reset_viewport_pass_3d").entered();
Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_core_pipeline/src/fxaa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ impl Plugin for FxaaPlugin {
Err(_) => return,
};
render_app
.init_resource::<FxaaPipeline>()
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
.add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare))
.add_render_graph_node::<FxaaNode>(CORE_3D, core_3d::graph::node::FXAA)
Expand All @@ -114,6 +113,14 @@ impl Plugin for FxaaPlugin {
],
);
}

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::<FxaaPipeline>();
}
}

#[derive(Resource, Deref)]
Expand Down
14 changes: 11 additions & 3 deletions crates/bevy_core_pipeline/src/skybox/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ impl Plugin for SkyboxPlugin {
Err(_) => return,
};

let render_device = render_app.world.resource::<RenderDevice>().clone();

render_app
.insert_resource(SkyboxPipeline::new(&render_device))
.init_resource::<SpecializedRenderPipelines<SkyboxPipeline>>()
.add_systems(
Render,
Expand All @@ -54,6 +51,17 @@ impl Plugin for SkyboxPlugin {
),
);
}

fn finish(&self, app: &mut App) {
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};

let render_device = render_app.world.resource::<RenderDevice>().clone();

render_app.insert_resource(SkyboxPipeline::new(&render_device));
}
}

/// Adds a skybox to a 3D camera, based on a cubemap texture.
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_core_pipeline/src/tonemapping/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,19 @@ impl Plugin for TonemappingPlugin {

if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<TonemappingPipeline>()
.init_resource::<SpecializedRenderPipelines<TonemappingPipeline>>()
.add_systems(
Render,
queue_view_tonemapping_pipelines.in_set(RenderSet::Queue),
);
}
}

fn finish(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<TonemappingPipeline>();
}
}
}

#[derive(Resource)]
Expand Down
Loading