From dd812b3e49bdd0fe4e69d540a819cd72bb07fbdf Mon Sep 17 00:00:00 2001 From: charlotte Date: Thu, 10 Oct 2024 11:47:04 -0700 Subject: [PATCH] Type safe retained render world (#15756) # Objective In the Render World, there are a number of collections that are derived from Main World entities and are used to drive rendering. The most notable are: - `VisibleEntities`, which is generated in the `check_visibility` system and contains visible entities for a view. - `ExtractedInstances`, which maps entity ids to asset ids. In the old model, these collections were trivially kept in sync -- any extracted phase item could look itself up because the render entity id was guaranteed to always match the corresponding main world id. After #15320, this became much more complicated, and was leading to a number of subtle bugs in the Render World. The main rendering systems, i.e. `queue_material_meshes` and `queue_material2d_meshes`, follow a similar pattern: ```rust for visible_entity in visible_entities.iter::>() { let Some(mesh_instance) = render_mesh_instances.get_mut(visible_entity) else { continue; }; // Look some more stuff up and specialize the pipeline... let bin_key = Opaque2dBinKey { pipeline: pipeline_id, draw_function: draw_opaque_2d, asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material_2d.get_bind_group_id().0, }; opaque_phase.add( bin_key, *visible_entity, BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching), ); } ``` In this case, `visible_entities` and `render_mesh_instances` are both collections that are created and keyed by Main World entity ids, and so this lookup happens to work by coincidence. However, there is a major unintentional bug here: namely, because `visible_entities` is a collection of Main World ids, the phase item being queued is created with a Main World id rather than its correct Render World id. This happens to not break mesh rendering because the render commands used for drawing meshes do not access the `ItemQuery` parameter, but demonstrates the confusion that is now possible: our UI phase items are correctly being queued with Render World ids while our meshes aren't. Additionally, this makes it very easy and error prone to use the wrong entity id to look up things like assets. For example, if instead we ignored visibility checks and queued our meshes via a query, we'd have to be extra careful to use `&MainEntity` instead of the natural `Entity`. ## Solution Make all collections that are derived from Main World data use `MainEntity` as their key, to ensure type safety and avoid accidentally looking up data with the wrong entity id: ```rust pub type MainEntityHashMap = hashbrown::HashMap; ``` Additionally, we make all `PhaseItem` be able to provide both their Main and Render World ids, to allow render phase implementors maximum flexibility as to what id should be used to look up data. You can think of this like tracking at the type level whether something in the Render World should use it's "primary key", i.e. entity id, or needs to use a foreign key, i.e. `MainEntity`. ## Testing ##### TODO: This will require extensive testing to make sure things didn't break! Additionally, some extraction logic has become more complicated and needs to be checked for regressions. ## Migration Guide With the advent of the retained render world, collections that contain references to `Entity` that are extracted into the render world have been changed to contain `MainEntity` in order to prevent errors where a render world entity id is used to look up an item by accident. Custom rendering code may need to be changed to query for `&MainEntity` in order to look up the correct item from such a collection. Additionally, users who implement their own extraction logic for collections of main world entity should strongly consider extracting into a different collection that uses `MainEntity` as a key. Additionally, render phases now require specifying both the `Entity` and `MainEntity` for a given `PhaseItem`. Custom render phases should ensure `MainEntity` is available when queuing a phase item. --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 34 ++++--- crates/bevy_core_pipeline/src/core_3d/mod.rs | 39 +++++--- crates/bevy_core_pipeline/src/deferred/mod.rs | 25 ++++-- crates/bevy_core_pipeline/src/prepass/mod.rs | 25 ++++-- crates/bevy_gizmos/src/pipeline_2d.rs | 13 +-- crates/bevy_gizmos/src/pipeline_3d.rs | 13 +-- crates/bevy_pbr/src/bundle.rs | 41 +++++++++ crates/bevy_pbr/src/lightmap/mod.rs | 9 +- crates/bevy_pbr/src/material.rs | 30 +++---- .../bevy_pbr/src/meshlet/instance_manager.rs | 7 +- crates/bevy_pbr/src/prepass/mod.rs | 15 ++-- crates/bevy_pbr/src/render/light.rs | 80 ++++++++++++----- crates/bevy_pbr/src/render/mesh.rs | 89 ++++++++++--------- crates/bevy_pbr/src/render/morph.rs | 11 ++- crates/bevy_pbr/src/render/skin.rs | 11 ++- .../src/batching/gpu_preprocessing.rs | 33 ++++--- crates/bevy_render/src/batching/mod.rs | 14 +-- .../src/batching/no_gpu_preprocessing.rs | 9 +- crates/bevy_render/src/camera/camera.rs | 30 ++++++- crates/bevy_render/src/extract_instances.rs | 8 +- crates/bevy_render/src/render_phase/mod.rs | 22 +++-- crates/bevy_render/src/sync_world.rs | 22 ++++- crates/bevy_render/src/view/visibility/mod.rs | 51 ++++++++++- .../bevy_render/src/view/visibility/range.rs | 14 +-- crates/bevy_sprite/src/mesh2d/material.rs | 33 +++---- crates/bevy_sprite/src/mesh2d/mesh.rs | 27 +++--- crates/bevy_sprite/src/render/mod.rs | 29 +++--- crates/bevy_text/src/text2d.rs | 5 +- crates/bevy_ui/src/render/box_shadow.rs | 22 +++-- crates/bevy_ui/src/render/mod.rs | 25 ++++-- crates/bevy_ui/src/render/render_pass.rs | 9 +- .../src/render/ui_material_pipeline.rs | 15 ++-- .../src/render/ui_texture_slice_pipeline.rs | 26 ++++-- examples/2d/mesh2d_manual.rs | 14 +-- examples/shader/custom_phase_item.rs | 4 +- examples/shader/custom_shader_instancing.rs | 12 +-- examples/shader/specialized_mesh_pipeline.rs | 8 +- 37 files changed, 584 insertions(+), 290 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index c621ebc7fd215..e40bad7239c2c 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -38,9 +38,11 @@ pub use camera_2d::*; pub use main_opaque_pass_2d_node::*; pub use main_transparent_pass_2d_node::*; +use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode}; use bevy_app::{App, Plugin}; use bevy_ecs::{entity::EntityHashSet, prelude::*}; use bevy_math::FloatOrd; +use bevy_render::sync_world::MainEntity; use bevy_render::{ camera::{Camera, ExtractedCamera}, extract_component::ExtractComponentPlugin, @@ -61,8 +63,6 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode}; - use self::graph::{Core2d, Node2d}; pub const CORE_2D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float; @@ -129,7 +129,7 @@ pub struct Opaque2d { pub key: Opaque2dBinKey, /// An entity from which data will be fetched, including the mesh if /// applicable. - pub representative_entity: Entity, + pub representative_entity: (Entity, MainEntity), /// The ranges of instances. pub batch_range: Range, /// An extra index, which is either a dynamic offset or an index in the @@ -156,7 +156,11 @@ pub struct Opaque2dBinKey { impl PhaseItem for Opaque2d { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -188,7 +192,7 @@ impl BinnedPhaseItem for Opaque2d { fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { @@ -214,7 +218,7 @@ pub struct AlphaMask2d { pub key: AlphaMask2dBinKey, /// An entity from which data will be fetched, including the mesh if /// applicable. - pub representative_entity: Entity, + pub representative_entity: (Entity, MainEntity), /// The ranges of instances. pub batch_range: Range, /// An extra index, which is either a dynamic offset or an index in the @@ -241,7 +245,12 @@ pub struct AlphaMask2dBinKey { impl PhaseItem for AlphaMask2d { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + #[inline] + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -273,7 +282,7 @@ impl BinnedPhaseItem for AlphaMask2d { fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { @@ -296,7 +305,7 @@ impl CachedRenderPipelinePhaseItem for AlphaMask2d { /// Transparent 2D [`SortedPhaseItem`]s. pub struct Transparent2d { pub sort_key: FloatOrd, - pub entity: Entity, + pub entity: (Entity, MainEntity), pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, pub batch_range: Range, @@ -306,7 +315,12 @@ pub struct Transparent2d { impl PhaseItem for Transparent2d { #[inline] fn entity(&self) -> Entity { - self.entity + self.entity.0 + } + + #[inline] + fn main_entity(&self) -> MainEntity { + self.entity.1 } #[inline] diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 5389e5c9bdf13..559e757d71d51 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -74,6 +74,7 @@ pub use main_transparent_pass_3d_node::*; use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::{entity::EntityHashSet, prelude::*}; use bevy_math::FloatOrd; +use bevy_render::sync_world::MainEntity; use bevy_render::{ camera::{Camera, ExtractedCamera}, extract_component::ExtractComponentPlugin, @@ -214,7 +215,7 @@ pub struct Opaque3d { pub key: Opaque3dBinKey, /// An entity from which data will be fetched, including the mesh if /// applicable. - pub representative_entity: Entity, + pub representative_entity: (Entity, MainEntity), /// The ranges of instances. pub batch_range: Range, /// An extra index, which is either a dynamic offset or an index in the @@ -249,7 +250,12 @@ pub struct Opaque3dBinKey { impl PhaseItem for Opaque3d { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + #[inline] + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -282,7 +288,7 @@ impl BinnedPhaseItem for Opaque3d { #[inline] fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { @@ -304,7 +310,7 @@ impl CachedRenderPipelinePhaseItem for Opaque3d { pub struct AlphaMask3d { pub key: OpaqueNoLightmap3dBinKey, - pub representative_entity: Entity, + pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, } @@ -312,7 +318,11 @@ pub struct AlphaMask3d { impl PhaseItem for AlphaMask3d { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -347,7 +357,7 @@ impl BinnedPhaseItem for AlphaMask3d { #[inline] fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { @@ -370,7 +380,7 @@ impl CachedRenderPipelinePhaseItem for AlphaMask3d { pub struct Transmissive3d { pub distance: f32, pub pipeline: CachedRenderPipelineId, - pub entity: Entity, + pub entity: (Entity, MainEntity), pub draw_function: DrawFunctionId, pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, @@ -390,7 +400,12 @@ impl PhaseItem for Transmissive3d { #[inline] fn entity(&self) -> Entity { - self.entity + self.entity.0 + } + + #[inline] + fn main_entity(&self) -> MainEntity { + self.entity.1 } #[inline] @@ -444,7 +459,7 @@ impl CachedRenderPipelinePhaseItem for Transmissive3d { pub struct Transparent3d { pub distance: f32, pub pipeline: CachedRenderPipelineId, - pub entity: Entity, + pub entity: (Entity, MainEntity), pub draw_function: DrawFunctionId, pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, @@ -453,7 +468,11 @@ pub struct Transparent3d { impl PhaseItem for Transparent3d { #[inline] fn entity(&self) -> Entity { - self.entity + self.entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.entity.1 } #[inline] diff --git a/crates/bevy_core_pipeline/src/deferred/mod.rs b/crates/bevy_core_pipeline/src/deferred/mod.rs index ef20bf7ee59d6..6ae4108d80492 100644 --- a/crates/bevy_core_pipeline/src/deferred/mod.rs +++ b/crates/bevy_core_pipeline/src/deferred/mod.rs @@ -3,7 +3,9 @@ pub mod node; use core::ops::Range; +use crate::prepass::OpaqueNoLightmap3dBinKey; use bevy_ecs::prelude::*; +use bevy_render::sync_world::MainEntity; use bevy_render::{ render_phase::{ BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem, @@ -12,8 +14,6 @@ use bevy_render::{ render_resource::{CachedRenderPipelineId, TextureFormat}, }; -use crate::prepass::OpaqueNoLightmap3dBinKey; - pub const DEFERRED_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgba32Uint; pub const DEFERRED_LIGHTING_PASS_ID_FORMAT: TextureFormat = TextureFormat::R8Uint; pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth16Unorm; @@ -26,7 +26,7 @@ pub const DEFERRED_LIGHTING_PASS_ID_DEPTH_FORMAT: TextureFormat = TextureFormat: #[derive(PartialEq, Eq, Hash)] pub struct Opaque3dDeferred { pub key: OpaqueNoLightmap3dBinKey, - pub representative_entity: Entity, + pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, } @@ -34,7 +34,11 @@ pub struct Opaque3dDeferred { impl PhaseItem for Opaque3dDeferred { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -69,7 +73,7 @@ impl BinnedPhaseItem for Opaque3dDeferred { #[inline] fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { @@ -96,7 +100,7 @@ impl CachedRenderPipelinePhaseItem for Opaque3dDeferred { /// Used to render all meshes with a material with an alpha mask. pub struct AlphaMask3dDeferred { pub key: OpaqueNoLightmap3dBinKey, - pub representative_entity: Entity, + pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, } @@ -104,7 +108,12 @@ pub struct AlphaMask3dDeferred { impl PhaseItem for AlphaMask3dDeferred { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + #[inline] + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -138,7 +147,7 @@ impl BinnedPhaseItem for AlphaMask3dDeferred { fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index 4829c76ee7653..f8151a0e163c4 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -29,10 +29,12 @@ pub mod node; use core::ops::Range; +use crate::deferred::{DEFERRED_LIGHTING_PASS_ID_FORMAT, DEFERRED_PREPASS_FORMAT}; use bevy_asset::UntypedAssetId; use bevy_ecs::prelude::*; use bevy_math::Mat4; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::sync_world::MainEntity; use bevy_render::{ render_phase::{ BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem, @@ -45,8 +47,6 @@ use bevy_render::{ texture::ColorAttachment, }; -use crate::deferred::{DEFERRED_LIGHTING_PASS_ID_FORMAT, DEFERRED_PREPASS_FORMAT}; - pub const NORMAL_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgb10a2Unorm; pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float; @@ -143,8 +143,7 @@ pub struct Opaque3dPrepass { /// An entity from which Bevy fetches data common to all instances in this /// batch, such as the mesh. - pub representative_entity: Entity, - + pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, } @@ -171,7 +170,11 @@ pub struct OpaqueNoLightmap3dBinKey { impl PhaseItem for Opaque3dPrepass { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -206,7 +209,7 @@ impl BinnedPhaseItem for Opaque3dPrepass { #[inline] fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { @@ -233,7 +236,7 @@ impl CachedRenderPipelinePhaseItem for Opaque3dPrepass { /// Used to render all meshes with a material with an alpha mask. pub struct AlphaMask3dPrepass { pub key: OpaqueNoLightmap3dBinKey, - pub representative_entity: Entity, + pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, } @@ -241,7 +244,11 @@ pub struct AlphaMask3dPrepass { impl PhaseItem for AlphaMask3dPrepass { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -276,7 +283,7 @@ impl BinnedPhaseItem for AlphaMask3dPrepass { #[inline] fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 3cffb4480c173..995b59af2243b 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -14,6 +14,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_math::FloatOrd; +use bevy_render::sync_world::MainEntity; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ @@ -283,7 +284,7 @@ fn queue_line_gizmos_2d( pipeline: Res, mut pipelines: ResMut>, pipeline_cache: Res, - line_gizmos: Query<(Entity, &GizmoMeshConfig)>, + line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, mut views: Query<(Entity, &ExtractedView, &Msaa, Option<&RenderLayers>)>, @@ -299,7 +300,7 @@ fn queue_line_gizmos_2d( | Mesh2dPipelineKey::from_hdr(view.hdr); let render_layers = render_layers.unwrap_or_default(); - for (entity, config) in &line_gizmos { + for (entity, main_entity, config) in &line_gizmos { if !config.render_layers.intersects(render_layers) { continue; } @@ -319,7 +320,7 @@ fn queue_line_gizmos_2d( ); transparent_phase.add(Transparent2d { - entity, + entity: (entity, *main_entity), draw_function, pipeline, sort_key: FloatOrd(f32::INFINITY), @@ -336,7 +337,7 @@ fn queue_line_joint_gizmos_2d( pipeline: Res, mut pipelines: ResMut>, pipeline_cache: Res, - line_gizmos: Query<(Entity, &GizmoMeshConfig)>, + line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, mut views: Query<(Entity, &ExtractedView, &Msaa, Option<&RenderLayers>)>, @@ -355,7 +356,7 @@ fn queue_line_joint_gizmos_2d( | Mesh2dPipelineKey::from_hdr(view.hdr); let render_layers = render_layers.unwrap_or_default(); - for (entity, config) in &line_gizmos { + for (entity, main_entity, config) in &line_gizmos { if !config.render_layers.intersects(render_layers) { continue; } @@ -377,7 +378,7 @@ fn queue_line_joint_gizmos_2d( }, ); transparent_phase.add(Transparent2d { - entity, + entity: (entity, *main_entity), draw_function, pipeline, sort_key: FloatOrd(f32::INFINITY), diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 5b9d156d06b70..6d16db4711622 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -18,6 +18,7 @@ use bevy_ecs::{ world::{FromWorld, World}, }; use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup}; +use bevy_render::sync_world::MainEntity; use bevy_render::{ render_asset::{prepare_assets, RenderAssets}, render_phase::{ @@ -278,7 +279,7 @@ fn queue_line_gizmos_3d( pipeline: Res, mut pipelines: ResMut>, pipeline_cache: Res, - line_gizmos: Query<(Entity, &GizmoMeshConfig)>, + line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, mut views: Query<( @@ -329,7 +330,7 @@ fn queue_line_gizmos_3d( view_key |= MeshPipelineKey::DEFERRED_PREPASS; } - for (entity, config) in &line_gizmos { + for (entity, main_entity, config) in &line_gizmos { if !config.render_layers.intersects(render_layers) { continue; } @@ -350,7 +351,7 @@ fn queue_line_gizmos_3d( ); transparent_phase.add(Transparent3d { - entity, + entity: (entity, *main_entity), draw_function, pipeline, distance: 0., @@ -367,7 +368,7 @@ fn queue_line_joint_gizmos_3d( pipeline: Res, mut pipelines: ResMut>, pipeline_cache: Res, - line_gizmos: Query<(Entity, &GizmoMeshConfig)>, + line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut transparent_render_phases: ResMut>, mut views: Query<( @@ -421,7 +422,7 @@ fn queue_line_joint_gizmos_3d( view_key |= MeshPipelineKey::DEFERRED_PREPASS; } - for (entity, config) in &line_gizmos { + for (entity, main_entity, config) in &line_gizmos { if !config.render_layers.intersects(render_layers) { continue; } @@ -445,7 +446,7 @@ fn queue_line_joint_gizmos_3d( ); transparent_phase.add(Transparent3d { - entity, + entity: (entity, *main_entity), draw_function, pipeline, distance: 0., diff --git a/crates/bevy_pbr/src/bundle.rs b/crates/bevy_pbr/src/bundle.rs index 107c56f719838..bdfdd695f5904 100644 --- a/crates/bevy_pbr/src/bundle.rs +++ b/crates/bevy_pbr/src/bundle.rs @@ -12,6 +12,7 @@ use bevy_ecs::{ reflect::ReflectComponent, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::sync_world::MainEntity; use bevy_render::{ mesh::Mesh3d, primitives::{CascadesFrusta, CubemapFrusta, Frustum}, @@ -71,6 +72,13 @@ pub struct VisibleMeshEntities { pub entities: Vec, } +#[derive(Component, Clone, Debug, Default, Reflect, Deref, DerefMut)] +#[reflect(Component, Debug, Default)] +pub struct RenderVisibleMeshEntities { + #[reflect(ignore)] + pub entities: Vec<(Entity, MainEntity)>, +} + #[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component, Debug, Default)] pub struct CubemapVisibleEntities { @@ -96,6 +104,31 @@ impl CubemapVisibleEntities { } } +#[derive(Component, Clone, Debug, Default, Reflect)] +#[reflect(Component, Debug, Default)] +pub struct RenderCubemapVisibleEntities { + #[reflect(ignore)] + pub(crate) data: [RenderVisibleMeshEntities; 6], +} + +impl RenderCubemapVisibleEntities { + pub fn get(&self, i: usize) -> &RenderVisibleMeshEntities { + &self.data[i] + } + + pub fn get_mut(&mut self, i: usize) -> &mut RenderVisibleMeshEntities { + &mut self.data[i] + } + + pub fn iter(&self) -> impl DoubleEndedIterator { + self.data.iter() + } + + pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { + self.data.iter_mut() + } +} + #[derive(Component, Clone, Debug, Default, Reflect)] #[reflect(Component)] pub struct CascadesVisibleEntities { @@ -104,6 +137,14 @@ pub struct CascadesVisibleEntities { pub entities: EntityHashMap>, } +#[derive(Component, Clone, Debug, Default, Reflect)] +#[reflect(Component)] +pub struct RenderCascadesVisibleEntities { + /// Map of view entity to the visible entities for each cascade frustum. + #[reflect(ignore)] + pub entities: EntityHashMap>, +} + /// A component bundle for [`PointLight`] entities. #[derive(Debug, Bundle, Default, Clone)] #[deprecated( diff --git a/crates/bevy_pbr/src/lightmap/mod.rs b/crates/bevy_pbr/src/lightmap/mod.rs index f1b03f97af3f8..982f7214a8c81 100644 --- a/crates/bevy_pbr/src/lightmap/mod.rs +++ b/crates/bevy_pbr/src/lightmap/mod.rs @@ -34,13 +34,14 @@ use bevy_app::{App, Plugin}; use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_ecs::{ component::Component, - entity::{Entity, EntityHashMap}, + entity::Entity, reflect::ReflectComponent, schedule::IntoSystemConfigs, system::{Query, Res, ResMut, Resource}, }; use bevy_math::{uvec2, vec4, Rect, UVec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ mesh::{Mesh, RenderMesh}, render_asset::RenderAssets, @@ -110,7 +111,7 @@ pub struct RenderLightmaps { /// /// Entities without lightmaps, or for which the mesh or lightmap isn't /// loaded, won't have entries in this table. - pub(crate) render_lightmaps: EntityHashMap, + pub(crate) render_lightmaps: MainEntityHashMap, /// All active lightmap images in the scene. /// @@ -161,7 +162,7 @@ fn extract_lightmaps( if !view_visibility.get() || images.get(&lightmap.image).is_none() || !render_mesh_instances - .mesh_asset_id(entity) + .mesh_asset_id(entity.into()) .and_then(|mesh_asset_id| meshes.get(mesh_asset_id)) .is_some_and(|mesh| mesh.layout.0.contains(Mesh::ATTRIBUTE_UV_1.id)) { @@ -170,7 +171,7 @@ fn extract_lightmaps( // Store information about the lightmap in the render world. render_lightmaps.render_lightmaps.insert( - entity, + entity.into(), RenderLightmap::new(lightmap.image.id(), lightmap.uv_rect), ); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 903daab5c0d75..2a06aee19c20f 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,3 +1,4 @@ +use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight}; #[cfg(feature = "meshlet")] use crate::meshlet::{ prepare_material_meshlet_meshes_main_opaque_pass, queue_material_meshlet_meshes, @@ -18,12 +19,13 @@ use bevy_core_pipeline::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::EntityHashMap, prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::Reflect; +use bevy_render::sync_world::MainEntityHashMap; +use bevy_render::view::RenderVisibleEntities; use bevy_render::{ camera::TemporalJitter, extract_resource::ExtractResource, @@ -32,7 +34,7 @@ use bevy_render::{ render_phase::*, render_resource::*, renderer::RenderDevice, - view::{ExtractedView, Msaa, RenderVisibilityRanges, ViewVisibility, VisibleEntities}, + view::{ExtractedView, Msaa, RenderVisibilityRanges, ViewVisibility}, Extract, }; use bevy_utils::tracing::error; @@ -43,8 +45,6 @@ use core::{ sync::atomic::{AtomicU32, Ordering}, }; -use self::{irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight}; - /// Materials are used alongside [`MaterialPlugin`], [`Mesh3d`], and [`MeshMaterial3d`] /// to spawn entities that are rendered with a specific [`Material`] type. They serve as an easy to use high level /// way to render [`Mesh3d`] entities with custom shader logic. @@ -479,7 +479,7 @@ impl RenderCommand

for SetMaterial let materials = materials.into_inner(); let material_instances = material_instances.into_inner(); - let Some(material_asset_id) = material_instances.get(&item.entity()) else { + let Some(material_asset_id) = material_instances.get(&item.main_entity()) else { return RenderCommandResult::Skip; }; let Some(material) = materials.get(*material_asset_id) else { @@ -492,7 +492,7 @@ impl RenderCommand

for SetMaterial /// Stores all extracted instances of a [`Material`] in the render world. #[derive(Resource, Deref, DerefMut)] -pub struct RenderMaterialInstances(pub EntityHashMap>); +pub struct RenderMaterialInstances(pub MainEntityHashMap>); impl Default for RenderMaterialInstances { fn default() -> Self { @@ -562,7 +562,7 @@ fn extract_mesh_materials( ) { for (entity, view_visibility, material) in &query { if view_visibility.get() { - material_instances.insert(entity, material.id()); + material_instances.insert(entity.into(), material.id()); } } } @@ -574,7 +574,7 @@ pub(super) fn extract_default_materials( ) { for (entity, view_visibility) in &query { if view_visibility.get() { - material_instances.insert(entity, AssetId::default()); + material_instances.insert(entity.into(), AssetId::default()); } } } @@ -610,7 +610,7 @@ pub fn queue_material_meshes( views: Query<( Entity, &ExtractedView, - &VisibleEntities, + &RenderVisibleEntities, &Msaa, Option<&Tonemapping>, Option<&DebandDither>, @@ -744,7 +744,7 @@ pub fn queue_material_meshes( } let rangefinder = view.rangefinder3d(); - for visible_entity in visible_entities.iter::>() { + for (render_entity, visible_entity) in visible_entities.iter::>() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -825,7 +825,7 @@ pub fn queue_material_meshes( let distance = rangefinder.distance_translation(&mesh_instance.translation) + material.properties.depth_bias; transmissive_phase.add(Transmissive3d { - entity: *visible_entity, + entity: (*render_entity, *visible_entity), draw_function: draw_transmissive_pbr, pipeline: pipeline_id, distance, @@ -842,7 +842,7 @@ pub fn queue_material_meshes( }; opaque_phase.add( bin_key, - *visible_entity, + (*render_entity, *visible_entity), BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } @@ -853,7 +853,7 @@ pub fn queue_material_meshes( let distance = rangefinder.distance_translation(&mesh_instance.translation) + material.properties.depth_bias; transmissive_phase.add(Transmissive3d { - entity: *visible_entity, + entity: (*render_entity, *visible_entity), draw_function: draw_transmissive_pbr, pipeline: pipeline_id, distance, @@ -869,7 +869,7 @@ pub fn queue_material_meshes( }; alpha_mask_phase.add( bin_key, - *visible_entity, + (*render_entity, *visible_entity), BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } @@ -878,7 +878,7 @@ pub fn queue_material_meshes( let distance = rangefinder.distance_translation(&mesh_instance.translation) + material.properties.depth_bias; transparent_phase.add(Transparent3d { - entity: *visible_entity, + entity: (*render_entity, *visible_entity), draw_function: draw_transparent_pbr, pipeline: pipeline_id, distance, diff --git a/crates/bevy_pbr/src/meshlet/instance_manager.rs b/crates/bevy_pbr/src/meshlet/instance_manager.rs index 768864fe6b610..eec2f32d35790 100644 --- a/crates/bevy_pbr/src/meshlet/instance_manager.rs +++ b/crates/bevy_pbr/src/meshlet/instance_manager.rs @@ -10,6 +10,7 @@ use bevy_ecs::{ query::Has, system::{Local, Query, Res, ResMut, Resource, SystemState}, }; +use bevy_render::sync_world::MainEntity; use bevy_render::{render_resource::StorageBuffer, view::RenderLayers, MainWorld}; use bevy_transform::components::GlobalTransform; use bevy_utils::{HashMap, HashSet}; @@ -21,8 +22,8 @@ pub struct InstanceManager { /// Amount of clusters in the scene (sum of all meshlet counts across all instances) pub scene_cluster_count: u32, - /// Per-instance [`Entity`], [`RenderLayers`], and [`NotShadowCaster`] - pub instances: Vec<(Entity, RenderLayers, bool)>, + /// Per-instance [`MainEntity`], [`RenderLayers`], and [`NotShadowCaster`] + pub instances: Vec<(MainEntity, RenderLayers, bool)>, /// Per-instance [`MeshUniform`] pub instance_uniforms: StorageBuffer>, /// Per-instance material ID @@ -107,7 +108,7 @@ impl InstanceManager { // Append instance data self.instances.push(( - instance, + instance.into(), render_layers.cloned().unwrap_or(RenderLayers::default()), not_shadow_caster, )); diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 4ac34878df6ce..5bbe90b37feea 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -26,7 +26,7 @@ use bevy_render::{ render_phase::*, render_resource::*, renderer::{RenderDevice, RenderQueue}, - view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, VisibleEntities}, + view::{ExtractedView, Msaa, ViewUniform, ViewUniformOffset, ViewUniforms}, Extract, }; use bevy_transform::prelude::GlobalTransform; @@ -39,6 +39,7 @@ use crate::meshlet::{ }; use crate::*; +use bevy_render::view::RenderVisibleEntities; use core::{hash::Hash, marker::PhantomData}; pub const PREPASS_SHADER_HANDLE: Handle = Handle::weak_from_u128(921124473254008983); @@ -697,7 +698,7 @@ pub fn queue_prepass_material_meshes( views: Query< ( Entity, - &VisibleEntities, + &RenderVisibleEntities, &Msaa, Option<&DepthPrepass>, Option<&NormalPrepass>, @@ -767,7 +768,7 @@ pub fn queue_prepass_material_meshes( view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } - for visible_entity in visible_entities.iter::>() { + for (render_entity, visible_entity) in visible_entities.iter::>() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -873,7 +874,7 @@ pub fn queue_prepass_material_meshes( asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material.get_bind_group_id().0, }, - *visible_entity, + (*render_entity, *visible_entity), BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } else if let Some(opaque_phase) = opaque_phase.as_mut() { @@ -884,7 +885,7 @@ pub fn queue_prepass_material_meshes( asset_id: mesh_instance.mesh_asset_id.into(), material_bind_group_id: material.get_bind_group_id().0, }, - *visible_entity, + (*render_entity, *visible_entity), BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } @@ -900,7 +901,7 @@ pub fn queue_prepass_material_meshes( }; alpha_mask_deferred_phase.as_mut().unwrap().add( bin_key, - *visible_entity, + (*render_entity, *visible_entity), BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } else if let Some(alpha_mask_phase) = alpha_mask_phase.as_mut() { @@ -912,7 +913,7 @@ pub fn queue_prepass_material_meshes( }; alpha_mask_phase.add( bin_key, - *visible_entity, + (*render_entity, *visible_entity), BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 18a5a0955d6a3..6ddb265885ab4 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -1,3 +1,4 @@ +use crate::*; use bevy_asset::UntypedAssetId; use bevy_color::ColorToComponents; use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT}; @@ -8,7 +9,7 @@ use bevy_ecs::{ system::lifetimeless::Read, }; use bevy_math::{ops, Mat4, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; -use bevy_render::sync_world::RenderEntity; +use bevy_render::sync_world::{MainEntity, RenderEntity, TemporaryRenderEntity}; use bevy_render::{ diagnostic::RecordDiagnostics, mesh::RenderMesh, @@ -31,8 +32,6 @@ use bevy_utils::{ }; use core::{hash::Hash, ops::Range}; -use crate::*; - #[derive(Component)] pub struct ExtractedPointLight { pub color: LinearRgba, @@ -269,9 +268,15 @@ pub fn extract_lights( if !view_visibility.get() { continue; } - // TODO: This is very much not ideal. We should be able to re-use the vector memory. - // However, since exclusive access to the main world in extract is ill-advised, we just clone here. - let render_cubemap_visible_entities = cubemap_visible_entities.clone(); + let render_cubemap_visible_entities = RenderCubemapVisibleEntities { + data: cubemap_visible_entities + .iter() + .map(|v| create_render_visible_mesh_entities(&mut commands, &mapper, v)) + .collect::>() + .try_into() + .unwrap(), + }; + let extracted_point_light = ExtractedPointLight { color: point_light.color.into(), // NOTE: Map from luminous power in lumens to luminous intensity in lumens per steradian @@ -319,9 +324,9 @@ pub fn extract_lights( if !view_visibility.get() { continue; } - // TODO: This is very much not ideal. We should be able to re-use the vector memory. - // However, since exclusive access to the main world in extract is ill-advised, we just clone here. - let render_visible_entities = visible_entities.clone(); + let render_visible_entities = + create_render_visible_mesh_entities(&mut commands, &mapper, visible_entities); + let texel_size = 2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32; @@ -397,7 +402,12 @@ pub fn extract_lights( } for (e, v) in visible_entities.entities.iter() { if let Ok(entity) = mapper.get(*e) { - cascade_visible_entities.insert(entity.id(), v.clone()); + cascade_visible_entities.insert( + entity.id(), + v.iter() + .map(|v| create_render_visible_mesh_entities(&mut commands, &mapper, v)) + .collect(), + ); } else { break; } @@ -423,13 +433,32 @@ pub fn extract_lights( frusta: extracted_frusta, render_layers: maybe_layers.unwrap_or_default().clone(), }, - CascadesVisibleEntities { + RenderCascadesVisibleEntities { entities: cascade_visible_entities, }, )); } } +fn create_render_visible_mesh_entities( + commands: &mut Commands, + mapper: &Extract>, + visible_entities: &VisibleMeshEntities, +) -> RenderVisibleMeshEntities { + RenderVisibleMeshEntities { + entities: visible_entities + .iter() + .map(|e| { + let render_entity = mapper + .get(*e) + .map(RenderEntity::id) + .unwrap_or_else(|_| commands.spawn(TemporaryRenderEntity).id()); + (render_entity, MainEntity::from(*e)) + }) + .collect(), + } +} + #[derive(Component, Default, Deref, DerefMut)] pub struct LightViewEntities(Vec); @@ -1352,9 +1381,12 @@ pub fn queue_shadows( render_lightmaps: Res, view_lights: Query<(Entity, &ViewLightEntities)>, view_light_entities: Query<&LightEntity>, - point_light_entities: Query<&CubemapVisibleEntities, With>, - directional_light_entities: Query<&CascadesVisibleEntities, With>, - spot_light_entities: Query<&VisibleMeshEntities, With>, + point_light_entities: Query<&RenderCubemapVisibleEntities, With>, + directional_light_entities: Query< + &RenderCascadesVisibleEntities, + With, + >, + spot_light_entities: Query<&RenderVisibleMeshEntities, With>, ) where M::Data: PartialEq + Eq + Hash + Clone, { @@ -1398,8 +1430,8 @@ pub fn queue_shadows( // NOTE: Lights with shadow mapping disabled will have no visible entities // so no meshes will be queued - for entity in visible_entities.iter().copied() { - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(entity) + for (entity, main_entity) in visible_entities.iter().copied() { + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(main_entity) else { continue; }; @@ -1409,7 +1441,7 @@ pub fn queue_shadows( { continue; } - let Some(material_asset_id) = render_material_instances.get(&entity) else { + let Some(material_asset_id) = render_material_instances.get(&main_entity) else { continue; }; let Some(material) = render_materials.get(*material_asset_id) else { @@ -1427,7 +1459,7 @@ pub fn queue_shadows( // we need to include the appropriate flag in the mesh pipeline key // to ensure that the necessary bind group layout entries are // present. - if render_lightmaps.render_lightmaps.contains_key(&entity) { + if render_lightmaps.render_lightmaps.contains_key(&main_entity) { mesh_key |= MeshPipelineKey::LIGHTMAPPED; } @@ -1467,7 +1499,7 @@ pub fn queue_shadows( pipeline: pipeline_id, asset_id: mesh_instance.mesh_asset_id.into(), }, - entity, + (entity, main_entity), BinnedRenderPhaseType::mesh(mesh_instance.should_batch()), ); } @@ -1477,7 +1509,7 @@ pub fn queue_shadows( pub struct Shadow { pub key: ShadowBinKey, - pub representative_entity: Entity, + pub representative_entity: (Entity, MainEntity), pub batch_range: Range, pub extra_index: PhaseItemExtraIndex, } @@ -1498,7 +1530,11 @@ pub struct ShadowBinKey { impl PhaseItem for Shadow { #[inline] fn entity(&self) -> Entity { - self.representative_entity + self.representative_entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.representative_entity.1 } #[inline] @@ -1533,7 +1569,7 @@ impl BinnedPhaseItem for Shadow { #[inline] fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self { diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index f59381b865358..af6621eb259e6 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -10,7 +10,6 @@ use bevy_core_pipeline::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::EntityHashMap, prelude::*, query::ROQueryItem, system::{lifetimeless::*, SystemParamItem, SystemState}, @@ -46,11 +45,6 @@ use bevy_utils::{ Entry, HashMap, Parallel, }; -use bytemuck::{Pod, Zeroable}; -use nonmax::{NonMaxU16, NonMaxU32}; -use smallvec::{smallvec, SmallVec}; -use static_assertions::const_assert_eq; - use crate::{ render::{ morph::{ @@ -61,6 +55,11 @@ use crate::{ }, *, }; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; +use bytemuck::{Pod, Zeroable}; +use nonmax::{NonMaxU16, NonMaxU32}; +use smallvec::{smallvec, SmallVec}; +use static_assertions::const_assert_eq; use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; @@ -557,11 +556,11 @@ pub enum RenderMeshInstanceGpuQueue { /// The version of [`RenderMeshInstanceGpuQueue`] that omits the /// [`MeshCullingData`], so that we don't waste space when GPU /// culling is disabled. - CpuCulling(Vec<(Entity, RenderMeshInstanceGpuBuilder)>), + CpuCulling(Vec<(MainEntity, RenderMeshInstanceGpuBuilder)>), /// The version of [`RenderMeshInstanceGpuQueue`] that contains the /// [`MeshCullingData`], used when any view has GPU culling /// enabled. - GpuCulling(Vec<(Entity, RenderMeshInstanceGpuBuilder, MeshCullingData)>), + GpuCulling(Vec<(MainEntity, RenderMeshInstanceGpuBuilder, MeshCullingData)>), } /// The per-thread queues containing mesh instances, populated during the @@ -623,12 +622,12 @@ pub enum RenderMeshInstances { /// Information that the render world keeps about each entity that contains a /// mesh, when using CPU mesh instance data building. #[derive(Default, Deref, DerefMut)] -pub struct RenderMeshInstancesCpu(EntityHashMap); +pub struct RenderMeshInstancesCpu(MainEntityHashMap); /// Information that the render world keeps about each entity that contains a /// mesh, when using GPU mesh instance data building. #[derive(Default, Deref, DerefMut)] -pub struct RenderMeshInstancesGpu(EntityHashMap); +pub struct RenderMeshInstancesGpu(MainEntityHashMap); impl RenderMeshInstances { /// Creates a new [`RenderMeshInstances`] instance. @@ -641,7 +640,7 @@ impl RenderMeshInstances { } /// Returns the ID of the mesh asset attached to the given entity, if any. - pub(crate) fn mesh_asset_id(&self, entity: Entity) -> Option> { + pub(crate) fn mesh_asset_id(&self, entity: MainEntity) -> Option> { match *self { RenderMeshInstances::CpuBuilding(ref instances) => instances.mesh_asset_id(entity), RenderMeshInstances::GpuBuilding(ref instances) => instances.mesh_asset_id(entity), @@ -650,7 +649,7 @@ impl RenderMeshInstances { /// Constructs [`RenderMeshQueueData`] for the given entity, if it has a /// mesh attached. - pub fn render_mesh_queue_data(&self, entity: Entity) -> Option { + pub fn render_mesh_queue_data(&self, entity: MainEntity) -> Option { match *self { RenderMeshInstances::CpuBuilding(ref instances) => { instances.render_mesh_queue_data(entity) @@ -663,7 +662,7 @@ impl RenderMeshInstances { /// Inserts the given flags into the CPU or GPU render mesh instance data /// for the given mesh as appropriate. - fn insert_mesh_instance_flags(&mut self, entity: Entity, flags: RenderMeshInstanceFlags) { + fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) { match *self { RenderMeshInstances::CpuBuilding(ref mut instances) => { instances.insert_mesh_instance_flags(entity, flags); @@ -676,12 +675,12 @@ impl RenderMeshInstances { } impl RenderMeshInstancesCpu { - fn mesh_asset_id(&self, entity: Entity) -> Option> { + fn mesh_asset_id(&self, entity: MainEntity) -> Option> { self.get(&entity) .map(|render_mesh_instance| render_mesh_instance.mesh_asset_id) } - fn render_mesh_queue_data(&self, entity: Entity) -> Option { + fn render_mesh_queue_data(&self, entity: MainEntity) -> Option { self.get(&entity) .map(|render_mesh_instance| RenderMeshQueueData { shared: &render_mesh_instance.shared, @@ -691,7 +690,7 @@ impl RenderMeshInstancesCpu { /// Inserts the given flags into the render mesh instance data for the given /// mesh. - fn insert_mesh_instance_flags(&mut self, entity: Entity, flags: RenderMeshInstanceFlags) { + fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) { if let Some(instance) = self.get_mut(&entity) { instance.flags.insert(flags); } @@ -699,12 +698,12 @@ impl RenderMeshInstancesCpu { } impl RenderMeshInstancesGpu { - fn mesh_asset_id(&self, entity: Entity) -> Option> { + fn mesh_asset_id(&self, entity: MainEntity) -> Option> { self.get(&entity) .map(|render_mesh_instance| render_mesh_instance.mesh_asset_id) } - fn render_mesh_queue_data(&self, entity: Entity) -> Option { + fn render_mesh_queue_data(&self, entity: MainEntity) -> Option { self.get(&entity) .map(|render_mesh_instance| RenderMeshQueueData { shared: &render_mesh_instance.shared, @@ -714,7 +713,7 @@ impl RenderMeshInstancesGpu { /// Inserts the given flags into the render mesh instance data for the given /// mesh. - fn insert_mesh_instance_flags(&mut self, entity: Entity, flags: RenderMeshInstanceFlags) { + fn insert_mesh_instance_flags(&mut self, entity: MainEntity, flags: RenderMeshInstanceFlags) { if let Some(instance) = self.get_mut(&entity) { instance.flags.insert(flags); } @@ -739,7 +738,7 @@ impl RenderMeshInstanceGpuQueue { /// Adds a new mesh to this queue. fn push( &mut self, - entity: Entity, + entity: MainEntity, instance_builder: RenderMeshInstanceGpuBuilder, culling_data_builder: Option, ) { @@ -772,8 +771,8 @@ impl RenderMeshInstanceGpuBuilder { /// [`MeshInputUniform`] tables. fn add_to( self, - entity: Entity, - render_mesh_instances: &mut EntityHashMap, + entity: MainEntity, + render_mesh_instances: &mut MainEntityHashMap, current_input_buffer: &mut RawBufferVec, mesh_allocator: &MeshAllocator, ) -> usize { @@ -907,7 +906,7 @@ pub fn extract_meshes_for_cpu_building( let mut lod_index = None; if visibility_range { - lod_index = render_visibility_ranges.lod_index_for_entity(entity); + lod_index = render_visibility_ranges.lod_index_for_entity(entity.into()); } let mesh_flags = MeshFlags::from_components( @@ -954,7 +953,7 @@ pub fn extract_meshes_for_cpu_building( render_mesh_instances.clear(); for queue in render_mesh_instance_queues.iter_mut() { for (entity, render_mesh_instance) in queue.drain(..) { - render_mesh_instances.insert_unique_unchecked(entity, render_mesh_instance); + render_mesh_instances.insert_unique_unchecked(entity.into(), render_mesh_instance); } } } @@ -1023,7 +1022,7 @@ pub fn extract_meshes_for_gpu_building( let mut lod_index = None; if visibility_range { - lod_index = render_visibility_ranges.lod_index_for_entity(entity); + lod_index = render_visibility_ranges.lod_index_for_entity(entity.into()); } let mesh_flags = MeshFlags::from_components( @@ -1049,7 +1048,7 @@ pub fn extract_meshes_for_gpu_building( .contains(RenderMeshInstanceFlags::HAS_PREVIOUS_TRANSFORM) { render_mesh_instances - .get(&entity) + .get(&MainEntity::from(entity)) .map(|render_mesh_instance| render_mesh_instance.current_uniform_index) } else { None @@ -1063,7 +1062,11 @@ pub fn extract_meshes_for_gpu_building( previous_input_index, }; - queue.push(entity, gpu_mesh_instance_builder, gpu_mesh_culling_data); + queue.push( + entity.into(), + gpu_mesh_instance_builder, + gpu_mesh_culling_data, + ); }, ); } @@ -1285,7 +1288,7 @@ impl GetBatchData for MeshPipeline { fn get_batch_data( (mesh_instances, lightmaps, _, mesh_allocator): &SystemParamItem, - entity: Entity, + (_entity, main_entity): (Entity, MainEntity), ) -> Option<(Self::BufferData, Option)> { let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { error!( @@ -1294,13 +1297,13 @@ impl GetBatchData for MeshPipeline { ); return None; }; - let mesh_instance = mesh_instances.get(&entity)?; + let mesh_instance = mesh_instances.get(&main_entity)?; let first_vertex_index = match mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) { Some(mesh_vertex_slice) => mesh_vertex_slice.range.start, None => 0, }; - let maybe_lightmap = lightmaps.render_lightmaps.get(&entity); + let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); Some(( MeshUniform::new( @@ -1322,7 +1325,7 @@ impl GetFullBatchData for MeshPipeline { fn get_index_and_compare_data( (mesh_instances, lightmaps, _, _): &SystemParamItem, - entity: Entity, + (_entity, main_entity): (Entity, MainEntity), ) -> Option<(NonMaxU32, Option)> { // This should only be called during GPU building. let RenderMeshInstances::GpuBuilding(ref mesh_instances) = **mesh_instances else { @@ -1333,8 +1336,8 @@ impl GetFullBatchData for MeshPipeline { return None; }; - let mesh_instance = mesh_instances.get(&entity)?; - let maybe_lightmap = lightmaps.render_lightmaps.get(&entity); + let mesh_instance = mesh_instances.get(&main_entity)?; + let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); Some(( mesh_instance.current_uniform_index, @@ -1348,7 +1351,7 @@ impl GetFullBatchData for MeshPipeline { fn get_binned_batch_data( (mesh_instances, lightmaps, _, mesh_allocator): &SystemParamItem, - entity: Entity, + (_entity, main_entity): (Entity, MainEntity), ) -> Option { let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { error!( @@ -1356,13 +1359,13 @@ impl GetFullBatchData for MeshPipeline { ); return None; }; - let mesh_instance = mesh_instances.get(&entity)?; + let mesh_instance = mesh_instances.get(&main_entity)?; let first_vertex_index = match mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id) { Some(mesh_vertex_slice) => mesh_vertex_slice.range.start, None => 0, }; - let maybe_lightmap = lightmaps.render_lightmaps.get(&entity); + let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); Some(MeshUniform::new( &mesh_instance.transforms, @@ -1373,7 +1376,7 @@ impl GetFullBatchData for MeshPipeline { fn get_binned_index( (mesh_instances, _, _, _): &SystemParamItem, - entity: Entity, + (_entity, main_entity): (Entity, MainEntity), ) -> Option { // This should only be called during GPU building. let RenderMeshInstances::GpuBuilding(ref mesh_instances) = **mesh_instances else { @@ -1385,14 +1388,14 @@ impl GetFullBatchData for MeshPipeline { }; mesh_instances - .get(&entity) + .get(&main_entity) .map(|entity| entity.current_uniform_index) } fn get_batch_indirect_parameters_index( (mesh_instances, _, meshes, mesh_allocator): &SystemParamItem, indirect_parameters_buffer: &mut IndirectParametersBuffer, - entity: Entity, + entity: (Entity, MainEntity), instance_index: u32, ) -> Option { get_batch_indirect_parameters_index( @@ -1414,7 +1417,7 @@ fn get_batch_indirect_parameters_index( meshes: &RenderAssets, mesh_allocator: &MeshAllocator, indirect_parameters_buffer: &mut IndirectParametersBuffer, - entity: Entity, + (_entity, main_entity): (Entity, MainEntity), instance_index: u32, ) -> Option { // This should only be called during GPU building. @@ -1426,7 +1429,7 @@ fn get_batch_indirect_parameters_index( return None; }; - let mesh_instance = mesh_instances.get(&entity)?; + let mesh_instance = mesh_instances.get(&main_entity)?; let mesh = meshes.get(mesh_instance.mesh_asset_id)?; let vertex_buffer_slice = mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id)?; @@ -2259,7 +2262,7 @@ impl RenderCommand

for SetMeshBindGroup { let skin_indices = skin_indices.into_inner(); let morph_indices = morph_indices.into_inner(); - let entity = &item.entity(); + let entity = &item.main_entity(); let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(*entity) else { return RenderCommandResult::Success; @@ -2380,7 +2383,7 @@ impl RenderCommand

for DrawMesh { let indirect_parameters_buffer = indirect_parameters_buffer.into_inner(); let mesh_allocator = mesh_allocator.into_inner(); - let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(item.entity()) else { + let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(item.main_entity()) else { return RenderCommandResult::Skip; }; let Some(gpu_mesh) = meshes.get(mesh_asset_id) else { diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index c2696652a7a2f..4b1ed68ce87a3 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -1,6 +1,7 @@ use core::{iter, mem}; -use bevy_ecs::{entity::EntityHashMap, prelude::*}; +use bevy_ecs::prelude::*; +use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::NoAutomaticBatching, mesh::morph::{MeshMorphWeights, MAX_MORPH_WEIGHTS}, @@ -25,11 +26,11 @@ pub struct MorphIndex { pub struct MorphIndices { /// Maps each entity with a morphed mesh to the appropriate offset within /// [`MorphUniforms::current_buffer`]. - pub current: EntityHashMap, + pub current: MainEntityHashMap, /// Maps each entity with a morphed mesh to the appropriate offset within /// [`MorphUniforms::prev_buffer`]. - pub prev: EntityHashMap, + pub prev: MainEntityHashMap, } /// The GPU buffers containing morph weights for all meshes with morph targets. @@ -131,7 +132,9 @@ pub fn extract_morphs( add_to_alignment::(&mut uniform.current_buffer); let index = (start * size_of::()) as u32; - morph_indices.current.insert(entity, MorphIndex { index }); + morph_indices + .current + .insert(entity.into(), MorphIndex { index }); } } diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index 1719789d55420..b6f35fc0bf49d 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -1,8 +1,9 @@ use core::mem::{self, size_of}; use bevy_asset::Assets; -use bevy_ecs::{entity::EntityHashMap, prelude::*}; +use bevy_ecs::prelude::*; use bevy_math::Mat4; +use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::NoAutomaticBatching, mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, @@ -39,11 +40,11 @@ impl SkinIndex { pub struct SkinIndices { /// Maps each skinned mesh to the applicable offset within /// [`SkinUniforms::current_buffer`]. - pub current: EntityHashMap, + pub current: MainEntityHashMap, /// Maps each skinned mesh to the applicable offset within /// [`SkinUniforms::prev_buffer`]. - pub prev: EntityHashMap, + pub prev: MainEntityHashMap, } /// The GPU buffers containing joint matrices for all skinned meshes. @@ -168,7 +169,9 @@ pub fn extract_skins( buffer.push(Mat4::ZERO); } - skin_indices.current.insert(entity, SkinIndex::new(start)); + skin_indices + .current + .insert(entity.into(), SkinIndex::new(start)); } // Pad out the buffer to ensure that there's enough space for bindings diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index bd515c0840625..d6b490f8c40b4 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -433,10 +433,10 @@ pub fn batch_and_prepare_sorted_render_phase( for current_index in 0..phase.items.len() { // Get the index of the input data, and comparison metadata, for // this entity. - let current_batch_input_index = GFBD::get_index_and_compare_data( - &system_param_item, - phase.items[current_index].entity(), - ); + let item = &phase.items[current_index]; + let entity = (item.entity(), item.main_entity()); + let current_batch_input_index = + GFBD::get_index_and_compare_data(&system_param_item, entity); // Unpack that index and metadata. Note that it's possible for index // and/or metadata to not be present, which signifies that this @@ -464,7 +464,8 @@ pub fn batch_and_prepare_sorted_render_phase( }); // Make space in the data buffer for this instance. - let current_entity = phase.items[current_index].entity(); + let item = &phase.items[current_index]; + let entity = (item.entity(), item.main_entity()); let output_index = data_buffer.add() as u32; // If we can't batch, break the existing batch and make a new one. @@ -479,7 +480,7 @@ pub fn batch_and_prepare_sorted_render_phase( GFBD::get_batch_indirect_parameters_index( &system_param_item, &mut indirect_parameters_buffer, - current_entity, + entity, output_index, ) } else { @@ -551,8 +552,10 @@ pub fn batch_and_prepare_binned_render_phase( for key in &phase.batchable_mesh_keys { let mut batch: Option = None; - for &entity in &phase.batchable_mesh_values[key] { - let Some(input_index) = GFBD::get_binned_index(&system_param_item, entity) else { + for &(entity, main_entity) in &phase.batchable_mesh_values[key] { + let Some(input_index) = + GFBD::get_binned_index(&system_param_item, (entity, main_entity)) + else { continue; }; let output_index = data_buffer.add() as u32; @@ -573,7 +576,7 @@ pub fn batch_and_prepare_binned_render_phase( let indirect_parameters_index = GFBD::get_batch_indirect_parameters_index( &system_param_item, &mut indirect_parameters_buffer, - entity, + (entity, main_entity), output_index, ); work_item_buffer.buffer.push(PreprocessWorkItem { @@ -581,7 +584,7 @@ pub fn batch_and_prepare_binned_render_phase( output_index: indirect_parameters_index.unwrap_or_default().into(), }); batch = Some(BinnedRenderPhaseBatch { - representative_entity: entity, + representative_entity: (entity, main_entity), instance_range: output_index..output_index + 1, extra_index: PhaseItemExtraIndex::maybe_indirect_parameters_index( indirect_parameters_index, @@ -595,7 +598,7 @@ pub fn batch_and_prepare_binned_render_phase( output_index, }); batch = Some(BinnedRenderPhaseBatch { - representative_entity: entity, + representative_entity: (entity, main_entity), instance_range: output_index..output_index + 1, extra_index: PhaseItemExtraIndex::NONE, }); @@ -611,8 +614,10 @@ pub fn batch_and_prepare_binned_render_phase( // Prepare unbatchables. for key in &phase.unbatchable_mesh_keys { let unbatchables = phase.unbatchable_mesh_values.get_mut(key).unwrap(); - for &entity in &unbatchables.entities { - let Some(input_index) = GFBD::get_binned_index(&system_param_item, entity) else { + for &(entity, main_entity) in &unbatchables.entities { + let Some(input_index) = + GFBD::get_binned_index(&system_param_item, (entity, main_entity)) + else { continue; }; let output_index = data_buffer.add() as u32; @@ -621,7 +626,7 @@ pub fn batch_and_prepare_binned_render_phase( let indirect_parameters_index = GFBD::get_batch_indirect_parameters_index( &system_param_item, &mut indirect_parameters_buffer, - entity, + (entity, main_entity), output_index, ) .unwrap_or_default(); diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 6287911e06249..d4b7e76fcbb15 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -6,6 +6,8 @@ use bevy_ecs::{ use bytemuck::Pod; use nonmax::NonMaxU32; +use self::gpu_preprocessing::IndirectParametersBuffer; +use crate::sync_world::MainEntity; use crate::{ render_phase::{ BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId, SortedPhaseItem, @@ -14,8 +16,6 @@ use crate::{ render_resource::{CachedRenderPipelineId, GpuArrayBufferable}, }; -use self::gpu_preprocessing::IndirectParametersBuffer; - pub mod gpu_preprocessing; pub mod no_gpu_preprocessing; @@ -88,7 +88,7 @@ pub trait GetBatchData { /// [`GetFullBatchData::get_index_and_compare_data`] instead. fn get_batch_data( param: &SystemParamItem, - query_item: Entity, + query_item: (Entity, MainEntity), ) -> Option<(Self::BufferData, Option)>; } @@ -109,7 +109,7 @@ pub trait GetFullBatchData: GetBatchData { /// [`GetFullBatchData::get_index_and_compare_data`] instead. fn get_binned_batch_data( param: &SystemParamItem, - query_item: Entity, + query_item: (Entity, MainEntity), ) -> Option; /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the @@ -121,7 +121,7 @@ pub trait GetFullBatchData: GetBatchData { /// function will never be called. fn get_index_and_compare_data( param: &SystemParamItem, - query_item: Entity, + query_item: (Entity, MainEntity), ) -> Option<(NonMaxU32, Option)>; /// Returns the index of the [`GetFullBatchData::BufferInputData`] that the @@ -133,7 +133,7 @@ pub trait GetFullBatchData: GetBatchData { /// function will never be called. fn get_binned_index( param: &SystemParamItem, - query_item: Entity, + query_item: (Entity, MainEntity), ) -> Option; /// Pushes [`gpu_preprocessing::IndirectParameters`] necessary to draw this @@ -145,7 +145,7 @@ pub trait GetFullBatchData: GetBatchData { fn get_batch_indirect_parameters_index( param: &SystemParamItem, indirect_parameters_buffer: &mut IndirectParametersBuffer, - entity: Entity, + entity: (Entity, MainEntity), instance_index: u32, ) -> Option; } diff --git a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs index 51176cb42b240..63fced58d72d0 100644 --- a/crates/bevy_render/src/batching/no_gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/no_gpu_preprocessing.rs @@ -75,7 +75,7 @@ pub fn batch_and_prepare_sorted_render_phase( for phase in phases.values_mut() { super::batch_and_prepare_sorted_render_phase::(phase, |item| { let (buffer_data, compare_data) = - GBD::get_batch_data(&system_param_item, item.entity())?; + GBD::get_batch_data(&system_param_item, (item.entity(), item.main_entity()))?; let buffer_index = batched_instance_buffer.push(buffer_data); let index = buffer_index.index; @@ -106,8 +106,9 @@ pub fn batch_and_prepare_binned_render_phase( for key in &phase.batchable_mesh_keys { let mut batch_set: SmallVec<[BinnedRenderPhaseBatch; 1]> = smallvec![]; - for &entity in &phase.batchable_mesh_values[key] { - let Some(buffer_data) = GFBD::get_binned_batch_data(&system_param_item, entity) + for &(entity, main_entity) in &phase.batchable_mesh_values[key] { + let Some(buffer_data) = + GFBD::get_binned_batch_data(&system_param_item, (entity, main_entity)) else { continue; }; @@ -124,7 +125,7 @@ pub fn batch_and_prepare_binned_render_phase( == PhaseItemExtraIndex::maybe_dynamic_offset(instance.dynamic_offset) }) { batch_set.push(BinnedRenderPhaseBatch { - representative_entity: entity, + representative_entity: (entity, main_entity), instance_range: instance.index..instance.index, extra_index: PhaseItemExtraIndex::maybe_dynamic_offset( instance.dynamic_offset, diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 58fda6529c891..4c22cf93ff9b3 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1,3 +1,6 @@ +use super::{ClearColorConfig, Projection}; +use crate::sync_world::TemporaryRenderEntity; +use crate::view::RenderVisibleEntities; use crate::{ batching::gpu_preprocessing::GpuPreprocessingSupport, camera::{CameraProjection, ManualTextureViewHandle, ManualTextureViews}, @@ -40,8 +43,6 @@ use core::ops::Range; use derive_more::derive::From; use wgpu::{BlendState, TextureFormat, TextureUsages}; -use super::{ClearColorConfig, Projection}; - /// Render viewport configuration for the [`Camera`] component. /// /// The viewport defines the area on the render target to which the camera renders its image. @@ -1034,6 +1035,7 @@ pub fn extract_cameras( >, primary_window: Extract>>, gpu_preprocessing_support: Res, + mapper: Extract>, ) { let primary_window = primary_window.iter().next(); for ( @@ -1072,6 +1074,28 @@ pub fn extract_cameras( continue; } + let render_visible_entities = RenderVisibleEntities { + entities: visible_entities + .entities + .iter() + .map(|(type_id, entities)| { + let entities = entities + .iter() + .map(|entity| { + let render_entity = mapper + .get(*entity) + .cloned() + .map(|entity| entity.id()) + .unwrap_or_else(|_e| { + commands.spawn(TemporaryRenderEntity).id() + }); + (render_entity, (*entity).into()) + }) + .collect(); + (*type_id, entities) + }) + .collect(), + }; let mut commands = commands.entity(render_entity.id()); commands.insert(( ExtractedCamera { @@ -1104,7 +1128,7 @@ pub fn extract_cameras( ), color_grading, }, - visible_entities.clone(), + render_visible_entities, *frustum, )); diff --git a/crates/bevy_render/src/extract_instances.rs b/crates/bevy_render/src/extract_instances.rs index 24cde74619bb1..b1344c0e78648 100644 --- a/crates/bevy_render/src/extract_instances.rs +++ b/crates/bevy_render/src/extract_instances.rs @@ -9,12 +9,12 @@ use core::marker::PhantomData; use bevy_app::{App, Plugin}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::EntityHashMap, prelude::Entity, query::{QueryFilter, QueryItem, ReadOnlyQueryData}, system::{Query, ResMut, Resource}, }; +use crate::sync_world::MainEntityHashMap; use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp}; /// Describes how to extract data needed for rendering from a component or @@ -52,7 +52,7 @@ where /// Stores all extract instances of a type in the render world. #[derive(Resource, Deref, DerefMut)] -pub struct ExtractedInstances(EntityHashMap) +pub struct ExtractedInstances(MainEntityHashMap) where EI: ExtractInstance; @@ -113,7 +113,7 @@ fn extract_all( extracted_instances.clear(); for (entity, other) in &query { if let Some(extract_instance) = EI::extract(other) { - extracted_instances.insert(entity, extract_instance); + extracted_instances.insert(entity.into(), extract_instance); } } } @@ -128,7 +128,7 @@ fn extract_visible( for (entity, view_visibility, other) in &query { if view_visibility.get() { if let Some(extract_instance) = EI::extract(other) { - extracted_instances.insert(entity, extract_instance); + extracted_instances.insert(entity.into(), extract_instance); } } } diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 2fa3e673640e4..5f05bf1870c53 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -37,6 +37,7 @@ use encase::{internal::WriteInto, ShaderSize}; use nonmax::NonMaxU32; pub use rangefinder::*; +use crate::sync_world::MainEntity; use crate::{ batching::{ self, @@ -100,7 +101,7 @@ where /// /// Each bin corresponds to a single batch set. For unbatchable entities, /// prefer `unbatchable_values` instead. - pub(crate) batchable_mesh_values: HashMap>, + pub(crate) batchable_mesh_values: HashMap>, /// A list of `BinKey`s for unbatchable items. /// @@ -120,7 +121,7 @@ where /// entity are simply called in order at rendering time. /// /// See the `custom_phase_item` example for an example of how to use this. - pub non_mesh_items: Vec<(BPI::BinKey, Entity)>, + pub non_mesh_items: Vec<(BPI::BinKey, (Entity, MainEntity))>, /// Information on each batch set. /// @@ -142,8 +143,7 @@ pub struct BinnedRenderPhaseBatch { /// An entity that's *representative* of this batch. /// /// Bevy uses this to fetch the mesh. It can be any entity in the batch. - pub representative_entity: Entity, - + pub representative_entity: (Entity, MainEntity), /// The range of instance indices in this batch. pub instance_range: Range, @@ -157,7 +157,7 @@ pub struct BinnedRenderPhaseBatch { /// Information about the unbatchable entities in a bin. pub(crate) struct UnbatchableBinnedEntities { /// The entities. - pub(crate) entities: Vec, + pub(crate) entities: Vec<(Entity, MainEntity)>, /// The GPU array buffer indices of each unbatchable binned entity. pub(crate) buffer_indices: UnbatchableBinnedEntityIndexSet, @@ -276,7 +276,12 @@ where /// The `phase_type` parameter specifies whether the entity is a /// preprocessable mesh and whether it can be binned with meshes of the same /// type. - pub fn add(&mut self, key: BPI::BinKey, entity: Entity, phase_type: BinnedRenderPhaseType) { + pub fn add( + &mut self, + key: BPI::BinKey, + entity: (Entity, MainEntity), + phase_type: BinnedRenderPhaseType, + ) { match phase_type { BinnedRenderPhaseType::BatchableMesh => { match self.batchable_mesh_values.entry(key.clone()) { @@ -846,6 +851,9 @@ pub trait PhaseItem: Sized + Send + Sync + 'static { /// from the render world . fn entity(&self) -> Entity; + /// The main world entity represented by this `PhaseItem`. + fn main_entity(&self) -> MainEntity; + /// Specifies the [`Draw`] function used to render the item. fn draw_function(&self) -> DrawFunctionId; @@ -1018,7 +1026,7 @@ pub trait BinnedPhaseItem: PhaseItem { /// structures, resulting in significant memory savings. fn new( key: Self::BinKey, - representative_entity: Entity, + representative_entity: (Entity, MainEntity), batch_range: Range, extra_index: PhaseItemExtraIndex, ) -> Self; diff --git a/crates/bevy_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index d32462e56f320..ed6414ece2b24 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -1,5 +1,6 @@ use bevy_app::Plugin; use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::entity::EntityHash; use bevy_ecs::{ component::Component, entity::Entity, @@ -10,6 +11,7 @@ use bevy_ecs::{ world::{Mut, OnAdd, OnRemove, World}, }; use bevy_reflect::Reflect; +use bevy_utils::hashbrown; /// A plugin that synchronizes entities with [`SyncToRenderWorld`] between the main world and the render world. /// @@ -128,10 +130,16 @@ impl RenderEntity { } } +impl From for RenderEntity { + fn from(entity: Entity) -> Self { + RenderEntity(entity) + } +} + /// Component added on the render world entities to keep track of the corresponding main world entity. /// /// Can also be used as a newtype wrapper for main world entities. -#[derive(Component, Deref, Clone, Debug)] +#[derive(Component, Deref, Copy, Clone, Debug, Eq, Hash, PartialEq)] pub struct MainEntity(Entity); impl MainEntity { #[inline] @@ -140,6 +148,18 @@ impl MainEntity { } } +impl From for MainEntity { + fn from(entity: Entity) -> Self { + MainEntity(entity) + } +} + +/// A [`HashMap`](hashbrown::HashMap) pre-configured to use [`EntityHash`] hashing with a [`MainEntity`]. +pub type MainEntityHashMap = hashbrown::HashMap; + +/// A [`HashSet`](hashbrown::HashSet) pre-configured to use [`EntityHash`] hashing with a [`MainEntity`].. +pub type MainEntityHashSet = hashbrown::HashSet; + /// Marker component that indicates that its entity needs to be despawned at the end of the frame. #[derive(Component, Clone, Debug, Default, Reflect)] #[component(storage = "SparseSet")] diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 011686a9a4148..e2e439ec79699 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -17,14 +17,14 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_transform::{components::GlobalTransform, TransformSystem}; use bevy_utils::{Parallel, TypeIdMap}; +use super::NoCpuCulling; +use crate::sync_world::MainEntity; use crate::{ camera::{Camera, CameraProjection}, mesh::{Mesh, Mesh3d, MeshAabb}, primitives::{Aabb, Frustum, Sphere}, }; -use super::NoCpuCulling; - /// User indication of whether an entity is visible. Propagates down the entity hierarchy. /// /// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) who @@ -205,8 +205,8 @@ pub struct NoFrustumCulling; /// /// This component contains all entities which are visible from the currently /// rendered view. The collection is updated automatically by the [`VisibilitySystems::CheckVisibility`] -/// system set, and renderers can use it to optimize rendering of a particular view, to -/// prevent drawing items not visible from that view. +/// system set. Renderers can use the equivalent [`RenderVisibleEntities`] to optimize rendering of +/// a particular view, to prevent drawing items not visible from that view. /// /// This component is intended to be attached to the same entity as the [`Camera`] and /// the [`Frustum`] defining the view. @@ -271,6 +271,49 @@ impl VisibleEntities { } } +/// Collection of entities visible from the current view. +/// +/// This component is extracted from [`VisibleEntities`]. +#[derive(Clone, Component, Default, Debug, Reflect)] +#[reflect(Component, Default, Debug)] +pub struct RenderVisibleEntities { + #[reflect(ignore)] + pub entities: TypeIdMap>, +} + +impl RenderVisibleEntities { + pub fn get(&self) -> &[(Entity, MainEntity)] + where + QF: 'static, + { + match self.entities.get(&TypeId::of::()) { + Some(entities) => &entities[..], + None => &[], + } + } + + pub fn iter(&self) -> impl DoubleEndedIterator + where + QF: 'static, + { + self.get::().iter() + } + + pub fn len(&self) -> usize + where + QF: 'static, + { + self.get::().len() + } + + pub fn is_empty(&self) -> bool + where + QF: 'static, + { + self.get::().is_empty() + } +} + #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum VisibilitySystems { /// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems, diff --git a/crates/bevy_render/src/view/visibility/range.rs b/crates/bevy_render/src/view/visibility/range.rs index 74e089ed9ffc1..c476c9e9c640e 100644 --- a/crates/bevy_render/src/view/visibility/range.rs +++ b/crates/bevy_render/src/view/visibility/range.rs @@ -22,6 +22,8 @@ use bevy_utils::{prelude::default, HashMap}; use nonmax::NonMaxU16; use wgpu::{BufferBindingType, BufferUsages}; +use super::{check_visibility, VisibilitySystems}; +use crate::sync_world::{MainEntity, MainEntityHashMap}; use crate::{ camera::Camera, mesh::Mesh3d, @@ -31,8 +33,6 @@ use crate::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use super::{check_visibility, VisibilitySystems}; - /// We need at least 4 storage buffer bindings available to enable the /// visibility range buffer. /// @@ -195,7 +195,7 @@ impl VisibilityRange { #[derive(Resource)] pub struct RenderVisibilityRanges { /// Information corresponding to each entity. - entities: EntityHashMap, + entities: MainEntityHashMap, /// Maps a [`VisibilityRange`] to its index within the `buffer`. /// @@ -246,7 +246,7 @@ impl RenderVisibilityRanges { } /// Inserts a new entity into the [`RenderVisibilityRanges`]. - fn insert(&mut self, entity: Entity, visibility_range: &VisibilityRange) { + fn insert(&mut self, entity: MainEntity, visibility_range: &VisibilityRange) { // Grab a slot in the GPU buffer, or take the existing one if there // already is one. let buffer_index = *self @@ -276,14 +276,14 @@ impl RenderVisibilityRanges { /// /// If the entity has no visible range, returns `None`. #[inline] - pub fn lod_index_for_entity(&self, entity: Entity) -> Option { + pub fn lod_index_for_entity(&self, entity: MainEntity) -> Option { self.entities.get(&entity).map(|info| info.buffer_index) } /// Returns true if the entity has a visibility range and it isn't abrupt: /// i.e. if it has a crossfade. #[inline] - pub fn entity_has_crossfading_visibility_ranges(&self, entity: Entity) -> bool { + pub fn entity_has_crossfading_visibility_ranges(&self, entity: MainEntity) -> bool { self.entities .get(&entity) .is_some_and(|info| !info.is_abrupt) @@ -423,7 +423,7 @@ pub fn extract_visibility_ranges( render_visibility_ranges.clear(); for (entity, visibility_range) in visibility_ranges_query.iter() { - render_visibility_ranges.insert(entity, visibility_range); + render_visibility_ranges.insert(entity.into(), visibility_range); } } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 847a3104fcf90..2d67164a0399c 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -1,5 +1,9 @@ #![expect(deprecated)] +use crate::{ + DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, + SetMesh2dBindGroup, SetMesh2dViewBindGroup, +}; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetApp, AssetId, AssetServer, Handle}; use bevy_core_pipeline::{ @@ -8,12 +12,13 @@ use bevy_core_pipeline::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::EntityHashMap, prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; use bevy_math::FloatOrd; use bevy_reflect::{prelude::ReflectDefault, Reflect}; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; +use bevy_render::view::RenderVisibleEntities; use bevy_render::{ mesh::{MeshVertexBufferLayoutRef, RenderMesh}, render_asset::{ @@ -30,7 +35,7 @@ use bevy_render::{ SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }, renderer::RenderDevice, - view::{ExtractedView, InheritedVisibility, Msaa, ViewVisibility, Visibility, VisibleEntities}, + view::{ExtractedView, InheritedVisibility, Msaa, ViewVisibility, Visibility}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::{GlobalTransform, Transform}; @@ -38,11 +43,6 @@ use bevy_utils::tracing::error; use core::{hash::Hash, marker::PhantomData}; use derive_more::derive::From; -use crate::{ - DrawMesh2d, Mesh2d, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, - SetMesh2dBindGroup, SetMesh2dViewBindGroup, -}; - use super::ColorMaterial; /// Materials are used alongside [`Material2dPlugin`], [`Mesh2d`], and [`MeshMaterial2d`] @@ -319,7 +319,7 @@ where } #[derive(Resource, Deref, DerefMut)] -pub struct RenderMaterial2dInstances(EntityHashMap>); +pub struct RenderMaterial2dInstances(MainEntityHashMap>); impl Default for RenderMaterial2dInstances { fn default() -> Self { @@ -339,7 +339,7 @@ fn extract_mesh_materials_2d( ) { for (entity, view_visibility, material) in &query { if view_visibility.get() { - material_instances.insert(entity, material.id()); + material_instances.insert(entity.into(), material.id()); } } } @@ -353,7 +353,7 @@ pub(crate) fn extract_default_materials_2d( for (entity, view_visibility) in &query { if view_visibility.get() { - material_instances.insert(entity, default_material); + material_instances.insert(entity.into(), default_material); } } } @@ -501,7 +501,8 @@ impl RenderCommand

) -> RenderCommandResult { let materials = materials.into_inner(); let material_instances = material_instances.into_inner(); - let Some(material_instance) = material_instances.get(&item.entity()) else { + let Some(material_instance) = material_instances.get(&MainEntity::from(item.entity())) + else { return RenderCommandResult::Skip; }; let Some(material2d) = materials.get(*material_instance) else { @@ -553,7 +554,7 @@ pub fn queue_material2d_meshes( views: Query<( Entity, &ExtractedView, - &VisibleEntities, + &RenderVisibleEntities, &Msaa, Option<&Tonemapping>, Option<&DebandDither>, @@ -592,7 +593,7 @@ pub fn queue_material2d_meshes( view_key |= Mesh2dPipelineKey::DEBAND_DITHER; } } - for visible_entity in visible_entities.iter::>() { + for (render_entity, visible_entity) in visible_entities.iter::>() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; @@ -640,7 +641,7 @@ pub fn queue_material2d_meshes( }; opaque_phase.add( bin_key, - *visible_entity, + (*render_entity, *visible_entity), BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching), ); } @@ -653,13 +654,13 @@ pub fn queue_material2d_meshes( }; alpha_mask_phase.add( bin_key, - *visible_entity, + (*render_entity, *visible_entity), BinnedRenderPhaseType::mesh(mesh_instance.automatic_batching), ); } AlphaMode2d::Blend => { transparent_phase.add(Transparent2d { - entity: *visible_entity, + entity: (*render_entity, *visible_entity), draw_function: draw_transparent_2d, pipeline: pipeline_id, // NOTE: Back-to-front ordering for transparent with ascending sort means far should have the diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index b0b5f39bc9af8..fd65b19d4a02f 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -1,6 +1,7 @@ use bevy_app::Plugin; use bevy_asset::{load_internal_asset, AssetId, Handle}; +use crate::Material2dBindGroupId; use bevy_core_pipeline::{ core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT}, tonemapping::{ @@ -9,12 +10,12 @@ use bevy_core_pipeline::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::EntityHashMap, prelude::*, query::ROQueryItem, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Affine3, Vec4}; +use bevy_render::sync_world::{MainEntity, MainEntityHashMap}; use bevy_render::{ batching::{ gpu_preprocessing::IndirectParameters, @@ -46,8 +47,6 @@ use bevy_transform::components::GlobalTransform; use bevy_utils::tracing::error; use nonmax::NonMaxU32; -use crate::Material2dBindGroupId; - #[derive(Default)] pub struct Mesh2dRenderPlugin; @@ -202,7 +201,7 @@ pub struct RenderMesh2dInstance { } #[derive(Default, Resource, Deref, DerefMut)] -pub struct RenderMesh2dInstances(EntityHashMap); +pub struct RenderMesh2dInstances(MainEntityHashMap); #[derive(Component)] pub struct Mesh2dMarker; @@ -226,7 +225,7 @@ pub fn extract_mesh2d( continue; } render_mesh_instances.insert( - entity, + entity.into(), RenderMesh2dInstance { transforms: Mesh2dTransforms { world_from_local: (&transform.affine()).into(), @@ -358,9 +357,9 @@ impl GetBatchData for Mesh2dPipeline { fn get_batch_data( (mesh_instances, _, _): &SystemParamItem, - entity: Entity, + (_entity, main_entity): (Entity, MainEntity), ) -> Option<(Self::BufferData, Option)> { - let mesh_instance = mesh_instances.get(&entity)?; + let mesh_instance = mesh_instances.get(&main_entity)?; Some(( (&mesh_instance.transforms).into(), mesh_instance.automatic_batching.then_some(( @@ -376,15 +375,15 @@ impl GetFullBatchData for Mesh2dPipeline { fn get_binned_batch_data( (mesh_instances, _, _): &SystemParamItem, - entity: Entity, + (_entity, main_entity): (Entity, MainEntity), ) -> Option { - let mesh_instance = mesh_instances.get(&entity)?; + let mesh_instance = mesh_instances.get(&main_entity)?; Some((&mesh_instance.transforms).into()) } fn get_index_and_compare_data( _: &SystemParamItem, - _query_item: Entity, + _query_item: (Entity, MainEntity), ) -> Option<(NonMaxU32, Option)> { error!( "`get_index_and_compare_data` is only intended for GPU mesh uniform building, \ @@ -395,7 +394,7 @@ impl GetFullBatchData for Mesh2dPipeline { fn get_binned_index( _: &SystemParamItem, - _query_item: Entity, + _query_item: (Entity, MainEntity), ) -> Option { error!( "`get_binned_index` is only intended for GPU mesh uniform building, \ @@ -407,10 +406,10 @@ impl GetFullBatchData for Mesh2dPipeline { fn get_batch_indirect_parameters_index( (mesh_instances, meshes, mesh_allocator): &SystemParamItem, indirect_parameters_buffer: &mut bevy_render::batching::gpu_preprocessing::IndirectParametersBuffer, - entity: Entity, + (_entity, main_entity): (Entity, MainEntity), instance_index: u32, ) -> Option { - let mesh_instance = mesh_instances.get(&entity)?; + let mesh_instance = mesh_instances.get(&main_entity)?; let mesh = meshes.get(mesh_instance.mesh_asset_id)?; let vertex_buffer_slice = mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id)?; @@ -817,7 +816,7 @@ impl RenderCommand

for DrawMesh2d { let mesh_allocator = mesh_allocator.into_inner(); let Some(RenderMesh2dInstance { mesh_asset_id, .. }) = - render_mesh2d_instances.get(&item.entity()) + render_mesh2d_instances.get(&item.main_entity()) else { return RenderCommandResult::Skip; }; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index c3e9ba9b5b96c..300a16ce637c6 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -14,12 +14,13 @@ use bevy_core_pipeline::{ }, }; use bevy_ecs::{ - entity::EntityHashMap, prelude::*, query::ROQueryItem, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4}; +use bevy_render::sync_world::MainEntity; +use bevy_render::view::RenderVisibleEntities; use bevy_render::{ render_asset::RenderAssets, render_phase::{ @@ -38,7 +39,7 @@ use bevy_render::{ }, view::{ ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, - ViewVisibility, VisibleEntities, + ViewVisibility, }, Extract, }; @@ -346,7 +347,7 @@ pub struct ExtractedSprite { #[derive(Resource, Default)] pub struct ExtractedSprites { - pub sprites: EntityHashMap, + pub sprites: HashMap<(Entity, MainEntity), ExtractedSprite>, } #[derive(Resource, Default)] @@ -392,7 +393,15 @@ pub fn extract_sprites( extracted_sprites.sprites.extend( slices .extract_sprites(transform, original_entity, sprite) - .map(|e| (commands.spawn(TemporaryRenderEntity).id(), e)), + .map(|e| { + ( + ( + commands.spawn(TemporaryRenderEntity).id(), + original_entity.into(), + ), + e, + ) + }), ); } else { let atlas_rect = sprite @@ -413,7 +422,7 @@ pub fn extract_sprites( // PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive extracted_sprites.sprites.insert( - entity.id(), + (entity.id(), original_entity.into()), ExtractedSprite { color: sprite.color.into(), transform: *transform, @@ -498,7 +507,7 @@ pub fn queue_sprites( mut transparent_render_phases: ResMut>, mut views: Query<( Entity, - &VisibleEntities, + &RenderVisibleEntities, &ExtractedView, &Msaa, Option<&Tonemapping>, @@ -544,14 +553,14 @@ pub fn queue_sprites( view_entities.extend( visible_entities .iter::() - .map(|e| e.index() as usize), + .map(|(_, e)| e.index() as usize), ); transparent_phase .items .reserve(extracted_sprites.sprites.len()); - for (entity, extracted_sprite) in extracted_sprites.sprites.iter() { + for ((entity, main_entity), extracted_sprite) in extracted_sprites.sprites.iter() { let index = extracted_sprite.original_entity.unwrap_or(*entity).index(); if !view_entities.contains(index as usize) { @@ -565,7 +574,7 @@ pub fn queue_sprites( transparent_phase.add(Transparent2d { draw_function: draw_sprite_function, pipeline, - entity: *entity, + entity: (*entity, *main_entity), sort_key, // batch_range and dynamic_offset will be calculated in prepare_sprites batch_range: 0..0, @@ -739,7 +748,7 @@ pub fn prepare_sprite_image_bind_groups( batch_item_index = item_index; batches.push(( - item.entity, + item.entity(), SpriteBatch { image_handle_id: batch_image_handle, range: index..index, diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 405783bc2153b..03fa387e05a34 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -193,7 +193,10 @@ pub fn extract_text2d_sprite( let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); extracted_sprites.sprites.insert( - commands.spawn(TemporaryRenderEntity).id(), + ( + commands.spawn(TemporaryRenderEntity).id(), + original_entity.into(), + ), ExtractedSprite { transform: transform * GlobalTransform::from_translation(position.extend(0.)), color, diff --git a/crates/bevy_ui/src/render/box_shadow.rs b/crates/bevy_ui/src/render/box_shadow.rs index 8f56581ce6f9d..b874a93919e64 100644 --- a/crates/bevy_ui/src/render/box_shadow.rs +++ b/crates/bevy_ui/src/render/box_shadow.rs @@ -1,5 +1,9 @@ use core::{hash::Hash, ops::Range}; +use crate::{ + BoxShadow, CalculatedClip, DefaultUiCamera, Node, RenderUiSystem, ResolvedBorderRadius, + TargetCamera, TransparentUi, UiBoxShadowSamples, UiScale, Val, +}; use bevy_app::prelude::*; use bevy_asset::*; use bevy_color::{Alpha, ColorToComponents, LinearRgba}; @@ -13,6 +17,7 @@ use bevy_ecs::{ }, }; use bevy_math::{vec2, FloatOrd, Mat4, Rect, Vec2, Vec3Swizzles, Vec4Swizzles}; +use bevy_render::sync_world::MainEntity; use bevy_render::RenderApp; use bevy_render::{ camera::Camera, @@ -27,11 +32,6 @@ use bevy_render::{ use bevy_transform::prelude::GlobalTransform; use bytemuck::{Pod, Zeroable}; -use crate::{ - BoxShadow, CalculatedClip, DefaultUiCamera, Node, RenderUiSystem, ResolvedBorderRadius, - TargetCamera, TransparentUi, UiBoxShadowSamples, UiScale, Val, -}; - use super::{QUAD_INDICES, QUAD_VERTEX_POSITIONS}; pub const BOX_SHADOW_SHADER_HANDLE: Handle = Handle::weak_from_u128(17717747047134343426); @@ -221,6 +221,7 @@ pub struct ExtractedBoxShadow { pub radius: ResolvedBorderRadius, pub blur_radius: f32, pub size: Vec2, + pub main_entity: MainEntity, } /// List of extracted shadows to be sorted and queued for rendering @@ -237,6 +238,7 @@ pub fn extract_shadows( camera_query: Extract>, box_shadow_query: Extract< Query<( + Entity, &Node, &GlobalTransform, &ViewVisibility, @@ -247,7 +249,8 @@ pub fn extract_shadows( >, mapping: Extract>, ) { - for (uinode, transform, view_visibility, box_shadow, clip, camera) in &box_shadow_query { + for (entity, uinode, transform, view_visibility, box_shadow, clip, camera) in &box_shadow_query + { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) else { continue; @@ -322,6 +325,7 @@ pub fn extract_shadows( radius, blur_radius, size: shadow_size, + main_entity: entity.into(), }, ); } @@ -359,7 +363,7 @@ pub fn queue_shadows( transparent_phase.add(TransparentUi { draw_function, pipeline, - entity: *entity, + entity: (*entity, extracted_shadow.main_entity), sort_key: ( FloatOrd(extracted_shadow.stack_index as f32 - 0.1), entity.index(), @@ -402,7 +406,7 @@ pub fn prepare_shadows( while item_index < ui_phase.items.len() { let item = &mut ui_phase.items[item_index]; - if let Some(box_shadow) = extracted_shadows.box_shadows.get(item.entity) { + if let Some(box_shadow) = extracted_shadows.box_shadows.get(item.entity()) { let uinode_rect = box_shadow.rect; let rect_size = uinode_rect.size().extend(1.0); @@ -485,7 +489,7 @@ pub fn prepare_shadows( } batches.push(( - item.entity, + item.entity(), UiShadowsBatch { range: vertices_index..vertices_index + 6, camera: box_shadow.camera_entity, diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 87fce7413c9dc..645356b5d6a0f 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -18,6 +18,7 @@ use bevy_ecs::entity::{EntityHashMap, EntityHashSet}; use bevy_ecs::prelude::*; use bevy_math::{FloatOrd, Mat4, Rect, URect, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4Swizzles}; use bevy_render::render_phase::ViewSortedRenderPhases; +use bevy_render::sync_world::MainEntity; use bevy_render::texture::TRANSPARENT_IMAGE_HANDLE; use bevy_render::{ camera::Camera, @@ -167,6 +168,7 @@ pub struct ExtractedUiNode { // Nodes with ambiguous camera will be ignored. pub camera_entity: Entity, pub item: ExtractedUiItem, + pub main_entity: MainEntity, } /// The type of UI node. @@ -224,6 +226,7 @@ pub fn extract_uinode_background_colors( default_ui_camera: Extract, uinode_query: Extract< Query<( + Entity, &Node, &GlobalTransform, &ViewVisibility, @@ -234,7 +237,9 @@ pub fn extract_uinode_background_colors( >, mapping: Extract>, ) { - for (uinode, transform, view_visibility, clip, camera, background_color) in &uinode_query { + for (entity, uinode, transform, view_visibility, clip, camera, background_color) in + &uinode_query + { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) else { continue; @@ -270,6 +275,7 @@ pub fn extract_uinode_background_colors( border_radius: uinode.border_radius(), node_type: NodeType::Rect, }, + main_entity: entity.into(), }, ); } @@ -284,6 +290,7 @@ pub fn extract_uinode_images( uinode_query: Extract< Query< ( + Entity, &Node, &GlobalTransform, &ViewVisibility, @@ -297,7 +304,7 @@ pub fn extract_uinode_images( >, mapping: Extract>, ) { - for (uinode, transform, view_visibility, clip, camera, image, atlas) in &uinode_query { + for (entity, uinode, transform, view_visibility, clip, camera, image, atlas) in &uinode_query { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) else { continue; @@ -360,6 +367,7 @@ pub fn extract_uinode_images( border_radius: uinode.border_radius, node_type: NodeType::Rect, }, + main_entity: entity.into(), }, ); } @@ -371,6 +379,7 @@ pub fn extract_uinode_borders( default_ui_camera: Extract, uinode_query: Extract< Query<( + Entity, &Node, &GlobalTransform, &ViewVisibility, @@ -384,6 +393,7 @@ pub fn extract_uinode_borders( let image = AssetId::::default(); for ( + entity, uinode, global_transform, view_visibility, @@ -435,6 +445,7 @@ pub fn extract_uinode_borders( border_radius: uinode.border_radius(), node_type: NodeType::Border, }, + main_entity: entity.into(), }, ); } @@ -463,6 +474,7 @@ pub fn extract_uinode_borders( border_radius: uinode.outline_radius(), node_type: NodeType::Border, }, + main_entity: entity.into(), }, ); } @@ -584,6 +596,7 @@ pub fn extract_text_sections( ui_scale: Extract>, uinode_query: Extract< Query<( + Entity, &Node, &GlobalTransform, &ViewVisibility, @@ -601,6 +614,7 @@ pub fn extract_text_sections( let default_ui_camera = default_ui_camera.get(); for ( + entity, uinode, global_transform, view_visibility, @@ -706,6 +720,7 @@ pub fn extract_text_sections( atlas_scaling: Vec2::splat(inverse_scale_factor), range: start..end, }, + main_entity: entity.into(), }, ); start = end; @@ -809,7 +824,7 @@ pub fn queue_uinodes( transparent_phase.add(TransparentUi { draw_function, pipeline, - entity: *entity, + entity: (*entity, extracted_uinode.main_entity), sort_key: ( FloatOrd(extracted_uinode.stack_index as f32), entity.index(), @@ -875,7 +890,7 @@ pub fn prepare_uinodes( for item_index in 0..ui_phase.items.len() { let item = &mut ui_phase.items[item_index]; - if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(&item.entity) { + if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(&item.entity()) { let mut existing_batch = batches.last_mut(); if batch_image_handle == AssetId::invalid() @@ -896,7 +911,7 @@ pub fn prepare_uinodes( camera: extracted_uinode.camera_entity, }; - batches.push((item.entity, new_batch)); + batches.push((item.entity(), new_batch)); image_bind_groups .values diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 08ee870668fcf..6c035c3838efc 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -7,6 +7,7 @@ use bevy_ecs::{ system::{lifetimeless::*, SystemParamItem}, }; use bevy_math::FloatOrd; +use bevy_render::sync_world::MainEntity; use bevy_render::{ camera::ExtractedCamera, render_graph::*, @@ -91,7 +92,7 @@ impl Node for UiPassNode { pub struct TransparentUi { pub sort_key: (FloatOrd, u32), - pub entity: Entity, + pub entity: (Entity, MainEntity), pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, pub batch_range: Range, @@ -101,7 +102,11 @@ pub struct TransparentUi { impl PhaseItem for TransparentUi { #[inline] fn entity(&self) -> Entity { - self.entity + self.entity.0 + } + + fn main_entity(&self) -> MainEntity { + self.entity.1 } #[inline] diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index 91c05bc6528f2..990f48555c912 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -1,5 +1,6 @@ use core::{hash::Hash, marker::PhantomData, ops::Range}; +use crate::*; use bevy_asset::*; use bevy_ecs::{ prelude::Component, @@ -11,6 +12,7 @@ use bevy_ecs::{ }, }; use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles}; +use bevy_render::sync_world::MainEntity; use bevy_render::{ extract_component::ExtractComponentPlugin, globals::{GlobalsBuffer, GlobalsUniform}, @@ -26,8 +28,6 @@ use bevy_render::{ use bevy_transform::prelude::GlobalTransform; use bytemuck::{Pod, Zeroable}; -use crate::*; - pub const UI_MATERIAL_SHADER_HANDLE: Handle = Handle::weak_from_u128(10074188772096983955); const UI_VERTEX_OUTPUT_SHADER_HANDLE: Handle = Handle::weak_from_u128(10123618247720234751); @@ -341,6 +341,7 @@ pub struct ExtractedUiMaterialNode { // it is defaulted to a single camera if only one exists. // Nodes with ambiguous camera will be ignored. pub camera_entity: Entity, + pub main_entity: MainEntity, } #[derive(Resource)] @@ -364,6 +365,7 @@ pub fn extract_ui_material_nodes( uinode_query: Extract< Query< ( + Entity, &Node, &GlobalTransform, &UiMaterialHandle, @@ -379,7 +381,7 @@ pub fn extract_ui_material_nodes( // If there is only one camera, we use it as default let default_single_camera = default_ui_camera.get(); - for (uinode, transform, handle, view_visibility, clip, camera) in uinode_query.iter() { + for (entity, uinode, transform, handle, view_visibility, clip, camera) in uinode_query.iter() { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) else { continue; }; @@ -418,6 +420,7 @@ pub fn extract_ui_material_nodes( border, clip: clip.map(|clip| clip.clip), camera_entity: camera_entity.id(), + main_entity: entity.into(), }, ); } @@ -456,7 +459,7 @@ pub fn prepare_uimaterial_nodes( for item_index in 0..ui_phase.items.len() { let item = &mut ui_phase.items[item_index]; - if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(item.entity) { + if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(item.entity()) { let mut existing_batch = batches .last_mut() .filter(|_| batch_shader_handle == extracted_uinode.material); @@ -470,7 +473,7 @@ pub fn prepare_uimaterial_nodes( material: extracted_uinode.material, }; - batches.push((item.entity, new_batch)); + batches.push((item.entity(), new_batch)); existing_batch = batches.last_mut(); } @@ -645,7 +648,7 @@ pub fn queue_ui_material_nodes( transparent_phase.add(TransparentUi { draw_function, pipeline, - entity: *entity, + entity: (*entity, extracted_uinode.main_entity), sort_key: ( FloatOrd(extracted_uinode.stack_index as f32), entity.index(), diff --git a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs index f33f50b1717f3..b33d2707d4522 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -1,5 +1,6 @@ use core::{hash::Hash, ops::Range}; +use crate::*; use bevy_asset::*; use bevy_color::{Alpha, ColorToComponents, LinearRgba}; use bevy_ecs::{ @@ -11,6 +12,7 @@ use bevy_ecs::{ }, }; use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles}; +use bevy_render::sync_world::MainEntity; use bevy_render::{ render_asset::RenderAssets, render_phase::*, @@ -30,8 +32,6 @@ use bevy_utils::HashMap; use binding_types::{sampler, texture_2d}; use bytemuck::{Pod, Zeroable}; -use crate::*; - pub const UI_SLICER_SHADER_HANDLE: Handle = Handle::weak_from_u128(11156288772117983964); pub struct UiTextureSlicerPlugin; @@ -235,6 +235,7 @@ pub struct ExtractedUiTextureSlice { pub image_scale_mode: ImageScaleMode, pub flip_x: bool, pub flip_y: bool, + pub main_entity: MainEntity, } #[derive(Resource, Default)] @@ -249,6 +250,7 @@ pub fn extract_ui_texture_slices( texture_atlases: Extract>>, slicers_query: Extract< Query<( + Entity, &Node, &GlobalTransform, &ViewVisibility, @@ -261,8 +263,17 @@ pub fn extract_ui_texture_slices( >, mapping: Extract>, ) { - for (uinode, transform, view_visibility, clip, camera, image, image_scale_mode, atlas) in - &slicers_query + for ( + entity, + uinode, + transform, + view_visibility, + clip, + camera, + image, + image_scale_mode, + atlas, + ) in &slicers_query { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get()) else { @@ -313,6 +324,7 @@ pub fn extract_ui_texture_slices( atlas_rect, flip_x: image.flip_x, flip_y: image.flip_y, + main_entity: entity.into(), }, ); } @@ -346,7 +358,7 @@ pub fn queue_ui_slices( transparent_phase.add(TransparentUi { draw_function, pipeline, - entity: *entity, + entity: (*entity, extracted_slicer.main_entity), sort_key: ( FloatOrd(extracted_slicer.stack_index as f32), entity.index(), @@ -407,7 +419,7 @@ pub fn prepare_ui_slices( for item_index in 0..ui_phase.items.len() { let item = &mut ui_phase.items[item_index]; - if let Some(texture_slices) = extracted_slices.slices.get(item.entity) { + if let Some(texture_slices) = extracted_slices.slices.get(item.entity()) { let mut existing_batch = batches.last_mut(); if batch_image_handle == AssetId::invalid() @@ -429,7 +441,7 @@ pub fn prepare_ui_slices( camera: texture_slices.camera_entity, }; - batches.push((item.entity, new_batch)); + batches.push((item.entity(), new_batch)); image_bind_groups .values diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index e3b471610b8b1..75c4c9b37b3d5 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -8,7 +8,6 @@ use bevy::{ color::palettes::basic::YELLOW, core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}, - ecs::entity::EntityHashMap, math::{ops, FloatOrd}, prelude::*, render::{ @@ -25,8 +24,9 @@ use bevy::{ SpecializedRenderPipeline, SpecializedRenderPipelines, StencilFaceState, StencilState, TextureFormat, VertexBufferLayout, VertexFormat, VertexState, VertexStepMode, }, + sync_world::MainEntityHashMap, texture::BevyDefault, - view::{ExtractedView, ViewTarget, VisibleEntities}, + view::{ExtractedView, RenderVisibleEntities, ViewTarget}, Extract, Render, RenderApp, RenderSet, }, sprite::{ @@ -290,7 +290,7 @@ pub const COLORED_MESH2D_SHADER_HANDLE: Handle = /// Our custom pipeline needs its own instance storage #[derive(Resource, Deref, DerefMut, Default)] -pub struct RenderColoredMesh2dInstances(EntityHashMap); +pub struct RenderColoredMesh2dInstances(MainEntityHashMap); impl Plugin for ColoredMesh2dPlugin { fn build(&self, app: &mut App) { @@ -346,7 +346,7 @@ pub fn extract_colored_mesh2d( values.push((entity, ColoredMesh2d)); render_mesh_instances.insert( - entity, + entity.into(), RenderMesh2dInstance { mesh_asset_id: handle.0.id(), transforms, @@ -369,7 +369,7 @@ pub fn queue_colored_mesh2d( render_meshes: Res>, render_mesh_instances: Res, mut transparent_render_phases: ResMut>, - views: Query<(Entity, &VisibleEntities, &ExtractedView, &Msaa)>, + views: Query<(Entity, &RenderVisibleEntities, &ExtractedView, &Msaa)>, ) { if render_mesh_instances.is_empty() { return; @@ -386,7 +386,7 @@ pub fn queue_colored_mesh2d( | Mesh2dPipelineKey::from_hdr(view.hdr); // Queue all entities visible to that view - for visible_entity in visible_entities.iter::>() { + for (render_entity, visible_entity) in visible_entities.iter::>() { if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) { let mesh2d_handle = mesh_instance.mesh_asset_id; let mesh2d_transforms = &mesh_instance.transforms; @@ -402,7 +402,7 @@ pub fn queue_colored_mesh2d( let mesh_z = mesh2d_transforms.world_from_local.translation.z; transparent_phase.add(Transparent2d { - entity: *visible_entity, + entity: (*render_entity, *visible_entity), draw_function: draw_colored_mesh2d, pipeline: pipeline_id, // The 2d render items are sorted according to their z value before rendering, diff --git a/examples/shader/custom_phase_item.rs b/examples/shader/custom_phase_item.rs index bb32a62c4eb91..5dbff53f2c727 100644 --- a/examples/shader/custom_phase_item.rs +++ b/examples/shader/custom_phase_item.rs @@ -31,7 +31,7 @@ use bevy::{ }, renderer::{RenderDevice, RenderQueue}, texture::BevyDefault as _, - view::{self, ExtractedView, VisibilitySystems, VisibleEntities}, + view::{self, ExtractedView, RenderVisibleEntities, VisibilitySystems}, Render, RenderApp, RenderSet, }, }; @@ -234,7 +234,7 @@ fn queue_custom_phase_item( mut opaque_render_phases: ResMut>, opaque_draw_functions: Res>, mut specialized_render_pipelines: ResMut>, - views: Query<(Entity, &VisibleEntities, &Msaa), With>, + views: Query<(Entity, &RenderVisibleEntities, &Msaa), With>, ) { let draw_custom_phase_item = opaque_draw_functions .read() diff --git a/examples/shader/custom_shader_instancing.rs b/examples/shader/custom_shader_instancing.rs index 839d0ea2a13d7..ac0bbf2d6a602 100644 --- a/examples/shader/custom_shader_instancing.rs +++ b/examples/shader/custom_shader_instancing.rs @@ -29,6 +29,7 @@ use bevy::{ }, render_resource::*, renderer::RenderDevice, + sync_world::MainEntity, view::{ExtractedView, NoFrustumCulling}, Render, RenderApp, RenderSet, }, @@ -127,7 +128,7 @@ fn queue_custom( pipeline_cache: Res, meshes: Res>, render_mesh_instances: Res, - material_meshes: Query>, + material_meshes: Query<(Entity, &MainEntity), With>, mut transparent_render_phases: ResMut>, views: Query<(Entity, &ExtractedView, &Msaa)>, ) { @@ -142,8 +143,9 @@ fn queue_custom( let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); let rangefinder = view.rangefinder3d(); - for entity in &material_meshes { - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(entity) else { + for (entity, main_entity) in &material_meshes { + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*main_entity) + else { continue; }; let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else { @@ -155,7 +157,7 @@ fn queue_custom( .specialize(&pipeline_cache, &custom_pipeline, key, &mesh.layout) .unwrap(); transparent_phase.add(Transparent3d { - entity, + entity: (entity, *main_entity), pipeline, draw_function: draw_custom, distance: rangefinder.distance_translation(&mesh_instance.translation), @@ -268,7 +270,7 @@ impl RenderCommand

for DrawMeshInstanced { // A borrow check workaround. let mesh_allocator = mesh_allocator.into_inner(); - let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.entity()) + let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.main_entity()) else { return RenderCommandResult::Skip; }; diff --git a/examples/shader/specialized_mesh_pipeline.rs b/examples/shader/specialized_mesh_pipeline.rs index cc8cae101d853..291c61bae9a67 100644 --- a/examples/shader/specialized_mesh_pipeline.rs +++ b/examples/shader/specialized_mesh_pipeline.rs @@ -29,7 +29,7 @@ use bevy::{ SpecializedMeshPipelines, TextureFormat, VertexState, }, texture::BevyDefault as _, - view::{self, ExtractedView, ViewTarget, VisibilitySystems, VisibleEntities}, + view::{self, ExtractedView, RenderVisibleEntities, ViewTarget, VisibilitySystems}, Render, RenderApp, RenderSet, }, }; @@ -280,7 +280,7 @@ fn queue_custom_mesh_pipeline( mut opaque_render_phases: ResMut>, opaque_draw_functions: Res>, mut specialized_mesh_pipelines: ResMut>, - views: Query<(Entity, &VisibleEntities, &ExtractedView, &Msaa), With>, + views: Query<(Entity, &RenderVisibleEntities, &ExtractedView, &Msaa), With>, render_meshes: Res>, render_mesh_instances: Res, ) { @@ -303,7 +303,7 @@ fn queue_custom_mesh_pipeline( // Find all the custom rendered entities that are visible from this // view. - for &visible_entity in view_visible_entities + for &(render_entity, visible_entity) in view_visible_entities .get::() .iter() { @@ -348,7 +348,7 @@ fn queue_custom_mesh_pipeline( material_bind_group_id: None, lightmap_image: None, }, - visible_entity, + (render_entity, visible_entity), // This example supports batching, but if your pipeline doesn't // support it you can use `BinnedRenderPhaseType::UnbatchableMesh` BinnedRenderPhaseType::BatchableMesh,