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},
|
||||
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::<M>();
|
||||
app.init_asset::<M>()
|
||||
.add_plugins(RenderInstancePlugin::<AssetId<M>>::extract_visible());
|
||||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app
|
||||
@ -187,12 +189,8 @@ where
|
||||
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
|
||||
.init_resource::<ExtractedMaterials<M>>()
|
||||
.init_resource::<RenderMaterials<M>>()
|
||||
.init_resource::<RenderMaterialInstances<M>>()
|
||||
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
|
||||
.add_systems(
|
||||
ExtractSchedule,
|
||||
(extract_materials::<M>, extract_material_meshes::<M>),
|
||||
)
|
||||
.add_systems(ExtractSchedule, extract_materials::<M>)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
@ -372,26 +370,7 @@ impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterial
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Deref, DerefMut)]
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type RenderMaterialInstances<M> = RenderInstances<AssetId<M>>;
|
||||
|
||||
const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey {
|
||||
match alpha_mode {
|
||||
|
||||
@ -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;
|
||||
|
||||
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