Skip to content

Commit e67d63a

Browse files
authored
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.
1 parent 1f95a48 commit e67d63a

File tree

3 files changed

+161
-28
lines changed

3 files changed

+161
-28
lines changed

crates/bevy_pbr/src/material.rs

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use bevy_render::{
2020
mesh::{Mesh, MeshVertexBufferLayout},
2121
prelude::Image,
2222
render_asset::{prepare_assets, RenderAssets},
23+
render_instances::{RenderInstancePlugin, RenderInstances},
2324
render_phase::{
2425
AddRenderCommand, DrawFunctions, PhaseItem, RenderCommand, RenderCommandResult,
2526
RenderPhase, SetItemPipeline, TrackedRenderPass,
@@ -31,10 +32,10 @@ use bevy_render::{
3132
},
3233
renderer::RenderDevice,
3334
texture::FallbackImage,
34-
view::{ExtractedView, Msaa, ViewVisibility, VisibleEntities},
35+
view::{ExtractedView, Msaa, VisibleEntities},
3536
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
3637
};
37-
use bevy_utils::{tracing::error, EntityHashMap, HashMap, HashSet};
38+
use bevy_utils::{tracing::error, HashMap, HashSet};
3839
use std::hash::Hash;
3940
use std::marker::PhantomData;
4041

@@ -176,7 +177,8 @@ where
176177
M::Data: PartialEq + Eq + Hash + Clone,
177178
{
178179
fn build(&self, app: &mut App) {
179-
app.init_asset::<M>();
180+
app.init_asset::<M>()
181+
.add_plugins(RenderInstancePlugin::<AssetId<M>>::extract_visible());
180182

181183
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
182184
render_app
@@ -187,12 +189,8 @@ where
187189
.add_render_command::<AlphaMask3d, DrawMaterial<M>>()
188190
.init_resource::<ExtractedMaterials<M>>()
189191
.init_resource::<RenderMaterials<M>>()
190-
.init_resource::<RenderMaterialInstances<M>>()
191192
.init_resource::<SpecializedMeshPipelines<MaterialPipeline<M>>>()
192-
.add_systems(
193-
ExtractSchedule,
194-
(extract_materials::<M>, extract_material_meshes::<M>),
195-
)
193+
.add_systems(ExtractSchedule, extract_materials::<M>)
196194
.add_systems(
197195
Render,
198196
(
@@ -372,26 +370,7 @@ impl<P: PhaseItem, M: Material, const I: usize> RenderCommand<P> for SetMaterial
372370
}
373371
}
374372

375-
#[derive(Resource, Deref, DerefMut)]
376-
pub struct RenderMaterialInstances<M: Material>(EntityHashMap<Entity, AssetId<M>>);
377-
378-
impl<M: Material> Default for RenderMaterialInstances<M> {
379-
fn default() -> Self {
380-
Self(Default::default())
381-
}
382-
}
383-
384-
fn extract_material_meshes<M: Material>(
385-
mut material_instances: ResMut<RenderMaterialInstances<M>>,
386-
query: Extract<Query<(Entity, &ViewVisibility, &Handle<M>)>>,
387-
) {
388-
material_instances.clear();
389-
for (entity, view_visibility, handle) in &query {
390-
if view_visibility.get() {
391-
material_instances.insert(entity, handle.id());
392-
}
393-
}
394-
}
373+
pub type RenderMaterialInstances<M> = RenderInstances<AssetId<M>>;
395374

396375
const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey {
397376
match alpha_mode {

crates/bevy_render/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod pipelined_rendering;
1818
pub mod primitives;
1919
pub mod render_asset;
2020
pub mod render_graph;
21+
pub mod render_instances;
2122
pub mod render_phase;
2223
pub mod render_resource;
2324
pub mod renderer;
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//! Convenience logic for turning components from the main world into render
2+
//! instances in the render world.
3+
//!
4+
//! This is essentially the same as the `extract_component` module, but
5+
//! higher-performance because it avoids the ECS overhead.
6+
7+
use std::marker::PhantomData;
8+
9+
use bevy_app::{App, Plugin};
10+
use bevy_asset::{Asset, AssetId, Handle};
11+
use bevy_derive::{Deref, DerefMut};
12+
use bevy_ecs::{
13+
prelude::Entity,
14+
query::{QueryItem, ReadOnlyWorldQuery, WorldQuery},
15+
system::{lifetimeless::Read, Query, ResMut, Resource},
16+
};
17+
use bevy_utils::EntityHashMap;
18+
19+
use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp};
20+
21+
/// Describes how to extract data needed for rendering from a component or
22+
/// components.
23+
///
24+
/// Before rendering, any applicable components will be transferred from the
25+
/// main world to the render world in the [`ExtractSchedule`] step.
26+
///
27+
/// This is essentially the same as
28+
/// [`ExtractComponent`](crate::extract_component::ExtractComponent), but
29+
/// higher-performance because it avoids the ECS overhead.
30+
pub trait RenderInstance: Send + Sync + Sized + 'static {
31+
/// ECS [`WorldQuery`] to fetch the components to extract.
32+
type Query: WorldQuery + ReadOnlyWorldQuery;
33+
/// Filters the entities with additional constraints.
34+
type Filter: WorldQuery + ReadOnlyWorldQuery;
35+
36+
/// Defines how the component is transferred into the "render world".
37+
fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option<Self>;
38+
}
39+
40+
/// This plugin extracts one or more components into the "render world" as
41+
/// render instances.
42+
///
43+
/// Therefore it sets up the [`ExtractSchedule`] step for the specified
44+
/// [`RenderInstances`].
45+
#[derive(Default)]
46+
pub struct RenderInstancePlugin<RI>
47+
where
48+
RI: RenderInstance,
49+
{
50+
only_extract_visible: bool,
51+
marker: PhantomData<fn() -> RI>,
52+
}
53+
54+
/// Stores all render instances of a type in the render world.
55+
#[derive(Resource, Deref, DerefMut)]
56+
pub struct RenderInstances<RI>(EntityHashMap<Entity, RI>)
57+
where
58+
RI: RenderInstance;
59+
60+
impl<RI> Default for RenderInstances<RI>
61+
where
62+
RI: RenderInstance,
63+
{
64+
fn default() -> Self {
65+
Self(Default::default())
66+
}
67+
}
68+
69+
impl<RI> RenderInstancePlugin<RI>
70+
where
71+
RI: RenderInstance,
72+
{
73+
/// Creates a new [`RenderInstancePlugin`] that unconditionally extracts to
74+
/// the render world, whether the entity is visible or not.
75+
pub fn new() -> Self {
76+
Self {
77+
only_extract_visible: false,
78+
marker: PhantomData,
79+
}
80+
}
81+
}
82+
83+
impl<RI> RenderInstancePlugin<RI>
84+
where
85+
RI: RenderInstance,
86+
{
87+
/// Creates a new [`RenderInstancePlugin`] that extracts to the render world
88+
/// if and only if the entity it's attached to is visible.
89+
pub fn extract_visible() -> Self {
90+
Self {
91+
only_extract_visible: true,
92+
marker: PhantomData,
93+
}
94+
}
95+
}
96+
97+
impl<RI> Plugin for RenderInstancePlugin<RI>
98+
where
99+
RI: RenderInstance,
100+
{
101+
fn build(&self, app: &mut App) {
102+
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
103+
render_app.init_resource::<RenderInstances<RI>>();
104+
if self.only_extract_visible {
105+
render_app.add_systems(ExtractSchedule, extract_visible_to_render_instances::<RI>);
106+
} else {
107+
render_app.add_systems(ExtractSchedule, extract_to_render_instances::<RI>);
108+
}
109+
}
110+
}
111+
}
112+
113+
fn extract_to_render_instances<RI>(
114+
mut instances: ResMut<RenderInstances<RI>>,
115+
query: Extract<Query<(Entity, RI::Query), RI::Filter>>,
116+
) where
117+
RI: RenderInstance,
118+
{
119+
instances.clear();
120+
for (entity, other) in &query {
121+
if let Some(render_instance) = RI::extract_to_render_instance(other) {
122+
instances.insert(entity, render_instance);
123+
}
124+
}
125+
}
126+
127+
fn extract_visible_to_render_instances<RI>(
128+
mut instances: ResMut<RenderInstances<RI>>,
129+
query: Extract<Query<(Entity, &ViewVisibility, RI::Query), RI::Filter>>,
130+
) where
131+
RI: RenderInstance,
132+
{
133+
instances.clear();
134+
for (entity, view_visibility, other) in &query {
135+
if view_visibility.get() {
136+
if let Some(render_instance) = RI::extract_to_render_instance(other) {
137+
instances.insert(entity, render_instance);
138+
}
139+
}
140+
}
141+
}
142+
143+
impl<A> RenderInstance for AssetId<A>
144+
where
145+
A: Asset,
146+
{
147+
type Query = Read<Handle<A>>;
148+
type Filter = ();
149+
150+
fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option<Self> {
151+
Some(item.id())
152+
}
153+
}

0 commit comments

Comments
 (0)