From 09ff7ce9f6312e33d75ebdca8c0c68f043751023 Mon Sep 17 00:00:00 2001 From: Greeble <166992735+greeble-dev@users.noreply.github.com> Date: Sun, 9 Mar 2025 20:14:27 +0000 Subject: [PATCH] Partially fix panics when setting `WGPU_SETTINGS_PRIO=webgl2` (#18113) # Overview Fixes https://github.com/bevyengine/bevy/issues/17869. # Summary `WGPU_SETTINGS_PRIO` changes various limits on `RenderDevice`. This is useful to simulate platforms with lower limits. However, some plugins only check the limits on `RenderAdapter` (the actual GPU) - these limits are not affected by `WGPU_SETTINGS_PRIO`. So the plugins try to use features that are unavailable and wgpu says "oh no". See https://github.com/bevyengine/bevy/issues/17869 for details. The PR adds various checks on `RenderDevice` limits. This is enough to get most examples working, but some are not fixed (see below). # Testing - Tested native, with and without "WGPU_SETTINGS=webgl2". Win10/Vulkan/Nvidia". - Also WebGL. Win10/Chrome/Nvidia. ``` $env:WGPU_SETTINGS_PRIO = "webgl2" cargo run --example testbed_3d cargo run --example testbed_2d cargo run --example testbed_ui cargo run --example deferred_rendering cargo run --example many_lights cargo run --example order_independent_transparency # Still broken, see below. cargo run --example occlusion_culling # Still broken, see below. ``` # Not Fixed While testing I found a few other cases of limits being broken. "Compatibility" settings (WebGPU minimums) breaks native in various examples. ``` $env:WGPU_SETTINGS_PRIO = "compatibility" cargo run --example testbed_3d In Device::create_bind_group_layout, label = 'build mesh uniforms GPU early occlusion culling bind group layout' Too many bindings of type StorageBuffers in Stage ShaderStages(COMPUTE), limit is 8, count was 9. Check the limit `max_storage_buffers_per_shader_stage` passed to `Adapter::request_device` ``` `occlusion_culling` breaks fake webgl. ``` $env:WGPU_SETTINGS_PRIO = "webgl2" cargo run --example occlusion_culling thread '' panicked at C:\Projects\bevy\crates\bevy_render\src\render_resource\pipeline_cache.rs:555:28: index out of bounds: the len is 0 but the index is 2 Encountered a panic in system `bevy_render::renderer::render_system`! ``` `occlusion_culling` breaks real webgl. ``` cargo run --example occlusion_culling --target wasm32-unknown-unknown ERROR app: panicked at C:\Users\T\.cargo\registry\src\index.crates.io-1949cf8c6b5b557f\glow-0.16.0\src\web_sys.rs:4223:9: Tex storage 2D multisample is not supported ``` OIT breaks fake webgl. ``` $env:WGPU_SETTINGS_PRIO = "webgl2" cargo run --example order_independent_transparency In Device::create_bind_group, label = 'mesh_view_bind_group' Number of bindings in bind group descriptor (28) does not match the number of bindings defined in the bind group layout (25) ``` OIT breaks real webgl ``` cargo run --example order_independent_transparency --target wasm32-unknown-unknown In Device::create_render_pipeline, label = 'pbr_oit_mesh_pipeline' Error matching ShaderStages(FRAGMENT) shader requirements against the pipeline Shader global ResourceBinding { group: 0, binding: 34 } is not available in the pipeline layout Binding is missing from the pipeline layout ``` --- .../src/experimental/mip_generation/mod.rs | 18 +++++--- .../bevy_core_pipeline/src/oit/resolve/mod.rs | 44 +++++++++++++++---- .../bevy_pbr/src/render/mesh_view_bindings.rs | 15 +++---- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs index f324193983..8b8cbacaff 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs @@ -325,6 +325,18 @@ pub struct DownsampleDepthPipelines { sampler: Sampler, } +fn supports_compute_shaders(device: &RenderDevice, adapter: &RenderAdapter) -> bool { + adapter + .get_downlevel_capabilities() + .flags + .contains(DownlevelFlags::COMPUTE_SHADERS) + // Even if the adapter supports compute, we might be simulating a lack of + // compute via device limits (see `WgpuSettingsPriority::WebGL2` and + // `wgpu::Limits::downlevel_webgl2_defaults()`). This will have set all the + // `max_compute_*` limits to zero, so we arbitrarily pick one as a canary. + && (device.limits().max_compute_workgroup_storage_size != 0) +} + /// Creates the [`DownsampleDepthPipelines`] if downsampling is supported on the /// current platform. fn create_downsample_depth_pipelines( @@ -346,11 +358,7 @@ fn create_downsample_depth_pipelines( // If we don't have compute shaders, we can't invoke the downsample depth // compute shader. - if !render_adapter - .get_downlevel_capabilities() - .flags - .contains(DownlevelFlags::COMPUTE_SHADERS) - { + if !supports_compute_shaders(&render_device, &render_adapter) { return; } diff --git a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs index f73192b19d..95ded7a5c0 100644 --- a/crates/bevy_core_pipeline/src/oit/resolve/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/resolve/mod.rs @@ -33,6 +33,9 @@ pub const OIT_RESOLVE_SHADER_HANDLE: Handle = /// Contains the render node used to run the resolve pass. pub mod node; +/// Minimum required value of `wgpu::Limits::max_storage_buffers_per_shader_stage`. +pub const OIT_REQUIRED_STORAGE_BUFFERS: u32 = 2; + /// Plugin needed to resolve the Order Independent Transparency (OIT) buffer to the screen. pub struct OitResolvePlugin; impl Plugin for OitResolvePlugin { @@ -50,14 +53,11 @@ impl Plugin for OitResolvePlugin { return; }; - if !render_app - .world() - .resource::() - .get_downlevel_capabilities() - .flags - .contains(DownlevelFlags::FRAGMENT_WRITABLE_STORAGE) - { - warn!("OrderIndependentTransparencyPlugin not loaded. GPU lacks support: DownlevelFlags::FRAGMENT_WRITABLE_STORAGE."); + if !is_oit_supported( + render_app.world().resource::(), + render_app.world().resource::(), + true, + ) { return; } @@ -73,6 +73,34 @@ impl Plugin for OitResolvePlugin { } } +pub fn is_oit_supported(adapter: &RenderAdapter, device: &RenderDevice, warn: bool) -> bool { + if !adapter + .get_downlevel_capabilities() + .flags + .contains(DownlevelFlags::FRAGMENT_WRITABLE_STORAGE) + { + if warn { + warn!("OrderIndependentTransparencyPlugin not loaded. GPU lacks support: DownlevelFlags::FRAGMENT_WRITABLE_STORAGE."); + } + return false; + } + + let max_storage_buffers_per_shader_stage = device.limits().max_storage_buffers_per_shader_stage; + + if max_storage_buffers_per_shader_stage < OIT_REQUIRED_STORAGE_BUFFERS { + if warn { + warn!( + max_storage_buffers_per_shader_stage, + OIT_REQUIRED_STORAGE_BUFFERS, + "OrderIndependentTransparencyPlugin not loaded. RenderDevice lacks support: max_storage_buffers_per_shader_stage < OIT_REQUIRED_STORAGE_BUFFERS." + ); + } + return false; + } + + true +} + /// Bind group for the OIT resolve pass. #[derive(Resource, Deref)] pub struct OitResolveBindGroup(pub BindGroup); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 796ae096c2..8e231886ba 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -1,7 +1,7 @@ use alloc::sync::Arc; use bevy_core_pipeline::{ core_3d::ViewTransmissionTexture, - oit::{OitBuffers, OrderIndependentTransparencySettings}, + oit::{resolve::is_oit_supported, OitBuffers, OrderIndependentTransparencySettings}, prepass::ViewPrepassTextures, tonemapping::{ get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts, @@ -380,15 +380,10 @@ fn layout_entries( // OIT if layout_key.contains(MeshPipelineViewLayoutKey::OIT_ENABLED) { - // Check if the GPU supports writable storage buffers in the fragment shader - // If not, we can't use OIT, so we skip the OIT bindings. - // This is a hack to avoid errors on webgl -- the OIT plugin will warn the user that OIT - // is not supported on their platform, so we don't need to do it here. - if render_adapter - .get_downlevel_capabilities() - .flags - .contains(DownlevelFlags::FRAGMENT_WRITABLE_STORAGE) - { + // Check if we can use OIT. This is a hack to avoid errors on webgl -- + // the OIT plugin will warn the user that OIT is not supported on their + // platform, so we don't need to do it here. + if is_oit_supported(render_adapter, render_device, false) { entries = entries.extend_with_indices(( // oit_layers (34, storage_buffer_sized(false, None)),