Fix the WebGL 2 backend by giving the visibility_ranges array a fixed length. (#13210)

WebGL 2 doesn't support variable-length uniform buffer arrays. So we
arbitrarily set the length of the visibility ranges field to 64 on that
platform.

---------

Co-authored-by: IceSentry <c.giguere42@gmail.com>
This commit is contained in:
Patrick Walton 2024-05-08 02:34:59 -05:00 committed by GitHub
parent 4350ad0bd1
commit 0dddfa07ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 7 deletions

View file

@ -1,7 +1,11 @@
#define_import_path bevy_pbr::mesh_functions
#import bevy_pbr::{
mesh_view_bindings::{view, visibility_ranges},
mesh_view_bindings::{
view,
visibility_ranges,
VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE
},
mesh_bindings::mesh,
mesh_types::MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT,
view_transformations::position_world_to_clip,
@ -90,8 +94,16 @@ fn mesh_tangent_local_to_world(model: mat4x4<f32>, vertex_tangent: vec4<f32>, in
// camera distance to determine the dithering level.
#ifdef VISIBILITY_RANGE_DITHER
fn get_visibility_range_dither_level(instance_index: u32, world_position: vec4<f32>) -> i32 {
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
// If we're using a storage buffer, then the length is variable.
let visibility_buffer_array_len = arrayLength(&visibility_ranges);
#else // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
// If we're using a uniform buffer, then the length is constant
let visibility_buffer_array_len = VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE;
#endif // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
let visibility_buffer_index = mesh[instance_index].flags & 0xffffu;
if (visibility_buffer_index > arrayLength(&visibility_ranges)) {
if (visibility_buffer_index > visibility_buffer_array_len) {
return -16;
}

View file

@ -35,10 +35,11 @@
@group(0) @binding(10) var<uniform> fog: types::Fog;
@group(0) @binding(11) var<uniform> light_probes: types::LightProbes;
const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u;
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
@group(0) @binding(12) var<storage> visibility_ranges: array<vec4<f32>>;
#else
@group(0) @binding(12) var<uniform> visibility_ranges: array<vec4<f32>>;
@group(0) @binding(12) var<uniform> visibility_ranges: array<vec4<f32>, VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE>;
#endif
@group(0) @binding(13) var screen_space_ambient_occlusion_texture: texture_2d<f32>;

View file

@ -19,7 +19,7 @@ use bevy_reflect::Reflect;
use bevy_transform::components::GlobalTransform;
use bevy_utils::{prelude::default, EntityHashMap, HashMap};
use nonmax::NonMaxU16;
use wgpu::BufferUsages;
use wgpu::{BufferBindingType, BufferUsages};
use crate::{
camera::Camera,
@ -38,6 +38,11 @@ use super::{check_visibility, VisibilitySystems, WithMesh};
/// buffer slot.
pub const VISIBILITY_RANGES_STORAGE_BUFFER_COUNT: u32 = 4;
/// The size of the visibility ranges buffer in elements (not bytes) when fewer
/// than 6 storage buffers are available and we're forced to use a uniform
/// buffer instead (most notably, on WebGL 2).
const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: usize = 64;
/// A plugin that enables [`VisibilityRange`]s, which allow entities to be
/// hidden or shown based on distance to the camera.
pub struct VisibilityRangePlugin;
@ -424,9 +429,33 @@ pub fn write_render_visibility_ranges(
return;
}
// If the buffer is empty, push *something* so that we allocate it.
if render_visibility_ranges.buffer.is_empty() {
render_visibility_ranges.buffer.push(default());
// Mess with the length of the buffer to meet API requirements if necessary.
match render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT)
{
// If we're using a uniform buffer, we must have *exactly*
// `VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE` elements.
BufferBindingType::Uniform
if render_visibility_ranges.buffer.len() > VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE =>
{
render_visibility_ranges
.buffer
.truncate(VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE);
}
BufferBindingType::Uniform
if render_visibility_ranges.buffer.len() < VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE =>
{
while render_visibility_ranges.buffer.len() < VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE {
render_visibility_ranges.buffer.push(default());
}
}
// Otherwise, if we're using a storage buffer, just ensure there's
// something in the buffer, or else it won't get allocated.
BufferBindingType::Storage { .. } if render_visibility_ranges.buffer.is_empty() => {
render_visibility_ranges.buffer.push(default());
}
_ => {}
}
// Schedule the write.