diff --git a/assets/shaders/custom_gltf_2d.wgsl b/assets/shaders/custom_gltf_2d.wgsl index 58058d9501033..5e0a908c875d4 100644 --- a/assets/shaders/custom_gltf_2d.wgsl +++ b/assets/shaders/custom_gltf_2d.wgsl @@ -1,8 +1,9 @@ #import bevy_sprite::mesh2d_view_bindings globals #import bevy_sprite::mesh2d_bindings mesh -#import bevy_sprite::mesh2d_functions mesh2d_position_local_to_clip +#import bevy_sprite::mesh2d_functions get_model_matrix, mesh2d_position_local_to_clip struct Vertex { + @builtin(instance_index) instance_index: u32, @location(0) position: vec3, @location(1) color: vec4, @location(2) barycentric: vec3, @@ -17,7 +18,8 @@ struct VertexOutput { @vertex fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; - out.clip_position = mesh2d_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0)); + let model = get_model_matrix(vertex.instance_index); + out.clip_position = mesh2d_position_local_to_clip(model, vec4(vertex.position, 1.0)); out.color = vertex.color; out.barycentric = vertex.barycentric; return out; diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 084205ee24752..530d48cde38a5 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -19,6 +19,8 @@ pub mod graph { } pub const CORE_2D: &str = graph::NAME; +use std::ops::Range; + pub use camera_2d::*; pub use main_pass_2d_node::*; @@ -35,7 +37,7 @@ use bevy_render::{ render_resource::CachedRenderPipelineId, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_utils::FloatOrd; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode}; @@ -83,7 +85,8 @@ pub struct Transparent2d { pub entity: Entity, pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Transparent2d { @@ -111,8 +114,23 @@ impl PhaseItem for Transparent2d { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 04f4f0973998f..e30b20b8d5e49 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -24,7 +24,7 @@ pub mod graph { } pub const CORE_3D: &str = graph::NAME; -use std::cmp::Reverse; +use std::{cmp::Reverse, ops::Range}; pub use camera_3d::*; pub use main_opaque_pass_3d_node::*; @@ -50,7 +50,7 @@ use bevy_render::{ view::ViewDepthTexture, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_utils::{FloatOrd, HashMap}; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd, HashMap}; use crate::{ prepass::{ @@ -135,7 +135,8 @@ pub struct Opaque3d { pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Opaque3d { @@ -164,8 +165,23 @@ impl PhaseItem for Opaque3d { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } @@ -181,7 +197,8 @@ pub struct AlphaMask3d { pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for AlphaMask3d { @@ -210,8 +227,23 @@ impl PhaseItem for AlphaMask3d { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } @@ -227,7 +259,8 @@ pub struct Transparent3d { pub pipeline: CachedRenderPipelineId, pub entity: Entity, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Transparent3d { @@ -255,8 +288,23 @@ impl PhaseItem for Transparent3d { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 38c71050a194b..f408a168e7c7c 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -27,7 +27,7 @@ pub mod node; -use std::cmp::Reverse; +use std::{cmp::Reverse, ops::Range}; use bevy_ecs::prelude::*; use bevy_reflect::Reflect; @@ -36,7 +36,7 @@ use bevy_render::{ render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat}, texture::CachedTexture, }; -use bevy_utils::FloatOrd; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; pub const DEPTH_PREPASS_FORMAT: TextureFormat = TextureFormat::Depth32Float; pub const NORMAL_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgb10a2Unorm; @@ -83,6 +83,8 @@ pub struct Opaque3dPrepass { pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Opaque3dPrepass { @@ -109,6 +111,26 @@ impl PhaseItem for Opaque3dPrepass { // Key negated to match reversed SortKey ordering radsort::sort_by_key(items, |item| -item.distance); } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } } impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { @@ -128,6 +150,8 @@ pub struct AlphaMask3dPrepass { pub entity: Entity, pub pipeline_id: CachedRenderPipelineId, pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for AlphaMask3dPrepass { @@ -154,6 +178,26 @@ impl PhaseItem for AlphaMask3dPrepass { // Key negated to match reversed SortKey ordering radsort::sort_by_key(items, |item| -item.distance); } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } } impl CachedRenderPipelinePhaseItem for AlphaMask3dPrepass { diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 2ec777e8ca52b..5b64598eb403f 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -178,7 +178,8 @@ fn queue_line_gizmos_2d( draw_function, pipeline, sort_key: FloatOrd(f32::INFINITY), - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index acb827dff3346..c9a465f595549 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -192,7 +192,8 @@ fn queue_line_gizmos_3d( draw_function, pipeline, distance: 0., - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } diff --git a/crates/bevy_math/src/affine3.rs b/crates/bevy_math/src/affine3.rs index 51598e4beae12..a03f12dd57e65 100644 --- a/crates/bevy_math/src/affine3.rs +++ b/crates/bevy_math/src/affine3.rs @@ -1,4 +1,4 @@ -use glam::{Affine3A, Mat3, Vec3}; +use glam::{Affine3A, Mat3, Vec3, Vec3Swizzles, Vec4}; /// Reduced-size version of `glam::Affine3A` for use when storage has /// significant performance impact. Convert to `glam::Affine3A` to do @@ -10,6 +10,36 @@ pub struct Affine3 { pub translation: Vec3, } +impl Affine3 { + /// Calculates the transpose of the affine 4x3 matrix to a 3x4 and formats it for packing into GPU buffers + #[inline] + pub fn to_transpose(&self) -> [Vec4; 3] { + let transpose_3x3 = self.matrix3.transpose(); + [ + transpose_3x3.x_axis.extend(self.translation.x), + transpose_3x3.y_axis.extend(self.translation.y), + transpose_3x3.z_axis.extend(self.translation.z), + ] + } + + /// Calculates the inverse transpose of the 3x3 matrix and formats it for packing into GPU buffers + #[inline] + pub fn inverse_transpose_3x3(&self) -> ([Vec4; 2], f32) { + let inverse_transpose_3x3 = Affine3A::from(self).inverse().matrix3.transpose(); + ( + [ + (inverse_transpose_3x3.x_axis, inverse_transpose_3x3.y_axis.x).into(), + ( + inverse_transpose_3x3.y_axis.yz(), + inverse_transpose_3x3.z_axis.xy(), + ) + .into(), + ], + inverse_transpose_3x3.z_axis.z, + ) + } +} + impl From<&Affine3A> for Affine3 { fn from(affine: &Affine3A) -> Self { Self { diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 20873ed0f341d..d9c835abcaffa 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -20,7 +20,6 @@ use bevy_ecs::{ }, }; use bevy_render::{ - extract_component::ExtractComponentPlugin, mesh::{Mesh, MeshVertexBufferLayout}, prelude::Image, render_asset::{prepare_assets, RenderAssets}, @@ -29,13 +28,13 @@ use bevy_render::{ RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ - AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource, - PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline, - SpecializedMeshPipelineError, SpecializedMeshPipelines, + AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, + OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, texture::FallbackImage, - view::{ExtractedView, Msaa, VisibleEntities}, + view::{ExtractedView, Msaa, ViewVisibility, VisibleEntities}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::{tracing::error, HashMap, HashSet}; @@ -180,8 +179,7 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { - app.init_asset::() - .add_plugins(ExtractComponentPlugin::>::extract_visible()); + app.init_asset::(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -193,7 +191,10 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_systems(ExtractSchedule, extract_materials::) + .add_systems( + ExtractSchedule, + (extract_materials::, extract_material_meshes::), + ) .add_systems( Render, ( @@ -225,6 +226,26 @@ where } } +fn extract_material_meshes( + mut commands: Commands, + mut previous_len: Local, + query: Extract)>>, +) { + let mut values = Vec::with_capacity(*previous_len); + for (entity, view_visibility, material) in &query { + if view_visibility.get() { + // NOTE: MaterialBindGroupId is inserted here to avoid a table move. Upcoming changes + // to use SparseSet for render world entity storage will do this automatically. + values.push(( + entity, + (material.clone_weak(), MaterialBindGroupId::default()), + )); + } + } + *previous_len = values.len(); + commands.insert_or_spawn_batch(values); +} + /// A key uniquely identifying a specialized [`MaterialPipeline`]. pub struct MaterialPipelineKey { pub mesh_key: MeshPipelineKey, @@ -403,7 +424,12 @@ pub fn queue_material_meshes( msaa: Res, render_meshes: Res>, render_materials: Res>, - material_meshes: Query<(&Handle, &Handle, &MeshTransforms)>, + mut material_meshes: Query<( + &Handle, + &mut MaterialBindGroupId, + &Handle, + &MeshTransforms, + )>, images: Res>, mut views: Query<( &ExtractedView, @@ -467,8 +493,8 @@ pub fn queue_material_meshes( } let rangefinder = view.rangefinder3d(); for visible_entity in &visible_entities.entities { - let Ok((material_handle, mesh_handle, mesh_transforms)) = - material_meshes.get(*visible_entity) + let Ok((material_handle, mut material_bind_group_id, mesh_handle, mesh_transforms)) = + material_meshes.get_mut(*visible_entity) else { continue; }; @@ -504,6 +530,8 @@ pub fn queue_material_meshes( } }; + *material_bind_group_id = material.get_bind_group_id(); + let distance = rangefinder.distance_translation(&mesh_transforms.transform.translation) + material.properties.depth_bias; match material.properties.alpha_mode { @@ -513,7 +541,8 @@ pub fn queue_material_meshes( draw_function: draw_opaque_pbr, pipeline: pipeline_id, distance, - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } AlphaMode::Mask(_) => { @@ -522,7 +551,8 @@ pub fn queue_material_meshes( draw_function: draw_alpha_mask_pbr, pipeline: pipeline_id, distance, - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } AlphaMode::Blend @@ -534,7 +564,8 @@ pub fn queue_material_meshes( draw_function: draw_transparent_pbr, pipeline: pipeline_id, distance, - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } @@ -560,6 +591,15 @@ pub struct PreparedMaterial { pub properties: MaterialProperties, } +#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)] +pub struct MaterialBindGroupId(Option); + +impl PreparedMaterial { + pub fn get_bind_group_id(&self) -> MaterialBindGroupId { + MaterialBindGroupId(Some(self.bind_group.id())) + } +} + #[derive(Resource)] pub struct ExtractedMaterials { extracted: Vec<(AssetId, M)>, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 3c9bdd7ddb4ec..ca1caf5c44720 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -17,6 +17,7 @@ use bevy_ecs::{ }; use bevy_math::{Affine3A, Mat4}; use bevy_render::{ + batching::batch_and_prepare_render_phase, globals::{GlobalsBuffer, GlobalsUniform}, mesh::MeshVertexBufferLayout, prelude::{Camera, Mesh}, @@ -158,7 +159,12 @@ where .add_systems(ExtractSchedule, extract_camera_previous_view_projection) .add_systems( Render, - prepare_previous_view_projection_uniforms.in_set(RenderSet::PrepareResources), + ( + prepare_previous_view_projection_uniforms, + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + ) + .in_set(RenderSet::PrepareResources), ); } @@ -849,6 +855,8 @@ pub fn queue_prepass_material_meshes( draw_function: opaque_draw_prepass, pipeline_id, distance, + batch_range: 0..1, + dynamic_offset: None, }); } AlphaMode::Mask(_) => { @@ -857,6 +865,8 @@ pub fn queue_prepass_material_meshes( draw_function: alpha_mask_draw_prepass, pipeline_id, distance, + batch_range: 0..1, + dynamic_offset: None, }); } AlphaMode::Blend diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 6c5087e721b9a..bad686b92f2f9 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -27,10 +27,11 @@ use bevy_render::{ }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; use bevy_utils::{ + nonmax::NonMaxU32, tracing::{error, warn}, HashMap, }; -use std::{hash::Hash, num::NonZeroU64}; +use std::{hash::Hash, num::NonZeroU64, ops::Range}; #[derive(Component)] pub struct ExtractedPointLight { @@ -1641,6 +1642,8 @@ pub fn queue_shadows( pipeline: pipeline_id, entity, distance: 0.0, // TODO: sort front-to-back + batch_range: 0..1, + dynamic_offset: None, }); } } @@ -1652,6 +1655,8 @@ pub struct Shadow { pub entity: Entity, pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for Shadow { @@ -1679,6 +1684,26 @@ impl PhaseItem for Shadow { // better than rebinding everything at a high rate. radsort::sort_by_key(items, |item| item.sort_key()); } + + #[inline] + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset + } } impl CachedRenderPipelinePhaseItem for Shadow { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 9661016e9a35f..995c8bfa59f2c 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1,8 +1,8 @@ use crate::{ environment_map, prepass, EnvironmentMapLight, FogMeta, GlobalLightMeta, GpuFog, GpuLights, - GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver, PreviousGlobalTransform, - ScreenSpaceAmbientOcclusionTextures, Shadow, ShadowSamplers, ViewClusterBindings, - ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings, + GpuPointLights, LightMeta, MaterialBindGroupId, NotShadowCaster, NotShadowReceiver, + PreviousGlobalTransform, ScreenSpaceAmbientOcclusionTextures, Shadow, ShadowSamplers, + ViewClusterBindings, ViewFogUniformOffset, ViewLightsUniformOffset, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS, }; use bevy_app::Plugin; @@ -16,11 +16,15 @@ use bevy_core_pipeline::{ }; use bevy_ecs::{ prelude::*, - query::ROQueryItem, + query::{QueryItem, ROQueryItem}, system::{lifetimeless::*, SystemParamItem, SystemState}, }; -use bevy_math::{Affine3, Affine3A, Mat4, Vec2, Vec3Swizzles, Vec4}; +use bevy_math::{Affine3, Mat4, Vec2, Vec4}; use bevy_render::{ + batching::{ + batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData, + NoAutomaticBatching, + }, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{ skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, @@ -29,7 +33,7 @@ use bevy_render::{ }, prelude::Msaa, render_asset::RenderAssets, - render_phase::{PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, TrackedRenderPass}, + render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, renderer::{RenderDevice, RenderQueue}, texture::{ @@ -41,7 +45,6 @@ use bevy_render::{ }; use bevy_transform::components::GlobalTransform; use bevy_utils::{tracing::error, HashMap, Hashed}; -use fixedbitset::FixedBitSet; use crate::render::{ morph::{extract_morphs, prepare_morphs, MorphIndex, MorphUniform}, @@ -119,7 +122,15 @@ impl Plugin for MeshRenderPlugin { .add_systems( Render, ( - prepare_mesh_uniforms.in_set(RenderSet::PrepareResources), + ( + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + batch_and_prepare_render_phase::, + ) + .in_set(RenderSet::PrepareResources), + write_batched_instance_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), @@ -184,48 +195,13 @@ pub struct MeshUniform { impl From<&MeshTransforms> for MeshUniform { fn from(mesh_transforms: &MeshTransforms) -> Self { - let transpose_model_3x3 = mesh_transforms.transform.matrix3.transpose(); - let transpose_previous_model_3x3 = mesh_transforms.previous_transform.matrix3.transpose(); - let inverse_transpose_model_3x3 = Affine3A::from(&mesh_transforms.transform) - .inverse() - .matrix3 - .transpose(); + let (inverse_transpose_model_a, inverse_transpose_model_b) = + mesh_transforms.transform.inverse_transpose_3x3(); Self { - transform: [ - transpose_model_3x3 - .x_axis - .extend(mesh_transforms.transform.translation.x), - transpose_model_3x3 - .y_axis - .extend(mesh_transforms.transform.translation.y), - transpose_model_3x3 - .z_axis - .extend(mesh_transforms.transform.translation.z), - ], - previous_transform: [ - transpose_previous_model_3x3 - .x_axis - .extend(mesh_transforms.previous_transform.translation.x), - transpose_previous_model_3x3 - .y_axis - .extend(mesh_transforms.previous_transform.translation.y), - transpose_previous_model_3x3 - .z_axis - .extend(mesh_transforms.previous_transform.translation.z), - ], - inverse_transpose_model_a: [ - ( - inverse_transpose_model_3x3.x_axis, - inverse_transpose_model_3x3.y_axis.x, - ) - .into(), - ( - inverse_transpose_model_3x3.y_axis.yz(), - inverse_transpose_model_3x3.z_axis.xy(), - ) - .into(), - ], - inverse_transpose_model_b: inverse_transpose_model_3x3.z_axis.z, + transform: mesh_transforms.transform.to_transpose(), + previous_transform: mesh_transforms.previous_transform.to_transpose(), + inverse_transpose_model_a, + inverse_transpose_model_b, flags: mesh_transforms.flags, } } @@ -234,7 +210,7 @@ impl From<&MeshTransforms> for MeshUniform { // NOTE: These must match the bit flags in bevy_pbr/src/render/mesh_types.wgsl! bitflags::bitflags! { #[repr(transparent)] - struct MeshFlags: u32 { + pub struct MeshFlags: u32 { const SHADOW_RECEIVER = (1 << 0); // Indicates the sign of the determinant of the 3x3 model matrix. If the sign is positive, // then the flag should be set, else it should not be set. @@ -361,7 +337,12 @@ pub fn extract_skinned_meshes( SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut uniform.buffer) { last_start = last_start.max(skinned_joints.index as usize); - values.push((entity, skinned_joints.to_buffer_index())); + // NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per + // entity and so cannot currently be batched. + values.push(( + entity, + (skinned_joints.to_buffer_index(), NoAutomaticBatching), + )); } } @@ -374,63 +355,6 @@ pub fn extract_skinned_meshes( commands.insert_or_spawn_batch(values); } -#[allow(clippy::too_many_arguments)] -pub fn prepare_mesh_uniforms( - mut seen: Local, - mut commands: Commands, - mut previous_len: Local, - render_device: Res, - render_queue: Res, - mut gpu_array_buffer: ResMut>, - views: Query<( - &RenderPhase, - &RenderPhase, - &RenderPhase, - )>, - shadow_views: Query<&RenderPhase>, - meshes: Query<(Entity, &MeshTransforms)>, -) { - gpu_array_buffer.clear(); - seen.clear(); - - let mut indices = Vec::with_capacity(*previous_len); - let mut push_indices = |(mesh, mesh_uniform): (Entity, &MeshTransforms)| { - let index = mesh.index() as usize; - if !seen.contains(index) { - if index >= seen.len() { - seen.grow(index + 1); - } - seen.insert(index); - indices.push((mesh, gpu_array_buffer.push(mesh_uniform.into()))); - } - }; - - for (opaque_phase, transparent_phase, alpha_phase) in &views { - meshes - .iter_many(opaque_phase.iter_entities()) - .for_each(&mut push_indices); - - meshes - .iter_many(transparent_phase.iter_entities()) - .for_each(&mut push_indices); - - meshes - .iter_many(alpha_phase.iter_entities()) - .for_each(&mut push_indices); - } - - for shadow_phase in &shadow_views { - meshes - .iter_many(shadow_phase.iter_entities()) - .for_each(&mut push_indices); - } - - *previous_len = indices.len(); - commands.insert_or_spawn_batch(indices); - - gpu_array_buffer.write_buffer(&render_device, &render_queue); -} - #[derive(Resource, Clone)] pub struct MeshPipeline { pub view_layout: BindGroupLayout, @@ -713,6 +637,26 @@ impl MeshPipeline { } } +impl GetBatchData for MeshPipeline { + type Query = ( + Option<&'static MaterialBindGroupId>, + &'static Handle, + &'static MeshTransforms, + ); + type CompareData = (Option, AssetId); + type BufferData = MeshUniform; + + fn get_buffer_data(&(.., mesh_transforms): &QueryItem) -> Self::BufferData { + mesh_transforms.into() + } + + fn get_compare_data( + &(material_bind_group_id, mesh_handle, ..): &QueryItem, + ) -> Self::CompareData { + (material_bind_group_id.copied(), mesh_handle.id()) + } +} + bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] @@ -1364,16 +1308,15 @@ impl RenderCommand

for SetMeshBindGroup { type ViewWorldQuery = (); type ItemWorldQuery = ( Read>, - Read>, Option>, Option>, ); #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - (mesh, batch_indices, skin_index, morph_index): ROQueryItem, + (mesh, skin_index, morph_index): ROQueryItem, bind_groups: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { @@ -1391,20 +1334,20 @@ impl RenderCommand

for SetMeshBindGroup { }; let mut dynamic_offsets: [u32; 3] = Default::default(); - let mut index_count = 0; - if let Some(mesh_index) = batch_indices.dynamic_offset { - dynamic_offsets[index_count] = mesh_index; - index_count += 1; + let mut offset_count = 0; + if let Some(dynamic_offset) = item.dynamic_offset() { + dynamic_offsets[offset_count] = dynamic_offset.get(); + offset_count += 1; } if let Some(skin_index) = skin_index { - dynamic_offsets[index_count] = skin_index.index; - index_count += 1; + dynamic_offsets[offset_count] = skin_index.index; + offset_count += 1; } if let Some(morph_index) = morph_index { - dynamic_offsets[index_count] = morph_index.index; - index_count += 1; + dynamic_offsets[offset_count] = morph_index.index; + offset_count += 1; } - pass.set_bind_group(I, bind_group, &dynamic_offsets[0..index_count]); + pass.set_bind_group(I, bind_group, &dynamic_offsets[0..offset_count]); RenderCommandResult::Success } @@ -1414,22 +1357,23 @@ pub struct DrawMesh; impl RenderCommand

for DrawMesh { type Param = SRes>; type ViewWorldQuery = (); - type ItemWorldQuery = (Read>, Read>); + type ItemWorldQuery = Read>; #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - (batch_indices, mesh_handle): ROQueryItem<'_, Self::ItemWorldQuery>, + mesh_handle: ROQueryItem<'_, Self::ItemWorldQuery>, meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { if let Some(gpu_mesh) = meshes.into_inner().get(mesh_handle) { + let batch_range = item.batch_range(); pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); #[cfg(all(feature = "webgl", target_arch = "wasm32"))] pass.set_push_constants( ShaderStages::VERTEX, 0, - &(batch_indices.index as i32).to_le_bytes(), + &(batch_range.start as i32).to_le_bytes(), ); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { @@ -1438,13 +1382,10 @@ impl RenderCommand

for DrawMesh { count, } => { pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, batch_indices.index..batch_indices.index + 1); + pass.draw_indexed(0..*count, 0, batch_range.clone()); } GpuBufferInfo::NonIndexed => { - pass.draw( - 0..gpu_mesh.vertex_count, - batch_indices.index..batch_indices.index + 1, - ); + pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); } } RenderCommandResult::Success diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index 753c366726934..5b98de2ad84d9 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -2,6 +2,7 @@ use std::{iter, mem}; use bevy_ecs::prelude::*; use bevy_render::{ + batching::NoAutomaticBatching, mesh::morph::{MeshMorphWeights, MAX_MORPH_WEIGHTS}, render_resource::{BufferUsages, BufferVec}, renderer::{RenderDevice, RenderQueue}, @@ -89,7 +90,9 @@ pub fn extract_morphs( add_to_alignment::(&mut uniform.buffer); let index = (start * mem::size_of::()) as u32; - values.push((entity, MorphIndex { index })); + // NOTE: Because morph targets require per-morph target texture bindings, they cannot + // currently be batched. + values.push((entity, (MorphIndex { index }, NoAutomaticBatching))); } *previous_len = values.len(); commands.insert_or_spawn_batch(values); diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 128609071a398..b1be7a2ef5ccb 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -152,7 +152,8 @@ fn queue_wireframes( pipeline: pipeline_id, draw_function: draw_custom, distance: rangefinder.distance_translation(&mesh_transforms.transform.translation), - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); }; diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs new file mode 100644 index 0000000000000..715402b2b4b16 --- /dev/null +++ b/crates/bevy_render/src/batching/mod.rs @@ -0,0 +1,123 @@ +use bevy_ecs::{ + component::Component, + prelude::Res, + query::{Has, QueryItem, ReadOnlyWorldQuery}, + system::{Query, ResMut}, +}; +use bevy_utils::nonmax::NonMaxU32; + +use crate::{ + render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, RenderPhase}, + render_resource::{CachedRenderPipelineId, GpuArrayBuffer, GpuArrayBufferable}, + renderer::{RenderDevice, RenderQueue}, +}; + +/// Add this component to mesh entities to disable automatic batching +#[derive(Component)] +pub struct NoAutomaticBatching; + +/// Data necessary to be equal for two draw commands to be mergeable +/// +/// This is based on the following assumptions: +/// - Only entities with prepared assets (pipelines, materials, meshes) are +/// queued to phases +/// - View bindings are constant across a phase for a given draw function as +/// phases are per-view +/// - `batch_and_prepare_render_phase` is the only system that performs this +/// batching and has sole responsibility for preparing the per-object data. +/// As such the mesh binding and dynamic offsets are assumed to only be +/// variable as a result of the `batch_and_prepare_render_phase` 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. +#[derive(PartialEq)] +struct BatchMeta { + /// The pipeline id encompasses all pipeline configuration including vertex + /// buffers and layouts, shaders and their specializations, bind group + /// layouts, etc. + pipeline_id: CachedRenderPipelineId, + /// The draw function id defines the RenderCommands that are called to + /// set the pipeline and bindings, and make the draw command + draw_function_id: DrawFunctionId, + dynamic_offset: Option, + user_data: T, +} + +impl BatchMeta { + fn new(item: &impl CachedRenderPipelinePhaseItem, user_data: T) -> Self { + BatchMeta { + pipeline_id: item.cached_pipeline(), + draw_function_id: item.draw_function(), + dynamic_offset: item.dynamic_offset(), + user_data, + } + } +} + +/// A trait to support getting data used for batching draw commands via phase +/// items. +pub trait GetBatchData { + type Query: ReadOnlyWorldQuery; + /// Data used for comparison between phase items. If the pipeline id, draw + /// function id, per-instance data buffer dynamic offset and this data + /// matches, the draws can be batched. + type CompareData: PartialEq; + /// The per-instance data to be inserted into the [`GpuArrayBuffer`] + /// containing these data for all instances. + type BufferData: GpuArrayBufferable + Sync + Send + 'static; + /// Get the per-instance data to be inserted into the [`GpuArrayBuffer`]. + fn get_buffer_data(query_item: &QueryItem) -> Self::BufferData; + /// Get the data used for comparison when deciding whether draws can be + /// batched. + fn get_compare_data(query_item: &QueryItem) -> Self::CompareData; +} + +/// 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>, + mut views: Query<&mut RenderPhase>, + query: Query<(Has, F::Query)>, +) { + let gpu_array_buffer = gpu_array_buffer.into_inner(); + + let mut process_item = |item: &mut I| { + let (no_auto_batching, batch_query_item) = query.get(item.entity()).ok()?; + + let buffer_data = F::get_buffer_data(&batch_query_item); + let buffer_index = gpu_array_buffer.push(buffer_data); + + let index = buffer_index.index.get(); + *item.batch_range_mut() = index..index + 1; + *item.dynamic_offset_mut() = buffer_index.dynamic_offset; + + (!no_auto_batching).then(|| { + let compare_data = F::get_compare_data(&batch_query_item); + BatchMeta::new(item, compare_data) + }) + }; + + for mut phase in &mut views { + let items = phase.items.iter_mut().map(|item| { + let batch_data = process_item(item); + (item.batch_range_mut(), batch_data) + }); + items.reduce(|(start_range, prev_batch_meta), (range, batch_meta)| { + if batch_meta.is_some() && prev_batch_meta == batch_meta { + start_range.end = range.end; + (start_range, prev_batch_meta) + } else { + (range, batch_meta) + } + }); + } +} + +pub fn write_batched_instance_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_render/src/lib.rs b/crates/bevy_render/src/lib.rs index cc62f3fcead84..c72353df47fd6 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -5,6 +5,7 @@ compile_error!("bevy_render cannot compile for a 16-bit platform."); extern crate core; +pub mod batching; pub mod camera; pub mod color; pub mod extract_component; diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 54870cfc260b7..6230d2e1d9cfa 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -29,6 +29,7 @@ mod draw; mod draw_state; mod rangefinder; +use bevy_utils::nonmax::NonMaxU32; pub use draw::*; pub use draw_state::*; pub use rangefinder::*; @@ -38,7 +39,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; -use std::ops::Range; +use std::{ops::Range, slice::SliceIndex}; /// A collection of all rendering instructions, that will be executed by the GPU, for a /// single render phase for a single view. @@ -86,22 +87,7 @@ impl RenderPhase { world: &'w World, view: Entity, ) { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - draw_functions.prepare(world); - - let mut index = 0; - while index < self.items.len() { - let item = &self.items[index]; - let batch_size = item.batch_size(); - if batch_size > 0 { - let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); - draw_function.draw(world, render_pass, view, item); - index += batch_size; - } else { - index += 1; - } - } + self.render_range(render_pass, world, view, ..); } /// Renders all [`PhaseItem`]s in the provided `range` (based on their index in `self.items`) using their corresponding draw functions. @@ -110,27 +96,27 @@ impl RenderPhase { render_pass: &mut TrackedRenderPass<'w>, world: &'w World, view: Entity, - range: Range, + range: impl SliceIndex<[I], Output = [I]>, ) { - let draw_functions = world.resource::>(); - let mut draw_functions = draw_functions.write(); - draw_functions.prepare(world); - let items = self .items .get(range) .expect("`Range` provided to `render_range()` is out of bounds"); + let draw_functions = world.resource::>(); + let mut draw_functions = draw_functions.write(); + draw_functions.prepare(world); + let mut index = 0; while index < items.len() { let item = &items[index]; - let batch_size = item.batch_size(); - if batch_size > 0 { + let batch_range = item.batch_range(); + if batch_range.is_empty() { + index += 1; + } else { let draw_function = draw_functions.get_mut(item.draw_function()).unwrap(); draw_function.draw(world, render_pass, view, item); - index += batch_size; - } else { - index += 1; + index += batch_range.len(); } } } @@ -182,12 +168,14 @@ pub trait PhaseItem: Sized + Send + Sync + 'static { items.sort_unstable_by_key(|item| item.sort_key()); } - /// The number of items to skip after rendering this [`PhaseItem`]. - /// - /// Items with a `batch_size` of 0 will not be rendered. - fn batch_size(&self) -> usize { - 1 - } + /// The range of instances that the batch covers. After doing a batched draw, batch range + /// length phase items will be skipped. This design is to avoid having to restructure the + /// render phase unnecessarily. + fn batch_range(&self) -> &Range; + fn batch_range_mut(&mut self) -> &mut Range; + + fn dynamic_offset(&self) -> Option; + fn dynamic_offset_mut(&mut self) -> &mut Option; } /// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline, diff --git a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs index a9fba2ac7fb42..08c29a8664856 100644 --- a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs @@ -3,6 +3,7 @@ use crate::{ render_resource::DynamicUniformBuffer, renderer::{RenderDevice, RenderQueue}, }; +use bevy_utils::nonmax::NonMaxU32; use encase::{ private::{ArrayMetadata, BufferMut, Metadata, RuntimeSizedArray, WriteInto, Writer}, ShaderType, @@ -76,8 +77,8 @@ impl BatchedUniformBuffer { pub fn push(&mut self, component: T) -> GpuArrayBufferIndex { let result = GpuArrayBufferIndex { - index: self.temp.0.len() as u32, - dynamic_offset: Some(self.current_offset), + index: NonMaxU32::new(self.temp.0.len() as u32).unwrap(), + dynamic_offset: NonMaxU32::new(self.current_offset), element_type: PhantomData, }; self.temp.0.push(component); diff --git a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs index 45eaba4f73246..13694439ba5dc 100644 --- a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs +++ b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs @@ -4,6 +4,7 @@ use crate::{ renderer::{RenderDevice, RenderQueue}, }; use bevy_ecs::{prelude::Component, system::Resource}; +use bevy_utils::nonmax::NonMaxU32; use encase::{private::WriteInto, ShaderSize, ShaderType}; use std::{marker::PhantomData, mem}; use wgpu::{BindGroupLayoutEntry, BindingResource, BindingType, BufferBindingType, ShaderStages}; @@ -52,7 +53,7 @@ impl GpuArrayBuffer { match self { GpuArrayBuffer::Uniform(buffer) => buffer.push(value), GpuArrayBuffer::Storage((_, buffer)) => { - let index = buffer.len() as u32; + let index = NonMaxU32::new(buffer.len() as u32).unwrap(); buffer.push(value); GpuArrayBufferIndex { index, @@ -118,12 +119,12 @@ impl GpuArrayBuffer { } /// An index into a [`GpuArrayBuffer`] for a given element. -#[derive(Component)] +#[derive(Component, Clone)] pub struct GpuArrayBufferIndex { /// The index to use in a shader into the array. - pub index: u32, + pub index: NonMaxU32, /// The dynamic offset to use when setting the bind group in a pass. /// Only used on platforms that don't support storage buffers. - pub dynamic_offset: Option, + pub dynamic_offset: Option, pub element_type: PhantomData, } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 605115d2403f8..4b496c7242ac4 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -15,7 +15,6 @@ use bevy_ecs::{ }; use bevy_log::error; use bevy_render::{ - extract_component::ExtractComponentPlugin, mesh::{Mesh, MeshVertexBufferLayout}, prelude::Image, render_asset::{prepare_assets, RenderAssets}, @@ -24,9 +23,9 @@ use bevy_render::{ RenderPhase, SetItemPipeline, TrackedRenderPass, }, render_resource::{ - AsBindGroup, AsBindGroupError, BindGroup, BindGroupLayout, OwnedBindingResource, - PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, SpecializedMeshPipeline, - SpecializedMeshPipelineError, SpecializedMeshPipelines, + AsBindGroup, AsBindGroupError, BindGroup, BindGroupId, BindGroupLayout, + OwnedBindingResource, PipelineCache, RenderPipelineDescriptor, Shader, ShaderRef, + SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, texture::FallbackImage, @@ -39,8 +38,8 @@ use std::hash::Hash; use std::marker::PhantomData; use crate::{ - DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, SetMesh2dBindGroup, - SetMesh2dViewBindGroup, + DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dTransforms, + SetMesh2dBindGroup, SetMesh2dViewBindGroup, }; /// Materials are used alongside [`Material2dPlugin`] and [`MaterialMesh2dBundle`] @@ -144,8 +143,7 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { - app.init_asset::() - .add_plugins(ExtractComponentPlugin::>::extract_visible()); + app.init_asset::(); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -153,7 +151,10 @@ where .init_resource::>() .init_resource::>() .init_resource::>>() - .add_systems(ExtractSchedule, extract_materials_2d::) + .add_systems( + ExtractSchedule, + (extract_materials_2d::, extract_material_meshes_2d::), + ) .add_systems( Render, ( @@ -175,6 +176,26 @@ where } } +fn extract_material_meshes_2d( + mut commands: Commands, + mut previous_len: Local, + query: Extract)>>, +) { + let mut values = Vec::with_capacity(*previous_len); + for (entity, view_visibility, material) in &query { + if view_visibility.get() { + // NOTE: Material2dBindGroupId is inserted here to avoid a table move. Upcoming changes + // to use SparseSet for render world entity storage will do this automatically. + values.push(( + entity, + (material.clone_weak(), Material2dBindGroupId::default()), + )); + } + } + *previous_len = values.len(); + commands.insert_or_spawn_batch(values); +} + /// Render pipeline data for a given [`Material2d`] #[derive(Resource)] pub struct Material2dPipeline { @@ -343,7 +364,12 @@ pub fn queue_material2d_meshes( msaa: Res, render_meshes: Res>, render_materials: Res>, - material2d_meshes: Query<(&Handle, &Mesh2dHandle, &Mesh2dUniform)>, + mut material2d_meshes: Query<( + &Handle, + &mut Material2dBindGroupId, + &Mesh2dHandle, + &Mesh2dTransforms, + )>, mut views: Query<( &ExtractedView, &VisibleEntities, @@ -374,8 +400,12 @@ pub fn queue_material2d_meshes( } } for visible_entity in &visible_entities.entities { - let Ok((material2d_handle, mesh2d_handle, mesh2d_uniform)) = - material2d_meshes.get(*visible_entity) + let Ok(( + material2d_handle, + mut material2d_bind_group_id, + mesh2d_handle, + mesh2d_uniform, + )) = material2d_meshes.get_mut(*visible_entity) else { continue; }; @@ -406,7 +436,8 @@ pub fn queue_material2d_meshes( } }; - let mesh_z = mesh2d_uniform.transform.w_axis.z; + *material2d_bind_group_id = material2d.get_bind_group_id(); + let mesh_z = mesh2d_uniform.transform.translation.z; transparent_phase.add(Transparent2d { entity: *visible_entity, draw_function: draw_transparent_pbr, @@ -416,13 +447,17 @@ pub fn queue_material2d_meshes( // -z in front of the camera, the largest distance is -far with values increasing toward the // camera. As such we can just use mesh_z as the distance sort_key: FloatOrd(mesh_z), - // This material is not batched - batch_size: 1, + // Batching is done in batch_and_prepare_render_phase + batch_range: 0..1, + dynamic_offset: None, }); } } } +#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Deref, DerefMut)] +pub struct Material2dBindGroupId(Option); + /// Data prepared for a [`Material2d`] instance. pub struct PreparedMaterial2d { pub bindings: Vec, @@ -430,6 +465,12 @@ pub struct PreparedMaterial2d { pub key: T::Data, } +impl PreparedMaterial2d { + pub fn get_bind_group_id(&self) -> Material2dBindGroupId { + Material2dBindGroupId(Some(self.bind_group.id())) + } +} + #[derive(Resource)] pub struct ExtractedMaterials2d { extracted: Vec<(AssetId, M)>, diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 09b69b296a664..2717acd394d4e 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,15 +1,16 @@ 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::{ prelude::*, - query::ROQueryItem, + query::{QueryItem, ROQueryItem}, system::{lifetimeless::*, SystemParamItem, SystemState}, }; -use bevy_math::{Mat4, Vec2}; +use bevy_math::{Affine3, Vec2, Vec4}; use bevy_reflect::Reflect; use bevy_render::{ - extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, + batching::{batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData}, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, @@ -26,10 +27,12 @@ use bevy_render::{ }; use bevy_transform::components::GlobalTransform; +use crate::Material2dBindGroupId; + /// Component for rendering with meshes in the 2d pipeline, usually with a [2d material](crate::Material2d) such as [`ColorMaterial`](crate::ColorMaterial). /// /// It wraps a [`Handle`] to differentiate from the 3d pipelines which use the handles directly as components -#[derive(Default, Clone, Component, Debug, Reflect)] +#[derive(Default, Clone, Component, Debug, Reflect, PartialEq, Eq)] #[reflect(Component)] pub struct Mesh2dHandle(pub Handle); @@ -76,12 +79,6 @@ impl Plugin for Mesh2dRenderPlugin { "mesh2d_types.wgsl", Shader::from_wgsl ); - load_internal_asset!( - app, - MESH2D_BINDINGS_HANDLE, - "mesh2d_bindings.wgsl", - Shader::from_wgsl - ); load_internal_asset!( app, MESH2D_FUNCTIONS_HANDLE, @@ -90,8 +87,6 @@ impl Plugin for Mesh2dRenderPlugin { ); load_internal_asset!(app, MESH2D_SHADER_HANDLE, "mesh2d.wgsl", Shader::from_wgsl); - app.add_plugins(UniformComponentPlugin::::default()); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() @@ -99,6 +94,10 @@ impl Plugin for Mesh2dRenderPlugin { .add_systems( Render, ( + batch_and_prepare_render_phase:: + .in_set(RenderSet::PrepareResources), + write_batched_instance_buffer:: + .in_set(RenderSet::PrepareResourcesFlush), prepare_mesh2d_bind_group.in_set(RenderSet::PrepareBindGroups), prepare_mesh2d_view_bind_groups.in_set(RenderSet::PrepareBindGroups), ), @@ -107,19 +106,69 @@ impl Plugin for Mesh2dRenderPlugin { } fn finish(&self, app: &mut bevy_app::App) { + let mut mesh_bindings_shader_defs = Vec::with_capacity(1); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::(); + if let Some(per_object_buffer_batch_size) = GpuArrayBuffer::::batch_size( + render_app.world.resource::(), + ) { + mesh_bindings_shader_defs.push(ShaderDefVal::UInt( + "PER_OBJECT_BUFFER_BATCH_SIZE".into(), + per_object_buffer_batch_size, + )); + } + + render_app + .insert_resource(GpuArrayBuffer::::new( + render_app.world.resource::(), + )) + .init_resource::(); } + + // Load the mesh_bindings shader module here as it depends on runtime information about + // whether storage buffers are supported, or the maximum uniform buffer binding size. + load_internal_asset!( + app, + MESH2D_BINDINGS_HANDLE, + "mesh2d_bindings.wgsl", + Shader::from_wgsl_with_defs, + mesh_bindings_shader_defs + ); } } -#[derive(Component, ShaderType, Clone)] +#[derive(Component)] +pub struct Mesh2dTransforms { + pub transform: Affine3, + pub flags: u32, +} + +#[derive(ShaderType, Clone)] pub struct Mesh2dUniform { - pub transform: Mat4, - pub inverse_transpose_model: Mat4, + // Affine 4x3 matrix transposed to 3x4 + pub transform: [Vec4; 3], + // 3x3 matrix packed in mat2x4 and f32 as: + // [0].xyz, [1].x, + // [1].yz, [2].xy + // [2].z + pub inverse_transpose_model_a: [Vec4; 2], + pub inverse_transpose_model_b: f32, pub flags: u32, } +impl From<&Mesh2dTransforms> for Mesh2dUniform { + fn from(mesh_transforms: &Mesh2dTransforms) -> Self { + let (inverse_transpose_model_a, inverse_transpose_model_b) = + mesh_transforms.transform.inverse_transpose_3x3(); + Self { + transform: mesh_transforms.transform.to_transpose(), + inverse_transpose_model_a, + inverse_transpose_model_b, + flags: mesh_transforms.flags, + } + } +} + // NOTE: These must match the bit flags in bevy_sprite/src/mesh2d/mesh2d.wgsl! bitflags::bitflags! { #[repr(transparent)] @@ -139,15 +188,13 @@ pub fn extract_mesh2d( if !view_visibility.get() { continue; } - let transform = transform.compute_matrix(); values.push(( entity, ( Mesh2dHandle(handle.0.clone_weak()), - Mesh2dUniform { + Mesh2dTransforms { + transform: (&transform.affine()).into(), flags: MeshFlags::empty().bits(), - transform, - inverse_transpose_model: transform.inverse().transpose(), }, ), )); @@ -162,13 +209,18 @@ pub struct Mesh2dPipeline { pub mesh_layout: BindGroupLayout, // This dummy white texture is to be used in place of optional textures pub dummy_white_gpu_image: GpuImage, + pub per_object_buffer_batch_size: Option, } impl FromWorld for Mesh2dPipeline { fn from_world(world: &mut World) -> Self { - let mut system_state: SystemState<(Res, Res)> = - SystemState::new(world); - let (render_device, default_sampler) = system_state.get_mut(world); + let mut system_state: SystemState<( + Res, + Res, + Res, + )> = SystemState::new(world); + let (render_device, render_queue, default_sampler) = system_state.get_mut(world); + let render_device = render_device.into_inner(); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View @@ -197,16 +249,11 @@ impl FromWorld for Mesh2dPipeline { }); let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: Some(Mesh2dUniform::min_size()), - }, - count: None, - }], + entries: &[GpuArrayBuffer::::binding_layout( + 0, + ShaderStages::VERTEX_FRAGMENT, + render_device, + )], label: Some("mesh2d_layout"), }); // A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures @@ -219,7 +266,6 @@ impl FromWorld for Mesh2dPipeline { }; let format_size = image.texture_descriptor.format.pixel_size(); - let render_queue = world.resource_mut::(); render_queue.write_texture( ImageCopyTexture { texture: &texture, @@ -253,6 +299,9 @@ impl FromWorld for Mesh2dPipeline { view_layout, mesh_layout, dummy_white_gpu_image, + per_object_buffer_batch_size: GpuArrayBuffer::::batch_size( + render_device, + ), } } } @@ -275,6 +324,26 @@ impl Mesh2dPipeline { } } +impl GetBatchData for Mesh2dPipeline { + type Query = ( + Option<&'static Material2dBindGroupId>, + &'static Mesh2dHandle, + &'static Mesh2dTransforms, + ); + type CompareData = (Option, AssetId); + type BufferData = Mesh2dUniform; + + fn get_buffer_data(&(.., mesh_transforms): &QueryItem) -> Self::BufferData { + mesh_transforms.into() + } + + fn get_compare_data( + &(material_bind_group_id, mesh_handle, ..): &QueryItem, + ) -> Self::CompareData { + (material_bind_group_id.copied(), mesh_handle.0.id()) + } +} + bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] @@ -477,9 +546,9 @@ pub fn prepare_mesh2d_bind_group( mut commands: Commands, mesh2d_pipeline: Res, render_device: Res, - mesh2d_uniforms: Res>, + mesh2d_uniforms: Res>, ) { - if let Some(binding) = mesh2d_uniforms.uniforms().binding() { + if let Some(binding) = mesh2d_uniforms.binding() { commands.insert_resource(Mesh2dBindGroup { value: render_device.create_bind_group(&BindGroupDescriptor { entries: &[BindGroupEntry { @@ -557,20 +626,26 @@ pub struct SetMesh2dBindGroup; impl RenderCommand

for SetMesh2dBindGroup { type Param = SRes; type ViewWorldQuery = (); - type ItemWorldQuery = Read>; + type ItemWorldQuery = (); #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), - mesh2d_index: &'_ DynamicUniformIndex, + _item_query: (), mesh2d_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { + let mut dynamic_offsets: [u32; 1] = Default::default(); + let mut offset_count = 0; + if let Some(dynamic_offset) = item.dynamic_offset() { + dynamic_offsets[offset_count] = dynamic_offset.get(); + offset_count += 1; + } pass.set_bind_group( I, &mesh2d_bind_group.into_inner().value, - &[mesh2d_index.index()], + &dynamic_offsets[..offset_count], ); RenderCommandResult::Success } @@ -584,14 +659,21 @@ impl RenderCommand

for DrawMesh2d { #[inline] fn render<'w>( - _item: &P, + item: &P, _view: (), mesh_handle: ROQueryItem<'w, Self::ItemWorldQuery>, meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { + let batch_range = item.batch_range(); if let Some(gpu_mesh) = meshes.into_inner().get(&mesh_handle.0) { pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); + #[cfg(all(feature = "webgl", target_arch = "wasm32"))] + pass.set_push_constants( + ShaderStages::VERTEX, + 0, + &(batch_range.start as i32).to_le_bytes(), + ); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { buffer, @@ -599,10 +681,10 @@ impl RenderCommand

for DrawMesh2d { count, } => { pass.set_index_buffer(buffer.slice(..), 0, *index_format); - pass.draw_indexed(0..*count, 0, 0..1); + pass.draw_indexed(0..*count, 0, batch_range.clone()); } GpuBufferInfo::NonIndexed => { - pass.draw(0..gpu_mesh.vertex_count, 0..1); + pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); } } RenderCommandResult::Success diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl index 2b99639836d31..003f7dda13af9 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d.wgsl @@ -8,6 +8,7 @@ #endif struct Vertex { + @builtin(instance_index) instance_index: u32, #ifdef VERTEX_POSITIONS @location(0) position: vec3, #endif @@ -33,20 +34,21 @@ fn vertex(vertex: Vertex) -> MeshVertexOutput { #endif #ifdef VERTEX_POSITIONS + var model = mesh_functions::get_model_matrix(vertex.instance_index); out.world_position = mesh_functions::mesh2d_position_local_to_world( - mesh.model, + model, vec4(vertex.position, 1.0) ); out.position = mesh_functions::mesh2d_position_world_to_clip(out.world_position); #endif #ifdef VERTEX_NORMALS - out.world_normal = mesh_functions::mesh2d_normal_local_to_world(vertex.normal); + out.world_normal = mesh_functions::mesh2d_normal_local_to_world(vertex.normal, vertex.instance_index); #endif #ifdef VERTEX_TANGENTS out.world_tangent = mesh_functions::mesh2d_tangent_local_to_world( - mesh.model, + model, vertex.tangent ); #endif diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl index 521ccfa846e43..e673ef23f06b6 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_bindings.wgsl @@ -1,5 +1,21 @@ #define_import_path bevy_sprite::mesh2d_bindings -#import bevy_sprite::mesh2d_types +#import bevy_sprite::mesh2d_types Mesh2d -@group(2) @binding(0) var mesh: bevy_sprite::mesh2d_types::Mesh2d; +#ifdef MESH_BINDGROUP_1 + +#ifdef PER_OBJECT_BUFFER_BATCH_SIZE +@group(1) @binding(0) var mesh: array; +#else +@group(1) @binding(0) var mesh: array; +#endif // PER_OBJECT_BUFFER_BATCH_SIZE + +#else // MESH_BINDGROUP_1 + +#ifdef PER_OBJECT_BUFFER_BATCH_SIZE +@group(2) @binding(0) var mesh: array; +#else +@group(2) @binding(0) var mesh: array; +#endif // PER_OBJECT_BUFFER_BATCH_SIZE + +#endif // MESH_BINDGROUP_1 diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl index cf8d6e2522068..b936cad10f66f 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_functions.wgsl @@ -2,6 +2,12 @@ #import bevy_sprite::mesh2d_view_bindings view #import bevy_sprite::mesh2d_bindings mesh +#import bevy_render::instance_index get_instance_index +#import bevy_render::maths affine_to_square, mat2x4_f32_to_mat3x3_unpack + +fn get_model_matrix(instance_index: u32) -> mat4x4 { + return affine_to_square(mesh[get_instance_index(instance_index)].model); +} fn mesh2d_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { return model * vertex_position; @@ -19,11 +25,10 @@ fn mesh2d_position_local_to_clip(model: mat4x4, vertex_position: vec4) return mesh2d_position_world_to_clip(world_position); } -fn mesh2d_normal_local_to_world(vertex_normal: vec3) -> vec3 { - return mat3x3( - mesh.inverse_transpose_model[0].xyz, - mesh.inverse_transpose_model[1].xyz, - mesh.inverse_transpose_model[2].xyz +fn mesh2d_normal_local_to_world(vertex_normal: vec3, instance_index: u32) -> vec3 { + return mat2x4_f32_to_mat3x3_unpack( + mesh[instance_index].inverse_transpose_model_a, + mesh[instance_index].inverse_transpose_model_b, ) * vertex_normal; } diff --git a/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl b/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl index 1de0218112a47..f855707790001 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl +++ b/crates/bevy_sprite/src/mesh2d/mesh2d_types.wgsl @@ -1,8 +1,16 @@ #define_import_path bevy_sprite::mesh2d_types struct Mesh2d { - model: mat4x4, - inverse_transpose_model: mat4x4, + // Affine 4x3 matrix transposed to 3x4 + // Use bevy_render::maths::affine_to_square to unpack + model: mat3x4, + // 3x3 matrix packed in mat2x4 and f32 as: + // [0].xyz, [1].x, + // [1].yz, [2].xy + // [2].z + // Use bevy_render::maths::mat2x4_f32_to_mat3x3_unpack to unpack + inverse_transpose_model_a: mat2x4, + inverse_transpose_model_b: f32, // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. flags: u32, }; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index a0a16ea612796..2d5343a867adc 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -565,8 +565,9 @@ pub fn queue_sprites( pipeline: colored_pipeline, entity: *entity, sort_key, - // batch_size will be calculated in prepare_sprites - batch_size: 0, + // batch_range and dynamic_offset will be calculated in prepare_sprites + batch_range: 0..0, + dynamic_offset: None, }); } else { transparent_phase.add(Transparent2d { @@ -574,8 +575,9 @@ pub fn queue_sprites( pipeline, entity: *entity, sort_key, - // batch_size will be calculated in prepare_sprites - batch_size: 0, + // batch_range and dynamic_offset will be calculated in prepare_sprites + batch_range: 0..0, + dynamic_offset: None, }); } } @@ -739,7 +741,9 @@ pub fn prepare_sprites( )); } - transparent_phase.items[batch_item_index].batch_size += 1; + transparent_phase.items[batch_item_index] + .batch_range_mut() + .end += 1; batches.last_mut().unwrap().1.range.end += 1; index += 1; } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index c4195a6f41200..d146e53beb79d 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -4,6 +4,7 @@ mod render_pass; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_ecs::storage::SparseSet; use bevy_hierarchy::Parent; +use bevy_render::render_phase::PhaseItem; use bevy_render::view::ViewVisibility; use bevy_render::{ExtractSchedule, Render}; use bevy_window::{PrimaryWindow, Window}; @@ -665,8 +666,9 @@ pub fn queue_uinodes( pipeline, entity: *entity, sort_key: FloatOrd(extracted_uinode.stack_index as f32), - // batch_size will be calculated in prepare_uinodes - batch_size: 0, + // batch_range will be calculated in prepare_uinodes + batch_range: 0..0, + dynamic_offset: None, }); } } @@ -892,7 +894,7 @@ pub fn prepare_uinodes( } index += QUAD_INDICES.len() as u32; existing_batch.unwrap().1.range.end = index; - ui_phase.items[batch_item_index].batch_size += 1; + ui_phase.items[batch_item_index].batch_range_mut().end += 1; } else { batch_image_handle = AssetId::invalid(); } diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 1eb3836b8c634..f483c8cf0bd90 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + use super::{UiBatch, UiImageBindGroups, UiMeta}; use crate::{prelude::UiCameraConfig, DefaultCameraView}; use bevy_ecs::{ @@ -11,7 +13,7 @@ use bevy_render::{ renderer::*, view::*, }; -use bevy_utils::FloatOrd; +use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; pub struct UiPassNode { ui_view_query: QueryState< @@ -90,7 +92,8 @@ pub struct TransparentUi { pub entity: Entity, pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, - pub batch_size: usize, + pub batch_range: Range, + pub dynamic_offset: Option, } impl PhaseItem for TransparentUi { @@ -117,8 +120,23 @@ impl PhaseItem for TransparentUi { } #[inline] - fn batch_size(&self) -> usize { - self.batch_size + fn batch_range(&self) -> &Range { + &self.batch_range + } + + #[inline] + fn batch_range_mut(&mut self) -> &mut Range { + &mut self.batch_range + } + + #[inline] + fn dynamic_offset(&self) -> Option { + self.dynamic_offset + } + + #[inline] + fn dynamic_offset_mut(&mut self) -> &mut Option { + &mut self.dynamic_offset } } diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index ba887e8057220..20a4cb32bb99c 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -20,6 +20,7 @@ hashbrown = { version = "0.14", features = ["serde"] } bevy_utils_proc_macros = {version = "0.12.0-dev", path = "macros"} petgraph = "0.6" thiserror = "1.0" +nonmax = "0.5" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = {version = "0.2.0", features = ["js"]} diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 7916caf769476..52f33f31d7dc6 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -34,6 +34,11 @@ pub use thiserror; pub use tracing; pub use uuid::Uuid; +#[allow(missing_docs)] +pub mod nonmax { + pub use nonmax::*; +} + use hashbrown::hash_map::RawEntryMut; use std::{ fmt::Debug, diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 780b56f9920fe..f1047a9fb88d4 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -21,7 +21,7 @@ use bevy::{ Extract, Render, RenderApp, RenderSet, }, sprite::{ - DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dUniform, + DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dTransforms, SetMesh2dBindGroup, SetMesh2dViewBindGroup, }, utils::FloatOrd, @@ -148,19 +148,24 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline { false => TextureFormat::bevy_default(), }; + // Meshes typically live in bind group 2. Because we are using bind group 1 + // we need to add the MESH_BINDGROUP_1 shader def so that the bindings are correctly + // linked in the shader. + let shader_defs = vec!["MESH_BINDGROUP_1".into()]; + RenderPipelineDescriptor { vertex: VertexState { // Use our custom shader shader: COLORED_MESH2D_SHADER_HANDLE, entry_point: "vertex".into(), - shader_defs: Vec::new(), + shader_defs: shader_defs.clone(), // Use our custom vertex buffer buffers: vec![vertex_layout], }, fragment: Some(FragmentState { // Use our custom shader shader: COLORED_MESH2D_SHADER_HANDLE, - shader_defs: Vec::new(), + shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { format, @@ -212,13 +217,12 @@ type DrawColoredMesh2d = ( // using `include_str!()`, or loaded like any other asset with `asset_server.load()`. const COLORED_MESH2D_SHADER: &str = r" // Import the standard 2d mesh uniforms and set their bind groups -#import bevy_sprite::mesh2d_types as MeshTypes +#import bevy_sprite::mesh2d_bindings mesh #import bevy_sprite::mesh2d_functions as MeshFunctions -@group(1) @binding(0) var mesh: MeshTypes::Mesh2d; - // The structure of the vertex buffer is as specified in `specialize()` struct Vertex { + @builtin(instance_index) instance_index: u32, @location(0) position: vec3, @location(1) color: u32, }; @@ -235,7 +239,8 @@ struct VertexOutput { fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; // Project the world position of the mesh into screen position - out.clip_position = MeshFunctions::mesh2d_position_local_to_clip(mesh.model, vec4(vertex.position, 1.0)); + let model = MeshFunctions::get_model_matrix(vertex.instance_index); + out.clip_position = MeshFunctions::mesh2d_position_local_to_clip(model, vec4(vertex.position, 1.0)); // Unpack the `u32` from the vertex buffer into the `vec4` used by the fragment shader out.color = vec4((vec4(vertex.color) >> vec4(0u, 8u, 16u, 24u)) & vec4(255u)) / 255.0; return out; @@ -315,7 +320,7 @@ pub fn queue_colored_mesh2d( pipeline_cache: Res, msaa: Res, render_meshes: Res>, - colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dUniform), With>, + colored_mesh2d: Query<(&Mesh2dHandle, &Mesh2dTransforms), With>, mut views: Query<( &VisibleEntities, &mut RenderPhase, @@ -334,7 +339,7 @@ pub fn queue_colored_mesh2d( // Queue all entities visible to that view for visible_entity in &visible_entities.entities { - if let Ok((mesh2d_handle, mesh2d_uniform)) = colored_mesh2d.get(*visible_entity) { + if let Ok((mesh2d_handle, mesh2d_transforms)) = colored_mesh2d.get(*visible_entity) { // Get our specialized pipeline let mut mesh2d_key = mesh_key; if let Some(mesh) = render_meshes.get(&mesh2d_handle.0) { @@ -345,7 +350,7 @@ pub fn queue_colored_mesh2d( let pipeline_id = pipelines.specialize(&pipeline_cache, &colored_mesh2d_pipeline, mesh2d_key); - let mesh_z = mesh2d_uniform.transform.w_axis.z; + let mesh_z = mesh2d_transforms.transform.translation.z; transparent_phase.add(Transparent2d { entity: *visible_entity, draw_function: draw_colored_mesh2d, @@ -354,7 +359,8 @@ pub fn queue_colored_mesh2d( // in order to get correct transparency sort_key: FloatOrd(mesh_z), // This material is not batched - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } } diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index 326183d917a0b..d5e751ae0fa1d 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -136,7 +136,8 @@ fn queue_custom( draw_function: draw_custom, distance: rangefinder .distance_translation(&mesh_transforms.transform.translation), - batch_size: 1, + batch_range: 0..1, + dynamic_offset: None, }); } }