From ce8d88d282a82bb9207c3fdd10e4619f9ea407db Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 2 Oct 2023 17:59:08 -0700 Subject: [PATCH 1/6] Refactor the render instance logic in #9903 so that it's easier for other components to adopt. 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. This commit creates a new `ExtractToRenderInstance` trait that mirrors the existing `ExtractComponent` method but uses the higher-performance approach that #9903 uses. This makes high-performance rendering components essentially as easy to write as the existing ones based on component extraction. --- crates/bevy_pbr/src/material.rs | 35 +---- crates/bevy_render/src/lib.rs | 1 + crates/bevy_render/src/render_instances.rs | 157 +++++++++++++++++++++ examples/wasm/assets | 0 4 files changed, 165 insertions(+), 28 deletions(-) create mode 100644 crates/bevy_render/src/render_instances.rs mode change 120000 => 100644 examples/wasm/assets diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index c3287af0d9cbf..108e06f126c6b 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::{ExtractToRenderInstancePlugin, 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(ExtractToRenderInstancePlugin::>::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 1709c0ea048c5..1bac94896de63 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -18,6 +18,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 0000000000000..d03a14d3fd241 --- /dev/null +++ b/crates/bevy_render/src/render_instances.rs @@ -0,0 +1,157 @@ +//! 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::{ + component::Component, + 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 a component gets turned into a render instance for rendering. +/// +/// Before rendering, a component will be transferred from the main world to the +/// render world in the [`ExtractSchedule`](crate::ExtractSchedule) step. +/// +/// This is essentially the same as +/// [`ExtractComponent`](crate::extract_component::ExtractComponent), but +/// higher-performance because it avoids the ECS overhead. +pub trait ExtractToRenderInstance: Component { + /// ECS [`WorldQuery`] to fetch the components to extract. + type Query: WorldQuery + ReadOnlyWorldQuery; + /// Filters the entities with additional constraints. + type Filter: WorldQuery + ReadOnlyWorldQuery; + + type Instance: Send + Sync; + + /// Defines how the component is transferred into the "render world". + fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option; +} + +/// This plugin extracts the components into the "render world" as render +/// instances. +/// +/// Therefore it sets up the [`ExtractSchedule`](crate::ExtractSchedule) step +/// for the specified [`RenderInstances`]. +#[derive(Default)] +pub struct ExtractToRenderInstancePlugin +where + C: ExtractToRenderInstance, +{ + only_extract_visible: bool, + marker: PhantomData C>, +} + +/// Stores all render instances corresponding to the given component in the render world. +#[derive(Resource, Deref, DerefMut)] +pub struct RenderInstances(EntityHashMap) +where + C: ExtractToRenderInstance; + +impl Default for RenderInstances +where + C: ExtractToRenderInstance, +{ + fn default() -> Self { + Self(Default::default()) + } +} + +impl ExtractToRenderInstancePlugin +where + C: ExtractToRenderInstance, +{ + /// Creates a new [`ExtractToRenderInstancePlugin`] that unconditionally + /// extracts the component to the render world, whether visible or not. + pub fn new() -> Self { + Self { + only_extract_visible: false, + marker: PhantomData, + } + } +} + +impl ExtractToRenderInstancePlugin +where + C: ExtractToRenderInstance, +{ + /// Creates a new [`ExtractToRenderInstancePlugin`] that extracts the + /// component 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 ExtractToRenderInstancePlugin +where + C: ExtractToRenderInstance, +{ + 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 + C: ExtractToRenderInstance, +{ + instances.clear(); + for (entity, other) in &query { + if let Some(render_instance) = C::extract_to_render_instance(other) { + instances.insert(entity, render_instance); + } + } +} + +fn extract_visible_to_render_instances( + mut instances: ResMut>, + query: Extract>, +) where + C: ExtractToRenderInstance, +{ + instances.clear(); + for (entity, view_visibility, other) in &query { + if view_visibility.get() { + if let Some(render_instance) = C::extract_to_render_instance(other) { + instances.insert(entity, render_instance); + } + } + } +} + +impl ExtractToRenderInstance for Handle +where + A: Asset, +{ + type Query = Read>; + type Filter = (); + type Instance = AssetId; + + fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option { + Some(item.id()) + } +} diff --git a/examples/wasm/assets b/examples/wasm/assets deleted file mode 120000 index 41aef43f18246..0000000000000 --- a/examples/wasm/assets +++ /dev/null @@ -1 +0,0 @@ -../../assets \ No newline at end of file diff --git a/examples/wasm/assets b/examples/wasm/assets new file mode 100644 index 0000000000000..41aef43f18246 --- /dev/null +++ b/examples/wasm/assets @@ -0,0 +1 @@ +../../assets \ No newline at end of file From 84cb2d75465275b39d45c80140c3a6de4fe7b1a7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 7 Oct 2023 12:45:41 -0700 Subject: [PATCH 2/6] Fix symlink issue --- examples/wasm/assets | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 120000 examples/wasm/assets diff --git a/examples/wasm/assets b/examples/wasm/assets deleted file mode 100644 index 41aef43f18246..0000000000000 --- a/examples/wasm/assets +++ /dev/null @@ -1 +0,0 @@ -../../assets \ No newline at end of file diff --git a/examples/wasm/assets b/examples/wasm/assets new file mode 120000 index 0000000000000..41aef43f18246 --- /dev/null +++ b/examples/wasm/assets @@ -0,0 +1 @@ +../../assets \ No newline at end of file From 205c3647792190a6df83f8692a2bf8c58aeea8be Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 7 Oct 2023 17:04:46 -0700 Subject: [PATCH 3/6] Fix doc test --- crates/bevy_render/src/render_instances.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/render_instances.rs b/crates/bevy_render/src/render_instances.rs index d03a14d3fd241..76911bfcd7114 100644 --- a/crates/bevy_render/src/render_instances.rs +++ b/crates/bevy_render/src/render_instances.rs @@ -22,7 +22,7 @@ use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp}; /// Describes how a component gets turned into a render instance for rendering. /// /// Before rendering, a component will be transferred from the main world to the -/// render world in the [`ExtractSchedule`](crate::ExtractSchedule) step. +/// render world in the [`ExtractSchedule`] step. /// /// This is essentially the same as /// [`ExtractComponent`](crate::extract_component::ExtractComponent), but From aa49ce3eeb0549c08a9a685725db9b8f805641c5 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sun, 8 Oct 2023 02:26:25 -0700 Subject: [PATCH 4/6] Implement on the target, not on the component --- crates/bevy_pbr/src/material.rs | 6 +- crates/bevy_render/src/render_instances.rs | 71 +++++++++++----------- 2 files changed, 37 insertions(+), 40 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 1537d2d0937fe..50738889f5910 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -20,7 +20,7 @@ use bevy_render::{ mesh::{Mesh, MeshVertexBufferLayout}, prelude::Image, render_asset::{prepare_assets, RenderAssets}, - render_instances::{ExtractToRenderInstancePlugin, RenderInstances}, + render_instances::{RenderInstancePlugin, RenderInstances}, render_phase::{ AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, @@ -178,7 +178,7 @@ where { fn build(&self, app: &mut App) { app.init_asset::() - .add_plugins(ExtractToRenderInstancePlugin::>::extract_visible()); + .add_plugins(RenderInstancePlugin::>::extract_visible()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app @@ -370,7 +370,7 @@ impl RenderCommand

for SetMaterial } } -pub type RenderMaterialInstances = RenderInstances>; +pub type RenderMaterialInstances = RenderInstances>; const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey { match alpha_mode { diff --git a/crates/bevy_render/src/render_instances.rs b/crates/bevy_render/src/render_instances.rs index 76911bfcd7114..63bbaf970ddfc 100644 --- a/crates/bevy_render/src/render_instances.rs +++ b/crates/bevy_render/src/render_instances.rs @@ -10,7 +10,6 @@ use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetId, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - component::Component, prelude::Entity, query::{QueryItem, ReadOnlyWorldQuery, WorldQuery}, system::{lifetimeless::Read, Query, ResMut, Resource}, @@ -19,60 +18,59 @@ use bevy_utils::EntityHashMap; use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp}; -/// Describes how a component gets turned into a render instance for rendering. +/// Describes how to extract data needed for rendering from a component or +/// components. /// -/// Before rendering, a component will be transferred from the main world to the -/// render world in the [`ExtractSchedule`] step. +/// 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 ExtractToRenderInstance: Component { +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; - type Instance: Send + Sync; - /// Defines how the component is transferred into the "render world". - fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option; + fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option; } -/// This plugin extracts the components into the "render world" as render -/// instances. +/// This plugin extracts one or more components into the "render world" as +/// render instances. /// /// Therefore it sets up the [`ExtractSchedule`](crate::ExtractSchedule) step /// for the specified [`RenderInstances`]. #[derive(Default)] -pub struct ExtractToRenderInstancePlugin +pub struct RenderInstancePlugin where - C: ExtractToRenderInstance, + RI: RenderInstance, { only_extract_visible: bool, - marker: PhantomData C>, + marker: PhantomData RI>, } /// Stores all render instances corresponding to the given component in the render world. #[derive(Resource, Deref, DerefMut)] -pub struct RenderInstances(EntityHashMap) +pub struct RenderInstances(EntityHashMap) where - C: ExtractToRenderInstance; + RI: RenderInstance; -impl Default for RenderInstances +impl Default for RenderInstances where - C: ExtractToRenderInstance, + RI: RenderInstance, { fn default() -> Self { Self(Default::default()) } } -impl ExtractToRenderInstancePlugin +impl RenderInstancePlugin where - C: ExtractToRenderInstance, + RI: RenderInstance, { - /// Creates a new [`ExtractToRenderInstancePlugin`] that unconditionally + /// Creates a new [`RenderInstancePlugin`] that unconditionally /// extracts the component to the render world, whether visible or not. pub fn new() -> Self { Self { @@ -82,11 +80,11 @@ where } } -impl ExtractToRenderInstancePlugin +impl RenderInstancePlugin where - C: ExtractToRenderInstance, + RI: RenderInstance, { - /// Creates a new [`ExtractToRenderInstancePlugin`] that extracts the + /// Creates a new [`RenderInstancePlugin`] that extracts the /// component to the render world if and only if the entity it's attached to /// is visible. pub fn extract_visible() -> Self { @@ -97,31 +95,31 @@ where } } -impl Plugin for ExtractToRenderInstancePlugin +impl Plugin for RenderInstancePlugin where - C: ExtractToRenderInstance, + RI: RenderInstance, { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::>(); + render_app.init_resource::>(); if self.only_extract_visible { - render_app.add_systems(ExtractSchedule, extract_visible_to_render_instances::); + render_app.add_systems(ExtractSchedule, extract_visible_to_render_instances::); } else { - render_app.add_systems(ExtractSchedule, extract_to_render_instances::); + render_app.add_systems(ExtractSchedule, extract_to_render_instances::); } } } } -fn extract_to_render_instances( - mut instances: ResMut>, - query: Extract>, +fn extract_to_render_instances( + mut instances: ResMut>, + query: Extract>, ) where - C: ExtractToRenderInstance, + RI: RenderInstance, { instances.clear(); for (entity, other) in &query { - if let Some(render_instance) = C::extract_to_render_instance(other) { + if let Some(render_instance) = RI::extract_to_render_instance(other) { instances.insert(entity, render_instance); } } @@ -131,7 +129,7 @@ fn extract_visible_to_render_instances( mut instances: ResMut>, query: Extract>, ) where - C: ExtractToRenderInstance, + C: RenderInstance, { instances.clear(); for (entity, view_visibility, other) in &query { @@ -143,15 +141,14 @@ fn extract_visible_to_render_instances( } } -impl ExtractToRenderInstance for Handle +impl RenderInstance for AssetId where A: Asset, { type Query = Read>; type Filter = (); - type Instance = AssetId; - fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option { + fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option { Some(item.id()) } } From 6868e8680574cd8e02ff4124008913f19c35364a Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sun, 8 Oct 2023 02:30:11 -0700 Subject: [PATCH 5/6] Fix some naming --- crates/bevy_render/src/render_instances.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/crates/bevy_render/src/render_instances.rs b/crates/bevy_render/src/render_instances.rs index 63bbaf970ddfc..4603b07a21fdf 100644 --- a/crates/bevy_render/src/render_instances.rs +++ b/crates/bevy_render/src/render_instances.rs @@ -51,7 +51,7 @@ where marker: PhantomData RI>, } -/// Stores all render instances corresponding to the given component in the render world. +/// Stores all render instances of a type in the render world. #[derive(Resource, Deref, DerefMut)] pub struct RenderInstances(EntityHashMap) where @@ -70,8 +70,8 @@ impl RenderInstancePlugin where RI: RenderInstance, { - /// Creates a new [`RenderInstancePlugin`] that unconditionally - /// extracts the component to the render world, whether visible or not. + /// 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, @@ -84,9 +84,8 @@ impl RenderInstancePlugin where RI: RenderInstance, { - /// Creates a new [`RenderInstancePlugin`] that extracts the - /// component to the render world if and only if the entity it's attached to - /// is visible. + /// 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, @@ -125,16 +124,16 @@ fn extract_to_render_instances( } } -fn extract_visible_to_render_instances( - mut instances: ResMut>, - query: Extract>, +fn extract_visible_to_render_instances( + mut instances: ResMut>, + query: Extract>, ) where - C: RenderInstance, + RI: RenderInstance, { instances.clear(); for (entity, view_visibility, other) in &query { if view_visibility.get() { - if let Some(render_instance) = C::extract_to_render_instance(other) { + if let Some(render_instance) = RI::extract_to_render_instance(other) { instances.insert(entity, render_instance); } } From 079bdcb2821578736d1f05735125efe85a60259c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sun, 8 Oct 2023 03:13:41 -0700 Subject: [PATCH 6/6] Fix doc failure --- crates/bevy_render/src/render_instances.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/render_instances.rs b/crates/bevy_render/src/render_instances.rs index 4603b07a21fdf..5eb7f05368a50 100644 --- a/crates/bevy_render/src/render_instances.rs +++ b/crates/bevy_render/src/render_instances.rs @@ -40,8 +40,8 @@ pub trait RenderInstance: Send + Sync + Sized + 'static { /// This plugin extracts one or more components into the "render world" as /// render instances. /// -/// Therefore it sets up the [`ExtractSchedule`](crate::ExtractSchedule) step -/// for the specified [`RenderInstances`]. +/// Therefore it sets up the [`ExtractSchedule`] step for the specified +/// [`RenderInstances`]. #[derive(Default)] pub struct RenderInstancePlugin where