Skip to content

Commit ce8d88d

Browse files
committed
Refactor the render instance logic in bevyengine#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 bevyengine#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 bevyengine#9903 uses. This makes high-performance rendering components essentially as easy to write as the existing ones based on component extraction.
1 parent 4eb9b9f commit ce8d88d

File tree

4 files changed

+165
-28
lines changed

4 files changed

+165
-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::{ExtractToRenderInstancePlugin, 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(ExtractToRenderInstancePlugin::<Handle<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<Handle<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: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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+
component::Component,
14+
prelude::Entity,
15+
query::{QueryItem, ReadOnlyWorldQuery, WorldQuery},
16+
system::{lifetimeless::Read, Query, ResMut, Resource},
17+
};
18+
use bevy_utils::EntityHashMap;
19+
20+
use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp};
21+
22+
/// Describes how a component gets turned into a render instance for rendering.
23+
///
24+
/// Before rendering, a component will be transferred from the main world to the
25+
/// render world in the [`ExtractSchedule`](crate::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 ExtractToRenderInstance: Component {
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+
type Instance: Send + Sync;
37+
38+
/// Defines how the component is transferred into the "render world".
39+
fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option<Self::Instance>;
40+
}
41+
42+
/// This plugin extracts the components into the "render world" as render
43+
/// instances.
44+
///
45+
/// Therefore it sets up the [`ExtractSchedule`](crate::ExtractSchedule) step
46+
/// for the specified [`RenderInstances`].
47+
#[derive(Default)]
48+
pub struct ExtractToRenderInstancePlugin<C>
49+
where
50+
C: ExtractToRenderInstance,
51+
{
52+
only_extract_visible: bool,
53+
marker: PhantomData<fn() -> C>,
54+
}
55+
56+
/// Stores all render instances corresponding to the given component in the render world.
57+
#[derive(Resource, Deref, DerefMut)]
58+
pub struct RenderInstances<C>(EntityHashMap<Entity, C::Instance>)
59+
where
60+
C: ExtractToRenderInstance;
61+
62+
impl<C> Default for RenderInstances<C>
63+
where
64+
C: ExtractToRenderInstance,
65+
{
66+
fn default() -> Self {
67+
Self(Default::default())
68+
}
69+
}
70+
71+
impl<C> ExtractToRenderInstancePlugin<C>
72+
where
73+
C: ExtractToRenderInstance,
74+
{
75+
/// Creates a new [`ExtractToRenderInstancePlugin`] that unconditionally
76+
/// extracts the component to the render world, whether visible or not.
77+
pub fn new() -> Self {
78+
Self {
79+
only_extract_visible: false,
80+
marker: PhantomData,
81+
}
82+
}
83+
}
84+
85+
impl<C> ExtractToRenderInstancePlugin<C>
86+
where
87+
C: ExtractToRenderInstance,
88+
{
89+
/// Creates a new [`ExtractToRenderInstancePlugin`] that extracts the
90+
/// component to the render world if and only if the entity it's attached to
91+
/// is visible.
92+
pub fn extract_visible() -> Self {
93+
Self {
94+
only_extract_visible: true,
95+
marker: PhantomData,
96+
}
97+
}
98+
}
99+
100+
impl<C> Plugin for ExtractToRenderInstancePlugin<C>
101+
where
102+
C: ExtractToRenderInstance,
103+
{
104+
fn build(&self, app: &mut App) {
105+
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
106+
render_app.init_resource::<RenderInstances<C>>();
107+
if self.only_extract_visible {
108+
render_app.add_systems(ExtractSchedule, extract_visible_to_render_instances::<C>);
109+
} else {
110+
render_app.add_systems(ExtractSchedule, extract_to_render_instances::<C>);
111+
}
112+
}
113+
}
114+
}
115+
116+
fn extract_to_render_instances<C>(
117+
mut instances: ResMut<RenderInstances<C>>,
118+
query: Extract<Query<(Entity, C::Query), C::Filter>>,
119+
) where
120+
C: ExtractToRenderInstance,
121+
{
122+
instances.clear();
123+
for (entity, other) in &query {
124+
if let Some(render_instance) = C::extract_to_render_instance(other) {
125+
instances.insert(entity, render_instance);
126+
}
127+
}
128+
}
129+
130+
fn extract_visible_to_render_instances<C>(
131+
mut instances: ResMut<RenderInstances<C>>,
132+
query: Extract<Query<(Entity, &ViewVisibility, C::Query), C::Filter>>,
133+
) where
134+
C: ExtractToRenderInstance,
135+
{
136+
instances.clear();
137+
for (entity, view_visibility, other) in &query {
138+
if view_visibility.get() {
139+
if let Some(render_instance) = C::extract_to_render_instance(other) {
140+
instances.insert(entity, render_instance);
141+
}
142+
}
143+
}
144+
}
145+
146+
impl<A> ExtractToRenderInstance for Handle<A>
147+
where
148+
A: Asset,
149+
{
150+
type Query = Read<Handle<A>>;
151+
type Filter = ();
152+
type Instance = AssetId<A>;
153+
154+
fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option<Self::Instance> {
155+
Some(item.id())
156+
}
157+
}

examples/wasm/assets

Lines changed: 0 additions & 1 deletion
This file was deleted.

examples/wasm/assets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../assets

0 commit comments

Comments
 (0)