diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 6faff60cf82e6..61bf8ee470eb9 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -21,7 +21,7 @@ use bevy_ecs::{ }; use bevy_math::{Affine3, Mat4, Vec2, Vec4}; use bevy_render::{ - batching::{batch_render_phase, BatchMeta, NoAutomaticBatching}, + batching::{batch_render_phase, flush_buffer, GetBatchData, NoAutomaticBatching}, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{ skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, @@ -30,10 +30,7 @@ use bevy_render::{ }, prelude::Msaa, render_asset::RenderAssets, - render_phase::{ - CachedRenderPipelinePhaseItem, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, - TrackedRenderPass, - }, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ @@ -122,7 +119,17 @@ impl Plugin for MeshRenderPlugin { .add_systems( Render, ( - prepare_and_batch_meshes.in_set(RenderSet::PrepareResources), + ( + batch_render_phase::, + batch_render_phase::, + batch_render_phase::, + batch_render_phase::, + batch_render_phase::, + batch_render_phase::, + ) + .chain() + .in_set(RenderSet::PrepareResources), + flush_buffer::.in_set(RenderSet::PrepareResourcesFlush), prepare_skinned_meshes.in_set(RenderSet::PrepareResources), prepare_morphs.in_set(RenderSet::PrepareResources), prepare_mesh_bind_group.in_set(RenderSet::PrepareBindGroups), @@ -347,84 +354,6 @@ pub fn extract_skinned_meshes( commands.insert_or_spawn_batch(values); } -#[allow(clippy::too_many_arguments)] -pub fn prepare_and_batch_meshes( - render_device: Res, - render_queue: Res, - gpu_array_buffer: ResMut>, - mut views: Query<( - Option<&mut RenderPhase>, - Option<&mut RenderPhase>, - &mut RenderPhase, - &mut RenderPhase, - &mut RenderPhase, - )>, - mut shadow_views: Query<&mut RenderPhase>, - meshes: Query< - (Option<&MaterialBindGroupId>, &Handle, &MeshTransforms), - Without, - >, -) { - let gpu_array_buffer = gpu_array_buffer.into_inner(); - - gpu_array_buffer.clear(); - - let mut get_batch_meta = |entity, pipeline_id, draw_function_id| { - let Ok((material_bind_group_id, mesh_handle, mesh_transforms)) = meshes.get(entity) else { - return None; - }; - let gpu_array_buffer_index = gpu_array_buffer.push(mesh_transforms.into()); - Some(( - BatchMeta:: { - pipeline_id, - draw_function_id, - material_bind_group_id: material_bind_group_id.cloned(), - mesh_asset_id: mesh_handle.id(), - dynamic_offset: gpu_array_buffer_index.dynamic_offset, - }, - gpu_array_buffer_index.index, - gpu_array_buffer_index.dynamic_offset, - )) - }; - - for ( - opaque_prepass_phase, - alpha_mask_prepass_phase, - opaque_phase, - alpha_mask_phase, - transparent_phase, - ) in &mut views - { - if let Some(opaque_prepass_phase) = opaque_prepass_phase { - batch_render_phase(opaque_prepass_phase.into_inner(), |item| { - get_batch_meta(item.entity(), item.cached_pipeline(), item.draw_function()) - }); - } - if let Some(alpha_mask_prepass_phase) = alpha_mask_prepass_phase { - batch_render_phase(alpha_mask_prepass_phase.into_inner(), |item| { - get_batch_meta(item.entity(), item.cached_pipeline(), item.draw_function()) - }); - } - batch_render_phase(opaque_phase.into_inner(), |item| { - get_batch_meta(item.entity(), item.cached_pipeline(), item.draw_function()) - }); - batch_render_phase(alpha_mask_phase.into_inner(), |item| { - get_batch_meta(item.entity(), item.cached_pipeline(), item.draw_function()) - }); - batch_render_phase(transparent_phase.into_inner(), |item| { - get_batch_meta(item.entity(), item.cached_pipeline(), item.draw_function()) - }); - } - - for shadow_phase in &mut shadow_views { - batch_render_phase(shadow_phase.into_inner(), |item| { - get_batch_meta(item.entity(), item.cached_pipeline(), item.draw_function()) - }); - } - - gpu_array_buffer.write_buffer(&render_device, &render_queue); -} - #[derive(Resource, Clone)] pub struct MeshPipeline { pub view_layout: BindGroupLayout, @@ -709,6 +638,25 @@ impl MeshPipeline { } } +impl GetBatchData for MeshPipeline { + type Query = ( + Option<&'static MaterialBindGroupId>, + &'static Handle, + &'static MeshTransforms, + ); + type CompareData = (Option, AssetId); + type BufferData = MeshUniform; + + fn get_batch_data( + (material_bind_group_id, mesh_handle, mesh_transforms): ::Item<'_>, + ) -> (Self::CompareData, Self::BufferData) { + ( + (material_bind_group_id.cloned(), mesh_handle.id()), + mesh_transforms.into(), + ) + } +} + bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 7cadbee0afda5..e990ae9d0d116 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -1,11 +1,15 @@ -use bevy_asset::AssetId; -use bevy_ecs::component::Component; +use bevy_ecs::{ + component::Component, + prelude::Res, + query::{ReadOnlyWorldQuery, WorldQuery}, + system::{Query, ResMut}, +}; use nonmax::NonMaxU32; use crate::{ - mesh::Mesh, render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, RenderPhase}, - render_resource::CachedRenderPipelineId, + render_resource::{CachedRenderPipelineId, GpuArrayBuffer, GpuArrayBufferable}, + renderer::{RenderDevice, RenderQueue}, }; /// Add this component to mesh entities to disable automatic batching @@ -25,7 +29,7 @@ pub struct NoAutomaticBatching; /// result of the `prepare_and_batch_meshes` system, e.g. due to having to split /// data across separate uniform bindings within the same buffer due to the /// maximum uniform buffer binding size. -pub struct BatchMeta { +struct BatchMeta { /// The pipeline id encompasses all pipeline configuration including vertex /// buffers and layouts, shaders and their specializations, bind group /// layouts, etc. @@ -33,9 +37,8 @@ pub struct BatchMeta { /// The draw function id defines the RenderCommands that are called to /// set the pipeline and bindings, and make the draw command pub draw_function_id: DrawFunctionId, - pub material_bind_group_id: Option, - pub mesh_asset_id: AssetId, pub dynamic_offset: Option, + pub user_data: T, } impl PartialEq for BatchMeta { @@ -43,47 +46,90 @@ impl PartialEq for BatchMeta { fn eq(&self, other: &BatchMeta) -> bool { self.pipeline_id == other.pipeline_id && self.draw_function_id == other.draw_function_id - && self.mesh_asset_id == other.mesh_asset_id && self.dynamic_offset == other.dynamic_offset - && self.material_bind_group_id == other.material_bind_group_id + && self.user_data == other.user_data } } +pub trait GetBatchData { + type Query: ReadOnlyWorldQuery; + type CompareData: PartialEq; + type BufferData: GpuArrayBufferable + Sync + Send + 'static; + fn get_batch_data( + batch_data: ::Item<'_>, + ) -> (Self::CompareData, Self::BufferData); +} + /// 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_render_phase< - I: CachedRenderPipelinePhaseItem, - T: PartialEq, // Batch metadata used for distinguishing batches ->( - phase: &mut RenderPhase, - mut get_batch_meta: impl FnMut(&I) -> Option<(T, NonMaxU32, Option)>, +pub fn batch_render_phase( + gpu_array_buffer: ResMut>, + mut views: Query<&mut RenderPhase>, + query: Query<(Option<&NoAutomaticBatching>, F::Query)>, ) { - let mut items = phase.items.iter_mut().peekable(); - let mut batch_start_item = None; - let mut batch_start_index = 0; - let mut next_batch = items.peek().and_then(|item| get_batch_meta(item)); - while let Some(item) = items.next() { - // Get the current batch meta and update the next batch meta - let Some((batch_meta, index, dynamic_offset)) = std::mem::replace( - &mut next_batch, - items.peek().and_then(|item| get_batch_meta(item)), - ) else { - // If the current phase item doesn't match the query, we don't modify it - continue; + let gpu_array_buffer = gpu_array_buffer.into_inner(); + + let mut process_item = |item: &mut I| -> Option> { + let Ok((no_batching, batch_data)) = query.get(item.entity()) else { + return None; }; - // If we are beginning a new batch, record the start item and index - if batch_start_item.is_none() { - batch_start_item = Some(item); - batch_start_index = index.get(); + let (user_data, buffer_data) = F::get_batch_data(batch_data); + + let buffer_index = gpu_array_buffer.push(buffer_data); + *item.batch_range_mut() = buffer_index.index.get()..buffer_index.index.get() + 1; + *item.dynamic_offset_mut() = buffer_index.dynamic_offset; + + if no_batching.is_some() { + None + } else { + Some(BatchMeta { + pipeline_id: item.cached_pipeline(), + draw_function_id: item.draw_function(), + dynamic_offset: buffer_index.dynamic_offset, + user_data, + }) } + }; - if Some(&batch_meta) != next_batch.as_ref().map(|(meta, ..)| meta) { - // The next item doesn't match the current batch (or doesn't exist). - // Update the phase item to render this batch. - let batch_start_item = batch_start_item.take().unwrap(); - *batch_start_item.batch_range_mut() = batch_start_index..index.get() + 1; - *batch_start_item.dynamic_offset_mut() = dynamic_offset; + for mut phase in &mut views { + let mut items = phase.items.iter_mut().peekable(); + let mut batch_start_item = None; + let mut next_batch = items.peek_mut().and_then(|i| process_item(i)); + while let Some(item) = items.next() { + // Get the current batch meta and update the next batch meta + let Some(batch_meta) = std::mem::replace( + &mut next_batch, + items.peek_mut().and_then(|i| process_item(i)), + ) else { + // If the current phase item doesn't match the query or has NoAutomaticBatching, + // we don't modify it any further + continue; + }; + + let batch_end_item = item.batch_range().end; + + // If we are beginning a new batch, record the start item + if batch_start_item.is_none() { + batch_start_item = Some(item); + } + + if Some(&batch_meta) != next_batch.as_ref() { + // The next item doesn't match the current batch (or doesn't exist). + // Update the first phase item to render the full batch. + let batch_start_item = batch_start_item.take().unwrap(); + batch_start_item.batch_range_mut().end = batch_end_item; + } } } } + +pub fn flush_buffer( + render_device: Res, + render_queue: Res, + gpu_array_buffer: ResMut>, +) { + let gpu_array_buffer = gpu_array_buffer.into_inner(); + gpu_array_buffer.write_buffer(&render_device, &render_queue); + gpu_array_buffer.clear(); +} diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 7a15ecec7c866..88e8ad59dd5d4 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,5 +1,5 @@ use bevy_app::Plugin; -use bevy_asset::{load_internal_asset, Handle}; +use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::core_2d::Transparent2d; use bevy_ecs::{ @@ -10,14 +10,11 @@ use bevy_ecs::{ use bevy_math::{Affine3, Vec2, Vec4}; use bevy_reflect::Reflect; use bevy_render::{ - batching::{batch_render_phase, BatchMeta, NoAutomaticBatching}, + batching::{batch_render_phase, flush_buffer, GetBatchData}, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, - render_phase::{ - CachedRenderPipelinePhaseItem, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, - TrackedRenderPass, - }, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ @@ -97,7 +94,9 @@ impl Plugin for Mesh2dRenderPlugin { .add_systems( Render, ( - prepare_and_batch_meshes2d.in_set(RenderSet::PrepareResources), + batch_render_phase:: + .in_set(RenderSet::PrepareResources), + flush_buffer::.in_set(RenderSet::PrepareResourcesFlush), prepare_mesh2d_bind_group.in_set(RenderSet::PrepareBindGroups), prepare_mesh2d_view_bind_groups.in_set(RenderSet::PrepareBindGroups), ), @@ -203,54 +202,6 @@ pub fn extract_mesh2d( commands.insert_or_spawn_batch(values); } -#[allow(clippy::too_many_arguments)] -pub fn prepare_and_batch_meshes2d( - render_device: Res, - render_queue: Res, - gpu_array_buffer: ResMut>, - mut views: Query<&mut RenderPhase>, - meshes: Query< - ( - Option<&Material2dBindGroupId>, - &Mesh2dHandle, - &Mesh2dTransforms, - ), - Without, - >, -) { - if meshes.is_empty() { - return; - } - - let gpu_array_buffer = gpu_array_buffer.into_inner(); - - gpu_array_buffer.clear(); - - for transparent_phase in &mut views { - batch_render_phase(transparent_phase.into_inner(), |item| { - let Ok((material2d_bind_group_id, mesh_handle, mesh_transforms)) = - meshes.get(item.entity()) - else { - return None; - }; - let gpu_array_buffer_index = gpu_array_buffer.push(mesh_transforms.into()); - Some(( - BatchMeta:: { - pipeline_id: item.cached_pipeline(), - draw_function_id: item.draw_function(), - material_bind_group_id: material2d_bind_group_id.cloned(), - mesh_asset_id: mesh_handle.0.id(), - dynamic_offset: gpu_array_buffer_index.dynamic_offset, - }, - gpu_array_buffer_index.index, - gpu_array_buffer_index.dynamic_offset, - )) - }); - } - - gpu_array_buffer.write_buffer(&render_device, &render_queue); -} - #[derive(Resource, Clone)] pub struct Mesh2dPipeline { pub view_layout: BindGroupLayout, @@ -372,6 +323,25 @@ impl Mesh2dPipeline { } } +impl GetBatchData for Mesh2dPipeline { + type Query = ( + Option<&'static Material2dBindGroupId>, + &'static Mesh2dHandle, + &'static Mesh2dTransforms, + ); + type CompareData = (Option, AssetId); + type BufferData = Mesh2dUniform; + + fn get_batch_data( + (material_bind_group_id, mesh_handle, mesh_transforms): ::Item<'_>, + ) -> (Self::CompareData, Self::BufferData) { + ( + (material_bind_group_id.cloned(), mesh_handle.0.id()), + mesh_transforms.into(), + ) + } +} + bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)]