diff --git a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl index 395d22698b21c..ecdc5fd49422e 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl +++ b/crates/bevy_core_pipeline/src/tonemapping/tonemapping_shared.wgsl @@ -7,8 +7,8 @@ @group(0) @binding(3) var dt_lut_texture: texture_3d; @group(0) @binding(4) var dt_lut_sampler: sampler; #else - @group(0) @binding(18) var dt_lut_texture: texture_3d; - @group(0) @binding(19) var dt_lut_sampler: sampler; + @group(0) @binding(19) var dt_lut_texture: texture_3d; + @group(0) @binding(20) var dt_lut_sampler: sampler; #endif fn sample_current_lut(p: vec3) -> vec3 { diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 9163a12bf669a..e9b78766b4fb2 100755 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -434,6 +434,7 @@ impl RenderCommand

for SetLineGizmoBindGroup #[inline] fn render<'w>( _item: &P, + _index: usize, _view: ROQueryItem<'w, Self::ViewQuery>, uniform_index: Option>, bind_group: SystemParamItem<'w, '_, Self::Param>, @@ -460,6 +461,7 @@ impl RenderCommand

for DrawLineGizmo { #[inline] fn render<'w>( _item: &P, + _index: usize, _view: ROQueryItem<'w, Self::ViewQuery>, handle: Option>, line_gizmos: SystemParamItem<'w, '_, Self::Param>, @@ -506,6 +508,7 @@ impl RenderCommand

for DrawLineJointGizmo { #[inline] fn render<'w>( _item: &P, + _index: usize, _view: ROQueryItem<'w, Self::ViewQuery>, handle: Option>, line_gizmos: SystemParamItem<'w, '_, Self::Param>, diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index 0bd154a779479..01bf05965b56c 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -301,6 +301,10 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { shader_defs.push("MOTION_VECTOR_PREPASS".into()); } + if key.contains(MeshPipelineKey::INDIRECT) { + shader_defs.push("INDIRECT".into()); + } + // Always true, since we're in the deferred lighting pipeline shader_defs.push("DEFERRED_PREPASS".into()); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index c969db7944c08..1a31084bcfb9f 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -62,6 +62,8 @@ pub mod graph { /// Label for the screen space ambient occlusion render node. ScreenSpaceAmbientOcclusion, DeferredLightingPass, + /// Label for the GPU culling node. + GpuCull, } } @@ -267,6 +269,7 @@ impl Plugin for PbrPlugin { ExtractComponentPlugin::::default(), LightmapPlugin, LightProbePlugin, + GpuCullPlugin, )) .configure_sets( PostUpdate, diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 96c32bfd6834c..505c63d9f3bc1 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -24,7 +24,7 @@ use bevy_render::{ render_resource::*, renderer::RenderDevice, texture::FallbackImage, - view::{ExtractedView, Msaa, VisibleEntities}, + view::{ExtractedView, GpuCulling, Msaa, VisibleEntities}, Extract, }; use bevy_utils::{tracing::error, HashMap, HashSet}; @@ -396,6 +396,7 @@ impl RenderCommand

for SetMaterial #[inline] fn render<'w>( item: &P, + _index: usize, _view: (), _item_query: Option<()>, (materials, material_instances): SystemParamItem<'w, '_, Self::Param>, @@ -492,15 +493,16 @@ pub fn queue_material_meshes( Has, ), Option<&Camera3d>, - Has, Option<&Projection>, &mut RenderPhase, &mut RenderPhase, &mut RenderPhase, &mut RenderPhase, ( + Has, Has>, Has>, + Has, ), )>, ) where @@ -515,13 +517,12 @@ pub fn queue_material_meshes( ssao, (normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass), camera_3d, - temporal_jitter, projection, mut opaque_phase, mut alpha_mask_phase, mut transmissive_phase, mut transparent_phase, - (has_environment_maps, has_irradiance_volumes), + (temporal_jitter, has_environment_maps, has_irradiance_volumes, gpu_culling), ) in &mut views { let draw_opaque_pbr = opaque_draw_functions.read().id::>(); @@ -560,6 +561,10 @@ pub fn queue_material_meshes( view_key |= MeshPipelineKey::IRRADIANCE_VOLUME; } + if gpu_culling { + view_key |= MeshPipelineKey::INDIRECT; + } + if let Some(projection) = projection { view_key |= match projection { Projection::Perspective(_) => MeshPipelineKey::VIEW_PROJECTION_PERSPECTIVE, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index f8d9ecc18808c..ead26053b0415 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -412,6 +412,10 @@ where shader_defs.push("MOTION_VECTOR_PREPASS".into()); } + if key.mesh_key.contains(MeshPipelineKey::INDIRECT) { + shader_defs.push("INDIRECT".into()); + } + if key.mesh_key.intersects( MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS @@ -902,6 +906,7 @@ impl RenderCommand

for SetPrepassViewBindGroup< #[inline] fn render<'w>( _item: &P, + _index: usize, (view_uniform_offset, previous_view_projection_uniform_offset): ( &'_ ViewUniformOffset, Option<&'_ PreviousViewProjectionUniformOffset>, diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 094364a631f38..56a9ec1108c63 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -42,12 +42,14 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { var vertex = vertex_no_morph; #endif + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 + let instance_index = mesh_functions::get_mesh_instance_index(vertex_no_morph.instance_index); + #ifdef SKINNED var model = skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else // SKINNED - // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. - // See https://github.com/gfx-rs/naga/issues/2416 - var model = mesh_functions::get_model_matrix(vertex_no_morph.instance_index); + var model = mesh_functions::get_model_matrix(instance_index); #endif // SKINNED out.position = mesh_functions::mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); @@ -68,21 +70,14 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef SKINNED out.world_normal = skinning::skin_normals(model, vertex.normal); #else // SKINNED - out.world_normal = mesh_functions::mesh_normal_local_to_world( - vertex.normal, - // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. - // See https://github.com/gfx-rs/naga/issues/2416 - vertex_no_morph.instance_index - ); + out.world_normal = mesh_functions::mesh_normal_local_to_world(vertex.normal, instance_index); #endif // SKINNED #ifdef VERTEX_TANGENTS out.world_tangent = mesh_functions::mesh_tangent_local_to_world( model, vertex.tangent, - // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. - // See https://github.com/gfx-rs/naga/issues/2416 - vertex_no_morph.instance_index + instance_index ); #endif // VERTEX_TANGENTS #endif // NORMAL_PREPASS_OR_DEFERRED_PREPASS @@ -97,7 +92,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 out.previous_world_position = mesh_functions::mesh_position_local_to_world( - mesh_functions::get_previous_model_matrix(vertex_no_morph.instance_index), + mesh_functions::get_previous_model_matrix(instance_index), vec4(vertex.position, 1.0) ); #endif // MOTION_VECTOR_PREPASS @@ -105,7 +100,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef VERTEX_OUTPUT_INSTANCE_INDEX // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 - out.instance_index = vertex_no_morph.instance_index; + out.instance_index = instance_index; #endif return out; diff --git a/crates/bevy_pbr/src/render/cull.rs b/crates/bevy_pbr/src/render/cull.rs new file mode 100644 index 0000000000000..7684165424e8e --- /dev/null +++ b/crates/bevy_pbr/src/render/cull.rs @@ -0,0 +1,228 @@ +//! GPU culling. + +use bevy_app::{App, Plugin}; +use bevy_asset::{load_internal_asset, Handle}; +use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; +use bevy_ecs::{ + component::Component, + entity::Entity, + query::{QueryItem, With}, + schedule::IntoSystemConfigs as _, + system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_render::{ + indirect::{ + GpuIndirectInstanceDescriptor, GpuIndirectParameters, IndirectBuffers, MeshIndirectUniform, + }, + render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner}, + render_resource::{ + binding_types::{storage_buffer, storage_buffer_read_only, uniform_buffer}, + BindGroupEntries, BindGroupLayout, CachedComputePipelineId, ComputePassDescriptor, + ComputePipelineDescriptor, DynamicBindGroupLayoutEntries, GpuArrayBuffer, PipelineCache, + Shader, ShaderStages, SpecializedComputePipeline, SpecializedComputePipelines, + }, + renderer::{RenderContext, RenderDevice}, + view::{GpuCulling, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms}, + Render, RenderApp, RenderSet, +}; +use bevy_utils::tracing::warn; + +use crate::{graph::NodePbr, MeshUniform}; + +const WORKGROUP_SIZE: usize = 64; + +pub const CULL_SHADER_HANDLE: Handle = Handle::weak_from_u128(10372890860177113771); + +pub struct GpuCullPlugin; + +#[derive(Default)] +pub struct GpuCullNode; + +#[derive(Resource)] +pub struct CullingPipeline { + culling_bind_group_layout: BindGroupLayout, +} + +#[derive(Component)] +pub struct ViewCullingPipeline(CachedComputePipelineId); + +impl Plugin for GpuCullPlugin { + fn build(&self, app: &mut App) { + load_internal_asset!(app, CULL_SHADER_HANDLE, "cull.wgsl", Shader::from_wgsl); + + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app.add_systems(Render, prepare_culling_pipelines.in_set(RenderSet::Prepare)); + } + + fn finish(&self, app: &mut App) { + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app + .add_render_graph_node::>(Core3d, NodePbr::GpuCull) + .add_render_graph_edges( + Core3d, + ( + Node3d::StartMainPass, + NodePbr::GpuCull, + Node3d::MainOpaquePass, + ), + ) + .init_resource::() + .init_resource::>(); + } +} + +impl ViewNode for GpuCullNode { + type ViewQuery = (Entity, Read, Read); + + fn run<'w>( + &self, + _: &mut RenderGraphContext, + render_context: &mut RenderContext<'w>, + (view_entity, view_uniform_offset, view_culling_pipeline): QueryItem<'w, Self::ViewQuery>, + world: &'w World, + ) -> Result<(), NodeRunError> { + let pipeline_cache = world.resource::(); + let culling_pipeline = world.resource::(); + let indirect_buffers = world.resource::(); + let mesh_uniforms_buffer = world.resource::>(); + let view_uniforms = world.resource::(); + + let Some(view_culling_pipeline) = + pipeline_cache.get_compute_pipeline(view_culling_pipeline.0) + else { + warn!("View culling pipeline wasn't present"); + return Ok(()); + }; + + let Some(view_instance_buffers) = indirect_buffers.view_instances.get(&view_entity) else { + warn!("Failed to find view instance buffers"); + return Ok(()); + }; + + let Some(view_uniforms_buffer) = view_uniforms.uniforms.binding() else { + warn!("View uniforms buffer wasn't uploaded"); + return Ok(()); + }; + + let Some(mesh_uniforms_buffer) = mesh_uniforms_buffer.binding() else { + warn!("Mesh uniforms buffer wasn't uploaded"); + return Ok(()); + }; + + let Some(mesh_indirect_uniforms_buffer) = indirect_buffers.mesh_indirect_uniform.buffer() + else { + warn!("Failed to find mesh indirect uniforms buffer"); + return Ok(()); + }; + + let Some(indirect_parameters_buffer) = indirect_buffers.params.buffer() else { + warn!("Failed to find indirect parameters buffer"); + return Ok(()); + }; + + let (Some(instances_buffer), Some(descriptors_buffer)) = ( + view_instance_buffers.instances.buffer(), + view_instance_buffers.descriptors.buffer(), + ) else { + warn!("View instance buffers weren't uploaded"); + return Ok(()); + }; + + // TODO: Do this in a separate pass and cache them. + let cull_bind_group = render_context.render_device().create_bind_group( + "cull_bind_group", + &culling_pipeline.culling_bind_group_layout, + &BindGroupEntries::sequential(( + view_uniforms_buffer, + mesh_uniforms_buffer, + instances_buffer.as_entire_binding(), + descriptors_buffer.as_entire_binding(), + mesh_indirect_uniforms_buffer.as_entire_binding(), + indirect_parameters_buffer.as_entire_binding(), + )), + ); + + let mut compute_pass = + render_context + .command_encoder() + .begin_compute_pass(&ComputePassDescriptor { + label: Some("cull"), + timestamp_writes: None, + }); + + compute_pass.set_pipeline(view_culling_pipeline); + compute_pass.set_bind_group(0, &cull_bind_group, &[view_uniform_offset.offset]); + let workgroup_count = div_round_up(view_instance_buffers.descriptors.len(), WORKGROUP_SIZE); + compute_pass.dispatch_workgroups(workgroup_count as u32, 1, 1); + + Ok(()) + } +} + +impl SpecializedComputePipeline for CullingPipeline { + type Key = (); + + fn specialize(&self, _: Self::Key) -> ComputePipelineDescriptor { + ComputePipelineDescriptor { + label: Some("cull".into()), + layout: vec![self.culling_bind_group_layout.clone()], + push_constant_ranges: vec![], + shader: CULL_SHADER_HANDLE, + shader_defs: vec![], + entry_point: "main".into(), + } + } +} + +impl FromWorld for CullingPipeline { + fn from_world(render_world: &mut World) -> Self { + let render_device = render_world.resource::(); + + let culling_bind_group_layout_entries = DynamicBindGroupLayoutEntries::sequential( + ShaderStages::COMPUTE, + ( + uniform_buffer::(/*has_dynamic_offset=*/ true), + GpuArrayBuffer::::binding_layout(render_device), + storage_buffer::(/*has_dynamic_offset=*/ false), + storage_buffer_read_only::( + /*has_dynamic_offset=*/ false, + ), + storage_buffer_read_only::(/*has_dynamic_offset=*/ false), + storage_buffer::(/*has_dynamic_offset=*/ false), + ), + ); + + let culling_bind_group_layout = render_device + .create_bind_group_layout("cull_bind_group_layout", &culling_bind_group_layout_entries); + + CullingPipeline { + culling_bind_group_layout, + } + } +} + +pub fn prepare_culling_pipelines( + mut commands: Commands, + pipeline_cache: Res, + mut pipelines: ResMut>, + culling_pipeline: Res, + view_query: Query, With)>, +) { + for entity in view_query.iter() { + let culling_pipeline_id = pipelines.specialize(&pipeline_cache, &culling_pipeline, ()); + commands + .entity(entity) + .insert(ViewCullingPipeline(culling_pipeline_id)); + } +} + +fn div_round_up(a: usize, b: usize) -> usize { + (a + b - 1) / b +} diff --git a/crates/bevy_pbr/src/render/cull.wgsl b/crates/bevy_pbr/src/render/cull.wgsl new file mode 100644 index 0000000000000..6835accd457ee --- /dev/null +++ b/crates/bevy_pbr/src/render/cull.wgsl @@ -0,0 +1,71 @@ +#import bevy_pbr::mesh_types::Mesh +#import bevy_render::view::View +#import bevy_render::maths + +struct IndirectParameters { + vertex_count: u32, + instance_count: atomic, + first_index_or_vertex: u32, + first_vertex_or_instance: u32, + first_instance: u32, +} + +struct IndirectInstanceDescriptor { + parameters_index: u32, + instance_index: u32, +} + +struct MeshIndirect { + aabb_center: vec3, + aabb_half_extents: vec3, +} + +@group(0) @binding(0) var view: View; +@group(0) @binding(1) var meshes: array; +@group(0) @binding(2) var indirect_instances: array; +@group(0) @binding(3) var indirect_descriptors: array; +@group(0) @binding(4) var indirect_meshes: array; +@group(0) @binding(5) var indirect_parameters: array; + +fn transform_radius(transform: mat4x4, half_extents: vec3) -> f32 { + return length(maths::mat4x4_to_mat3x3(transform) * half_extents); +} + +fn view_frustum_intersects_sphere(sphere_center: vec3, sphere_radius: f32) -> bool { + let center = vec4(sphere_center, 1.0); + for (var i = 0; i < 5; i += 1) { + if (dot(view.frustum[i], center) + sphere_radius <= 0.0) { + return false; + } + } + return true; +} + +@compute +@workgroup_size(64) +fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { + let mesh_instance_index = global_invocation_id.x; + if (mesh_instance_index >= arrayLength(&indirect_descriptors)) { + return; + } + + let indirect_descriptor = indirect_descriptors[mesh_instance_index]; + let instance_index = indirect_descriptor.instance_index; + + let indirect_data_index = meshes[instance_index].indirect_data_index; + let mesh_indirect = indirect_meshes[indirect_data_index]; + + let model = maths::affine3_to_square(meshes[instance_index].model); + + let sphere_center = (model * vec4(mesh_indirect.aabb_center, 1.0)).xyz; + let sphere_radius = transform_radius(model, mesh_indirect.aabb_half_extents); + if (!view_frustum_intersects_sphere(sphere_center, sphere_radius)) { + return; + } + + let parameters_index = indirect_descriptor.parameters_index; + let instance_handle = atomicAdd(&indirect_parameters[parameters_index].instance_count, 1u) + + indirect_parameters[parameters_index].first_instance; + + indirect_instances[instance_handle] = indirect_descriptor.instance_index; +} diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 31f3a29352a28..5a339455faf8b 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,6 +1,10 @@ +use std::{any::TypeId, iter, marker::PhantomData, mem}; + use bevy_asset::{load_internal_asset, AssetId}; use bevy_core_pipeline::{ - core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT}, + core_3d::{ + AlphaMask3d, Camera3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT, + }, deferred::{AlphaMask3dDeferred, Opaque3dDeferred}, }; use bevy_derive::{Deref, DerefMut}; @@ -16,20 +20,29 @@ use bevy_render::{ batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, NoAutomaticBatching, }, + indirect::{ + GpuIndirectInstanceDescriptor, GpuIndirectParameters, IndirectBuffers, MeshIndirectUniform, + RenderMeshIndirectInstance, ViewIndirectInstances, + }, mesh::*, + primitives::Aabb, render_asset::RenderAssets, - render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, + render_phase::{ + CachedRenderPipelinePhaseItem, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, + TrackedRenderPass, + }, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, DefaultImageSampler, GpuImage, ImageSampler, TextureFormatPixelInfo}, - view::{ViewTarget, ViewUniformOffset, ViewVisibility}, + view::{GpuCulling, ViewTarget, ViewUniformOffset, ViewVisibility}, Extract, }; use bevy_transform::components::GlobalTransform; -use bevy_utils::{tracing::error, Entry, HashMap, Parallel}; +use bevy_utils::{default, tracing::error, Entry, HashMap, Parallel}; #[cfg(debug_assertions)] use bevy_utils::warn_once; +use nonmax::NonMaxU32; use crate::render::{ morph::{ @@ -111,6 +124,13 @@ impl Plugin for MeshRenderPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() + .init_resource::>() + .init_resource::>() + .init_resource::>() + .init_resource::>() + .init_resource::>() + .init_resource::>() + .init_resource::>() .init_resource::() .init_resource::() .init_resource::() @@ -125,15 +145,55 @@ impl Plugin for MeshRenderPlugin { Render, ( ( - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, - batch_and_prepare_render_phase::, + ( + batch_and_prepare_render_phase::, + prepare_indirect_buffers::, + ) + .chain(), + ( + batch_and_prepare_render_phase::, + prepare_indirect_buffers::, + ) + .chain(), + ( + batch_and_prepare_render_phase::, + prepare_indirect_buffers::, + ) + .chain(), + ( + batch_and_prepare_render_phase::, + prepare_indirect_buffers::, + ) + .chain(), + ( + batch_and_prepare_render_phase::, + prepare_indirect_buffers::, + ) + .chain(), + ( + batch_and_prepare_render_phase::, + prepare_indirect_buffers::, + ) + .chain(), + ( + batch_and_prepare_render_phase::, + prepare_indirect_buffers::, + ) + .chain(), ) .in_set(RenderSet::PrepareResources), + clear_indirect_buffers + .before(batch_and_prepare_render_phase::) + .before(batch_and_prepare_render_phase::) + .before(batch_and_prepare_render_phase::) + .before(batch_and_prepare_render_phase::) + .before( + batch_and_prepare_render_phase::, + ) + .before( + batch_and_prepare_render_phase::, + ) + .in_set(RenderSet::PrepareResources), write_batched_instance_buffer:: .in_set(RenderSet::PrepareResourcesFlush), prepare_skins.in_set(RenderSet::PrepareResources), @@ -195,6 +255,7 @@ pub struct MeshUniform { // [2].z pub inverse_transpose_model_a: [Vec4; 2], pub inverse_transpose_model_b: f32, + pub indirect_data_index: u32, pub flags: u32, // Four 16-bit unsigned normalized UV values packed into a `UVec2`: // @@ -209,7 +270,11 @@ pub struct MeshUniform { } impl MeshUniform { - pub fn new(mesh_transforms: &MeshTransforms, maybe_lightmap_uv_rect: Option) -> Self { + pub fn new( + mesh_transforms: &MeshTransforms, + indirect_data_index: Option, + maybe_lightmap_uv_rect: Option, + ) -> Self { let (inverse_transpose_model_a, inverse_transpose_model_b) = mesh_transforms.transform.inverse_transpose_3x3(); Self { @@ -218,6 +283,10 @@ impl MeshUniform { lightmap_uv_rect: lightmap::pack_lightmap_uv_rect(maybe_lightmap_uv_rect), inverse_transpose_model_a, inverse_transpose_model_b, + indirect_data_index: match indirect_data_index { + Some(indirect_data_index) => indirect_data_index.into(), + None => !0, + }, flags: mesh_transforms.flags, } } @@ -241,6 +310,7 @@ pub struct RenderMeshInstance { pub transforms: MeshTransforms, pub mesh_asset_id: AssetId, pub material_bind_group_id: AtomicMaterialBindGroupId, + pub indirect_uniform_index: Option, pub shadow_caster: bool, pub automatic_batching: bool, } @@ -251,12 +321,73 @@ impl RenderMeshInstance { } } +struct RenderMeshInstanceBuilder { + pub transforms: MeshTransforms, + pub mesh_asset_id: AssetId, + pub shadow_caster: bool, + pub automatic_batching: bool, +} + +impl RenderMeshInstanceBuilder { + fn into_render_mesh_instance( + self, + indirect_uniform_index: Option, + ) -> RenderMeshInstance { + RenderMeshInstance { + transforms: self.transforms, + mesh_asset_id: self.mesh_asset_id, + material_bind_group_id: AtomicMaterialBindGroupId::default(), + indirect_uniform_index, + shadow_caster: self.shadow_caster, + automatic_batching: self.automatic_batching, + } + } +} + #[derive(Default, Resource, Deref, DerefMut)] pub struct RenderMeshInstances(EntityHashMap); +#[derive(Default)] +pub struct RenderMeshIndirectBatch { + pub indirect_parameters_offset: u32, +} + +#[derive(Resource)] +pub struct RenderMeshIndirectBatches +where + PI: PhaseItem, +{ + /// Maps a view to all the indirect batches that are potentially visible + /// from it. + /// + /// The batches are keyed off first instance index in the batch. + pub instances: EntityHashMap>, + phantom: PhantomData, +} + +impl Default for RenderMeshIndirectBatches +where + PI: PhaseItem, +{ + fn default() -> Self { + RenderMeshIndirectBatches { + instances: default(), + phantom: PhantomData, + } + } +} + +#[derive(Default)] +pub struct MeshExtractionThreadQueue { + entities: Vec, + instances: Vec, + indirect_instances: Vec, +} + pub fn extract_meshes( mut render_mesh_instances: ResMut, - mut thread_local_queues: Local>>, + mut indirect_buffers: ResMut, + mut thread_local_queues: Local>, meshes_query: Extract< Query<( Entity, @@ -264,13 +395,17 @@ pub fn extract_meshes( &GlobalTransform, Option<&PreviousGlobalTransform>, &Handle, + Option<&Aabb>, Has, Has, Has, Has, )>, >, + view_query: Extract, With)>>, ) { + let indirect_instance_data_needed = !view_query.is_empty(); + meshes_query.par_iter().for_each( |( entity, @@ -278,6 +413,7 @@ pub fn extract_meshes( transform, previous_transform, handle, + aabb, not_shadow_receiver, transmitted_receiver, not_shadow_caster, @@ -288,6 +424,7 @@ pub fn extract_meshes( } let transform = transform.affine(); let previous_transform = previous_transform.map(|t| t.0).unwrap_or(transform); + let mut flags = if not_shadow_receiver { MeshFlags::empty() } else { @@ -304,24 +441,173 @@ pub fn extract_meshes( previous_transform: (&previous_transform).into(), flags: flags.bits(), }; + + let aabb = aabb.cloned().unwrap_or_default(); + thread_local_queues.scope(|queue| { - queue.push(( - entity, - RenderMeshInstance { - mesh_asset_id: handle.id(), - transforms, - shadow_caster: !not_shadow_caster, - material_bind_group_id: AtomicMaterialBindGroupId::default(), - automatic_batching: !no_automatic_batching, - }, - )); + queue.entities.push(entity); + queue.instances.push(RenderMeshInstanceBuilder { + transforms, + mesh_asset_id: handle.id(), + shadow_caster: !not_shadow_caster, + automatic_batching: !no_automatic_batching, + }); + + if indirect_instance_data_needed { + queue + .indirect_instances + .push(RenderMeshIndirectInstance { aabb }); + } }); }, ); render_mesh_instances.clear(); + indirect_buffers.mesh_indirect_uniform.clear(); + for queue in thread_local_queues.iter_mut() { - render_mesh_instances.extend(queue.drain(..)); + for ((entity, render_mesh_instance_builder), render_indirect_uniform) in + queue.entities.drain(..).zip(queue.instances.drain(..)).zip( + queue + .indirect_instances + .drain(..) + .map(Some) + .chain(iter::repeat(None)), + ) + { + let indirect_uniform_index = + render_indirect_uniform.and_then(|render_mesh_indirect_instance| { + NonMaxU32::try_from(indirect_buffers.mesh_indirect_uniform.push( + MeshIndirectUniform { + aabb_center: render_mesh_indirect_instance.aabb.center.into(), + aabb_half_extents: + render_mesh_indirect_instance.aabb.half_extents.into(), + pad0: 0, + pad1: 0, + }, + ) as u32) + .ok() + }); + + let render_mesh_instance = + render_mesh_instance_builder.into_render_mesh_instance(indirect_uniform_index); + render_mesh_instances.insert(entity, render_mesh_instance); + } + } +} + +pub fn clear_indirect_buffers(mut indirect_buffers: ResMut) { + indirect_buffers.params.clear(); + indirect_buffers.view_instances.clear(); +} + +pub fn prepare_indirect_buffers( + mut render_phase_query: Query<(Entity, &mut RenderPhase), With>, + mut indirect_buffers: ResMut, + mut render_mesh_indirect_instances: ResMut>, + render_mesh_instances: Res, + render_meshes: Res>, +) where + PI: CachedRenderPipelinePhaseItem, +{ + let indirect_buffers = &mut *indirect_buffers; + for (view_entity, render_phase) in render_phase_query.iter_mut() { + let view_indirect_instances = indirect_buffers + .view_instances + .entry(view_entity) + .or_insert_with(ViewIndirectInstances::new); + + let mut view_indirect_instance_range = None; + let mut indirect_instances = HashMap::new(); + let mut item_instance_index = 0; + + while item_instance_index < render_phase.items.len() { + let phase_item = &render_phase.items[item_instance_index]; + + let (entity, batch_range) = (phase_item.entity(), phase_item.batch_range()); + if batch_range.is_empty() { + item_instance_index += 1; + continue; + } + + let Some(render_mesh_instance) = render_mesh_instances.get(&entity) else { + item_instance_index += batch_range.len(); + continue; + }; + let Some(render_mesh) = render_meshes.get(render_mesh_instance.mesh_asset_id) else { + item_instance_index += batch_range.len(); + continue; + }; + + // Push the indirect instance indices. + let mut first_instance_handle_in_batch = None; + for batch_item_instance_index in 0..batch_range.len() { + let instance_handle = view_indirect_instances.instance_count + + item_instance_index as u32 + + batch_item_instance_index as u32; + if first_instance_handle_in_batch.is_none() { + first_instance_handle_in_batch = Some(instance_handle); + } + view_indirect_instance_range = match view_indirect_instance_range { + None => Some(instance_handle..(instance_handle + 1)), + Some(ref view_indirect_instance_range) => { + Some(view_indirect_instance_range.start..(instance_handle + 1)) + } + } + } + + // Push the indirect parameters. + let indirect_parameters_index = + indirect_buffers.params.push(match render_mesh.buffer_info { + GpuBufferInfo::Indexed { count, .. } => GpuIndirectParameters { + vertex_count: count, + instance_count: 0, + extra_0: 0, + extra_1: 0, + first_instance: first_instance_handle_in_batch.unwrap_or_default(), + }, + GpuBufferInfo::NonIndexed => GpuIndirectParameters { + vertex_count: render_mesh.vertex_count, + instance_count: 0, + extra_0: 0, + extra_1: first_instance_handle_in_batch.unwrap_or_default(), + first_instance: first_instance_handle_in_batch.unwrap_or_default(), + }, + }); + + // Push the indirect instances. + for instance_index in (*batch_range).clone() { + view_indirect_instances + .descriptors + .push(GpuIndirectInstanceDescriptor { + parameters_index: indirect_parameters_index as u32, + instance_index, + }); + } + + indirect_instances.insert( + batch_range.start, + RenderMeshIndirectBatch { + indirect_parameters_offset: (indirect_parameters_index + * mem::size_of::()) + as u32, + }, + ); + + item_instance_index += batch_range.len(); + } + + view_indirect_instances.instance_count += item_instance_index as u32; + + if let Some(view_indirect_instance_range) = view_indirect_instance_range { + view_indirect_instances + .phase_item_ranges + .insert(TypeId::of::(), view_indirect_instance_range); + } + + render_mesh_indirect_instances + .instances + .insert(view_entity, indirect_instances); } } @@ -460,6 +746,7 @@ impl GetBatchData for MeshPipeline { Some(( MeshUniform::new( &mesh_instance.transforms, + mesh_instance.indirect_uniform_index, maybe_lightmap.map(|lightmap| lightmap.uv_rect), ), mesh_instance.should_batch().then_some(( @@ -495,6 +782,7 @@ bitflags::bitflags! { const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 13; const LIGHTMAPPED = 1 << 14; const IRRADIANCE_VOLUME = 1 << 15; + const INDIRECT = 1 << 16; // Indirect rendering is being used. const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state const BLEND_OPAQUE = 0 << Self::BLEND_SHIFT_BITS; // ← Values are just sequential within the mask, and can range from 0 to 3 const BLEND_PREMULTIPLIED_ALPHA = 1 << Self::BLEND_SHIFT_BITS; // @@ -776,6 +1064,10 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("VIEW_PROJECTION_ORTHOGRAPHIC".into()); } + if key.contains(MeshPipelineKey::INDIRECT) { + shader_defs.push("INDIRECT".into()); + } + #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))] shader_defs.push("WEBGL2".into()); @@ -1037,6 +1329,7 @@ impl RenderCommand

for SetMeshViewBindGroup #[inline] fn render<'w>( _item: &P, + _index: usize, (view_uniform, view_lights, view_fog, view_light_probes, mesh_view_bind_group): ROQueryItem< 'w, Self::ViewQuery, @@ -1075,6 +1368,7 @@ impl RenderCommand

for SetMeshBindGroup { #[inline] fn render<'w>( item: &P, + _index: usize, _view: (), _item_query: Option<()>, (bind_groups, mesh_instances, skin_indices, morph_indices, lightmaps): SystemParamItem< @@ -1138,19 +1432,30 @@ impl RenderCommand

for SetMeshBindGroup { pub struct DrawMesh; impl RenderCommand

for DrawMesh { - type Param = (SRes>, SRes); - type ViewQuery = (); + type Param = ( + SRes>, + SRes, + SRes>, + SRes, + ); + type ViewQuery = Entity; type ItemQuery = (); #[inline] fn render<'w>( item: &P, - _view: (), + index: usize, + view_entity: Entity, _item_query: Option<()>, - (meshes, mesh_instances): SystemParamItem<'w, '_, Self::Param>, + (meshes, mesh_instances, mesh_indirect_instances, indirect_buffers): SystemParamItem< + 'w, + '_, + Self::Param, + >, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let meshes = meshes.into_inner(); let mesh_instances = mesh_instances.into_inner(); + let indirect_buffers = indirect_buffers.into_inner(); let Some(mesh_instance) = mesh_instances.get(&item.entity()) else { return RenderCommandResult::Failure; @@ -1158,6 +1463,7 @@ impl RenderCommand

for DrawMesh { let Some(gpu_mesh) = meshes.get(mesh_instance.mesh_asset_id) else { return RenderCommandResult::Failure; }; + let mesh_indirect_instances = mesh_indirect_instances.instances.get(&view_entity); pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); @@ -1168,6 +1474,7 @@ impl RenderCommand

for DrawMesh { 0, &(batch_range.start as i32).to_le_bytes(), ); + match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { buffer, @@ -1175,12 +1482,39 @@ impl RenderCommand

for DrawMesh { count, } => { pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, batch_range.clone()); - } - GpuBufferInfo::NonIndexed => { - pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); + + match mesh_indirect_instances { + Some(indirect_instances) => { + let Some(indirect_buffer) = indirect_buffers.params.buffer() else { + return RenderCommandResult::Failure; + }; + pass.draw_indexed_indirect( + indirect_buffer, + indirect_instances[&batch_range.start].indirect_parameters_offset + as u64, + ); + } + None => { + pass.draw_indexed(0..*count, 0, batch_range.clone()); + } + } } + GpuBufferInfo::NonIndexed => match mesh_indirect_instances { + Some(indirect_instances) => { + let Some(indirect_buffer) = indirect_buffers.params.buffer() else { + return RenderCommandResult::Failure; + }; + pass.draw_indirect( + indirect_buffer, + indirect_instances[&batch_range.start].indirect_parameters_offset as u64, + ); + } + None => { + pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); + } + }, } + RenderCommandResult::Success } } diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 73e68dfe493c0..8fe2d585c05ab 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -31,6 +31,10 @@ fn morph_vertex(vertex_in: Vertex) -> Vertex { fn vertex(vertex_no_morph: Vertex) -> VertexOutput { var out: VertexOutput; + // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. + // See https://github.com/gfx-rs/naga/issues/2416 . + let instance_index = mesh_functions::get_mesh_instance_index(vertex_no_morph.instance_index); + #ifdef MORPH_TARGETS var vertex = morph_vertex(vertex_no_morph); #else @@ -40,9 +44,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #ifdef SKINNED var model = skinning::skin_model(vertex.joint_indices, vertex.joint_weights); #else - // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. - // See https://github.com/gfx-rs/naga/issues/2416 . - var model = mesh_functions::get_model_matrix(vertex_no_morph.instance_index); + var model = mesh_functions::get_model_matrix(instance_index); #endif #ifdef VERTEX_NORMALS @@ -51,9 +53,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #else out.world_normal = mesh_functions::mesh_normal_local_to_world( vertex.normal, - // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. - // See https://github.com/gfx-rs/naga/issues/2416 - vertex_no_morph.instance_index + instance_index ); #endif #endif @@ -75,9 +75,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.world_tangent = mesh_functions::mesh_tangent_local_to_world( model, vertex.tangent, - // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. - // See https://github.com/gfx-rs/naga/issues/2416 - vertex_no_morph.instance_index + instance_index ); #endif @@ -86,9 +84,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #endif #ifdef VERTEX_OUTPUT_INSTANCE_INDEX - // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. - // See https://github.com/gfx-rs/naga/issues/2416 - out.instance_index = vertex_no_morph.instance_index; + out.instance_index = instance_index; #endif return out; diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index e94b4f763306d..cee38d906e819 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -1,7 +1,7 @@ #define_import_path bevy_pbr::mesh_functions #import bevy_pbr::{ - mesh_view_bindings::view, + mesh_view_bindings::{view, instance_indices}, mesh_bindings::mesh, mesh_types::MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT, view_transformations::position_world_to_clip, @@ -83,3 +83,11 @@ fn mesh_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4, in return vertex_tangent; } } + +fn get_mesh_instance_index(instance_index: u32) -> u32 { +#ifdef INDIRECT + return instance_indices[instance_index]; +#else + return instance_index; +#endif +} diff --git a/crates/bevy_pbr/src/render/mesh_types.wgsl b/crates/bevy_pbr/src/render/mesh_types.wgsl index 258b6ceef9d00..634714611fb0e 100644 --- a/crates/bevy_pbr/src/render/mesh_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_types.wgsl @@ -12,6 +12,7 @@ struct Mesh { // Use bevy_pbr::mesh_functions::mat2x4_f32_to_mat3x3_unpack to unpack inverse_transpose_model_a: mat2x4, inverse_transpose_model_b: f32, + indirect_data_index: u32, // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. flags: u32, lightmap_uv_rect: vec2, diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 7d8f129dab366..4545aa0c3ff56 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -14,6 +14,7 @@ use bevy_ecs::{ }; use bevy_render::{ globals::{GlobalsBuffer, GlobalsUniform}, + indirect::{self, IndirectBuffers}, render_asset::RenderAssets, render_resource::{binding_types::*, *}, renderer::RenderDevice, @@ -58,6 +59,7 @@ bitflags::bitflags! { const NORMAL_PREPASS = 1 << 2; const MOTION_VECTOR_PREPASS = 1 << 3; const DEFERRED_PREPASS = 1 << 4; + const INDIRECT = 1 << 5; } } @@ -70,7 +72,7 @@ impl MeshPipelineViewLayoutKey { use MeshPipelineViewLayoutKey as Key; format!( - "mesh_view_layout{}{}{}{}{}", + "mesh_view_layout{}{}{}{}{}{}", self.contains(Key::MULTISAMPLED) .then_some("_multisampled") .unwrap_or_default(), @@ -86,6 +88,9 @@ impl MeshPipelineViewLayoutKey { self.contains(Key::DEFERRED_PREPASS) .then_some("_deferred") .unwrap_or_default(), + self.contains(Key::INDIRECT) + .then_some("_indirect") + .unwrap_or_default(), ) } } @@ -109,6 +114,9 @@ impl From for MeshPipelineViewLayoutKey { if value.contains(MeshPipelineKey::DEFERRED_PREPASS) { result |= MeshPipelineViewLayoutKey::DEFERRED_PREPASS; } + if value.contains(MeshPipelineKey::INDIRECT) { + result |= MeshPipelineViewLayoutKey::INDIRECT; + } result } @@ -266,12 +274,20 @@ fn layout_entries( ), ); + // Indirect + if layout_key.contains(MeshPipelineViewLayoutKey::INDIRECT) { + let indirect_entry: BindGroupLayoutEntryBuilder = + indirect::get_bind_group_layout_entry(/*read_only=*/ true) + .visibility(ShaderStages::VERTEX); + entries = entries.extend_with_indices(((13, indirect_entry),)); + } + // EnvironmentMapLight let environment_map_entries = environment_map::get_bind_group_layout_entries(render_device); entries = entries.extend_with_indices(( - (13, environment_map_entries[0]), - (14, environment_map_entries[1]), - (15, environment_map_entries[2]), + (14, environment_map_entries[0]), + (15, environment_map_entries[1]), + (16, environment_map_entries[2]), )); // Irradiance volumes @@ -279,16 +295,16 @@ fn layout_entries( let irradiance_volume_entries = irradiance_volume::get_bind_group_layout_entries(render_device); entries = entries.extend_with_indices(( - (16, irradiance_volume_entries[0]), - (17, irradiance_volume_entries[1]), + (17, irradiance_volume_entries[0]), + (18, irradiance_volume_entries[1]), )); } // Tonemapping let tonemapping_lut_entries = get_lut_bind_group_layout_entries(); entries = entries.extend_with_indices(( - (18, tonemapping_lut_entries[0]), - (19, tonemapping_lut_entries[1]), + (19, tonemapping_lut_entries[0]), + (20, tonemapping_lut_entries[1]), )); // Prepass @@ -298,7 +314,7 @@ fn layout_entries( { for (entry, binding) in prepass::get_bind_group_layout_entries(layout_key) .iter() - .zip([20, 21, 22, 23]) + .zip([21, 22, 23, 24]) { if let Some(entry) = entry { entries = entries.extend_with_indices(((binding as u32, *entry),)); @@ -309,10 +325,10 @@ fn layout_entries( // View Transmission Texture entries = entries.extend_with_indices(( ( - 24, + 25, texture_2d(TextureSampleType::Float { filterable: true }), ), - (25, sampler(SamplerBindingType::Filtering)), + (26, sampler(SamplerBindingType::Filtering)), )); entries.to_vec() @@ -358,6 +374,7 @@ pub fn prepare_mesh_view_bind_groups( global_light_meta: Res, fog_meta: Res, view_uniforms: Res, + indirect_buffers: Res, views: Query<( Entity, &ViewShadowBindings, @@ -415,10 +432,12 @@ pub fn prepare_mesh_view_bind_groups( .map(|t| &t.screen_space_ambient_occlusion_texture.default_view) .unwrap_or(&fallback_ssao); - let layout = &mesh_pipeline.get_view_layout( - MeshPipelineViewLayoutKey::from(*msaa) - | MeshPipelineViewLayoutKey::from(prepass_textures), - ); + let mut mesh_pipeline_view_layout_key = MeshPipelineViewLayoutKey::from(*msaa) + | MeshPipelineViewLayoutKey::from(prepass_textures); + if indirect_buffers.view_instances.contains_key(&entity) { + mesh_pipeline_view_layout_key |= MeshPipelineViewLayoutKey::INDIRECT; + } + let layout = &mesh_pipeline.get_view_layout(mesh_pipeline_view_layout_key); let mut entries = DynamicBindGroupEntries::new_with_indices(( (0, view_binding.clone()), @@ -436,6 +455,14 @@ pub fn prepare_mesh_view_bind_groups( (12, ssao_view), )); + // Set view instances buffer if we're using GPU culling. + if let Some(view_instances) = indirect_buffers.view_instances.get(&entity) { + if let Some(instances_buffer) = view_instances.instances.buffer() { + entries = + entries.extend_with_indices(((13, instances_buffer.as_entire_binding()),)); + } + } + let environment_map_bind_group_entries = RenderViewEnvironmentMapBindGroupEntries::get( render_view_environment_maps, &images, @@ -450,9 +477,9 @@ pub fn prepare_mesh_view_bind_groups( sampler, } => { entries = entries.extend_with_indices(( - (13, diffuse_texture_view), - (14, specular_texture_view), - (15, sampler), + (14, diffuse_texture_view), + (15, specular_texture_view), + (16, sampler), )); } RenderViewEnvironmentMapBindGroupEntries::Multiple { @@ -461,9 +488,9 @@ pub fn prepare_mesh_view_bind_groups( sampler, } => { entries = entries.extend_with_indices(( - (13, diffuse_texture_views.as_slice()), - (14, specular_texture_views.as_slice()), - (15, sampler), + (14, diffuse_texture_views.as_slice()), + (15, specular_texture_views.as_slice()), + (16, sampler), )); } } @@ -484,20 +511,20 @@ pub fn prepare_mesh_view_bind_groups( texture_view, sampler, }) => { - entries = entries.extend_with_indices(((16, texture_view), (17, sampler))); + entries = entries.extend_with_indices(((17, texture_view), (18, sampler))); } Some(RenderViewIrradianceVolumeBindGroupEntries::Multiple { ref texture_views, sampler, }) => { entries = entries - .extend_with_indices(((16, texture_views.as_slice()), (17, sampler))); + .extend_with_indices(((17, texture_views.as_slice()), (18, sampler))); } None => {} } let lut_bindings = get_lut_bindings(&images, &tonemapping_luts, tonemapping); - entries = entries.extend_with_indices(((18, lut_bindings.0), (19, lut_bindings.1))); + entries = entries.extend_with_indices(((19, lut_bindings.0), (20, lut_bindings.1))); // When using WebGL, we can't have a depth texture with multisampling let prepass_bindings; @@ -507,7 +534,7 @@ pub fn prepare_mesh_view_bind_groups( for (binding, index) in prepass_bindings .iter() .map(Option::as_ref) - .zip([20, 21, 22, 23]) + .zip([21, 22, 23, 24]) .flat_map(|(b, i)| b.map(|b| (b, i))) { entries = entries.extend_with_indices(((index, binding),)); @@ -523,7 +550,7 @@ pub fn prepare_mesh_view_bind_groups( .unwrap_or(&fallback_image_zero.sampler); entries = - entries.extend_with_indices(((24, transmission_view), (25, transmission_sampler))); + entries.extend_with_indices(((25, transmission_view), (26, transmission_sampler))); commands.entity(entity).insert(MeshViewBindGroup { value: render_device.create_bind_group("mesh_view_bind_group", layout, &entries), diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl index 20a3578af4704..59237ff0771e0 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.wgsl @@ -8,6 +8,7 @@ @group(0) @binding(0) var view: View; @group(0) @binding(1) var lights: types::Lights; + #ifdef NO_CUBE_ARRAY_TEXTURES_SUPPORT @group(0) @binding(2) var point_shadow_textures: texture_depth_cube; #else @@ -37,56 +38,60 @@ @group(0) @binding(12) var screen_space_ambient_occlusion_texture: texture_2d; +#ifdef INDIRECT +@group(0) @binding(13) var instance_indices: array; +#endif + #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY -@group(0) @binding(13) var diffuse_environment_maps: binding_array, 8u>; -@group(0) @binding(14) var specular_environment_maps: binding_array, 8u>; +@group(0) @binding(14) var diffuse_environment_maps: binding_array, 8u>; +@group(0) @binding(15) var specular_environment_maps: binding_array, 8u>; #else -@group(0) @binding(13) var diffuse_environment_map: texture_cube; -@group(0) @binding(14) var specular_environment_map: texture_cube; +@group(0) @binding(14) var diffuse_environment_map: texture_cube; +@group(0) @binding(15) var specular_environment_map: texture_cube; #endif -@group(0) @binding(15) var environment_map_sampler: sampler; +@group(0) @binding(16) var environment_map_sampler: sampler; #ifdef IRRADIANCE_VOLUMES_ARE_USABLE #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY -@group(0) @binding(16) var irradiance_volumes: binding_array, 8u>; +@group(0) @binding(17) var irradiance_volumes: binding_array, 8u>; #else -@group(0) @binding(16) var irradiance_volume: texture_3d; +@group(0) @binding(17) var irradiance_volume: texture_3d; #endif -@group(0) @binding(17) var irradiance_volume_sampler: sampler; +@group(0) @binding(18) var irradiance_volume_sampler: sampler; #endif // NB: If you change these, make sure to update `tonemapping_shared.wgsl` too. -@group(0) @binding(18) var dt_lut_texture: texture_3d; -@group(0) @binding(19) var dt_lut_sampler: sampler; +@group(0) @binding(19) var dt_lut_texture: texture_3d; +@group(0) @binding(20) var dt_lut_sampler: sampler; #ifdef MULTISAMPLED #ifdef DEPTH_PREPASS -@group(0) @binding(20) var depth_prepass_texture: texture_depth_multisampled_2d; +@group(0) @binding(21) var depth_prepass_texture: texture_depth_multisampled_2d; #endif // DEPTH_PREPASS #ifdef NORMAL_PREPASS -@group(0) @binding(21) var normal_prepass_texture: texture_multisampled_2d; +@group(0) @binding(22) var normal_prepass_texture: texture_multisampled_2d; #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(22) var motion_vector_prepass_texture: texture_multisampled_2d; +@group(0) @binding(23) var motion_vector_prepass_texture: texture_multisampled_2d; #endif // MOTION_VECTOR_PREPASS #else // MULTISAMPLED #ifdef DEPTH_PREPASS -@group(0) @binding(20) var depth_prepass_texture: texture_depth_2d; +@group(0) @binding(21) var depth_prepass_texture: texture_depth_2d; #endif // DEPTH_PREPASS #ifdef NORMAL_PREPASS -@group(0) @binding(21) var normal_prepass_texture: texture_2d; +@group(0) @binding(22) var normal_prepass_texture: texture_2d; #endif // NORMAL_PREPASS #ifdef MOTION_VECTOR_PREPASS -@group(0) @binding(22) var motion_vector_prepass_texture: texture_2d; +@group(0) @binding(23) var motion_vector_prepass_texture: texture_2d; #endif // MOTION_VECTOR_PREPASS #endif // MULTISAMPLED #ifdef DEFERRED_PREPASS -@group(0) @binding(23) var deferred_prepass_texture: texture_2d; +@group(0) @binding(24) var deferred_prepass_texture: texture_2d; #endif // DEFERRED_PREPASS -@group(0) @binding(24) var view_transmission_texture: texture_2d; -@group(0) @binding(25) var view_transmission_sampler: sampler; +@group(0) @binding(25) var view_transmission_texture: texture_2d; +@group(0) @binding(26) var view_transmission_sampler: sampler; diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 7efffc05681f8..e6d84a931257a 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -1,3 +1,4 @@ +mod cull; mod fog; mod light; pub(crate) mod mesh; @@ -6,6 +7,7 @@ mod mesh_view_bindings; mod morph; mod skin; +pub use cull::*; pub use fog::*; pub use light::*; pub use mesh::*; diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 54b0573081f67..f27bb28bb2838 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -55,7 +55,7 @@ impl BatchMeta { /// A trait to support getting data used for batching draw commands via phase /// items. -pub trait GetBatchData { +pub trait GetBatchData: Sized { type Param: SystemParam + 'static; /// Data used for comparison between phase items. If the pipeline id, draw /// function id, per-instance data buffer dynamic offset and this data @@ -77,11 +77,11 @@ pub trait GetBatchData { /// Batch the items in a render phase. This means comparing metadata needed to draw each phase item /// and trying to combine the draws into a batch. pub fn batch_and_prepare_render_phase( - gpu_array_buffer: ResMut>, + data_buffer: ResMut>, mut views: Query<&mut RenderPhase>, param: StaticSystemParam, ) { - let gpu_array_buffer = gpu_array_buffer.into_inner(); + let gpu_array_buffer = data_buffer.into_inner(); let system_param_item = param.into_inner(); let mut process_item = |item: &mut I| { diff --git a/crates/bevy_render/src/indirect.rs b/crates/bevy_render/src/indirect.rs new file mode 100644 index 0000000000000..33b776f6e1869 --- /dev/null +++ b/crates/bevy_render/src/indirect.rs @@ -0,0 +1,144 @@ +//! Logic for indirect rendering. +//! +//! Currently, indirect rendering is enabled whenever GPU culling is enabled. + +use std::{any::TypeId, ops::Range}; + +use bevy_app::{App, Plugin}; +use bevy_ecs::{ + entity::EntityHashMap, + schedule::IntoSystemConfigs as _, + system::{Res, ResMut, Resource}, +}; +use bevy_encase_derive::ShaderType; +use bevy_math::{Vec3, Vec3A}; +use bevy_utils::HashMap; +use bytemuck::{Pod, Zeroable}; +use wgpu::BufferUsages; + +use crate::{ + primitives::Aabb, + render_resource::{binding_types, BindGroupLayoutEntryBuilder, BufferVec}, + renderer::{RenderDevice, RenderQueue}, + Render, RenderApp, RenderSet, +}; + +pub struct IndirectRenderPlugin; + +/// This covers both the indexed indirect and regular indirect parameters. +#[derive(Clone, Copy, Pod, Zeroable, ShaderType)] +#[repr(C)] +pub struct GpuIndirectParameters { + pub vertex_count: u32, + pub instance_count: u32, + pub extra_0: u32, + pub extra_1: u32, + pub first_instance: u32, +} + +#[derive(Clone, Copy, Pod, Zeroable, ShaderType)] +#[repr(C)] +pub struct GpuIndirectInstanceDescriptor { + pub parameters_index: u32, + pub instance_index: u32, +} + +#[derive(Resource)] +pub struct IndirectBuffers { + pub params: BufferVec, + pub mesh_indirect_uniform: BufferVec, + pub view_instances: EntityHashMap, +} + +pub struct ViewIndirectInstances { + pub instances: BufferVec, + pub instance_count: u32, + /// These represent the culling work units. + pub descriptors: BufferVec, + /// The `TypeId` here is the type ID of a `PhaseItem`. + pub phase_item_ranges: HashMap>, +} + +#[derive(Clone)] +pub struct RenderMeshIndirectInstance { + pub aabb: Aabb, +} + +#[derive(Clone, Copy, Pod, Zeroable, ShaderType)] +#[repr(C)] +pub struct MeshIndirectUniform { + pub aabb_center: Vec3, + pub pad0: u32, + pub aabb_half_extents: Vec3, + pub pad1: u32, +} + +impl Plugin for IndirectRenderPlugin { + fn build(&self, app: &mut App) { + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { + return; + }; + + render_app.init_resource::().add_systems( + Render, + write_indirect_buffers.in_set(RenderSet::PrepareResourcesFlush), + ); + } +} + +impl Default for IndirectBuffers { + fn default() -> Self { + IndirectBuffers { + params: BufferVec::new(BufferUsages::STORAGE | BufferUsages::INDIRECT), + view_instances: EntityHashMap::default(), + mesh_indirect_uniform: BufferVec::new(BufferUsages::STORAGE), + } + } +} + +impl ViewIndirectInstances { + pub fn new() -> ViewIndirectInstances { + ViewIndirectInstances { + instances: BufferVec::new(BufferUsages::STORAGE), + instance_count: 0, + descriptors: BufferVec::new(BufferUsages::STORAGE), + phase_item_ranges: HashMap::new(), + } + } +} + +impl Default for ViewIndirectInstances { + fn default() -> Self { + Self::new() + } +} + +pub fn write_indirect_buffers( + render_device: Res, + render_queue: Res, + mut indirect_buffers: ResMut, +) { + indirect_buffers + .params + .write_buffer(&render_device, &render_queue); + indirect_buffers + .mesh_indirect_uniform + .write_buffer(&render_device, &render_queue); + + for view_instance in indirect_buffers.view_instances.values_mut() { + view_instance + .instances + .reserve(view_instance.instance_count as usize, &render_device); + view_instance + .descriptors + .write_buffer(&render_device, &render_queue); + } +} + +pub fn get_bind_group_layout_entry(read_only: bool) -> BindGroupLayoutEntryBuilder { + if read_only { + binding_types::storage_buffer_read_only::(/*has_dynamic_offset=*/ false) + } else { + binding_types::storage_buffer::(/*has_dynamic_offset=*/ false) + } +} diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 290b2cabcea73..97dba30b8b8b9 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -18,6 +18,7 @@ mod extract_param; pub mod extract_resource; pub mod globals; pub mod gpu_component_array_buffer; +pub mod indirect; pub mod mesh; #[cfg(not(target_arch = "wasm32"))] pub mod pipelined_rendering; @@ -55,6 +56,7 @@ pub use extract_param::Extract; use bevy_hierarchy::ValidParentCheckPlugin; use bevy_window::{PrimaryWindow, RawHandleWrapper}; use globals::GlobalsPlugin; +use indirect::IndirectRenderPlugin; use renderer::{RenderAdapter, RenderAdapterInfo, RenderDevice, RenderQueue}; use crate::deterministic::DeterministicRenderingConfig; @@ -327,6 +329,7 @@ impl Plugin for RenderPlugin { MeshPlugin, GlobalsPlugin, MorphPlugin, + IndirectRenderPlugin, )); app.register_type::() diff --git a/crates/bevy_render/src/maths.wgsl b/crates/bevy_render/src/maths.wgsl index 17d045154a317..0de9296ced14d 100644 --- a/crates/bevy_render/src/maths.wgsl +++ b/crates/bevy_render/src/maths.wgsl @@ -17,6 +17,10 @@ fn affine3_to_square(affine: mat3x4) -> mat4x4 { )); } +fn mat4x4_to_mat3x3(m: mat4x4) -> mat3x3 { + return mat3x3(m[0].xyz, m[1].xyz, m[2].xyz); +} + fn mat2x4_f32_to_mat3x3_unpack( a: mat2x4, b: f32, diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index aa05fa3e497f6..1ff05b6c71f46 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -34,6 +34,7 @@ pub trait Draw: Send + Sync + 'static { pass: &mut TrackedRenderPass<'w>, view: Entity, item: &P, + index: usize, ); } @@ -201,6 +202,7 @@ pub trait RenderCommand { /// issuing draw calls, etc.) via the [`TrackedRenderPass`]. fn render<'w>( item: &P, + index: usize, view: ROQueryItem<'w, Self::ViewQuery>, entity: Option>, param: SystemParamItem<'w, '_, Self::Param>, @@ -224,6 +226,7 @@ macro_rules! render_command_tuple_impl { #[allow(non_snake_case)] fn render<'w>( _item: &P, + _index: usize, ($($view,)*): ROQueryItem<'w, Self::ViewQuery>, maybe_entities: Option>, ($($name,)*): SystemParamItem<'w, '_, Self::Param>, @@ -231,12 +234,12 @@ macro_rules! render_command_tuple_impl { ) -> RenderCommandResult { match maybe_entities { None => { - $(if let RenderCommandResult::Failure = $name::render(_item, $view, None, $name, _pass) { + $(if let RenderCommandResult::Failure = $name::render(_item, _index, $view, None, $name, _pass) { return RenderCommandResult::Failure; })* } Some(($($entity,)*)) => { - $(if let RenderCommandResult::Failure = $name::render(_item, $view, Some($entity), $name, _pass) { + $(if let RenderCommandResult::Failure = $name::render(_item, _index, $view, Some($entity), $name, _pass) { return RenderCommandResult::Failure; })* } @@ -289,12 +292,13 @@ where pass: &mut TrackedRenderPass<'w>, view: Entity, item: &P, + index: usize, ) { let param = self.state.get_manual(world); let view = self.view.get_manual(world, view).unwrap(); let entity = self.entity.get_manual(world, item.entity()).ok(); // TODO: handle/log `RenderCommand` failure - C::render(item, view, entity, param, pass); + C::render(item, index, view, entity, param, pass); } } diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 1a3a544b464f4..fcdfa2d843ad6 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -115,7 +115,7 @@ impl RenderPhase { index += 1; } else { let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); - draw_function.draw(world, render_pass, view, item); + draw_function.draw(world, render_pass, view, item, index); index += batch_range.len(); } } @@ -201,6 +201,7 @@ impl RenderCommand

for SetItemPipeline { #[inline] fn render<'w>( item: &P, + _index: usize, _view: (), _entity: Option<()>, pipeline_cache: SystemParamItem<'w, '_, Self::Param>, diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 50ea668f14c7c..e9db56fca73ad 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -1,6 +1,7 @@ mod render_layers; use bevy_derive::Deref; +use bevy_render_macros::ExtractComponent; pub use render_layers::*; use bevy_app::{Plugin, PostUpdate}; @@ -12,6 +13,7 @@ use bevy_transform::{components::GlobalTransform, TransformSystem}; use bevy_utils::Parallel; use crate::deterministic::DeterministicRenderingConfig; +use crate::extract_component::ExtractComponentPlugin; use crate::{ camera::{ camera_system, Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, @@ -158,6 +160,10 @@ pub struct VisibilityBundle { #[reflect(Component, Default)] pub struct NoFrustumCulling; +#[derive(Clone, Copy, Component, Default, Reflect, ExtractComponent)] +#[reflect(Component, Default)] +pub struct GpuCulling; + /// Collection of entities visible from the current view. /// /// This component contains all entities which are visible from the currently @@ -213,42 +219,44 @@ impl Plugin for VisibilityPlugin { fn build(&self, app: &mut bevy_app::App) { use VisibilitySystems::*; - app.add_systems( - PostUpdate, - ( - calculate_bounds.in_set(CalculateBounds), - update_frusta:: - .in_set(UpdateOrthographicFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate) - // We assume that no camera will have more than one projection component, - // so these systems will run independently of one another. - // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(update_frusta::) - .ambiguous_with(update_frusta::), - update_frusta:: - .in_set(UpdatePerspectiveFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate) - // We assume that no camera will have more than one projection component, - // so these systems will run independently of one another. - // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. - .ambiguous_with(update_frusta::), - update_frusta:: - .in_set(UpdateProjectionFrusta) - .after(camera_system::) - .after(TransformSystem::TransformPropagate), - (visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate), - check_visibility - .in_set(CheckVisibility) - .after(CalculateBounds) - .after(UpdateOrthographicFrusta) - .after(UpdatePerspectiveFrusta) - .after(UpdateProjectionFrusta) - .after(VisibilityPropagate) - .after(TransformSystem::TransformPropagate), - ), - ); + app.add_plugins(ExtractComponentPlugin::::default()) + .add_systems( + PostUpdate, + ( + calculate_bounds.in_set(CalculateBounds), + update_frusta:: + .in_set(UpdateOrthographicFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate) + // We assume that no camera will have more than one projection component, + // so these systems will run independently of one another. + // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. + .ambiguous_with(update_frusta::) + .ambiguous_with(update_frusta::), + update_frusta:: + .in_set(UpdatePerspectiveFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate) + // We assume that no camera will have more than one projection component, + // so these systems will run independently of one another. + // FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481. + .ambiguous_with(update_frusta::), + update_frusta:: + .in_set(UpdateProjectionFrusta) + .after(camera_system::) + .after(TransformSystem::TransformPropagate), + (visibility_propagate_system, reset_view_visibility) + .in_set(VisibilityPropagate), + check_visibility + .in_set(CheckVisibility) + .after(CalculateBounds) + .after(UpdateOrthographicFrusta) + .after(UpdatePerspectiveFrusta) + .after(UpdateProjectionFrusta) + .after(VisibilityPropagate) + .after(TransformSystem::TransformPropagate), + ), + ); } } @@ -377,6 +385,7 @@ pub fn check_visibility( &Frustum, Option<&RenderLayers>, &Camera, + Has, )>, mut visible_aabb_query: Query<( Entity, @@ -389,7 +398,7 @@ pub fn check_visibility( )>, deterministic_rendering_config: Res, ) { - for (mut visible_entities, frustum, maybe_view_mask, camera) in &mut view_query { + for (mut visible_entities, frustum, maybe_view_mask, camera, gpu_culling) in &mut view_query { if !camera.is_active { continue; } @@ -420,7 +429,7 @@ pub fn check_visibility( } // If we have an aabb, do frustum culling - if !no_frustum_culling { + if !no_frustum_culling && !gpu_culling { if let Some(model_aabb) = maybe_model_aabb { let model = transform.affine(); let model_sphere = Sphere { diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 0b4a7be28c669..eb69e570e3465 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -338,6 +338,7 @@ impl RenderCommand

#[inline] fn render<'w>( item: &P, + _index: usize, _view: (), _item_query: Option<()>, (materials, material_instances): SystemParamItem<'w, '_, Self::Param>, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 5cd5fcb894374..5a8dbfedb516d 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -624,6 +624,7 @@ impl RenderCommand

for SetMesh2dViewBindGroup( _item: &P, + _index: usize, (view_uniform, mesh2d_view_bind_group): ROQueryItem<'w, Self::ViewQuery>, _view: Option<()>, _param: SystemParamItem<'w, '_, Self::Param>, @@ -644,6 +645,7 @@ impl RenderCommand

for SetMesh2dBindGroup { #[inline] fn render<'w>( item: &P, + _index: usize, _view: (), _item_query: Option<()>, mesh2d_bind_group: SystemParamItem<'w, '_, Self::Param>, @@ -673,6 +675,7 @@ impl RenderCommand

for DrawMesh2d { #[inline] fn render<'w>( item: &P, + _index: usize, _view: (), _item_query: Option<()>, (meshes, render_mesh2d_instances): SystemParamItem<'w, '_, Self::Param>, diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 00cb03febad18..7ae3262bcdcf8 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -752,6 +752,7 @@ impl RenderCommand

for SetSpriteViewBindGroup( _item: &P, + _index: usize, view_uniform: &'_ ViewUniformOffset, _entity: Option<()>, sprite_meta: SystemParamItem<'w, '_, Self::Param>, @@ -773,6 +774,7 @@ impl RenderCommand

for SetSpriteTextureBindGrou fn render<'w>( _item: &P, + _index: usize, _view: (), batch: Option<&'_ SpriteBatch>, image_bind_groups: SystemParamItem<'w, '_, Self::Param>, @@ -803,6 +805,7 @@ impl RenderCommand

for DrawSpriteBatch { fn render<'w>( _item: &P, + _index: usize, _view: (), batch: Option<&'_ SpriteBatch>, sprite_meta: SystemParamItem<'w, '_, Self::Param>, diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 892186478e678..e265dd17a27dc 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -161,6 +161,7 @@ impl RenderCommand

for SetUiViewBindGroup { fn render<'w>( _item: &P, + _index: usize, view_uniform: &'w ViewUniformOffset, _entity: Option<()>, ui_meta: SystemParamItem<'w, '_, Self::Param>, @@ -183,6 +184,7 @@ impl RenderCommand

for SetUiTextureBindGroup #[inline] fn render<'w>( _item: &P, + _index: usize, _view: (), batch: Option<&'w UiBatch>, image_bind_groups: SystemParamItem<'w, '_, Self::Param>, @@ -206,6 +208,7 @@ impl RenderCommand

for DrawUiNode { #[inline] fn render<'w>( _item: &P, + _index: usize, _view: (), batch: Option<&'w UiBatch>, ui_meta: SystemParamItem<'w, '_, Self::Param>, diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 1245cf3012f7a..c9057af2bd68d 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -268,6 +268,7 @@ impl RenderCommand

for SetMatUiV fn render<'w>( _item: &P, + _index: usize, view_uniform: &'w ViewUniformOffset, _entity: Option<()>, ui_meta: SystemParamItem<'w, '_, Self::Param>, @@ -292,6 +293,7 @@ impl RenderCommand

fn render<'w>( _item: &P, + _index: usize, _view: (), material_handle: Option>, materials: SystemParamItem<'w, '_, Self::Param>, @@ -317,6 +319,7 @@ impl RenderCommand

for DrawUiMaterialNode { #[inline] fn render<'w>( _item: &P, + _index: usize, _view: (), batch: Option<&'w UiMaterialBatch>, ui_meta: SystemParamItem<'w, '_, Self::Param>, diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index f0aaa9265bb5c..6c2b6a7383177 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -9,6 +9,7 @@ use bevy::{ render::{ render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, + view::GpuCulling, }, }; @@ -83,10 +84,13 @@ fn setup( ..default() }); - commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(0.0, 6., 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y), - ..default() - }); + commands + .spawn(Camera3dBundle { + transform: Transform::from_xyz(0.0, 6., 12.0) + .looking_at(Vec3::new(0., 1., 0.), Vec3::Y), + ..default() + }) + .insert(GpuCulling); } fn rotate(mut query: Query<&mut Transform, With>, time: Res

for DrawMeshInstanced { #[inline] fn render<'w>( item: &P, + _index: usize, _view: (), instance_buffer: Option<&'w InstanceBuffer>, (meshes, render_mesh_instances): SystemParamItem<'w, '_, Self::Param>, diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index 88755973c6863..2050d7fb47b8e 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -18,7 +18,7 @@ use bevy::{ render::{ render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, - view::NoFrustumCulling, + view::{GpuCulling, NoFrustumCulling}, }, window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, @@ -47,6 +47,10 @@ struct Args { /// whether to disable frustum culling, for stress testing purposes #[argh(switch)] no_frustum_culling: bool, + + /// whether to use GPU culling + #[argh(switch)] + gpu_culling: bool, } #[derive(Default, Clone)] @@ -148,7 +152,10 @@ fn setup( } // camera - commands.spawn(Camera3dBundle::default()); + let mut camera = commands.spawn(Camera3dBundle::default()); + if args.gpu_culling { + camera.insert(GpuCulling); + } } _ => { // NOTE: This pattern is good for demonstrating that frustum culling is working correctly diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index 45f3cf2020c5d..a2aef218701cc 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -9,6 +9,7 @@ use bevy::{ diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, pbr::CascadeShadowConfigBuilder, prelude::*, + render::view::GpuCulling, window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, }; @@ -23,6 +24,10 @@ struct Args { /// total number of foxes. #[argh(option, default = "1000")] count: usize, + + /// whether to use GPU culling + #[argh(switch)] + gpu_culling: bool, } #[derive(Resource)] @@ -65,6 +70,7 @@ fn main() { moving: true, sync: args.sync, }) + .insert_resource(args) .add_systems(Startup, setup) .add_systems( Update, @@ -113,6 +119,7 @@ fn setup( mut materials: ResMut>, mut animation_graphs: ResMut>, foxes: Res, + args: Res, ) { warn!(include_str!("warning_string.txt")); @@ -194,11 +201,14 @@ fn setup( radius * 0.5 * zoom, radius * 1.5 * zoom, ); - commands.spawn(Camera3dBundle { + let mut camera = commands.spawn(Camera3dBundle { transform: Transform::from_translation(translation) .looking_at(0.2 * Vec3::new(translation.x, 0.0, translation.z), Vec3::Y), ..default() }); + if args.gpu_culling { + camera.insert(GpuCulling); + } // Plane commands.spawn(PbrBundle {