diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 104d423afdab8d..50738889f5910f 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -20,6 +20,7 @@ use bevy_render::{ mesh::{Mesh, MeshVertexBufferLayout}, prelude::Image, render_asset::{prepare_assets, RenderAssets}, + render_instances::{RenderInstancePlugin, RenderInstances}, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, @@ -31,10 +32,10 @@ use bevy_render::{ }, renderer::RenderDevice, texture::FallbackImage, - view::{ExtractedView, Msaa, ViewVisibility, VisibleEntities}, + view::{ExtractedView, Msaa, VisibleEntities}, 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::marker::PhantomData; @@ -176,7 +177,8 @@ where M::Data: PartialEq + Eq + Hash + Clone, { fn build(&self, app: &mut App) { - app.init_asset::(); + app.init_asset::() + .add_plugins(RenderInstancePlugin::>::extract_visible()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -187,12 +189,8 @@ where .add_render_command::>() .init_resource::>() .init_resource::>() - .init_resource::>() .init_resource::>>() - .add_systems( - ExtractSchedule, - (extract_materials::, extract_material_meshes::), - ) + .add_systems(ExtractSchedule, extract_materials::) .add_systems( Render, ( @@ -372,26 +370,7 @@ impl RenderCommand

for SetMaterial } } -#[derive(Resource, Deref, DerefMut)] -pub struct RenderMaterialInstances(EntityHashMap>); - -impl Default for RenderMaterialInstances { - fn default() -> Self { - Self(Default::default()) - } -} - -fn extract_material_meshes( - mut material_instances: ResMut>, - query: Extract)>>, -) { - material_instances.clear(); - for (entity, view_visibility, handle) in &query { - if view_visibility.get() { - material_instances.insert(entity, handle.id()); - } - } -} +pub type RenderMaterialInstances = RenderInstances>; const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey { match alpha_mode { diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 7f8efd2315a3c9..d913aad7420e54 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -16,6 +16,7 @@ pub mod pipelined_rendering; pub mod primitives; pub mod render_asset; pub mod render_graph; +pub mod render_instances; pub mod render_phase; pub mod render_resource; pub mod renderer; diff --git a/crates/bevy_render/src/render_instances.rs b/crates/bevy_render/src/render_instances.rs new file mode 100644 index 00000000000000..5eb7f05368a504 --- /dev/null +++ b/crates/bevy_render/src/render_instances.rs @@ -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; +} + +/// 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 +where + RI: RenderInstance, +{ + only_extract_visible: bool, + marker: PhantomData RI>, +} + +/// Stores all render instances of a type in the render world. +#[derive(Resource, Deref, DerefMut)] +pub struct RenderInstances(EntityHashMap) +where + RI: RenderInstance; + +impl Default for RenderInstances +where + RI: RenderInstance, +{ + fn default() -> Self { + Self(Default::default()) + } +} + +impl RenderInstancePlugin +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 RenderInstancePlugin +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 Plugin for RenderInstancePlugin +where + RI: RenderInstance, +{ + fn build(&self, app: &mut App) { + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.init_resource::>(); + if self.only_extract_visible { + render_app.add_systems(ExtractSchedule, extract_visible_to_render_instances::); + } else { + render_app.add_systems(ExtractSchedule, extract_to_render_instances::); + } + } + } +} + +fn extract_to_render_instances( + mut instances: ResMut>, + query: Extract>, +) 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( + mut instances: ResMut>, + query: Extract>, +) 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 RenderInstance for AssetId +where + A: Asset, +{ + type Query = Read>; + type Filter = (); + + fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option { + Some(item.id()) + } +}