Skip to content

Commit

Permalink
Refactor the render instance logic in #9903 so that it's easier for
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pcwalton committed Oct 3, 2023
1 parent 4eb9b9f commit 21e0dae
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 28 deletions.
35 changes: 7 additions & 28 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;

Expand Down Expand Up @@ -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(ExtractToRenderInstancePlugin::<Handle<M>>::extract_visible());

if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
Expand All @@ -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,
(
Expand Down Expand Up @@ -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<Handle<M>>;

const fn alpha_mode_pipeline_key(alpha_mode: AlphaMode) -> MeshPipelineKey {
match alpha_mode {
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
154 changes: 154 additions & 0 deletions crates/bevy_render/src/render_instances.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//! 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::{Query, ResMut, Resource, lifetimeless::Read},
};
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<Self::Instance>;
}

/// 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<C>
where
C: ExtractToRenderInstance,
{
only_extract_visible: bool,
marker: PhantomData<fn() -> C>,
}

/// Stores all render instances corresponding to the given component in the render world.
#[derive(Resource, Deref, DerefMut)]
pub struct RenderInstances<C>(EntityHashMap<Entity, C::Instance>)
where
C: ExtractToRenderInstance;

impl<C> Default for RenderInstances<C>
where
C: ExtractToRenderInstance,
{
fn default() -> Self {
Self(Default::default())
}
}

impl<C> ExtractToRenderInstancePlugin<C>
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<C> ExtractToRenderInstancePlugin<C>
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<C> Plugin for ExtractToRenderInstancePlugin<C>
where
C: ExtractToRenderInstance,
{
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.init_resource::<RenderInstances<C>>();
if self.only_extract_visible {
render_app.add_systems(ExtractSchedule, extract_visible_to_render_instances::<C>);
} else {
render_app.add_systems(ExtractSchedule, extract_to_render_instances::<C>);
}
}
}
}

fn extract_to_render_instances<C>(
mut instances: ResMut<RenderInstances<C>>,
query: Extract<Query<(Entity, C::Query), C::Filter>>,
) 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<C>(
mut instances: ResMut<RenderInstances<C>>,
query: Extract<Query<(Entity, &ViewVisibility, C::Query), C::Filter>>,
) 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<A> ExtractToRenderInstance for Handle<A> where A: Asset {
type Query = Read<Handle<A>>;
type Filter = ();
type Instance = AssetId<A>;

fn extract_to_render_instance(item: QueryItem<'_, Self::Query>) -> Option<Self::Instance> {
Some(item.id())
}
}
1 change: 0 additions & 1 deletion examples/wasm/assets

This file was deleted.

1 change: 1 addition & 0 deletions examples/wasm/assets
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
../../assets

0 comments on commit 21e0dae

Please sign in to comment.