mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Webgpu support (#8336)
# 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>(); } } ```
This commit is contained in:
parent
5da8af7d37
commit
71842c5ac9
46 changed files with 519 additions and 170 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
|
@ -53,6 +53,7 @@ default = [
|
|||
"android_shared_stdcxx",
|
||||
"tonemapping_luts",
|
||||
"default_font",
|
||||
"webgl2",
|
||||
]
|
||||
|
||||
# Force dynamic linking, which improves iterative compile times
|
||||
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
@ -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.setup(self);
|
||||
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.cleanup(self);
|
||||
}
|
||||
self.plugin_registry = plugin_registry;
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
||||
#ifdef USE_THRESHOLD
|
||||
sample = soft_threshold(sample);
|
||||
|
|
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
@ -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)]
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
|
@ -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.
|
||||
|
|
|
@ -92,7 +92,6 @@ 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,
|
||||
|
@ -100,6 +99,12 @@ impl Plugin for TonemappingPlugin {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<TonemappingPipeline>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
|
|
|
@ -53,23 +53,23 @@ fn tonemap_curve3(v: vec3<f32>) -> vec3<f32> {
|
|||
}
|
||||
|
||||
fn somewhat_boring_display_transform(col: vec3<f32>) -> vec3<f32> {
|
||||
var col = col;
|
||||
let ycbcr = rgb_to_ycbcr(col);
|
||||
var boring_color = col;
|
||||
let ycbcr = rgb_to_ycbcr(boring_color);
|
||||
|
||||
let bt = tonemap_curve(length(ycbcr.yz) * 2.4);
|
||||
var desat = max((bt - 0.7) * 0.8, 0.0);
|
||||
desat *= desat;
|
||||
|
||||
let desat_col = mix(col.rgb, ycbcr.xxx, desat);
|
||||
let desat_col = mix(boring_color.rgb, ycbcr.xxx, desat);
|
||||
|
||||
let tm_luma = tonemap_curve(ycbcr.x);
|
||||
let tm0 = col.rgb * max(0.0, tm_luma / max(1e-5, tonemapping_luminance(col.rgb)));
|
||||
let tm0 = boring_color.rgb * max(0.0, tm_luma / max(1e-5, tonemapping_luminance(boring_color.rgb)));
|
||||
let final_mult = 0.97;
|
||||
let tm1 = tonemap_curve3(desat_col);
|
||||
|
||||
col = mix(tm0, tm1, bt * bt);
|
||||
boring_color = mix(tm0, tm1, bt * bt);
|
||||
|
||||
return col * final_mult;
|
||||
return boring_color * final_mult;
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
|
@ -110,7 +110,7 @@ fn RRTAndODTFit(v: vec3<f32>) -> vec3<f32> {
|
|||
}
|
||||
|
||||
fn ACESFitted(color: vec3<f32>) -> vec3<f32> {
|
||||
var color = color;
|
||||
var fitted_color = color;
|
||||
|
||||
// sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT
|
||||
let rgb_to_rrt = mat3x3<f32>(
|
||||
|
@ -126,17 +126,17 @@ fn ACESFitted(color: vec3<f32>) -> vec3<f32> {
|
|||
vec3(-0.00327, -0.07276, 1.07602)
|
||||
);
|
||||
|
||||
color *= rgb_to_rrt;
|
||||
fitted_color *= rgb_to_rrt;
|
||||
|
||||
// Apply RRT and ODT
|
||||
color = RRTAndODTFit(color);
|
||||
fitted_color = RRTAndODTFit(fitted_color);
|
||||
|
||||
color *= odt_to_rgb;
|
||||
fitted_color *= odt_to_rgb;
|
||||
|
||||
// Clamp to [0, 1]
|
||||
color = saturate(color);
|
||||
fitted_color = saturate(fitted_color);
|
||||
|
||||
return color;
|
||||
return fitted_color;
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
|
@ -171,30 +171,30 @@ fn convertOpenDomainToNormalizedLog2(color: vec3<f32>, minimum_ev: f32, maximum_
|
|||
let in_midgray = 0.18;
|
||||
|
||||
// remove negative before log transform
|
||||
var color = max(vec3(0.0), color);
|
||||
var normalized_color = max(vec3(0.0), color);
|
||||
// avoid infinite issue with log -- ref[1]
|
||||
color = select(color, 0.00001525878 + color, color < 0.00003051757);
|
||||
color = clamp(
|
||||
log2(color / in_midgray),
|
||||
normalized_color = select(normalized_color, 0.00001525878 + normalized_color, normalized_color < vec3<f32>(0.00003051757));
|
||||
normalized_color = clamp(
|
||||
log2(normalized_color / in_midgray),
|
||||
vec3(minimum_ev),
|
||||
vec3(maximum_ev)
|
||||
);
|
||||
let total_exposure = maximum_ev - minimum_ev;
|
||||
|
||||
return (color - minimum_ev) / total_exposure;
|
||||
return (normalized_color - minimum_ev) / total_exposure;
|
||||
}
|
||||
|
||||
// Inverse of above
|
||||
fn convertNormalizedLog2ToOpenDomain(color: vec3<f32>, minimum_ev: f32, maximum_ev: f32) -> vec3<f32> {
|
||||
var color = color;
|
||||
var open_color = color;
|
||||
let in_midgray = 0.18;
|
||||
let total_exposure = maximum_ev - minimum_ev;
|
||||
|
||||
color = (color * total_exposure) + minimum_ev;
|
||||
color = pow(vec3(2.0), color);
|
||||
color = color * in_midgray;
|
||||
open_color = (open_color * total_exposure) + minimum_ev;
|
||||
open_color = pow(vec3(2.0), open_color);
|
||||
open_color = open_color * in_midgray;
|
||||
|
||||
return color;
|
||||
return open_color;
|
||||
}
|
||||
|
||||
|
||||
|
@ -204,16 +204,16 @@ fn convertNormalizedLog2ToOpenDomain(color: vec3<f32>, minimum_ev: f32, maximum_
|
|||
|
||||
// Prepare the data for display encoding. Converted to log domain.
|
||||
fn applyAgXLog(Image: vec3<f32>) -> vec3<f32> {
|
||||
var Image = max(vec3(0.0), Image); // clamp negatives
|
||||
let r = dot(Image, vec3(0.84247906, 0.0784336, 0.07922375));
|
||||
let g = dot(Image, vec3(0.04232824, 0.87846864, 0.07916613));
|
||||
let b = dot(Image, vec3(0.04237565, 0.0784336, 0.87914297));
|
||||
Image = vec3(r, g, b);
|
||||
var prepared_image = max(vec3(0.0), Image); // clamp negatives
|
||||
let r = dot(prepared_image, vec3(0.84247906, 0.0784336, 0.07922375));
|
||||
let g = dot(prepared_image, vec3(0.04232824, 0.87846864, 0.07916613));
|
||||
let b = dot(prepared_image, vec3(0.04237565, 0.0784336, 0.87914297));
|
||||
prepared_image = vec3(r, g, b);
|
||||
|
||||
Image = convertOpenDomainToNormalizedLog2(Image, -10.0, 6.5);
|
||||
prepared_image = convertOpenDomainToNormalizedLog2(prepared_image, -10.0, 6.5);
|
||||
|
||||
Image = clamp(Image, vec3(0.0), vec3(1.0));
|
||||
return Image;
|
||||
prepared_image = clamp(prepared_image, vec3(0.0), vec3(1.0));
|
||||
return prepared_image;
|
||||
}
|
||||
|
||||
fn applyLUT3D(Image: vec3<f32>, block_size: f32) -> vec3<f32> {
|
||||
|
|
|
@ -97,7 +97,6 @@ impl Plugin for GizmoPlugin {
|
|||
|
||||
render_app
|
||||
.add_render_command::<Transparent2d, DrawGizmoLines>()
|
||||
.init_resource::<GizmoLinePipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<GizmoLinePipeline>>()
|
||||
.add_systems(Render, queue_gizmos_2d.in_set(RenderSet::Queue));
|
||||
}
|
||||
|
@ -109,11 +108,28 @@ impl Plugin for GizmoPlugin {
|
|||
|
||||
render_app
|
||||
.add_render_command::<Opaque3d, DrawGizmoLines>()
|
||||
.init_resource::<GizmoPipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<GizmoPipeline>>()
|
||||
.add_systems(Render, queue_gizmos_3d.in_set(RenderSet::Queue));
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
{
|
||||
use pipeline_2d::*;
|
||||
|
||||
render_app.init_resource::<GizmoLinePipeline>();
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
{
|
||||
use pipeline_3d::*;
|
||||
|
||||
render_app.init_resource::<GizmoPipeline>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Resource`] that stores configuration for gizmos.
|
||||
|
|
|
@ -293,9 +293,7 @@ impl Plugin for PbrPlugin {
|
|||
sort_phase_system::<Shadow>.in_set(RenderSet::PhaseSort),
|
||||
),
|
||||
)
|
||||
.init_resource::<ShadowSamplers>()
|
||||
.init_resource::<LightMeta>()
|
||||
.init_resource::<GlobalLightMeta>();
|
||||
.init_resource::<LightMeta>();
|
||||
|
||||
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
|
||||
let mut graph = render_app.world.resource_mut::<RenderGraph>();
|
||||
|
@ -308,4 +306,16 @@ impl Plugin for PbrPlugin {
|
|||
bevy_core_pipeline::core_3d::graph::node::START_MAIN_PASS,
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let render_app = match app.get_sub_app_mut(RenderApp) {
|
||||
Ok(render_app) => render_app,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
// Extract the required data from the main world
|
||||
render_app
|
||||
.init_resource::<ShadowSamplers>()
|
||||
.init_resource::<GlobalLightMeta>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -353,7 +353,7 @@ impl CascadeShadowConfigBuilder {
|
|||
|
||||
impl Default for CascadeShadowConfigBuilder {
|
||||
fn default() -> Self {
|
||||
if cfg!(feature = "webgl") {
|
||||
if cfg!(all(feature = "webgl", target_arch = "wasm32")) {
|
||||
// Currently only support one cascade in webgl.
|
||||
Self {
|
||||
num_cascades: 1,
|
||||
|
|
|
@ -196,7 +196,6 @@ where
|
|||
.add_render_command::<Transparent3d, DrawMaterial<M>>()
|
||||
.add_render_command::<Opaque3d, DrawMaterial<M>>()
|
||||
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
|
||||
.init_resource::<MaterialPipeline<M>>()
|
||||
.init_resource::<ExtractedMaterials<M>>()
|
||||
.init_resource::<RenderMaterials<M>>()
|
||||
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
|
||||
|
@ -220,6 +219,12 @@ where
|
|||
app.add_plugin(PrepassPlugin::<M>::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<MaterialPipeline<M>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A key uniquely identifying a specialized [`MaterialPipeline`].
|
||||
|
|
|
@ -108,11 +108,18 @@ where
|
|||
Render,
|
||||
queue_prepass_view_bind_group::<M>.in_set(RenderSet::Queue),
|
||||
)
|
||||
.init_resource::<PrepassPipeline<M>>()
|
||||
.init_resource::<PrepassViewBindGroup>()
|
||||
.init_resource::<SpecializedMeshPipelines<PrepassPipeline<M>>>()
|
||||
.init_resource::<PreviousViewProjectionUniforms>();
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.init_resource::<PrepassPipeline<M>>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the prepasses for a [`Material`].
|
||||
|
|
|
@ -217,9 +217,9 @@ pub struct GpuLights {
|
|||
// NOTE: this must be kept in sync with the same constants in pbr.frag
|
||||
pub const MAX_UNIFORM_BUFFER_POINT_LIGHTS: usize = 256;
|
||||
pub const MAX_DIRECTIONAL_LIGHTS: usize = 10;
|
||||
#[cfg(not(feature = "webgl"))]
|
||||
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
|
||||
pub const MAX_CASCADES_PER_LIGHT: usize = 4;
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
pub const MAX_CASCADES_PER_LIGHT: usize = 1;
|
||||
pub const SHADOW_FORMAT: TextureFormat = TextureFormat::Depth32Float;
|
||||
|
||||
|
@ -683,13 +683,13 @@ pub fn prepare_lights(
|
|||
let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();
|
||||
let mut directional_lights: Vec<_> = directional_lights.iter().collect::<Vec<_>>();
|
||||
|
||||
#[cfg(not(feature = "webgl"))]
|
||||
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
|
||||
let max_texture_array_layers = render_device.limits().max_texture_array_layers as usize;
|
||||
#[cfg(not(feature = "webgl"))]
|
||||
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
|
||||
let max_texture_cubes = max_texture_array_layers / 6;
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
let max_texture_array_layers = 1;
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
let max_texture_cubes = 1;
|
||||
|
||||
if !*max_directional_lights_warning_emitted && directional_lights.len() > MAX_DIRECTIONAL_LIGHTS
|
||||
|
@ -1162,9 +1162,9 @@ pub fn prepare_lights(
|
|||
.create_view(&TextureViewDescriptor {
|
||||
label: Some("point_light_shadow_map_array_texture_view"),
|
||||
format: None,
|
||||
#[cfg(not(feature = "webgl"))]
|
||||
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
|
||||
dimension: Some(TextureViewDimension::CubeArray),
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
dimension: Some(TextureViewDimension::Cube),
|
||||
aspect: TextureAspect::All,
|
||||
base_mip_level: 0,
|
||||
|
@ -1177,9 +1177,9 @@ pub fn prepare_lights(
|
|||
.create_view(&TextureViewDescriptor {
|
||||
label: Some("directional_light_shadow_map_array_texture_view"),
|
||||
format: None,
|
||||
#[cfg(not(feature = "webgl"))]
|
||||
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
|
||||
dimension: Some(TextureViewDimension::D2Array),
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
dimension: Some(TextureViewDimension::D2),
|
||||
aspect: TextureAspect::All,
|
||||
base_mip_level: 0,
|
||||
|
|
|
@ -106,7 +106,6 @@ impl Plugin for MeshRenderPlugin {
|
|||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.init_resource::<MeshPipeline>()
|
||||
.init_resource::<SkinnedMeshUniform>()
|
||||
.add_systems(ExtractSchedule, (extract_meshes, extract_skinned_meshes))
|
||||
.add_systems(
|
||||
|
@ -119,6 +118,12 @@ impl Plugin for MeshRenderPlugin {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<MeshPipeline>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, ShaderType, Clone)]
|
||||
|
@ -328,9 +333,9 @@ impl FromWorld for MeshPipeline {
|
|||
ty: BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: TextureSampleType::Depth,
|
||||
#[cfg(not(feature = "webgl"))]
|
||||
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
|
||||
view_dimension: TextureViewDimension::CubeArray,
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
view_dimension: TextureViewDimension::Cube,
|
||||
},
|
||||
count: None,
|
||||
|
@ -349,9 +354,9 @@ impl FromWorld for MeshPipeline {
|
|||
ty: BindingType::Texture {
|
||||
multisampled: false,
|
||||
sample_type: TextureSampleType::Depth,
|
||||
#[cfg(not(feature = "webgl"))]
|
||||
#[cfg(any(not(feature = "webgl"), not(target_arch = "wasm32")))]
|
||||
view_dimension: TextureViewDimension::D2Array,
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
view_dimension: TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
|
@ -439,7 +444,9 @@ impl FromWorld for MeshPipeline {
|
|||
let tonemapping_lut_entries = get_lut_bind_group_layout_entries([14, 15]);
|
||||
entries.extend_from_slice(&tonemapping_lut_entries);
|
||||
|
||||
if cfg!(not(feature = "webgl")) || (cfg!(feature = "webgl") && !multisampled) {
|
||||
if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32")))
|
||||
|| (cfg!(all(feature = "webgl", target_arch = "wasm32")) && !multisampled)
|
||||
{
|
||||
entries.extend_from_slice(&prepass::get_bind_group_layout_entries(
|
||||
[16, 17, 18],
|
||||
multisampled,
|
||||
|
@ -1060,7 +1067,9 @@ pub fn queue_mesh_view_bind_groups(
|
|||
entries.extend_from_slice(&tonemapping_luts);
|
||||
|
||||
// When using WebGL, we can't have a depth texture with multisampling
|
||||
if cfg!(not(feature = "webgl")) || (cfg!(feature = "webgl") && msaa.samples() == 1) {
|
||||
if cfg!(any(not(feature = "webgl"), not(target_arch = "wasm32")))
|
||||
|| (cfg!(all(feature = "webgl", target_arch = "wasm32")) && msaa.samples() == 1)
|
||||
{
|
||||
entries.extend_from_slice(&prepass::get_bindings(
|
||||
prepass_textures,
|
||||
&mut fallback_images,
|
||||
|
|
|
@ -21,14 +21,14 @@ fn parallaxed_uv(
|
|||
max_layer_count: f32,
|
||||
max_steps: u32,
|
||||
// The original interpolated uv
|
||||
uv: vec2<f32>,
|
||||
original_uv: vec2<f32>,
|
||||
// The vector from the camera to the fragment at the surface in tangent space
|
||||
Vt: vec3<f32>,
|
||||
) -> vec2<f32> {
|
||||
if max_layer_count < 1.0 {
|
||||
return uv;
|
||||
return original_uv;
|
||||
}
|
||||
var uv = uv;
|
||||
var uv = original_uv;
|
||||
|
||||
// Steep Parallax Mapping
|
||||
// ======================
|
||||
|
|
|
@ -46,11 +46,16 @@ impl Plugin for WireframePlugin {
|
|||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.add_render_command::<Opaque3d, DrawWireframes>()
|
||||
.init_resource::<WireframePipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<WireframePipeline>>()
|
||||
.add_systems(Render, queue_wireframes.in_set(RenderSet::Queue));
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<WireframePipeline>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled
|
||||
|
|
|
@ -54,7 +54,7 @@ pub struct GlobalsUniform {
|
|||
/// It wraps to zero when it reaches the maximum value of a u32.
|
||||
frame_count: u32,
|
||||
/// WebGL2 structs must be 16 byte aligned.
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
_wasm_padding: f32,
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ pub mod prelude {
|
|||
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
||||
use globals::GlobalsPlugin;
|
||||
pub use once_cell;
|
||||
use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue};
|
||||
use wgpu::Instance;
|
||||
|
||||
use crate::{
|
||||
camera::CameraPlugin,
|
||||
|
@ -57,7 +59,10 @@ use bevy_app::{App, AppLabel, Plugin, SubApp};
|
|||
use bevy_asset::{AddAsset, AssetServer};
|
||||
use bevy_ecs::{prelude::*, schedule::ScheduleLabel, system::SystemState};
|
||||
use bevy_utils::tracing::debug;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
/// Contains the default Bevy rendering backend based on wgpu.
|
||||
#[derive(Default)]
|
||||
|
@ -184,6 +189,21 @@ pub mod main_graph {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct FutureRendererResources(
|
||||
Arc<
|
||||
Mutex<
|
||||
Option<(
|
||||
RenderDevice,
|
||||
RenderQueue,
|
||||
RenderAdapterInfo,
|
||||
RenderAdapter,
|
||||
Instance,
|
||||
)>,
|
||||
>,
|
||||
>,
|
||||
);
|
||||
|
||||
/// A Label for the rendering sub-app.
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
|
||||
pub struct RenderApp;
|
||||
|
@ -196,41 +216,54 @@ impl Plugin for RenderPlugin {
|
|||
.init_asset_loader::<ShaderLoader>()
|
||||
.init_debug_asset_loader::<ShaderLoader>();
|
||||
|
||||
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
|
||||
SystemState::new(&mut app.world);
|
||||
let primary_window = system_state.get(&app.world);
|
||||
|
||||
if let Some(backends) = self.wgpu_settings.backends {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends,
|
||||
dx12_shader_compiler: self.wgpu_settings.dx12_shader_compiler.clone(),
|
||||
});
|
||||
let surface = primary_window.get_single().ok().map(|wrapper| unsafe {
|
||||
// SAFETY: Plugins should be set up on the main thread.
|
||||
let handle = wrapper.get_handle();
|
||||
instance
|
||||
.create_surface(&handle)
|
||||
.expect("Failed to create wgpu surface")
|
||||
});
|
||||
let future_renderer_resources_wrapper = Arc::new(Mutex::new(None));
|
||||
app.insert_resource(FutureRendererResources(
|
||||
future_renderer_resources_wrapper.clone(),
|
||||
));
|
||||
|
||||
let request_adapter_options = wgpu::RequestAdapterOptions {
|
||||
power_preference: self.wgpu_settings.power_preference,
|
||||
compatible_surface: surface.as_ref(),
|
||||
..Default::default()
|
||||
};
|
||||
let (device, queue, adapter_info, render_adapter) =
|
||||
futures_lite::future::block_on(renderer::initialize_renderer(
|
||||
&instance,
|
||||
&self.wgpu_settings,
|
||||
&request_adapter_options,
|
||||
));
|
||||
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
|
||||
debug!("Configured wgpu adapter Features: {:#?}", device.features());
|
||||
app.insert_resource(device.clone())
|
||||
.insert_resource(queue.clone())
|
||||
.insert_resource(adapter_info.clone())
|
||||
.insert_resource(render_adapter.clone())
|
||||
.init_resource::<ScratchMainWorld>();
|
||||
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
|
||||
SystemState::new(&mut app.world);
|
||||
let primary_window = system_state.get(&app.world).get_single().ok().cloned();
|
||||
|
||||
let settings = self.wgpu_settings.clone();
|
||||
bevy_tasks::IoTaskPool::get()
|
||||
.spawn_local(async move {
|
||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||
backends,
|
||||
dx12_shader_compiler: settings.dx12_shader_compiler.clone(),
|
||||
});
|
||||
let surface = primary_window.map(|wrapper| unsafe {
|
||||
// SAFETY: Plugins should be set up on the main thread.
|
||||
let handle = wrapper.get_handle();
|
||||
instance
|
||||
.create_surface(&handle)
|
||||
.expect("Failed to create wgpu surface")
|
||||
});
|
||||
|
||||
let request_adapter_options = wgpu::RequestAdapterOptions {
|
||||
power_preference: settings.power_preference,
|
||||
compatible_surface: surface.as_ref(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (device, queue, adapter_info, render_adapter) =
|
||||
renderer::initialize_renderer(
|
||||
&instance,
|
||||
&settings,
|
||||
&request_adapter_options,
|
||||
)
|
||||
.await;
|
||||
debug!("Configured wgpu adapter Limits: {:#?}", device.limits());
|
||||
debug!("Configured wgpu adapter Features: {:#?}", device.features());
|
||||
let mut future_renderer_resources_inner =
|
||||
future_renderer_resources_wrapper.lock().unwrap();
|
||||
*future_renderer_resources_inner =
|
||||
Some((device, queue, adapter_info, render_adapter, instance));
|
||||
})
|
||||
.detach();
|
||||
|
||||
app.init_resource::<ScratchMainWorld>();
|
||||
|
||||
let mut render_app = App::empty();
|
||||
render_app.main_schedule_label = Box::new(Render);
|
||||
|
@ -242,12 +275,6 @@ impl Plugin for RenderPlugin {
|
|||
.add_schedule(ExtractSchedule, extract_schedule)
|
||||
.add_schedule(Render, Render::base_schedule())
|
||||
.init_resource::<render_graph::RenderGraph>()
|
||||
.insert_resource(RenderInstance(instance))
|
||||
.insert_resource(PipelineCache::new(device.clone()))
|
||||
.insert_resource(device)
|
||||
.insert_resource(queue)
|
||||
.insert_resource(render_adapter)
|
||||
.insert_resource(adapter_info)
|
||||
.insert_resource(app.world.resource::<AssetServer>().clone())
|
||||
.add_systems(ExtractSchedule, PipelineCache::extract_shaders)
|
||||
.add_systems(
|
||||
|
@ -315,6 +342,37 @@ impl Plugin for RenderPlugin {
|
|||
.register_type::<primitives::CubemapFrusta>()
|
||||
.register_type::<primitives::Frustum>();
|
||||
}
|
||||
|
||||
fn ready(&self, app: &App) -> bool {
|
||||
app.world
|
||||
.get_resource::<FutureRendererResources>()
|
||||
.and_then(|frr| frr.0.try_lock().map(|locked| locked.is_some()).ok())
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Some(future_renderer_resources) =
|
||||
app.world.remove_resource::<FutureRendererResources>()
|
||||
{
|
||||
let (device, queue, adapter_info, render_adapter, instance) =
|
||||
future_renderer_resources.0.lock().unwrap().take().unwrap();
|
||||
|
||||
app.insert_resource(device.clone())
|
||||
.insert_resource(queue.clone())
|
||||
.insert_resource(adapter_info.clone())
|
||||
.insert_resource(render_adapter.clone());
|
||||
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
|
||||
render_app
|
||||
.insert_resource(RenderInstance(instance))
|
||||
.insert_resource(PipelineCache::new(device.clone()))
|
||||
.insert_resource(device)
|
||||
.insert_resource(queue)
|
||||
.insert_resource(render_adapter)
|
||||
.insert_resource(adapter_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A "scratch" world used to avoid allocating new worlds every frame when
|
||||
|
|
|
@ -77,7 +77,7 @@ impl Plugin for PipelinedRenderingPlugin {
|
|||
}
|
||||
|
||||
// Sets up the render thread and inserts resources into the main app used for controlling the render thread.
|
||||
fn setup(&self, app: &mut App) {
|
||||
fn cleanup(&self, app: &mut App) {
|
||||
// skip setting up when headless
|
||||
if app.get_sub_app(RenderExtractApp).is_err() {
|
||||
return;
|
||||
|
|
|
@ -10,7 +10,6 @@ use crate::{
|
|||
use bevy_utils::{default, detailed_trace};
|
||||
use std::ops::Range;
|
||||
use wgpu::{IndexFormat, RenderPass};
|
||||
use wgpu_hal::{MAX_BIND_GROUPS, MAX_VERTEX_BUFFERS};
|
||||
|
||||
/// Tracks the state of a [`TrackedRenderPass`].
|
||||
///
|
||||
|
@ -114,8 +113,8 @@ impl<'a> TrackedRenderPass<'a> {
|
|||
let max_vertex_buffers = limits.max_vertex_buffers as usize;
|
||||
Self {
|
||||
state: DrawState {
|
||||
bind_groups: vec![(None, Vec::new()); max_bind_groups.min(MAX_BIND_GROUPS)],
|
||||
vertex_buffers: vec![None; max_vertex_buffers.min(MAX_VERTEX_BUFFERS)],
|
||||
bind_groups: vec![(None, Vec::new()); max_bind_groups],
|
||||
vertex_buffers: vec![None; max_vertex_buffers],
|
||||
..default()
|
||||
},
|
||||
pass,
|
||||
|
|
|
@ -194,7 +194,7 @@ impl ShaderCache {
|
|||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
let mut shader_defs = shader_defs.to_vec();
|
||||
#[cfg(feature = "webgl")]
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32"))]
|
||||
{
|
||||
shader_defs.push("NO_ARRAY_TEXTURES_SUPPORT".into());
|
||||
shader_defs.push("SIXTEEN_BYTE_ALIGNMENT".into());
|
||||
|
|
|
@ -45,7 +45,7 @@ pub struct WgpuSettings {
|
|||
|
||||
impl Default for WgpuSettings {
|
||||
fn default() -> Self {
|
||||
let default_backends = if cfg!(feature = "webgl") {
|
||||
let default_backends = if cfg!(all(feature = "webgl", target_arch = "wasm32")) {
|
||||
Backends::GL
|
||||
} else {
|
||||
Backends::all()
|
||||
|
@ -55,7 +55,8 @@ impl Default for WgpuSettings {
|
|||
|
||||
let priority = settings_priority_from_env().unwrap_or(WgpuSettingsPriority::Functionality);
|
||||
|
||||
let limits = if cfg!(feature = "webgl") || matches!(priority, WgpuSettingsPriority::WebGL2)
|
||||
let limits = if cfg!(all(feature = "webgl", target_arch = "wasm32"))
|
||||
|| matches!(priority, WgpuSettingsPriority::WebGL2)
|
||||
{
|
||||
wgpu::Limits::downlevel_webgl2_defaults()
|
||||
} else {
|
||||
|
|
|
@ -103,6 +103,15 @@ impl Plugin for ImagePlugin {
|
|||
.resource_mut::<Assets<Image>>()
|
||||
.set_untracked(DEFAULT_IMAGE_HANDLE, Image::default());
|
||||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<TextureCache>().add_systems(
|
||||
Render,
|
||||
update_texture_cache_system.in_set(RenderSet::Cleanup),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
let default_sampler = {
|
||||
let device = render_app.world.resource::<RenderDevice>();
|
||||
|
@ -110,15 +119,10 @@ impl Plugin for ImagePlugin {
|
|||
};
|
||||
render_app
|
||||
.insert_resource(DefaultImageSampler(default_sampler))
|
||||
.init_resource::<TextureCache>()
|
||||
.init_resource::<FallbackImage>()
|
||||
.init_resource::<FallbackImageCubemap>()
|
||||
.init_resource::<FallbackImageMsaaCache>()
|
||||
.init_resource::<FallbackImageDepthCache>()
|
||||
.add_systems(
|
||||
Render,
|
||||
update_texture_cache_system.in_set(RenderSet::Cleanup),
|
||||
);
|
||||
.init_resource::<FallbackImageDepthCache>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -406,8 +406,11 @@ fn prepare_view_targets(
|
|||
format: main_texture_format,
|
||||
usage: TextureUsages::RENDER_ATTACHMENT
|
||||
| TextureUsages::TEXTURE_BINDING,
|
||||
// TODO: Consider changing this if main_texture_format is not sRGB
|
||||
view_formats: &[],
|
||||
view_formats: match main_texture_format {
|
||||
TextureFormat::Bgra8Unorm => &[TextureFormat::Bgra8UnormSrgb],
|
||||
TextureFormat::Rgba8Unorm => &[TextureFormat::Rgba8UnormSrgb],
|
||||
_ => &[],
|
||||
},
|
||||
};
|
||||
MainTargetTextures {
|
||||
a: texture_cache
|
||||
|
@ -440,7 +443,7 @@ fn prepare_view_targets(
|
|||
dimension: TextureDimension::D2,
|
||||
format: main_texture_format,
|
||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
view_formats: descriptor.view_formats,
|
||||
},
|
||||
)
|
||||
.default_view
|
||||
|
@ -454,7 +457,7 @@ fn prepare_view_targets(
|
|||
main_texture_format,
|
||||
main_texture: main_textures.main_texture.clone(),
|
||||
out_texture: out_texture_view.clone(),
|
||||
out_texture_format,
|
||||
out_texture_format: out_texture_format.add_srgb_suffix(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,12 +6,12 @@ use crate::{
|
|||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_utils::{tracing::debug, HashMap, HashSet};
|
||||
use bevy_utils::{default, tracing::debug, HashMap, HashSet};
|
||||
use bevy_window::{
|
||||
CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosed,
|
||||
};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use wgpu::{BufferUsages, TextureFormat, TextureUsages};
|
||||
use wgpu::{BufferUsages, TextureFormat, TextureUsages, TextureViewDescriptor};
|
||||
|
||||
pub mod screenshot;
|
||||
|
||||
|
@ -40,13 +40,18 @@ impl Plugin for WindowRenderPlugin {
|
|||
render_app
|
||||
.init_resource::<ExtractedWindows>()
|
||||
.init_resource::<WindowSurfaces>()
|
||||
.init_resource::<ScreenshotToScreenPipeline>()
|
||||
.init_non_send_resource::<NonSendMarker>()
|
||||
.add_systems(ExtractSchedule, extract_windows)
|
||||
.configure_set(Render, WindowSystem::Prepare.in_set(RenderSet::Prepare))
|
||||
.add_systems(Render, prepare_windows.in_set(WindowSystem::Prepare));
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<ScreenshotToScreenPipeline>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractedWindow {
|
||||
|
@ -71,8 +76,12 @@ pub struct ExtractedWindow {
|
|||
|
||||
impl ExtractedWindow {
|
||||
fn set_swapchain_texture(&mut self, frame: wgpu::SurfaceTexture) {
|
||||
let texture_view_descriptor = TextureViewDescriptor {
|
||||
format: Some(frame.texture.format().add_srgb_suffix()),
|
||||
..default()
|
||||
};
|
||||
self.swap_chain_texture_view = Some(TextureView::from(
|
||||
frame.texture.create_view(&Default::default()),
|
||||
frame.texture.create_view(&texture_view_descriptor),
|
||||
));
|
||||
self.swap_chain_texture = Some(SurfaceTexture::from(frame));
|
||||
}
|
||||
|
@ -269,8 +278,11 @@ pub fn prepare_windows(
|
|||
CompositeAlphaMode::PostMultiplied => wgpu::CompositeAlphaMode::PostMultiplied,
|
||||
CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit,
|
||||
},
|
||||
// TODO: Use an sRGB view format here on platforms that don't support sRGB surfaces. (afaik only WebGPU)
|
||||
view_formats: vec![],
|
||||
view_formats: if !surface_data.format.is_srgb() {
|
||||
vec![surface_data.format.add_srgb_suffix()]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
};
|
||||
|
||||
// This is an ugly hack to work around drivers that don't support MSAA.
|
||||
|
|
|
@ -135,7 +135,9 @@ impl Plugin for ScreenshotPlugin {
|
|||
"screenshot.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<SpecializedRenderPipelines<ScreenshotToScreenPipeline>>();
|
||||
}
|
||||
|
|
|
@ -75,7 +75,6 @@ impl Plugin for SpritePlugin {
|
|||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.init_resource::<ImageBindGroups>()
|
||||
.init_resource::<SpritePipeline>()
|
||||
.init_resource::<SpecializedRenderPipelines<SpritePipeline>>()
|
||||
.init_resource::<SpriteMeta>()
|
||||
.init_resource::<ExtractedSprites>()
|
||||
|
@ -96,6 +95,12 @@ impl Plugin for SpritePlugin {
|
|||
);
|
||||
};
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<SpritePipeline>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_bounds_2d(
|
||||
|
|
|
@ -157,7 +157,6 @@ where
|
|||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.add_render_command::<Transparent2d, DrawMaterial2d<M>>()
|
||||
.init_resource::<Material2dPipeline<M>>()
|
||||
.init_resource::<ExtractedMaterials2d<M>>()
|
||||
.init_resource::<RenderMaterials2d<M>>()
|
||||
.init_resource::<SpecializedMeshPipelines<Material2dPipeline<M>>>()
|
||||
|
@ -173,6 +172,12 @@ where
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<Material2dPipeline<M>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render pipeline data for a given [`Material2d`]
|
||||
|
|
|
@ -101,7 +101,6 @@ impl Plugin for Mesh2dRenderPlugin {
|
|||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
.init_resource::<Mesh2dPipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()
|
||||
.add_systems(ExtractSchedule, extract_mesh2d)
|
||||
.add_systems(
|
||||
|
@ -113,6 +112,12 @@ impl Plugin for Mesh2dRenderPlugin {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut bevy_app::App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<Mesh2dPipeline>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, ShaderType, Clone)]
|
||||
|
|
|
@ -21,7 +21,7 @@ pub mod widget;
|
|||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
use bevy_render::camera::CameraUpdateSystem;
|
||||
use bevy_render::extract_component::ExtractComponentPlugin;
|
||||
use bevy_render::{extract_component::ExtractComponentPlugin, RenderApp};
|
||||
pub use focus::*;
|
||||
pub use geometry::*;
|
||||
pub use layout::*;
|
||||
|
@ -169,4 +169,13 @@ impl Plugin for UiPlugin {
|
|||
|
||||
crate::render::build_ui_render(app);
|
||||
}
|
||||
|
||||
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::<UiPipeline>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,6 @@ pub fn build_ui_render(app: &mut App) {
|
|||
};
|
||||
|
||||
render_app
|
||||
.init_resource::<UiPipeline>()
|
||||
.init_resource::<SpecializedRenderPipelines<UiPipeline>>()
|
||||
.init_resource::<UiImageBindGroups>()
|
||||
.init_resource::<UiMeta>()
|
||||
|
|
|
@ -25,6 +25,7 @@ bevy_input = { path = "../bevy_input", version = "0.11.0-dev" }
|
|||
bevy_math = { path = "../bevy_math", version = "0.11.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.11.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.11.0-dev" }
|
||||
|
||||
# other
|
||||
winit = { version = "0.28", default-features = false }
|
||||
|
|
|
@ -17,6 +17,8 @@ mod winit_windows;
|
|||
|
||||
use bevy_a11y::AccessibilityRequested;
|
||||
use bevy_ecs::system::{SystemParam, SystemState};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use bevy_tasks::tick_global_task_pools_on_main_thread;
|
||||
use system::{changed_window, create_window, despawn_window, CachedWindow};
|
||||
|
||||
pub use winit_config::*;
|
||||
|
@ -327,12 +329,25 @@ pub fn winit_runner(mut app: App) {
|
|||
ResMut<CanvasParentResizeEventChannel>,
|
||||
)> = SystemState::from_world(&mut app.world);
|
||||
|
||||
let mut finished_and_setup_done = false;
|
||||
|
||||
let event_handler = move |event: Event<()>,
|
||||
event_loop: &EventLoopWindowTarget<()>,
|
||||
control_flow: &mut ControlFlow| {
|
||||
#[cfg(feature = "trace")]
|
||||
let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
|
||||
|
||||
if !finished_and_setup_done {
|
||||
if !app.ready() {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
tick_global_task_pools_on_main_thread();
|
||||
} else {
|
||||
app.finish();
|
||||
app.cleanup();
|
||||
finished_and_setup_done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
|
||||
if app_exit_event_reader.iter(app_exit_events).last().is_some() {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
|
@ -648,7 +663,7 @@ pub fn winit_runner(mut app: App) {
|
|||
false
|
||||
};
|
||||
|
||||
if update {
|
||||
if update && finished_and_setup_done {
|
||||
winit_state.last_update = Instant::now();
|
||||
app.update();
|
||||
}
|
||||
|
|
|
@ -207,7 +207,7 @@ Following is an example for `lighting`. For other examples, change the `lighting
|
|||
following commands.
|
||||
|
||||
```sh
|
||||
cargo build --release --example lighting --target wasm32-unknown-unknown
|
||||
cargo build --release --example lighting --target wasm32-unknown-unknown --features webgl
|
||||
wasm-bindgen --out-name wasm_example \
|
||||
--out-dir examples/wasm/target \
|
||||
--target web target/wasm32-unknown-unknown/release/examples/lighting.wasm
|
||||
|
@ -231,6 +231,19 @@ python3 -m http.server --directory examples/wasm
|
|||
ruby -run -ehttpd examples/wasm
|
||||
```
|
||||
|
||||
#### WebGL2 and WebGPU
|
||||
|
||||
Bevy support for WebGPU is being worked on, but is currently experimental.
|
||||
|
||||
To build for WebGPU, you'll need to disable default features and add all those you need, making sure to omit the `webgl2` feature.
|
||||
|
||||
Bevy has an helper to build its examples:
|
||||
|
||||
- Build for WebGL2: `cargo run -p build-wasm-example -- --api webgl2 load_gltf`
|
||||
- Build for WebGPU: `cargo run -p build-wasm-example -- --api webgpu load_gltf`
|
||||
|
||||
This helper will log the command used to build the examples.
|
||||
|
||||
### Optimizing
|
||||
|
||||
On the web, it's useful to reduce the size of the files that are distributed.
|
||||
|
|
|
@ -34,6 +34,7 @@ The default feature set enables most of the expected features of a game engine,
|
|||
|png|PNG image format support|
|
||||
|tonemapping_luts|Include tonemapping Look Up Tables KTX2 files|
|
||||
|vorbis|OGG/VORBIS audio format support|
|
||||
|webgl2|Enable some limitations to be able to use WebGL2. If not enabled, it will default to WebGPU in Wasm|
|
||||
|x11|X11 display server support|
|
||||
|zstd|For KTX2 supercompression|
|
||||
|
||||
|
|
|
@ -496,7 +496,7 @@ Following is an example for `lighting`. For other examples, change the `lighting
|
|||
following commands.
|
||||
|
||||
```sh
|
||||
cargo build --release --example lighting --target wasm32-unknown-unknown
|
||||
cargo build --release --example lighting --target wasm32-unknown-unknown --features webgl
|
||||
wasm-bindgen --out-name wasm_example \
|
||||
--out-dir examples/wasm/target \
|
||||
--target web target/wasm32-unknown-unknown/release/examples/lighting.wasm
|
||||
|
@ -520,6 +520,19 @@ python3 -m http.server --directory examples/wasm
|
|||
ruby -run -ehttpd examples/wasm
|
||||
```
|
||||
|
||||
#### WebGL2 and WebGPU
|
||||
|
||||
Bevy support for WebGPU is being worked on, but is currently experimental.
|
||||
|
||||
To build for WebGPU, you'll need to disable default features and add all those you need, making sure to omit the `webgl2` feature.
|
||||
|
||||
Bevy has an helper to build its examples:
|
||||
|
||||
- Build for WebGL2: `cargo run -p build-wasm-example -- --api webgl2 load_gltf`
|
||||
- Build for WebGPU: `cargo run -p build-wasm-example -- --api webgpu load_gltf`
|
||||
|
||||
This helper will log the command used to build the examples.
|
||||
|
||||
### Optimizing
|
||||
|
||||
On the web, it's useful to reduce the size of the files that are distributed.
|
||||
|
|
|
@ -72,9 +72,7 @@ impl Plugin for GameOfLifeComputePlugin {
|
|||
// for operation on by the compute shader and display on the sprite.
|
||||
app.add_plugin(ExtractResourcePlugin::<GameOfLifeImage>::default());
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
render_app
|
||||
.init_resource::<GameOfLifePipeline>()
|
||||
.add_systems(Render, queue_bind_group.in_set(RenderSet::Queue));
|
||||
render_app.add_systems(Render, queue_bind_group.in_set(RenderSet::Queue));
|
||||
|
||||
let mut render_graph = render_app.world.resource_mut::<RenderGraph>();
|
||||
render_graph.add_node("game_of_life", GameOfLifeNode::default());
|
||||
|
@ -83,6 +81,11 @@ impl Plugin for GameOfLifeComputePlugin {
|
|||
bevy::render::main_graph::node::CAMERA_DRIVER,
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
render_app.init_resource::<GameOfLifePipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone, Deref, ExtractResource)]
|
||||
|
|
|
@ -83,7 +83,6 @@ impl Plugin for CustomMaterialPlugin {
|
|||
app.add_plugin(ExtractComponentPlugin::<InstanceMaterialData>::default());
|
||||
app.sub_app_mut(RenderApp)
|
||||
.add_render_command::<Transparent3d, DrawCustom>()
|
||||
.init_resource::<CustomPipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<CustomPipeline>>()
|
||||
.add_systems(
|
||||
Render,
|
||||
|
@ -93,6 +92,10 @@ impl Plugin for CustomMaterialPlugin {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
app.sub_app_mut(RenderApp).init_resource::<CustomPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
use std::{fs::File, io::Write};
|
||||
|
||||
use clap::Parser;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use xshell::{cmd, Shell};
|
||||
|
||||
#[derive(Debug, Copy, Clone, ValueEnum)]
|
||||
enum Api {
|
||||
Webgl2,
|
||||
Webgpu,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Args {
|
||||
/// Examples to build
|
||||
|
@ -19,31 +25,77 @@ struct Args {
|
|||
#[arg(short, long)]
|
||||
/// Stop after this number of frames
|
||||
frames: Option<usize>,
|
||||
|
||||
#[arg(value_enum, short, long, default_value_t = Api::Webgl2)]
|
||||
/// Browser API to use for rendering
|
||||
api: Api,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Args::parse();
|
||||
eprintln!("{cli:?}");
|
||||
|
||||
assert!(!cli.examples.is_empty(), "must have at least one example");
|
||||
|
||||
let mut bevy_ci_testing = vec![];
|
||||
let mut default_features = true;
|
||||
let mut features = vec![];
|
||||
if let Some(frames) = cli.frames {
|
||||
let mut file = File::create("ci_testing_config.ron").unwrap();
|
||||
file.write_fmt(format_args!("(exit_after: Some({frames}))"))
|
||||
.unwrap();
|
||||
bevy_ci_testing = vec!["--features", "bevy_ci_testing"];
|
||||
features.push("bevy_ci_testing");
|
||||
}
|
||||
|
||||
match cli.api {
|
||||
Api::Webgl2 => (),
|
||||
Api::Webgpu => {
|
||||
features.push("animation");
|
||||
features.push("bevy_asset");
|
||||
features.push("bevy_audio");
|
||||
features.push("bevy_gilrs");
|
||||
features.push("bevy_scene");
|
||||
features.push("bevy_winit");
|
||||
features.push("bevy_core_pipeline");
|
||||
features.push("bevy_pbr");
|
||||
features.push("bevy_gltf");
|
||||
features.push("bevy_render");
|
||||
features.push("bevy_sprite");
|
||||
features.push("bevy_text");
|
||||
features.push("bevy_ui");
|
||||
features.push("png");
|
||||
features.push("hdr");
|
||||
features.push("ktx2");
|
||||
features.push("zstd");
|
||||
features.push("vorbis");
|
||||
features.push("x11");
|
||||
features.push("filesystem_watcher");
|
||||
features.push("bevy_gizmos");
|
||||
features.push("android_shared_stdcxx");
|
||||
features.push("tonemapping_luts");
|
||||
features.push("default_font");
|
||||
default_features = false;
|
||||
}
|
||||
}
|
||||
|
||||
for example in cli.examples {
|
||||
let sh = Shell::new().unwrap();
|
||||
let bevy_ci_testing = bevy_ci_testing.clone();
|
||||
cmd!(
|
||||
let features_string = features.join(",");
|
||||
let mut parameters = vec![];
|
||||
if !default_features {
|
||||
parameters.push("--no-default-features");
|
||||
}
|
||||
if !features.is_empty() {
|
||||
parameters.push("--features");
|
||||
parameters.push(&features_string);
|
||||
}
|
||||
let mut cmd = cmd!(
|
||||
sh,
|
||||
"cargo build {bevy_ci_testing...} --release --target wasm32-unknown-unknown --example {example}"
|
||||
)
|
||||
.run()
|
||||
.expect("Error building example");
|
||||
"cargo build {parameters...} --release --target wasm32-unknown-unknown --example {example}"
|
||||
);
|
||||
if matches!(cli.api, Api::Webgpu) {
|
||||
cmd = cmd.env("RUSTFLAGS", "--cfg=web_sys_unstable_apis");
|
||||
}
|
||||
cmd.run().expect("Error building example");
|
||||
|
||||
cmd!(
|
||||
sh,
|
||||
"wasm-bindgen --out-dir examples/wasm/target --out-name wasm_example --target web target/wasm32-unknown-unknown/release/examples/{example}.wasm"
|
||||
|
|
Loading…
Reference in a new issue