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_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"

View File

@ -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"]

View File

@ -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" }

View File

@ -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 };
}
}

View File

@ -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;
@ -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 {
@ -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 &(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),
});
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);
}
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);
}
}
@ -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,
@ -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,

View File

@ -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,
},
@ -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,
},
@ -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>(),

View File

@ -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)]]

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
// 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 {
@ -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> {

View File

@ -18,6 +18,7 @@ bmp = ["image/bmp"]
trace = []
wgpu_trace = ["wgpu/trace"]
ci_limits = []
webgl = ["wgpu/webgl"]
[dependencies]
# bevy
@ -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"] }

View File

@ -623,7 +623,7 @@ 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,
});
@ -631,7 +631,7 @@ impl RenderAsset for Mesh {
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(),

View File

@ -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")]

View File

@ -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,
}
}
}