From 0dddfa07ab5bdb95b0dcc1041e423678f92e83f7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 8 May 2024 02:34:59 -0500 Subject: [PATCH] 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 --- .../bevy_pbr/src/render/mesh_functions.wgsl | 16 +++++++- .../src/render/mesh_view_bindings.wgsl | 3 +- .../bevy_render/src/view/visibility/range.rs | 37 +++++++++++++++++-- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index c100f16f6f..ce8e970127 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -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, vertex_tangent: vec4, in // camera distance to determine the dithering level. #ifdef VISIBILITY_RANGE_DITHER fn get_visibility_range_dither_level(instance_index: u32, world_position: vec4) -> 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; } diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 4fd1f2327d..b8e74c60b8 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -35,10 +35,11 @@ @group(0) @binding(10) var fog: types::Fog; @group(0) @binding(11) var light_probes: types::LightProbes; +const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: u32 = 64u; #if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6 @group(0) @binding(12) var visibility_ranges: array>; #else -@group(0) @binding(12) var visibility_ranges: array>; +@group(0) @binding(12) var visibility_ranges: array, VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE>; #endif @group(0) @binding(13) var screen_space_ambient_occlusion_texture: texture_2d; diff --git a/crates/bevy_render/src/view/visibility/range.rs b/crates/bevy_render/src/view/visibility/range.rs index 62485e42bd..d1e1d6546f 100644 --- a/crates/bevy_render/src/view/visibility/range.rs +++ b/crates/bevy_render/src/view/visibility/range.rs @@ -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.