Skip to content

Commit

Permalink
Refactor the render instance logic in bevyengine#9903 so that it's ea…
Browse files Browse the repository at this point in the history
…sier for other components to adopt. (bevyengine#10002)

# Objective

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.

## Solution

This commit creates a new `RenderInstance` trait that mirrors the
existing `ExtractComponent` method but uses the higher-performance
approach that bevyengine#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.
  • Loading branch information
pcwalton authored and ameknite committed Nov 6, 2023
1 parent 0ceff17 commit cb39276
Show file tree
Hide file tree
Showing 3 changed files with 161 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::{RenderInstancePlugin, 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(RenderInstancePlugin::<AssetId<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<AssetId<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 @@ -16,6 +16,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
153 changes: 153 additions & 0 deletions crates/bevy_render/src/render_instances.rs
Original file line number Diff line number Diff line change
@@ -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())
}
}

0 comments on commit cb39276

Please sign in to comment.