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