Refactor the render instance logic in #9903 so that it's easier for other components to adopt. (#10002)
# Objective Currently, the only way for custom components that participate in rendering to opt into the higher-performance extraction method in #9903 is to implement the `RenderInstances` data structure and the extraction logic manually. This is inconvenient compared to the `ExtractComponent` API. ## Solution This commit creates a new `RenderInstance` trait that mirrors the existing `ExtractComponent` method but uses the higher-performance approach that #9903 uses. Additionally, `RenderInstance` is more flexible than `ExtractComponent`, because it can extract multiple components at once. This makes high-performance rendering components essentially as easy to write as the existing ones based on component extraction. --- ## Changelog ### Added A new `RenderInstance` trait is available mirroring `ExtractComponent`, but using a higher-performance method to extract one or more components to the render world. If you have custom components that rendering takes into account, you may consider migration from `ExtractComponent` to `RenderInstance` for higher performance.
This commit is contained in:
		
							parent
							
								
									1f95a484ed
								
							
						
					
					
						commit
						e67d63aa79
					
				| @ -20,6 +20,7 @@ use bevy_render::{ | |||||||
|     mesh::{Mesh, MeshVertexBufferLayout}, |     mesh::{Mesh, MeshVertexBufferLayout}, | ||||||
|     prelude::Image, |     prelude::Image, | ||||||
|     render_asset::{prepare_assets, RenderAssets}, |     render_asset::{prepare_assets, RenderAssets}, | ||||||
|  |     render_instances::{RenderInstancePlugin, RenderInstances}, | ||||||
|     render_phase::{ |     render_phase::{ | ||||||
|         AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, |         AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, | ||||||
|         RenderPhase, SetItemPipeline, TrackedRenderPass, |         RenderPhase, SetItemPipeline, TrackedRenderPass, | ||||||
| @ -31,10 +32,10 @@ use bevy_render::{ | |||||||
|     }, |     }, | ||||||
|     renderer::RenderDevice, |     renderer::RenderDevice, | ||||||
|     texture::FallbackImage, |     texture::FallbackImage, | ||||||
|     view::{ExtractedView, Msaa, ViewVisibility, VisibleEntities}, |     view::{ExtractedView, Msaa, VisibleEntities}, | ||||||
|     Extract, ExtractSchedule, Render, RenderApp, RenderSet, |     Extract, ExtractSchedule, Render, RenderApp, RenderSet, | ||||||
| }; | }; | ||||||
| use bevy_utils::{tracing::error, EntityHashMap, HashMap, HashSet}; | use bevy_utils::{tracing::error, HashMap, HashSet}; | ||||||
| use std::hash::Hash; | use std::hash::Hash; | ||||||
| use std::marker::PhantomData; | use std::marker::PhantomData; | ||||||
| 
 | 
 | ||||||
| @ -176,7 +177,8 @@ where | |||||||
|     M::Data: PartialEq + Eq + Hash + Clone, |     M::Data: PartialEq + Eq + Hash + Clone, | ||||||
| { | { | ||||||
|     fn build(&self, app: &mut App) { |     fn build(&self, app: &mut App) { | ||||||
|         app.init_asset::<M>(); |         app.init_asset::<M>() | ||||||
|  |             .add_plugins(RenderInstancePlugin::<AssetId<M>>::extract_visible()); | ||||||
| 
 | 
 | ||||||
|         if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { |         if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { | ||||||
|             render_app |             render_app | ||||||
| @ -187,12 +189,8 @@ where | |||||||
|                 .add_render_command::<AlphaMask3d, DrawMaterial<M>>() |                 .add_render_command::<AlphaMask3d, DrawMaterial<M>>() | ||||||
|                 .init_resource::<ExtractedMaterials<M>>() |                 .init_resource::<ExtractedMaterials<M>>() | ||||||
|                 .init_resource::<RenderMaterials<M>>() |                 .init_resource::<RenderMaterials<M>>() | ||||||
|                 .init_resource::<RenderMaterialInstances<M>>() |  | ||||||
|                 .init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>() |                 .init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>() | ||||||
|                 .add_systems( |                 .add_systems(ExtractSchedule, extract_materials::<M>) | ||||||
|                     ExtractSchedule, |  | ||||||
|                     (extract_materials::<M>, extract_material_meshes::<M>), |  | ||||||
|                 ) |  | ||||||
|                 .add_systems( |                 .add_systems( | ||||||
|                     Render, |                     Render, | ||||||
|                     ( |                     ( | ||||||
| @ -372,26 +370,7 @@ impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterial | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Resource, Deref, DerefMut)] | pub type RenderMaterialInstances<M> = RenderInstances<AssetId<M>>; | ||||||
| pub struct RenderMaterialInstances<M: Material>(EntityHashMap<Entity, AssetId<M>>); |  | ||||||
| 
 |  | ||||||
| impl<M: Material> Default for RenderMaterialInstances<M> { |  | ||||||
|     fn default() -> Self { |  | ||||||
|         Self(Default::default()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn extract_material_meshes<M: Material>( |  | ||||||
|     mut material_instances: ResMut<RenderMaterialInstances<M>>, |  | ||||||
|     query: Extract<Query<(Entity, &ViewVisibility, &Handle<M>)>>, |  | ||||||
| ) { |  | ||||||
|     material_instances.clear(); |  | ||||||
|     for (entity, view_visibility, handle) in &query { |  | ||||||
|         if view_visibility.get() { |  | ||||||
|             material_instances.insert(entity, handle.id()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey { | const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey { | ||||||
|     match alpha_mode { |     match alpha_mode { | ||||||
|  | |||||||
| @ -18,6 +18,7 @@ pub mod pipelined_rendering; | |||||||
| pub mod primitives; | pub mod primitives; | ||||||
| pub mod render_asset; | pub mod render_asset; | ||||||
| pub mod render_graph; | pub mod render_graph; | ||||||
|  | pub mod render_instances; | ||||||
| pub mod render_phase; | pub mod render_phase; | ||||||
| pub mod render_resource; | pub mod render_resource; | ||||||
| pub mod renderer; | pub mod renderer; | ||||||
|  | |||||||
							
								
								
									
										153
									
								
								crates/bevy_render/src/render_instances.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								crates/bevy_render/src/render_instances.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,153 @@ | |||||||
|  | //! Convenience logic for turning components from the main world into render
 | ||||||
|  | //! instances in the render world.
 | ||||||
|  | //!
 | ||||||
|  | //! This is essentially the same as the `extract_component` module, but
 | ||||||
|  | //! higher-performance because it avoids the ECS overhead.
 | ||||||
|  | 
 | ||||||
|  | use std::marker::PhantomData; | ||||||
|  | 
 | ||||||
|  | use bevy_app::{App, Plugin}; | ||||||
|  | use bevy_asset::{Asset, AssetId, Handle}; | ||||||
|  | use bevy_derive::{Deref, DerefMut}; | ||||||
|  | use bevy_ecs::{ | ||||||
|  |     prelude::Entity, | ||||||
|  |     query::{QueryItem, ReadOnlyWorldQuery, WorldQuery}, | ||||||
|  |     system::{lifetimeless::Read, Query, ResMut, Resource}, | ||||||
|  | }; | ||||||
|  | use bevy_utils::EntityHashMap; | ||||||
|  | 
 | ||||||
|  | use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp}; | ||||||
|  | 
 | ||||||
|  | /// Describes how to extract data needed for rendering from a component or
 | ||||||
|  | /// components.
 | ||||||
|  | ///
 | ||||||
|  | /// Before rendering, any applicable components will be transferred from the
 | ||||||
|  | /// main world to the render world in the [`ExtractSchedule`] step.
 | ||||||
|  | ///
 | ||||||
|  | /// This is essentially the same as
 | ||||||
|  | /// [`ExtractComponent`](crate::extract_component::ExtractComponent), but
 | ||||||
|  | /// higher-performance because it avoids the ECS overhead.
 | ||||||
|  | pub trait RenderInstance: Send + Sync + Sized + 'static { | ||||||
|  |     /// ECS [`WorldQuery`] to fetch the components to extract.
 | ||||||
|  |     type Query: WorldQuery + ReadOnlyWorldQuery; | ||||||
|  |     /// Filters the entities with additional constraints.
 | ||||||
|  |     type Filter: WorldQuery + ReadOnlyWorldQuery; | ||||||
|  | 
 | ||||||
|  |     /// Defines how the component is transferred into the "render world".
 | ||||||
|  |     fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option<Self>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// This plugin extracts one or more components into the "render world" as
 | ||||||
|  | /// render instances.
 | ||||||
|  | ///
 | ||||||
|  | /// Therefore it sets up the [`ExtractSchedule`] step for the specified
 | ||||||
|  | /// [`RenderInstances`].
 | ||||||
|  | #[derive(Default)] | ||||||
|  | pub struct RenderInstancePlugin<RI> | ||||||
|  | where | ||||||
|  |     RI: RenderInstance, | ||||||
|  | { | ||||||
|  |     only_extract_visible: bool, | ||||||
|  |     marker: PhantomData<fn() -> RI>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Stores all render instances of a type in the render world.
 | ||||||
|  | #[derive(Resource, Deref, DerefMut)] | ||||||
|  | pub struct RenderInstances<RI>(EntityHashMap<Entity, RI>) | ||||||
|  | where | ||||||
|  |     RI: RenderInstance; | ||||||
|  | 
 | ||||||
|  | impl<RI> Default for RenderInstances<RI> | ||||||
|  | where | ||||||
|  |     RI: RenderInstance, | ||||||
|  | { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self(Default::default()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<RI> RenderInstancePlugin<RI> | ||||||
|  | where | ||||||
|  |     RI: RenderInstance, | ||||||
|  | { | ||||||
|  |     /// Creates a new [`RenderInstancePlugin`] that unconditionally extracts to
 | ||||||
|  |     /// the render world, whether the entity is visible or not.
 | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         Self { | ||||||
|  |             only_extract_visible: false, | ||||||
|  |             marker: PhantomData, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<RI> RenderInstancePlugin<RI> | ||||||
|  | where | ||||||
|  |     RI: RenderInstance, | ||||||
|  | { | ||||||
|  |     /// Creates a new [`RenderInstancePlugin`] that extracts to the render world
 | ||||||
|  |     /// if and only if the entity it's attached to is visible.
 | ||||||
|  |     pub fn extract_visible() -> Self { | ||||||
|  |         Self { | ||||||
|  |             only_extract_visible: true, | ||||||
|  |             marker: PhantomData, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<RI> Plugin for RenderInstancePlugin<RI> | ||||||
|  | where | ||||||
|  |     RI: RenderInstance, | ||||||
|  | { | ||||||
|  |     fn build(&self, app: &mut App) { | ||||||
|  |         if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { | ||||||
|  |             render_app.init_resource::<RenderInstances<RI>>(); | ||||||
|  |             if self.only_extract_visible { | ||||||
|  |                 render_app.add_systems(ExtractSchedule, extract_visible_to_render_instances::<RI>); | ||||||
|  |             } else { | ||||||
|  |                 render_app.add_systems(ExtractSchedule, extract_to_render_instances::<RI>); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn extract_to_render_instances<RI>( | ||||||
|  |     mut instances: ResMut<RenderInstances<RI>>, | ||||||
|  |     query: Extract<Query<(Entity, RI::Query), RI::Filter>>, | ||||||
|  | ) where | ||||||
|  |     RI: RenderInstance, | ||||||
|  | { | ||||||
|  |     instances.clear(); | ||||||
|  |     for (entity, other) in &query { | ||||||
|  |         if let Some(render_instance) = RI::extract_to_render_instance(other) { | ||||||
|  |             instances.insert(entity, render_instance); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn extract_visible_to_render_instances<RI>( | ||||||
|  |     mut instances: ResMut<RenderInstances<RI>>, | ||||||
|  |     query: Extract<Query<(Entity, &ViewVisibility, RI::Query), RI::Filter>>, | ||||||
|  | ) where | ||||||
|  |     RI: RenderInstance, | ||||||
|  | { | ||||||
|  |     instances.clear(); | ||||||
|  |     for (entity, view_visibility, other) in &query { | ||||||
|  |         if view_visibility.get() { | ||||||
|  |             if let Some(render_instance) = RI::extract_to_render_instance(other) { | ||||||
|  |                 instances.insert(entity, render_instance); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<A> RenderInstance for AssetId<A> | ||||||
|  | where | ||||||
|  |     A: Asset, | ||||||
|  | { | ||||||
|  |     type Query = Read<Handle<A>>; | ||||||
|  |     type Filter = (); | ||||||
|  | 
 | ||||||
|  |     fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option<Self> { | ||||||
|  |         Some(item.id()) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Patrick Walton
						Patrick Walton