Skip to content

Commit

Permalink
enable Webgl2 optimisation in pbr under feature (#3291)
Browse files Browse the repository at this point in the history
# 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]>
  • Loading branch information
mockersf and mockersf committed Dec 22, 2021
1 parent a3c53e6 commit 6c47964
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 70 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ bevy_ci_testing = ["bevy_internal/bevy_ci_testing"]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.5.0", default-features = false, optional = true }
bevy_internal = { path = "crates/bevy_internal", version = "0.5.0", default-features = false }

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

[dev-dependencies]
anyhow = "1.0.4"
rand = "0.8.0"
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ x11 = ["bevy_winit/x11"]
# enable rendering of font glyphs using subpixel accuracy
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]

# Optimise for WebGL2
webgl = ["bevy_pbr/webgl", "bevy_render/webgl"]

# enable systems that allow for automated testing on CI
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"]

Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_pbr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

[features]
webgl = []

[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.5.0" }
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,10 @@ pub struct DirectionalLightShadowMap {

impl Default for DirectionalLightShadowMap {
fn default() -> Self {
Self { size: 4096 }
#[cfg(feature = "webgl")]
return Self { size: 2048 };
#[cfg(not(feature = "webgl"))]
return Self { size: 4096 };
}
}

Expand Down
149 changes: 87 additions & 62 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ pub struct GpuLights {
pub const MAX_POINT_LIGHTS: usize = 256;
// FIXME: How should we handle shadows for clustered forward? Limiting to maximum 10
// point light shadow maps for now
#[cfg(feature = "webgl")]
pub const MAX_POINT_LIGHT_SHADOW_MAPS: usize = 1;
#[cfg(not(feature = "webgl"))]
pub const MAX_POINT_LIGHT_SHADOW_MAPS: usize = 10;
pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
pub const POINT_SHADOW_LAYERS: u32 = (6 * MAX_POINT_LIGHT_SHADOW_MAPS) as u32;
Expand Down Expand Up @@ -587,14 +590,30 @@ pub fn prepare_lights(

global_light_meta.gpu_point_lights.clear();
global_light_meta.entity_to_index.clear();
let n_point_lights = point_lights.iter().count();
if global_light_meta.entity_to_index.capacity() < n_point_lights {
global_light_meta.entity_to_index.reserve(n_point_lights);

let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();

// Sort point lights with shadows enabled first, then by a stable key so that the index can be used
// to render at most `MAX_POINT_LIGHT_SHADOW_MAPS` point light shadows.
point_lights.sort_by(|(entity_1, light_1), (entity_2, light_2)| {
light_1
.shadows_enabled
.cmp(&light_2.shadows_enabled)
.reverse()
.then_with(|| entity_1.cmp(entity_2))
});

if global_light_meta.entity_to_index.capacity() < point_lights.len() {
global_light_meta
.entity_to_index
.reserve(point_lights.len());
}

let mut gpu_point_lights = [GpuPointLight::default(); MAX_POINT_LIGHTS];
for (index, (entity, light)) in point_lights.iter().enumerate() {
for (index, &(entity, light)) in point_lights.iter().enumerate() {
let mut flags = PointLightFlags::NONE;
if light.shadows_enabled {
// Lights are sorted, shadow enabled lights are first
if light.shadows_enabled && index < MAX_POINT_LIGHT_SHADOW_MAPS {
flags |= PointLightFlags::SHADOWS_ENABLED;
}
gpu_point_lights[index] = GpuPointLight {
Expand Down Expand Up @@ -683,63 +702,63 @@ pub fn prepare_lights(
};

// TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query
let mut point_light_count = 0;
for (light_entity, light) in point_lights.iter() {
if point_light_count < MAX_POINT_LIGHT_SHADOW_MAPS && light.shadows_enabled {
point_light_count += 1;
let light_index = *global_light_meta
.entity_to_index
.get(&light_entity)
.unwrap();
// ignore scale because we don't want to effectively scale light radius and range
// by applying those as a view transform to shadow map rendering of objects
// and ignore rotation because we want the shadow map projections to align with the axes
let view_translation =
GlobalTransform::from_translation(light.transform.translation);

for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() {
let depth_texture_view =
point_light_depth_texture
.texture
.create_view(&TextureViewDescriptor {
label: Some("point_light_shadow_map_texture_view"),
format: None,
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: (light_index * 6 + face_index) as u32,
array_layer_count: NonZeroU32::new(1),
});

let view_light_entity = commands
.spawn()
.insert_bundle((
ShadowView {
depth_texture_view,
pass_name: format!(
"shadow pass point light {} {}",
light_index,
face_index_to_name(face_index)
),
},
ExtractedView {
width: point_light_shadow_map.size as u32,
height: point_light_shadow_map.size as u32,
transform: view_translation * *view_rotation,
projection: cube_face_projection,
near: POINT_LIGHT_NEAR_Z,
far: light.range,
},
RenderPhase::<Shadow>::default(),
LightEntity::Point {
light_entity,
face_index,
},
))
.id();
view_lights.push(view_light_entity);
}
for &(light_entity, light) in point_lights
.iter()
// Lights are sorted, shadow enabled lights are first
.take(MAX_POINT_LIGHT_SHADOW_MAPS)
.filter(|(_, light)| light.shadows_enabled)
{
let light_index = *global_light_meta
.entity_to_index
.get(&light_entity)
.unwrap();
// ignore scale because we don't want to effectively scale light radius and range
// by applying those as a view transform to shadow map rendering of objects
// and ignore rotation because we want the shadow map projections to align with the axes
let view_translation = GlobalTransform::from_translation(light.transform.translation);

for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() {
let depth_texture_view =
point_light_depth_texture
.texture
.create_view(&TextureViewDescriptor {
label: Some("point_light_shadow_map_texture_view"),
format: None,
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: (light_index * 6 + face_index) as u32,
array_layer_count: NonZeroU32::new(1),
});

let view_light_entity = commands
.spawn()
.insert_bundle((
ShadowView {
depth_texture_view,
pass_name: format!(
"shadow pass point light {} {}",
light_index,
face_index_to_name(face_index)
),
},
ExtractedView {
width: point_light_shadow_map.size as u32,
height: point_light_shadow_map.size as u32,
transform: view_translation * *view_rotation,
projection: cube_face_projection,
near: POINT_LIGHT_NEAR_Z,
far: light.range,
},
RenderPhase::<Shadow>::default(),
LightEntity::Point {
light_entity,
face_index,
},
))
.id();
view_lights.push(view_light_entity);
}
}

Expand Down Expand Up @@ -830,7 +849,10 @@ pub fn prepare_lights(
.create_view(&TextureViewDescriptor {
label: Some("point_light_shadow_map_array_texture_view"),
format: None,
#[cfg(not(feature = "webgl"))]
dimension: Some(TextureViewDimension::CubeArray),
#[cfg(feature = "webgl")]
dimension: Some(TextureViewDimension::Cube),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
Expand All @@ -842,7 +864,10 @@ pub fn prepare_lights(
.create_view(&TextureViewDescriptor {
label: Some("directional_light_shadow_map_array_texture_view"),
format: None,
#[cfg(not(feature = "webgl"))]
dimension: Some(TextureViewDimension::D2Array),
#[cfg(feature = "webgl")]
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
Expand Down
9 changes: 9 additions & 0 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,10 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Depth,
#[cfg(not(feature = "webgl"))]
view_dimension: TextureViewDimension::CubeArray,
#[cfg(feature = "webgl")]
view_dimension: TextureViewDimension::Cube,
},
count: None,
},
Expand All @@ -220,7 +223,10 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Texture {
multisampled: false,
sample_type: TextureSampleType::Depth,
#[cfg(not(feature = "webgl"))]
view_dimension: TextureViewDimension::D2Array,
#[cfg(feature = "webgl")]
view_dimension: TextureViewDimension::D2,
},
count: None,
},
Expand Down Expand Up @@ -485,6 +491,9 @@ impl SpecializedPipeline for MeshPipeline {
depth_write_enabled = true;
}

#[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));

RenderPipelineDescriptor {
vertex: VertexState {
shader: MESH_SHADER_HANDLE.typed::<Shader>(),
Expand Down
10 changes: 10 additions & 0 deletions crates/bevy_pbr/src/render/mesh_view_bind_group.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,22 @@ struct ClusterOffsetsAndCounts {
var<uniform> view: View;
[[group(0), binding(1)]]
var<uniform> lights: Lights;
#ifdef NO_ARRAY_TEXTURES_SUPPORT
[[group(0), binding(2)]]
var point_shadow_textures: texture_depth_cube;
#else
[[group(0), binding(2)]]
var point_shadow_textures: texture_depth_cube_array;
#endif
[[group(0), binding(3)]]
var point_shadow_textures_sampler: sampler_comparison;
#ifdef NO_ARRAY_TEXTURES_SUPPORT
[[group(0), binding(4)]]
var directional_shadow_textures: texture_depth_2d;
#else
[[group(0), binding(4)]]
var directional_shadow_textures: texture_depth_2d_array;
#endif
[[group(0), binding(5)]]
var directional_shadow_textures_sampler: sampler_comparison;
[[group(0), binding(6)]]
Expand Down
8 changes: 8 additions & 0 deletions crates/bevy_pbr/src/render/pbr.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,11 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: v
// a quad (2x2 fragments) being processed not being sampled, and this messing with
// mip-mapping functionality. The shadow maps have no mipmaps so Level just samples
// from LOD 0.
#ifdef NO_ARRAY_TEXTURES_SUPPORT
return textureSampleCompare(point_shadow_textures, point_shadow_textures_sampler, frag_ls, depth);
#else
return textureSampleCompareLevel(point_shadow_textures, point_shadow_textures_sampler, frag_ls, i32(light_id), depth);
#endif
}

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

fn hsv2rgb(hue: f32, saturation: f32, value: f32) -> vec3<f32> {
Expand Down
4 changes: 1 addition & 3 deletions crates/bevy_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ bmp = ["image/bmp"]
trace = []
wgpu_trace = ["wgpu/trace"]
ci_limits = []
webgl = ["wgpu/webgl"]

[dependencies]
# bevy
Expand Down Expand Up @@ -51,6 +52,3 @@ hexasphere = "6.0.0"
parking_lot = "0.11.0"
regex = "1.5"
crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wgpu = { version = "0.12.0", features = ["spirv", "webgl"] }
4 changes: 2 additions & 2 deletions crates/bevy_render/src/mesh/mesh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -623,15 +623,15 @@ impl RenderAsset for Mesh {
let vertex_buffer_data = mesh.get_vertex_buffer_data();
let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: None,
label: Some("Mesh Vertex Buffer"),
contents: &vertex_buffer_data,
});

let index_info = mesh.get_index_buffer_bytes().map(|data| GpuIndexInfo {
buffer: render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::INDEX,
contents: data,
label: None,
label: Some("Mesh Index Buffer"),
}),
count: mesh.indices().unwrap().len() as u32,
index_format: mesh.indices().unwrap().into(),
Expand Down
1 change: 0 additions & 1 deletion crates/bevy_render/src/renderer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ pub async fn initialize_renderer(
.await
.expect("Unable to find a GPU! Make sure you have installed required drivers!");

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

#[cfg(feature = "wgpu_trace")]
Expand Down
8 changes: 7 additions & 1 deletion crates/bevy_render/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,18 @@ pub struct Msaa {
/// smoother edges. Note that WGPU currently only supports 1 or 4 samples.
/// Ultimately we plan on supporting whatever is natively supported on a given device.
/// Check out this issue for more info: <https://github.com/gfx-rs/wgpu/issues/1832>
/// It defaults to 1 in wasm - <https://github.com/gfx-rs/wgpu/issues/2149>
pub samples: u32,
}

impl Default for Msaa {
fn default() -> Self {
Self { samples: 4 }
Self {
#[cfg(feature = "webgl")]
samples: 1,
#[cfg(not(feature = "webgl"))]
samples: 4,
}
}
}

Expand Down

0 comments on commit 6c47964

Please sign in to comment.