From f3e9cb88868ae2062795b6e5d0b10a58b94a4ffb Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Sat, 28 Mar 2026 12:14:10 +0800 Subject: [PATCH 1/5] Fix errors when disabling gpu clustering --- crates/bevy_pbr/src/cluster/gpu.rs | 7 ++- crates/bevy_pbr/src/cluster/mod.rs | 46 +++++++++++++------ .../src/render/clustered_forward.wgsl | 8 ++-- crates/bevy_pbr/src/render/mesh.rs | 6 ++- .../bevy_pbr/src/render/mesh_view_bindings.rs | 5 +- .../src/render/mesh_view_bindings.wgsl | 2 +- .../bevy_pbr/src/render/mesh_view_types.wgsl | 2 +- examples/mobile/src/lib.rs | 5 ++ 8 files changed, 56 insertions(+), 25 deletions(-) diff --git a/crates/bevy_pbr/src/cluster/gpu.rs b/crates/bevy_pbr/src/cluster/gpu.rs index 408e03e82a273..ced52d77a9115 100644 --- a/crates/bevy_pbr/src/cluster/gpu.rs +++ b/crates/bevy_pbr/src/cluster/gpu.rs @@ -587,6 +587,9 @@ impl SpecializedRenderPipeline for ClusteringRasterPipeline { let mut vertex_shader_defs = fragment_shader_defs.clone(); vertex_shader_defs.push(ShaderDefVal::from("VERTEX_SHADER")); + fragment_shader_defs.push(ShaderDefVal::from("GPU_CLUSTERING_SUPPORT")); + vertex_shader_defs.push(ShaderDefVal::from("GPU_CLUSTERING_SUPPORT")); + RenderPipelineDescriptor { label: if key.populate_pass { Some("clustering populate pipeline".into()) @@ -678,7 +681,7 @@ impl SpecializedComputePipeline for ClusteringZSlicingPipeline { label: Some("clustering Z slicing pipeline".into()), layout: vec![self.bind_group_layout.clone()], shader: self.shader.clone(), - shader_defs: vec![], + shader_defs: vec!["GPU_CLUSTERING_SUPPORT".into()], entry_point: Some("z_slice_main".into()), zero_initialize_workgroup_memory: true, ..default() @@ -731,7 +734,7 @@ impl SpecializedComputePipeline for ClusteringAllocationPipeline { }, layout: vec![self.bind_group_layout.clone()], shader: self.shader.clone(), - shader_defs: vec![], + shader_defs: vec!["GPU_CLUSTERING_SUPPORT".into()], entry_point: if key.global_pass { Some("allocate_global_main".into()) } else { diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 959089758b07c..410f90976e88d 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -57,6 +57,35 @@ pub const GPU_CLUSTERING_INITIAL_Z_SLICE_LIST_CAPACITY: usize = 1024; /// [`GlobalClusterGpuSettings::initial_index_list_capacity`]. pub const GPU_CLUSTERING_INITIAL_INDEX_LIST_CAPACITY: usize = 65536; +pub(crate) fn gpu_clustering_supported(adapter: &RenderAdapter, device: &RenderDevice) -> bool { + // We need to support compute shaders to use GPU clustering. To deal with + // the `WGPU_SETTINGS_PRIO="webgl2"` environment setting, we check the + // `RenderDevice` limits in addition to the `RenderAdapter`. + // + // Some android devices report the capabilities and limits wrong, so we can't rely on them. + // See for Android issues + !cfg!(target_os = "android") + && adapter + .get_downlevel_capabilities() + .flags + .contains(DownlevelFlags::COMPUTE_SHADERS) + && matches!( + device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT), + BufferBindingType::Storage { .. } + ) +} + +pub(crate) fn get_clustered_forward_buffer_binding_type( + adapter: &RenderAdapter, + device: &RenderDevice, +) -> BufferBindingType { + if gpu_clustering_supported(adapter, device) { + BufferBindingType::Storage { read_only: true } + } else { + BufferBindingType::Uniform + } +} + /// Creates the default [`GlobalClusterSettings`] resource. pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettings { let device = world.resource::(); @@ -68,19 +97,7 @@ pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettin BufferBindingType::Storage { .. } ); - // We need to support compute shaders to use GPU clustering. To deal with - // the `WGPU_SETTINGS_PRIO="webgl2"` environment setting, we check the - // `RenderDevice` limits in addition to the `RenderAdapter`. - // Some android devices report the capabilities and limits wrong, so we can't rely on them. - // See for Android issues - let gpu_clustering_supported = !cfg!(target_os = "android") - && adapter - .get_downlevel_capabilities() - .flags - .contains(DownlevelFlags::COMPUTE_SHADERS) - && device.limits().max_storage_buffers_per_shader_stage > 0; - - let gpu_clustering = if gpu_clustering_supported { + let gpu_clustering = if gpu_clustering_supported(adapter, device) { info!("GPU clustering is supported on this device."); Some(GlobalClusterGpuSettings { initial_z_slice_list_capacity: GPU_CLUSTERING_INITIAL_Z_SLICE_LIST_CAPACITY, @@ -251,10 +268,11 @@ pub struct ViewClusterBindings { pub fn init_global_clusterable_object_meta( mut commands: Commands, + render_adapter: Res, render_device: Res, ) { commands.insert_resource(GlobalClusterableObjectMeta::new( - render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT), + get_clustered_forward_buffer_binding_type(&render_adapter, &render_device), )); } diff --git a/crates/bevy_pbr/src/render/clustered_forward.wgsl b/crates/bevy_pbr/src/render/clustered_forward.wgsl index 4bdf29494f85a..cc60885c2a90f 100644 --- a/crates/bevy_pbr/src/render/clustered_forward.wgsl +++ b/crates/bevy_pbr/src/render/clustered_forward.wgsl @@ -83,7 +83,7 @@ const CLUSTER_COUNT_SIZE = 9u; // primarily), light probes aren't clustered, and therefore both light probe // index ranges will be empty. fn unpack_clusterable_object_index_ranges(cluster_index: u32) -> ClusterableObjectIndexRanges { -#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 +#ifdef GPU_CLUSTERING_SUPPORT let offset_and_counts_a = bindings::cluster_offsets_and_counts.data[cluster_index][0]; let offset_and_counts_b = bindings::cluster_offsets_and_counts.data[cluster_index][1]; @@ -108,7 +108,7 @@ fn unpack_clusterable_object_index_ranges(cluster_index: u32) -> ClusterableObje last_clusterable_offset ); -#else // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 +#else // GPU_CLUSTERING_SUPPORT let raw_offset_and_counts = bindings::cluster_offsets_and_counts.data[cluster_index >> 2u][cluster_index & ((1u << 2u) - 1u)]; // [ 31 .. 18 | 17 .. 9 | 8 .. 0 ] @@ -130,7 +130,7 @@ fn unpack_clusterable_object_index_ranges(cluster_index: u32) -> ClusterableObje return ClusterableObjectIndexRanges(offset_a, offset_b, offset_c, offset_c, offset_c, offset_c); -#endif // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 +#endif // GPU_CLUSTERING_SUPPORT } // Returns the index of the clusterable object at the given offset. @@ -138,7 +138,7 @@ fn unpack_clusterable_object_index_ranges(cluster_index: u32) -> ClusterableObje // Note that, in the case of a light probe, the index refers to an element in // one of the two `light_probes` sublists, not the `clustered_lights` list. fn get_clusterable_object_id(index: u32) -> u32 { -#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 +#ifdef GPU_CLUSTERING_SUPPORT return bindings::clusterable_object_index_lists.data[index]; #else // The index is correct but in clusterable_object_index_lists we pack 4 u8s into a u32 diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index d08a86f3b78f8..3279d0f667179 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -304,6 +304,7 @@ impl Plugin for MeshRenderPlugin { ); }; + let render_adapter = render_app.world().resource::(); let render_device = render_app.world().resource::(); if let Some(per_object_buffer_batch_size) = GpuArrayBuffer::::batch_size(&render_device.limits()) @@ -313,6 +314,9 @@ impl Plugin for MeshRenderPlugin { per_object_buffer_batch_size, )); } + if gpu_clustering_supported(render_adapter, render_device) { + mesh_bindings_shader_defs.push("GPU_CLUSTERING_SUPPORT".into()); + } render_app.add_systems( RenderStartup, @@ -2735,7 +2739,7 @@ fn init_mesh_pipeline( let shader = load_embedded_asset!(asset_server.as_ref(), "mesh.wgsl"); let clustered_forward_buffer_binding_type = - render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); + get_clustered_forward_buffer_binding_type(&render_adapter, &render_device); let res = MeshPipeline { view_layouts: view_layouts.clone(), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index ed747b028efb0..ac4c7e9500d6b 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -41,6 +41,7 @@ use crate::{ }, }, environment_map::{self, RenderViewEnvironmentMapBindGroupEntries}, + get_clustered_forward_buffer_binding_type, irradiance_volume::{ self, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE, }, @@ -51,7 +52,7 @@ use crate::{ LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources, ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, - ViewTransmissionTexture, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, + ViewTransmissionTexture, }; #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] @@ -487,7 +488,7 @@ pub fn init_mesh_pipeline_view_layouts( // [`MeshPipelineViewLayoutKey`] flags. let clustered_forward_buffer_binding_type = - render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); + get_clustered_forward_buffer_binding_type(&render_adapter, &render_device); let visibility_ranges_buffer_binding_type = render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index cc6a5e8677bbc..6a1d2f67ee209 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -28,7 +28,7 @@ @group(0) @binding(7) var directional_shadow_textures_linear_sampler: sampler; #endif // PCSS_SAMPLERS_AVAILABLE -#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 +#ifdef GPU_CLUSTERING_SUPPORT @group(0) @binding(8) var clustered_lights: types::ClusteredLights; @group(0) @binding(9) var clusterable_object_index_lists: types::ClusterableObjectIndexLists; @group(0) @binding(10) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index bacde26fb99f8..408ad5d7364ea 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -99,7 +99,7 @@ const FOG_MODE_EXPONENTIAL: u32 = 2u; const FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3u; const FOG_MODE_ATMOSPHERIC: u32 = 4u; -#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 +#ifdef GPU_CLUSTERING_SUPPORT struct ClusteredLights { data: array, }; diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 68d01587dc065..70008230c7dc3 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -5,6 +5,7 @@ use bevy::{ input::{gestures::RotationGesture, touch::TouchPhase}, log::{Level, LogPlugin}, prelude::*, + render::renderer::RenderDevice, window::{AppLifecycle, ScreenEdge, WindowMode}, winit::WinitSettings, }; @@ -97,7 +98,11 @@ fn setup_scene( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, + device: Res, ) { + bevy::log::info!("Configured wgpu adapter Limits: {:#?}", device.limits()); + bevy::log::info!("Configured wgpu adapter Features: {:#?}", device.features()); + // plane commands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))), From c2d57252a10fe6eb79646a8d036cc6c1b530554d Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Sat, 28 Mar 2026 15:26:32 +0800 Subject: [PATCH 2/5] Revert "Fix errors when disabling gpu clustering" This reverts commit f3e9cb88868ae2062795b6e5d0b10a58b94a4ffb. --- crates/bevy_pbr/src/cluster/gpu.rs | 7 +-- crates/bevy_pbr/src/cluster/mod.rs | 46 ++++++------------- .../src/render/clustered_forward.wgsl | 8 ++-- crates/bevy_pbr/src/render/mesh.rs | 6 +-- .../bevy_pbr/src/render/mesh_view_bindings.rs | 5 +- .../src/render/mesh_view_bindings.wgsl | 2 +- .../bevy_pbr/src/render/mesh_view_types.wgsl | 2 +- examples/mobile/src/lib.rs | 5 -- 8 files changed, 25 insertions(+), 56 deletions(-) diff --git a/crates/bevy_pbr/src/cluster/gpu.rs b/crates/bevy_pbr/src/cluster/gpu.rs index ced52d77a9115..408e03e82a273 100644 --- a/crates/bevy_pbr/src/cluster/gpu.rs +++ b/crates/bevy_pbr/src/cluster/gpu.rs @@ -587,9 +587,6 @@ impl SpecializedRenderPipeline for ClusteringRasterPipeline { let mut vertex_shader_defs = fragment_shader_defs.clone(); vertex_shader_defs.push(ShaderDefVal::from("VERTEX_SHADER")); - fragment_shader_defs.push(ShaderDefVal::from("GPU_CLUSTERING_SUPPORT")); - vertex_shader_defs.push(ShaderDefVal::from("GPU_CLUSTERING_SUPPORT")); - RenderPipelineDescriptor { label: if key.populate_pass { Some("clustering populate pipeline".into()) @@ -681,7 +678,7 @@ impl SpecializedComputePipeline for ClusteringZSlicingPipeline { label: Some("clustering Z slicing pipeline".into()), layout: vec![self.bind_group_layout.clone()], shader: self.shader.clone(), - shader_defs: vec!["GPU_CLUSTERING_SUPPORT".into()], + shader_defs: vec![], entry_point: Some("z_slice_main".into()), zero_initialize_workgroup_memory: true, ..default() @@ -734,7 +731,7 @@ impl SpecializedComputePipeline for ClusteringAllocationPipeline { }, layout: vec![self.bind_group_layout.clone()], shader: self.shader.clone(), - shader_defs: vec!["GPU_CLUSTERING_SUPPORT".into()], + shader_defs: vec![], entry_point: if key.global_pass { Some("allocate_global_main".into()) } else { diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 410f90976e88d..959089758b07c 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -57,35 +57,6 @@ pub const GPU_CLUSTERING_INITIAL_Z_SLICE_LIST_CAPACITY: usize = 1024; /// [`GlobalClusterGpuSettings::initial_index_list_capacity`]. pub const GPU_CLUSTERING_INITIAL_INDEX_LIST_CAPACITY: usize = 65536; -pub(crate) fn gpu_clustering_supported(adapter: &RenderAdapter, device: &RenderDevice) -> bool { - // We need to support compute shaders to use GPU clustering. To deal with - // the `WGPU_SETTINGS_PRIO="webgl2"` environment setting, we check the - // `RenderDevice` limits in addition to the `RenderAdapter`. - // - // Some android devices report the capabilities and limits wrong, so we can't rely on them. - // See for Android issues - !cfg!(target_os = "android") - && adapter - .get_downlevel_capabilities() - .flags - .contains(DownlevelFlags::COMPUTE_SHADERS) - && matches!( - device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT), - BufferBindingType::Storage { .. } - ) -} - -pub(crate) fn get_clustered_forward_buffer_binding_type( - adapter: &RenderAdapter, - device: &RenderDevice, -) -> BufferBindingType { - if gpu_clustering_supported(adapter, device) { - BufferBindingType::Storage { read_only: true } - } else { - BufferBindingType::Uniform - } -} - /// Creates the default [`GlobalClusterSettings`] resource. pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettings { let device = world.resource::(); @@ -97,7 +68,19 @@ pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettin BufferBindingType::Storage { .. } ); - let gpu_clustering = if gpu_clustering_supported(adapter, device) { + // We need to support compute shaders to use GPU clustering. To deal with + // the `WGPU_SETTINGS_PRIO="webgl2"` environment setting, we check the + // `RenderDevice` limits in addition to the `RenderAdapter`. + // Some android devices report the capabilities and limits wrong, so we can't rely on them. + // See for Android issues + let gpu_clustering_supported = !cfg!(target_os = "android") + && adapter + .get_downlevel_capabilities() + .flags + .contains(DownlevelFlags::COMPUTE_SHADERS) + && device.limits().max_storage_buffers_per_shader_stage > 0; + + let gpu_clustering = if gpu_clustering_supported { info!("GPU clustering is supported on this device."); Some(GlobalClusterGpuSettings { initial_z_slice_list_capacity: GPU_CLUSTERING_INITIAL_Z_SLICE_LIST_CAPACITY, @@ -268,11 +251,10 @@ pub struct ViewClusterBindings { pub fn init_global_clusterable_object_meta( mut commands: Commands, - render_adapter: Res, render_device: Res, ) { commands.insert_resource(GlobalClusterableObjectMeta::new( - get_clustered_forward_buffer_binding_type(&render_adapter, &render_device), + render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT), )); } diff --git a/crates/bevy_pbr/src/render/clustered_forward.wgsl b/crates/bevy_pbr/src/render/clustered_forward.wgsl index cc60885c2a90f..4bdf29494f85a 100644 --- a/crates/bevy_pbr/src/render/clustered_forward.wgsl +++ b/crates/bevy_pbr/src/render/clustered_forward.wgsl @@ -83,7 +83,7 @@ const CLUSTER_COUNT_SIZE = 9u; // primarily), light probes aren't clustered, and therefore both light probe // index ranges will be empty. fn unpack_clusterable_object_index_ranges(cluster_index: u32) -> ClusterableObjectIndexRanges { -#ifdef GPU_CLUSTERING_SUPPORT +#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 let offset_and_counts_a = bindings::cluster_offsets_and_counts.data[cluster_index][0]; let offset_and_counts_b = bindings::cluster_offsets_and_counts.data[cluster_index][1]; @@ -108,7 +108,7 @@ fn unpack_clusterable_object_index_ranges(cluster_index: u32) -> ClusterableObje last_clusterable_offset ); -#else // GPU_CLUSTERING_SUPPORT +#else // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 let raw_offset_and_counts = bindings::cluster_offsets_and_counts.data[cluster_index >> 2u][cluster_index & ((1u << 2u) - 1u)]; // [ 31 .. 18 | 17 .. 9 | 8 .. 0 ] @@ -130,7 +130,7 @@ fn unpack_clusterable_object_index_ranges(cluster_index: u32) -> ClusterableObje return ClusterableObjectIndexRanges(offset_a, offset_b, offset_c, offset_c, offset_c, offset_c); -#endif // GPU_CLUSTERING_SUPPORT +#endif // AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 } // Returns the index of the clusterable object at the given offset. @@ -138,7 +138,7 @@ fn unpack_clusterable_object_index_ranges(cluster_index: u32) -> ClusterableObje // Note that, in the case of a light probe, the index refers to an element in // one of the two `light_probes` sublists, not the `clustered_lights` list. fn get_clusterable_object_id(index: u32) -> u32 { -#ifdef GPU_CLUSTERING_SUPPORT +#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 return bindings::clusterable_object_index_lists.data[index]; #else // The index is correct but in clusterable_object_index_lists we pack 4 u8s into a u32 diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3279d0f667179..d08a86f3b78f8 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -304,7 +304,6 @@ impl Plugin for MeshRenderPlugin { ); }; - let render_adapter = render_app.world().resource::(); let render_device = render_app.world().resource::(); if let Some(per_object_buffer_batch_size) = GpuArrayBuffer::::batch_size(&render_device.limits()) @@ -314,9 +313,6 @@ impl Plugin for MeshRenderPlugin { per_object_buffer_batch_size, )); } - if gpu_clustering_supported(render_adapter, render_device) { - mesh_bindings_shader_defs.push("GPU_CLUSTERING_SUPPORT".into()); - } render_app.add_systems( RenderStartup, @@ -2739,7 +2735,7 @@ fn init_mesh_pipeline( let shader = load_embedded_asset!(asset_server.as_ref(), "mesh.wgsl"); let clustered_forward_buffer_binding_type = - get_clustered_forward_buffer_binding_type(&render_adapter, &render_device); + render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); let res = MeshPipeline { view_layouts: view_layouts.clone(), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index ac4c7e9500d6b..ed747b028efb0 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -41,7 +41,6 @@ use crate::{ }, }, environment_map::{self, RenderViewEnvironmentMapBindGroupEntries}, - get_clustered_forward_buffer_binding_type, irradiance_volume::{ self, RenderViewIrradianceVolumeBindGroupEntries, IRRADIANCE_VOLUMES_ARE_USABLE, }, @@ -52,7 +51,7 @@ use crate::{ LightProbesBuffer, LightProbesUniform, MeshPipeline, MeshPipelineKey, RenderViewLightProbes, ScreenSpaceAmbientOcclusionResources, ScreenSpaceReflectionsBuffer, ScreenSpaceReflectionsUniform, ShadowSamplers, ViewClusterBindings, ViewShadowBindings, - ViewTransmissionTexture, + ViewTransmissionTexture, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, }; #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] @@ -488,7 +487,7 @@ pub fn init_mesh_pipeline_view_layouts( // [`MeshPipelineViewLayoutKey`] flags. let clustered_forward_buffer_binding_type = - get_clustered_forward_buffer_binding_type(&render_adapter, &render_device); + render_device.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT); let visibility_ranges_buffer_binding_type = render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT); diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 6a1d2f67ee209..cc6a5e8677bbc 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -28,7 +28,7 @@ @group(0) @binding(7) var directional_shadow_textures_linear_sampler: sampler; #endif // PCSS_SAMPLERS_AVAILABLE -#ifdef GPU_CLUSTERING_SUPPORT +#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 @group(0) @binding(8) var clustered_lights: types::ClusteredLights; @group(0) @binding(9) var clusterable_object_index_lists: types::ClusterableObjectIndexLists; @group(0) @binding(10) var cluster_offsets_and_counts: types::ClusterOffsetsAndCounts; diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index 408ad5d7364ea..bacde26fb99f8 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -99,7 +99,7 @@ const FOG_MODE_EXPONENTIAL: u32 = 2u; const FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3u; const FOG_MODE_ATMOSPHERIC: u32 = 4u; -#ifdef GPU_CLUSTERING_SUPPORT +#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3 struct ClusteredLights { data: array, }; diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 70008230c7dc3..68d01587dc065 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -5,7 +5,6 @@ use bevy::{ input::{gestures::RotationGesture, touch::TouchPhase}, log::{Level, LogPlugin}, prelude::*, - render::renderer::RenderDevice, window::{AppLifecycle, ScreenEdge, WindowMode}, winit::WinitSettings, }; @@ -98,11 +97,7 @@ fn setup_scene( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, - device: Res, ) { - bevy::log::info!("Configured wgpu adapter Limits: {:#?}", device.limits()); - bevy::log::info!("Configured wgpu adapter Features: {:#?}", device.features()); - // plane commands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))), From 72e17b2a08accec436eca8506f49987ac01cfb25 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Sat, 28 Mar 2026 15:49:14 +0800 Subject: [PATCH 3/5] Allow pushing clustered object to storage buffer --- crates/bevy_light/src/cluster/assign.rs | 2 +- crates/bevy_pbr/src/cluster/mod.rs | 32 ++++++++++++++----------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/bevy_light/src/cluster/assign.rs b/crates/bevy_light/src/cluster/assign.rs index 7ddc9f32b218e..8be51681ac313 100644 --- a/crates/bevy_light/src/cluster/assign.rs +++ b/crates/bevy_light/src/cluster/assign.rs @@ -176,7 +176,7 @@ pub(crate) fn assign_objects_to_clusters( clusterable_objects.clear(); - // Collect clusterable objects if GPU clustering is enabled. + // Collect clusterable objects if GPU clustering is disabled. if global_cluster_settings.gpu_clustering.is_none() { // collect just the relevant query data into a persisted vec to avoid reallocating each frame clusterable_objects.extend(point_lights_query.iter().filter_map( diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 959089758b07c..36ff976b29adb 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -13,7 +13,7 @@ use bevy_math::{uvec4, UVec3, UVec4, Vec4}; use bevy_render::{ render_resource::{ BindingResource, BufferBindingType, BufferUsages, DownlevelFlags, RawBufferVec, ShaderSize, - ShaderType, StorageBuffer, UniformBuffer, UninitBufferVec, + ShaderType, StorageBuffer, UniformBuffer, }, renderer::{RenderAdapter, RenderDevice, RenderQueue}, sync_world::{MainEntity, RenderEntity}, @@ -71,9 +71,12 @@ pub(crate) fn make_global_cluster_settings(world: &World) -> GlobalClusterSettin // We need to support compute shaders to use GPU clustering. To deal with // the `WGPU_SETTINGS_PRIO="webgl2"` environment setting, we check the // `RenderDevice` limits in addition to the `RenderAdapter`. + // // Some android devices report the capabilities and limits wrong, so we can't rely on them. // See for Android issues - let gpu_clustering_supported = !cfg!(target_os = "android") + // + // GPU clustering doesn't work properly on iOS simulator. See https://github.com/bevyengine/bevy/issues/23428 + let gpu_clustering_supported = !(cfg!(target_os = "android") || cfg!(target_abi = "sim")) && adapter .get_downlevel_capabilities() .flags @@ -237,7 +240,7 @@ enum ViewClusterBuffers { cluster_offsets_and_counts: UniformBuffer, }, Storage { - clusterable_object_index_lists: UninitBufferVec, + clusterable_object_index_lists: StorageBuffer, cluster_offsets_and_counts: StorageBuffer, }, } @@ -596,7 +599,7 @@ impl ViewClusterBindings { cluster_offsets_and_counts, .. } => { - clusterable_object_index_lists.clear(); + clusterable_object_index_lists.get_mut().data.clear(); cluster_offsets_and_counts.get_mut().data.clear(); } } @@ -657,11 +660,11 @@ impl ViewClusterBindings { clusterable_object_index_lists.get_mut().data[array_index][component] |= index << (8 * sub_index); } - ViewClusterBuffers::Storage { .. } => { - error!( - "Shouldn't be pushing a clusterable object index from CPU when GPU clustering \ - is in use" - ); + ViewClusterBuffers::Storage { + clusterable_object_index_lists, + .. + } => { + clusterable_object_index_lists.get_mut().data.push(index); } } @@ -714,7 +717,10 @@ impl ViewClusterBindings { clusterable_object_index_lists, .. } => { - clusterable_object_index_lists.add_multiple(elements); + clusterable_object_index_lists + .get_mut() + .data + .reserve(elements); self.n_indices += elements; } } @@ -733,7 +739,7 @@ impl ViewClusterBindings { clusterable_object_index_lists, cluster_offsets_and_counts, } => { - clusterable_object_index_lists.write_buffer(render_device); + clusterable_object_index_lists.write_buffer(render_device, render_queue); cluster_offsets_and_counts.write_buffer(render_device, render_queue); } } @@ -801,9 +807,7 @@ impl ViewClusterBuffers { fn storage() -> Self { ViewClusterBuffers::Storage { - clusterable_object_index_lists: UninitBufferVec::new( - BufferUsages::STORAGE | BufferUsages::COPY_DST, - ), + clusterable_object_index_lists: StorageBuffer::default(), cluster_offsets_and_counts: StorageBuffer::default(), } } From 78fe50778080ed719535673d41d7caa382b1323d Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Sat, 28 Mar 2026 15:50:26 +0800 Subject: [PATCH 4/5] Print device limits and features on startup in mobile example --- examples/mobile/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index 68d01587dc065..5efbc8a03ede9 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -97,7 +97,11 @@ fn setup_scene( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, + device: Res, ) { + bevy::log::info!("Configured wgpu adapter Limits: {:#?}", device.limits()); + bevy::log::info!("Configured wgpu adapter Features: {:#?}", device.features()); + // plane commands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))), From 9ee262f46659348371aa2d2b5ec034d71d54abcb Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Sat, 28 Mar 2026 16:58:52 +0800 Subject: [PATCH 5/5] Fix `reserve_indices` --- crates/bevy_pbr/src/cluster/mod.rs | 2 +- crates/bevy_pbr/src/lib.rs | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 36ff976b29adb..4294eca8cfcb8 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -720,7 +720,7 @@ impl ViewClusterBindings { clusterable_object_index_lists .get_mut() .data - .reserve(elements); + .extend(iter::repeat_n(0, elements)); self.n_indices += elements; } } diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 40ef01c3a2f93..498c47cb352e0 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -29,6 +29,7 @@ mod cluster; pub mod contact_shadows; #[cfg(feature = "bevy_gltf")] mod gltf; +use bevy_light::cluster::GlobalClusterSettings; use bevy_render::sync_component::SyncComponent; pub use contact_shadows::{ ContactShadows, ContactShadowsBuffer, ContactShadowsPlugin, ContactShadowsUniform, @@ -319,7 +320,13 @@ impl Plugin for PbrPlugin { prepare_lights .in_set(RenderSystems::CreateViews) .after(sort_cameras), - prepare_clusters_for_cpu_clustering.in_set(RenderSystems::PrepareResources), + prepare_clusters_for_cpu_clustering + .in_set(RenderSystems::PrepareResources) + .run_if( + |global_cluster_settings: Res| -> bool { + global_cluster_settings.gpu_clustering.is_none() + }, + ), ), ) .init_gpu_resource::()