Skip to content

Commit 6c47964

Browse files
committed
enable Webgl2 optimisation in pbr under feature (#3291)
# Objective - 3d examples fail to run in webgl2 because of unsupported texture formats or texture too large ## Solution - switch to supported formats if a feature is enabled. I choose a feature instead of a build target to not conflict with a potential webgpu support Very inspired by superdump@6813b2e, and need #3290 to work. I named the feature `webgl2`, but it's only needed if one want to use PBR in webgl2. Examples using only 2D already work. Co-authored-by: François <[email protected]>
1 parent a3c53e6 commit 6c47964

File tree

12 files changed

+137
-70
lines changed

12 files changed

+137
-70
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ bevy_ci_testing = ["bevy_internal/bevy_ci_testing"]
9292
bevy_dylib = { path = "crates/bevy_dylib", version = "0.5.0", default-features = false, optional = true }
9393
bevy_internal = { path = "crates/bevy_internal", version = "0.5.0", default-features = false }
9494

95+
[target.'cfg(target_arch = "wasm32")'.dependencies]
96+
bevy_internal = { path = "crates/bevy_internal", version = "0.5.0", default-features = false, features = ["webgl"] }
97+
9598
[dev-dependencies]
9699
anyhow = "1.0.4"
97100
rand = "0.8.0"

crates/bevy_internal/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ x11 = ["bevy_winit/x11"]
4141
# enable rendering of font glyphs using subpixel accuracy
4242
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
4343

44+
# Optimise for WebGL2
45+
webgl = ["bevy_pbr/webgl", "bevy_render/webgl"]
46+
4447
# enable systems that allow for automated testing on CI
4548
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"]
4649

crates/bevy_pbr/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ repository = "https://github.com/bevyengine/bevy"
88
license = "MIT OR Apache-2.0"
99
keywords = ["bevy"]
1010

11+
[features]
12+
webgl = []
13+
1114
[dependencies]
1215
# bevy
1316
bevy_app = { path = "../bevy_app", version = "0.5.0" }

crates/bevy_pbr/src/light.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,10 @@ pub struct DirectionalLightShadowMap {
151151

152152
impl Default for DirectionalLightShadowMap {
153153
fn default() -> Self {
154-
Self { size: 4096 }
154+
#[cfg(feature = "webgl")]
155+
return Self { size: 2048 };
156+
#[cfg(not(feature = "webgl"))]
157+
return Self { size: 4096 };
155158
}
156159
}
157160

crates/bevy_pbr/src/render/light.rs

Lines changed: 87 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ pub struct GpuLights {
142142
pub const MAX_POINT_LIGHTS: usize = 256;
143143
// FIXME: How should we handle shadows for clustered forward? Limiting to maximum 10
144144
// point light shadow maps for now
145+
#[cfg(feature = "webgl")]
146+
pub const MAX_POINT_LIGHT_SHADOW_MAPS: usize = 1;
147+
#[cfg(not(feature = "webgl"))]
145148
pub const MAX_POINT_LIGHT_SHADOW_MAPS: usize = 10;
146149
pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
147150
pub const POINT_SHADOW_LAYERS: u32 = (6 * MAX_POINT_LIGHT_SHADOW_MAPS) as u32;
@@ -587,14 +590,30 @@ pub fn prepare_lights(
587590

588591
global_light_meta.gpu_point_lights.clear();
589592
global_light_meta.entity_to_index.clear();
590-
let n_point_lights = point_lights.iter().count();
591-
if global_light_meta.entity_to_index.capacity() < n_point_lights {
592-
global_light_meta.entity_to_index.reserve(n_point_lights);
593+
594+
let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();
595+
596+
// Sort point lights with shadows enabled first, then by a stable key so that the index can be used
597+
// to render at most `MAX_POINT_LIGHT_SHADOW_MAPS` point light shadows.
598+
point_lights.sort_by(|(entity_1, light_1), (entity_2, light_2)| {
599+
light_1
600+
.shadows_enabled
601+
.cmp(&light_2.shadows_enabled)
602+
.reverse()
603+
.then_with(|| entity_1.cmp(entity_2))
604+
});
605+
606+
if global_light_meta.entity_to_index.capacity() < point_lights.len() {
607+
global_light_meta
608+
.entity_to_index
609+
.reserve(point_lights.len());
593610
}
611+
594612
let mut gpu_point_lights = [GpuPointLight::default(); MAX_POINT_LIGHTS];
595-
for (index, (entity, light)) in point_lights.iter().enumerate() {
613+
for (index, &(entity, light)) in point_lights.iter().enumerate() {
596614
let mut flags = PointLightFlags::NONE;
597-
if light.shadows_enabled {
615+
// Lights are sorted, shadow enabled lights are first
616+
if light.shadows_enabled && index < MAX_POINT_LIGHT_SHADOW_MAPS {
598617
flags |= PointLightFlags::SHADOWS_ENABLED;
599618
}
600619
gpu_point_lights[index] = GpuPointLight {
@@ -683,63 +702,63 @@ pub fn prepare_lights(
683702
};
684703

685704
// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
686-
let mut point_light_count = 0;
687-
for (light_entity, light) in point_lights.iter() {
688-
if point_light_count < MAX_POINT_LIGHT_SHADOW_MAPS && light.shadows_enabled {
689-
point_light_count += 1;
690-
let light_index = *global_light_meta
691-
.entity_to_index
692-
.get(&light_entity)
693-
.unwrap();
694-
// ignore scale because we don't want to effectively scale light radius and range
695-
// by applying those as a view transform to shadow map rendering of objects
696-
// and ignore rotation because we want the shadow map projections to align with the axes
697-
let view_translation =
698-
GlobalTransform::from_translation(light.transform.translation);
699-
700-
for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() {
701-
let depth_texture_view =
702-
point_light_depth_texture
703-
.texture
704-
.create_view(&TextureViewDescriptor {
705-
label: Some("point_light_shadow_map_texture_view"),
706-
format: None,
707-
dimension: Some(TextureViewDimension::D2),
708-
aspect: TextureAspect::All,
709-
base_mip_level: 0,
710-
mip_level_count: None,
711-
base_array_layer: (light_index * 6 + face_index) as u32,
712-
array_layer_count: NonZeroU32::new(1),
713-
});
714-
715-
let view_light_entity = commands
716-
.spawn()
717-
.insert_bundle((
718-
ShadowView {
719-
depth_texture_view,
720-
pass_name: format!(
721-
"shadow pass point light {} {}",
722-
light_index,
723-
face_index_to_name(face_index)
724-
),
725-
},
726-
ExtractedView {
727-
width: point_light_shadow_map.size as u32,
728-
height: point_light_shadow_map.size as u32,
729-
transform: view_translation * *view_rotation,
730-
projection: cube_face_projection,
731-
near: POINT_LIGHT_NEAR_Z,
732-
far: light.range,
733-
},
734-
RenderPhase::<Shadow>::default(),
735-
LightEntity::Point {
736-
light_entity,
737-
face_index,
738-
},
739-
))
740-
.id();
741-
view_lights.push(view_light_entity);
742-
}
705+
for &(light_entity, light) in point_lights
706+
.iter()
707+
// Lights are sorted, shadow enabled lights are first
708+
.take(MAX_POINT_LIGHT_SHADOW_MAPS)
709+
.filter(|(_, light)| light.shadows_enabled)
710+
{
711+
let light_index = *global_light_meta
712+
.entity_to_index
713+
.get(&light_entity)
714+
.unwrap();
715+
// ignore scale because we don't want to effectively scale light radius and range
716+
// by applying those as a view transform to shadow map rendering of objects
717+
// and ignore rotation because we want the shadow map projections to align with the axes
718+
let view_translation = GlobalTransform::from_translation(light.transform.translation);
719+
720+
for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() {
721+
let depth_texture_view =
722+
point_light_depth_texture
723+
.texture
724+
.create_view(&TextureViewDescriptor {
725+
label: Some("point_light_shadow_map_texture_view"),
726+
format: None,
727+
dimension: Some(TextureViewDimension::D2),
728+
aspect: TextureAspect::All,
729+
base_mip_level: 0,
730+
mip_level_count: None,
731+
base_array_layer: (light_index * 6 + face_index) as u32,
732+
array_layer_count: NonZeroU32::new(1),
733+
});
734+
735+
let view_light_entity = commands
736+
.spawn()
737+
.insert_bundle((
738+
ShadowView {
739+
depth_texture_view,
740+
pass_name: format!(
741+
"shadow pass point light {} {}",
742+
light_index,
743+
face_index_to_name(face_index)
744+
),
745+
},
746+
ExtractedView {
747+
width: point_light_shadow_map.size as u32,
748+
height: point_light_shadow_map.size as u32,
749+
transform: view_translation * *view_rotation,
750+
projection: cube_face_projection,
751+
near: POINT_LIGHT_NEAR_Z,
752+
far: light.range,
753+
},
754+
RenderPhase::<Shadow>::default(),
755+
LightEntity::Point {
756+
light_entity,
757+
face_index,
758+
},
759+
))
760+
.id();
761+
view_lights.push(view_light_entity);
743762
}
744763
}
745764

@@ -830,7 +849,10 @@ pub fn prepare_lights(
830849
.create_view(&TextureViewDescriptor {
831850
label: Some("point_light_shadow_map_array_texture_view"),
832851
format: None,
852+
#[cfg(not(feature = "webgl"))]
833853
dimension: Some(TextureViewDimension::CubeArray),
854+
#[cfg(feature = "webgl")]
855+
dimension: Some(TextureViewDimension::Cube),
834856
aspect: TextureAspect::All,
835857
base_mip_level: 0,
836858
mip_level_count: None,
@@ -842,7 +864,10 @@ pub fn prepare_lights(
842864
.create_view(&TextureViewDescriptor {
843865
label: Some("directional_light_shadow_map_array_texture_view"),
844866
format: None,
867+
#[cfg(not(feature = "webgl"))]
845868
dimension: Some(TextureViewDimension::D2Array),
869+
#[cfg(feature = "webgl")]
870+
dimension: Some(TextureViewDimension::D2),
846871
aspect: TextureAspect::All,
847872
base_mip_level: 0,
848873
mip_level_count: None,

crates/bevy_pbr/src/render/mesh.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,10 @@ impl FromWorld for MeshPipeline {
202202
ty: BindingType::Texture {
203203
multisampled: false,
204204
sample_type: TextureSampleType::Depth,
205+
#[cfg(not(feature = "webgl"))]
205206
view_dimension: TextureViewDimension::CubeArray,
207+
#[cfg(feature = "webgl")]
208+
view_dimension: TextureViewDimension::Cube,
206209
},
207210
count: None,
208211
},
@@ -220,7 +223,10 @@ impl FromWorld for MeshPipeline {
220223
ty: BindingType::Texture {
221224
multisampled: false,
222225
sample_type: TextureSampleType::Depth,
226+
#[cfg(not(feature = "webgl"))]
223227
view_dimension: TextureViewDimension::D2Array,
228+
#[cfg(feature = "webgl")]
229+
view_dimension: TextureViewDimension::D2,
224230
},
225231
count: None,
226232
},
@@ -485,6 +491,9 @@ impl SpecializedPipeline for MeshPipeline {
485491
depth_write_enabled = true;
486492
}
487493

494+
#[cfg(feature = "webgl")]
495+
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));
496+
488497
RenderPipelineDescriptor {
489498
vertex: VertexState {
490499
shader: MESH_SHADER_HANDLE.typed::<Shader>(),

crates/bevy_pbr/src/render/mesh_view_bind_group.wgsl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,22 @@ struct ClusterOffsetsAndCounts {
7373
var<uniform> view: View;
7474
[[group(0), binding(1)]]
7575
var<uniform> lights: Lights;
76+
#ifdef NO_ARRAY_TEXTURES_SUPPORT
77+
[[group(0), binding(2)]]
78+
var point_shadow_textures: texture_depth_cube;
79+
#else
7680
[[group(0), binding(2)]]
7781
var point_shadow_textures: texture_depth_cube_array;
82+
#endif
7883
[[group(0), binding(3)]]
7984
var point_shadow_textures_sampler: sampler_comparison;
85+
#ifdef NO_ARRAY_TEXTURES_SUPPORT
86+
[[group(0), binding(4)]]
87+
var directional_shadow_textures: texture_depth_2d;
88+
#else
8089
[[group(0), binding(4)]]
8190
var directional_shadow_textures: texture_depth_2d_array;
91+
#endif
8292
[[group(0), binding(5)]]
8393
var directional_shadow_textures_sampler: sampler_comparison;
8494
[[group(0), binding(6)]]

crates/bevy_pbr/src/render/pbr.wgsl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,11 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: v
381381
// a quad (2x2 fragments) being processed not being sampled, and this messing with
382382
// mip-mapping functionality. The shadow maps have no mipmaps so Level just samples
383383
// from LOD 0.
384+
#ifdef NO_ARRAY_TEXTURES_SUPPORT
385+
return textureSampleCompare(point_shadow_textures, point_shadow_textures_sampler, frag_ls, depth);
386+
#else
384387
return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth);
388+
#endif
385389
}
386390

387391
fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
@@ -412,7 +416,11 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_nor
412416
// do the lookup, using HW PCF and comparison
413417
// NOTE: Due to non-uniform control flow above, we must use the level variant of the texture
414418
// sampler to avoid use of implicit derivatives causing possible undefined behavior.
419+
#ifdef NO_ARRAY_TEXTURES_SUPPORT
420+
return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, depth);
421+
#else
415422
return textureSampleCompareLevel(directional_shadow_textures, directional_shadow_textures_sampler, light_local, i32(light_id), depth);
423+
#endif
416424
}
417425

418426
fn hsv2rgb(hue: f32, saturation: f32, value: f32) -> vec3<f32> {

crates/bevy_render/Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ bmp = ["image/bmp"]
1818
trace = []
1919
wgpu_trace = ["wgpu/trace"]
2020
ci_limits = []
21+
webgl = ["wgpu/webgl"]
2122

2223
[dependencies]
2324
# bevy
@@ -51,6 +52,3 @@ hexasphere = "6.0.0"
5152
parking_lot = "0.11.0"
5253
regex = "1.5"
5354
crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] }
54-
55-
[target.'cfg(target_arch = "wasm32")'.dependencies]
56-
wgpu = { version = "0.12.0", features = ["spirv", "webgl"] }

crates/bevy_render/src/mesh/mesh/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -623,15 +623,15 @@ impl RenderAsset for Mesh {
623623
let vertex_buffer_data = mesh.get_vertex_buffer_data();
624624
let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
625625
usage: BufferUsages::VERTEX,
626-
label: None,
626+
label: Some("Mesh Vertex Buffer"),
627627
contents: &vertex_buffer_data,
628628
});
629629

630630
let index_info = mesh.get_index_buffer_bytes().map(|data| GpuIndexInfo {
631631
buffer: render_device.create_buffer_with_data(&BufferInitDescriptor {
632632
usage: BufferUsages::INDEX,
633633
contents: data,
634-
label: None,
634+
label: Some("Mesh Index Buffer"),
635635
}),
636636
count: mesh.indices().unwrap().len() as u32,
637637
index_format: mesh.indices().unwrap().into(),

crates/bevy_render/src/renderer/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ pub async fn initialize_renderer(
7272
.await
7373
.expect("Unable to find a GPU! Make sure you have installed required drivers!");
7474

75-
#[cfg(not(target_arch = "wasm32"))]
7675
info!("{:?}", adapter.get_info());
7776

7877
#[cfg(feature = "wgpu_trace")]

crates/bevy_render/src/view/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,18 @@ pub struct Msaa {
4444
/// smoother edges. Note that WGPU currently only supports 1 or 4 samples.
4545
/// Ultimately we plan on supporting whatever is natively supported on a given device.
4646
/// Check out this issue for more info: <https://github.com/gfx-rs/wgpu/issues/1832>
47+
/// It defaults to 1 in wasm - <https://github.com/gfx-rs/wgpu/issues/2149>
4748
pub samples: u32,
4849
}
4950

5051
impl Default for Msaa {
5152
fn default() -> Self {
52-
Self { samples: 4 }
53+
Self {
54+
#[cfg(feature = "webgl")]
55+
samples: 1,
56+
#[cfg(not(feature = "webgl"))]
57+
samples: 4,
58+
}
5359
}
5460
}
5561

0 commit comments

Comments
 (0)