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 6813b2edc5, 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 <8672791+mockersf@users.noreply.github.com>
This commit is contained in:
François 2021-12-22 20:59:48 +00:00
parent a3c53e689d
commit 6c479649bf
12 changed files with 135 additions and 68 deletions

View File

@ -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_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 } 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] [dev-dependencies]
anyhow = "1.0.4" anyhow = "1.0.4"
rand = "0.8.0" rand = "0.8.0"

View File

@ -41,6 +41,9 @@ x11 = ["bevy_winit/x11"]
# enable rendering of font glyphs using subpixel accuracy # enable rendering of font glyphs using subpixel accuracy
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"] 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 # enable systems that allow for automated testing on CI
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"] bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"]

View File

@ -8,6 +8,9 @@ repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
keywords = ["bevy"] keywords = ["bevy"]
[features]
webgl = []
[dependencies] [dependencies]
# bevy # bevy
bevy_app = { path = "../bevy_app", version = "0.5.0" } bevy_app = { path = "../bevy_app", version = "0.5.0" }

View File

@ -151,7 +151,10 @@ pub struct DirectionalLightShadowMap {
impl Default for DirectionalLightShadowMap { impl Default for DirectionalLightShadowMap {
fn default() -> Self { fn default() -> Self {
Self { size: 4096 } #[cfg(feature = "webgl")]
return Self { size: 2048 };
#[cfg(not(feature = "webgl"))]
return Self { size: 4096 };
} }
} }

View File

@ -142,6 +142,9 @@ pub struct GpuLights {
pub const MAX_POINT_LIGHTS: usize = 256; pub const MAX_POINT_LIGHTS: usize = 256;
// FIXME: How should we handle shadows for clustered forward? Limiting to maximum 10 // FIXME: How should we handle shadows for clustered forward? Limiting to maximum 10
// point light shadow maps for now // 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_POINT_LIGHT_SHADOW_MAPS: usize = 10;
pub const MAX_DIRECTIONAL_LIGHTS: usize = 1; pub const MAX_DIRECTIONAL_LIGHTS: usize = 1;
pub const POINT_SHADOW_LAYERS: u32 = (6 * MAX_POINT_LIGHT_SHADOW_MAPS) as u32; pub const POINT_SHADOW_LAYERS: u32 = (6 * MAX_POINT_LIGHT_SHADOW_MAPS) as u32;
@ -587,14 +590,30 @@ pub fn prepare_lights(
global_light_meta.gpu_point_lights.clear(); global_light_meta.gpu_point_lights.clear();
global_light_meta.entity_to_index.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 { let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();
global_light_meta.entity_to_index.reserve(n_point_lights);
// 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]; 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; 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; flags |= PointLightFlags::SHADOWS_ENABLED;
} }
gpu_point_lights[index] = GpuPointLight { gpu_point_lights[index] = GpuPointLight {
@ -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 // 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
for (light_entity, light) in point_lights.iter() { .iter()
if point_light_count < MAX_POINT_LIGHT_SHADOW_MAPS && light.shadows_enabled { // Lights are sorted, shadow enabled lights are first
point_light_count += 1; .take(MAX_POINT_LIGHT_SHADOW_MAPS)
let light_index = *global_light_meta .filter(|(_, light)| light.shadows_enabled)
.entity_to_index {
.get(&light_entity) let light_index = *global_light_meta
.unwrap(); .entity_to_index
// ignore scale because we don't want to effectively scale light radius and range .get(&light_entity)
// by applying those as a view transform to shadow map rendering of objects .unwrap();
// and ignore rotation because we want the shadow map projections to align with the axes // ignore scale because we don't want to effectively scale light radius and range
let view_translation = // by applying those as a view transform to shadow map rendering of objects
GlobalTransform::from_translation(light.transform.translation); // 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() { for (face_index, view_rotation) in cube_face_rotations.iter().enumerate() {
let depth_texture_view = let depth_texture_view =
point_light_depth_texture point_light_depth_texture
.texture .texture
.create_view(&TextureViewDescriptor { .create_view(&TextureViewDescriptor {
label: Some("point_light_shadow_map_texture_view"), label: Some("point_light_shadow_map_texture_view"),
format: None, format: None,
dimension: Some(TextureViewDimension::D2), dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All, aspect: TextureAspect::All,
base_mip_level: 0, base_mip_level: 0,
mip_level_count: None, mip_level_count: None,
base_array_layer: (light_index * 6 + face_index) as u32, base_array_layer: (light_index * 6 + face_index) as u32,
array_layer_count: NonZeroU32::new(1), array_layer_count: NonZeroU32::new(1),
}); });
let view_light_entity = commands let view_light_entity = commands
.spawn() .spawn()
.insert_bundle(( .insert_bundle((
ShadowView { ShadowView {
depth_texture_view, depth_texture_view,
pass_name: format!( pass_name: format!(
"shadow pass point light {} {}", "shadow pass point light {} {}",
light_index, light_index,
face_index_to_name(face_index) face_index_to_name(face_index)
), ),
}, },
ExtractedView { ExtractedView {
width: point_light_shadow_map.size as u32, width: point_light_shadow_map.size as u32,
height: point_light_shadow_map.size as u32, height: point_light_shadow_map.size as u32,
transform: view_translation * *view_rotation, transform: view_translation * *view_rotation,
projection: cube_face_projection, projection: cube_face_projection,
near: POINT_LIGHT_NEAR_Z, near: POINT_LIGHT_NEAR_Z,
far: light.range, far: light.range,
}, },
RenderPhase::<Shadow>::default(), RenderPhase::<Shadow>::default(),
LightEntity::Point { LightEntity::Point {
light_entity, light_entity,
face_index, face_index,
}, },
)) ))
.id(); .id();
view_lights.push(view_light_entity); view_lights.push(view_light_entity);
}
} }
} }
@ -830,7 +849,10 @@ pub fn prepare_lights(
.create_view(&TextureViewDescriptor { .create_view(&TextureViewDescriptor {
label: Some("point_light_shadow_map_array_texture_view"), label: Some("point_light_shadow_map_array_texture_view"),
format: None, format: None,
#[cfg(not(feature = "webgl"))]
dimension: Some(TextureViewDimension::CubeArray), dimension: Some(TextureViewDimension::CubeArray),
#[cfg(feature = "webgl")]
dimension: Some(TextureViewDimension::Cube),
aspect: TextureAspect::All, aspect: TextureAspect::All,
base_mip_level: 0, base_mip_level: 0,
mip_level_count: None, mip_level_count: None,
@ -842,7 +864,10 @@ pub fn prepare_lights(
.create_view(&TextureViewDescriptor { .create_view(&TextureViewDescriptor {
label: Some("directional_light_shadow_map_array_texture_view"), label: Some("directional_light_shadow_map_array_texture_view"),
format: None, format: None,
#[cfg(not(feature = "webgl"))]
dimension: Some(TextureViewDimension::D2Array), dimension: Some(TextureViewDimension::D2Array),
#[cfg(feature = "webgl")]
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All, aspect: TextureAspect::All,
base_mip_level: 0, base_mip_level: 0,
mip_level_count: None, mip_level_count: None,

View File

@ -202,7 +202,10 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Texture { ty: BindingType::Texture {
multisampled: false, multisampled: false,
sample_type: TextureSampleType::Depth, sample_type: TextureSampleType::Depth,
#[cfg(not(feature = "webgl"))]
view_dimension: TextureViewDimension::CubeArray, view_dimension: TextureViewDimension::CubeArray,
#[cfg(feature = "webgl")]
view_dimension: TextureViewDimension::Cube,
}, },
count: None, count: None,
}, },
@ -220,7 +223,10 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Texture { ty: BindingType::Texture {
multisampled: false, multisampled: false,
sample_type: TextureSampleType::Depth, sample_type: TextureSampleType::Depth,
#[cfg(not(feature = "webgl"))]
view_dimension: TextureViewDimension::D2Array, view_dimension: TextureViewDimension::D2Array,
#[cfg(feature = "webgl")]
view_dimension: TextureViewDimension::D2,
}, },
count: None, count: None,
}, },
@ -485,6 +491,9 @@ impl SpecializedPipeline for MeshPipeline {
depth_write_enabled = true; depth_write_enabled = true;
} }
#[cfg(feature = "webgl")]
shader_defs.push(String::from("NO_ARRAY_TEXTURES_SUPPORT"));
RenderPipelineDescriptor { RenderPipelineDescriptor {
vertex: VertexState { vertex: VertexState {
shader: MESH_SHADER_HANDLE.typed::<Shader>(), shader: MESH_SHADER_HANDLE.typed::<Shader>(),

View File

@ -73,12 +73,22 @@ struct ClusterOffsetsAndCounts {
var<uniform> view: View; var<uniform> view: View;
[[group(0), binding(1)]] [[group(0), binding(1)]]
var<uniform> lights: Lights; 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)]] [[group(0), binding(2)]]
var point_shadow_textures: texture_depth_cube_array; var point_shadow_textures: texture_depth_cube_array;
#endif
[[group(0), binding(3)]] [[group(0), binding(3)]]
var point_shadow_textures_sampler: sampler_comparison; 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)]] [[group(0), binding(4)]]
var directional_shadow_textures: texture_depth_2d_array; var directional_shadow_textures: texture_depth_2d_array;
#endif
[[group(0), binding(5)]] [[group(0), binding(5)]]
var directional_shadow_textures_sampler: sampler_comparison; var directional_shadow_textures_sampler: sampler_comparison;
[[group(0), binding(6)]] [[group(0), binding(6)]]

View File

@ -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 // 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 // mip-mapping functionality. The shadow maps have no mipmaps so Level just samples
// from LOD 0. // 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); 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 { 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
// do the lookup, using HW PCF and comparison // 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 // 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. // 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); 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> { fn hsv2rgb(hue: f32, saturation: f32, value: f32) -> vec3<f32> {

View File

@ -18,6 +18,7 @@ bmp = ["image/bmp"]
trace = [] trace = []
wgpu_trace = ["wgpu/trace"] wgpu_trace = ["wgpu/trace"]
ci_limits = [] ci_limits = []
webgl = ["wgpu/webgl"]
[dependencies] [dependencies]
# bevy # bevy
@ -51,6 +52,3 @@ hexasphere = "6.0.0"
parking_lot = "0.11.0" parking_lot = "0.11.0"
regex = "1.5" regex = "1.5"
crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] } crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
wgpu = { version = "0.12.0", features = ["spirv", "webgl"] }

View File

@ -623,7 +623,7 @@ impl RenderAsset for Mesh {
let vertex_buffer_data = mesh.get_vertex_buffer_data(); let vertex_buffer_data = mesh.get_vertex_buffer_data();
let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX, usage: BufferUsages::VERTEX,
label: None, label: Some("Mesh Vertex Buffer"),
contents: &vertex_buffer_data, contents: &vertex_buffer_data,
}); });
@ -631,7 +631,7 @@ impl RenderAsset for Mesh {
buffer: render_device.create_buffer_with_data(&BufferInitDescriptor { buffer: render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::INDEX, usage: BufferUsages::INDEX,
contents: data, contents: data,
label: None, label: Some("Mesh Index Buffer"),
}), }),
count: mesh.indices().unwrap().len() as u32, count: mesh.indices().unwrap().len() as u32,
index_format: mesh.indices().unwrap().into(), index_format: mesh.indices().unwrap().into(),

View File

@ -72,7 +72,6 @@ pub async fn initialize_renderer(
.await .await
.expect("Unable to find a GPU! Make sure you have installed required drivers!"); .expect("Unable to find a GPU! Make sure you have installed required drivers!");
#[cfg(not(target_arch = "wasm32"))]
info!("{:?}", adapter.get_info()); info!("{:?}", adapter.get_info());
#[cfg(feature = "wgpu_trace")] #[cfg(feature = "wgpu_trace")]

View File

@ -44,12 +44,18 @@ pub struct Msaa {
/// smoother edges. Note that WGPU currently only supports 1 or 4 samples. /// 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. /// 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> /// 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, pub samples: u32,
} }
impl Default for Msaa { impl Default for Msaa {
fn default() -> Self { fn default() -> Self {
Self { samples: 4 } Self {
#[cfg(feature = "webgl")]
samples: 1,
#[cfg(not(feature = "webgl"))]
samples: 4,
}
} }
} }