Skip to content

Commit

Permalink
updated the documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtkuehnert committed Jan 13, 2023
1 parent 6d8a3dd commit 570d23c
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 37 deletions.
6 changes: 3 additions & 3 deletions crates/bevy_render/src/render_phase/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//! Finally the items are rendered using a single [`TrackedRenderPass`], during the
//! [`RenderStage::Render`](crate::RenderStage::Render).
//!
//! Therefore each phase item is assigned a [`RenderCommand`].
//! Each phase item is drawn using a [`RenderCommand`].
//! These set up the state of the [`TrackedRenderPass`] (i.e. select the
//! [`RenderPipeline`](crate::render_resource::RenderPipeline), configure the
//! [`BindGroup`](crate::render_resource::BindGroup)s, etc.) and then issue a draw call,
Expand All @@ -39,7 +39,7 @@ use bevy_ecs::{
};
use std::ops::Range;

/// A collection of all rendering instructions, that will be executed by the GPU, for a
/// A collection of all rendering commands, that will be executed by the GPU, for a
/// single render phase for a single view.
///
/// Each view (camera, or shadow-casting light, etc.) can have one or multiple render phases.
Expand Down Expand Up @@ -72,7 +72,7 @@ impl<P: PhaseItem> RenderPhase<P> {
P::sort(&mut self.items);
}

/// Renders all of its [`PhaseItem`]s using their corresponding draw functions.
/// Renders all of its [`PhaseItem`]s using their corresponding [`RenderCommand`].
pub fn render<'w>(
&self,
render_pass: &mut TrackedRenderPass<'w>,
Expand Down
151 changes: 117 additions & 34 deletions crates/bevy_render/src/render_phase/render_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,66 @@ use bevy_utils::HashMap;
use parking_lot::RwLock;
use std::{any::TypeId, fmt::Debug, hash::Hash};

/// The result of a [`RenderCommand`].
pub enum RenderCommandResult {
Success,
Failure,
}

// TODO: make this generic?
/// An identifier of a [`RenderCommand`] stored in the [`RenderCommands`] collection.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct RenderCommandId(u32);

/// [`RenderCommand`]s are modular pieces of render logic that are used to render [`PhaseItem`]s.
///
/// These phase items are rendered during a [`RenderPhase`] for a specific view,
/// by recording commands (e.g. setting pipelines, binding bind groups,
/// setting vertex/index buffers, and issuing draw calls) via the [`TrackedRenderPass`].
///
/// The read only ECS data, required by the [`render`](Self::render) method, is fetch automatically,
/// from the render world, using the [`Param`](Self::Param),
/// [`ViewWorldQuery`](Self::ViewWorldQuery), and [`ItemWorldQuery`](Self::ItemWorldQuery).
/// These three parameters are used to access render world resources,
/// components of the view entity, and components of the item entity respectively.
///
/// Before they can be used, render commands have to be registered on the render app via the
/// [`AddRenderCommand::add_render_command`] method.
///
/// Multiple render commands can be combined together by wrapping them in a tuple.
///
/// # Example
/// The `DrawPbr` render command is composed of the following render command tuple.
/// Const generics are used to set specific bind group locations:
///
/// ```ignore
/// pub type DrawPbr = (
/// SetItemPipeline,
/// SetMeshViewBindGroup<0>,
/// SetStandardMaterialBindGroup<1>,
/// SetTransformBindGroup<2>,
/// DrawMesh,
/// );
/// ```
pub trait RenderCommand<P: PhaseItem>: Send + Sync + 'static {
/// Specifies the general ECS data (e.g. resources) required by [`Self::render`].
///
/// All parameters have to be read only.
type Param: ReadOnlySystemParam;
/// Specifies the ECS data of the view entity required by [`Self::render`].
///
/// The view entity refers to the camera, or shadow-casting light, etc. from which the phase
/// item will be rendered from.
/// All components have to be accessed read only.
type ViewWorldQuery: ReadOnlyWorldQuery;
/// Specifies the ECS data of the item entity required by [`RenderCommand::render`].
///
/// The item is the entity that will be rendered for the corresponding view.
/// All components have to be accessed read only.
type ItemWorldQuery: ReadOnlyWorldQuery;

/// Renders a [`PhaseItem`] by recording commands (e.g. setting pipelines, binding bind groups,
/// setting vertex/index buffers, and issuing draw calls) via the [`TrackedRenderPass`].
fn render<'w>(
item: &P,
view: ROQueryItem<'w, Self::ViewWorldQuery>,
Expand Down Expand Up @@ -59,6 +106,10 @@ macro_rules! render_command_tuple_impl {

all_tuples!(render_command_tuple_impl, 0, 15, C, V, E);

/// A collection of all [`RenderCommands`] for the [`PhaseItem`] type.
///
/// To select the render command for each [`PhaseItem`] use the [`id`](Self::id) or
/// [`get_id`](Self::get_id) methods.
#[derive(Resource)]
pub struct RenderCommands<P: PhaseItem> {
internal: RwLock<RenderCommandsInternal<P>>,
Expand All @@ -76,46 +127,51 @@ impl<P: PhaseItem> Default for RenderCommands<P> {
}

impl<P: PhaseItem> RenderCommands<P> {
pub fn get_id<T: 'static>(&self) -> Option<RenderCommandId> {
self.internal.read().get_id::<T>()
/// Retrieves the id of the corresponding [`RenderCommand`].
///
/// Fallible wrapper for [`Self::get_id()`]
///
/// ## Panics
/// If the id doesn't exist, this function will panic.
pub fn get_id<C: RenderCommand<P>>(&self) -> Option<RenderCommandId> {
self.internal.read().get_id::<C>()
}

pub fn id<T: 'static>(&self) -> RenderCommandId {
self.internal.read().id::<T>()
}

fn add<T: 'static, C: Command<P>>(&self, render_command: C) -> RenderCommandId {
let mut internal = self.internal.write();
internal.add::<T, C>(render_command)
/// Retrieves the id of the corresponding [`RenderCommand`].
pub fn id<C: RenderCommand<P>>(&self) -> RenderCommandId {
self.internal.read().id::<C>()
}

/// Renders all items of the `render_phase` using their corresponding [`RenderCommand`].
pub(crate) fn render<'w>(
&self,
world: &'w World,
render_phase: &RenderPhase<P>,
render_pass: &mut TrackedRenderPass<'w>,
view: Entity,
) {
let mut internal = self.internal.write();

for render_command in &mut internal.render_commands {
render_command.prepare(world);
}
self.internal
.write()
.render(world, render_phase, render_pass, view);
}

for item in &render_phase.items {
let render_command = &mut internal.render_commands[item.render_command().0 as usize];
render_command.render(world, render_pass, view, item);
}
/// Adds a [`RenderCommand`] (wrapped with a [`RenderCommandState`]) to this collection.
fn add<C: RenderCommand<P>>(&self, render_command: Box<dyn Command<P>>) -> RenderCommandId {
self.internal.write().add::<C>(render_command)
}
}

/// Registers a [`RenderCommand`] on the render app.
///
/// They are stored inside the [`RenderCommands`] resource of the app.
pub trait AddRenderCommand {
/// Adds the [`RenderCommand`] for the specified [`PhaseItem`] type to the app.
fn add_render_command<P: PhaseItem, C: RenderCommand<P>>(&mut self) -> &mut Self;
}

impl AddRenderCommand for App {
fn add_render_command<P: PhaseItem, C: RenderCommand<P>>(&mut self) -> &mut Self {
let render_command = RenderCommandState::<P, C>::new(&mut self.world);
let render_command = RenderCommandState::<P, C>::initialize(&mut self.world);
let render_commands = self
.world
.get_resource::<RenderCommands<P>>()
Expand All @@ -126,14 +182,14 @@ impl AddRenderCommand for App {
std::any::type_name::<P>(),
);
});
render_commands.add::<C, _>(render_command);
render_commands.add::<C>(render_command);
self
}
}

// TODO: can we get rid of this trait entirely?
trait Command<P: PhaseItem>: Send + Sync + 'static {
#[allow(unused_variables)]
fn prepare(&mut self, world: &World) {}
fn prepare(&mut self, world: &World);

fn render<'w>(
&mut self,
Expand All @@ -144,28 +200,38 @@ trait Command<P: PhaseItem>: Send + Sync + 'static {
);
}

/// Wraps a [`RenderCommand`] into a state so that it can store system and query states to supply
/// the necessary data in the [`RenderCommand::render`] method.
///
/// The [`RenderCommand::Param`], [`RenderCommand::ViewWorldQuery`] and
/// [`RenderCommand::ItemWorldQuery`] are fetched from the ECS and passed to the command.
struct RenderCommandState<P: PhaseItem, C: RenderCommand<P>> {
state: SystemState<C::Param>,
view: QueryState<C::ViewWorldQuery>,
entity: QueryState<C::ItemWorldQuery>,
}

impl<P: PhaseItem, C: RenderCommand<P>> RenderCommandState<P, C> {
pub fn new(world: &mut World) -> Self {
Self {
/// Creates a new [`RenderCommandState`] for the [`RenderCommand`].
pub fn initialize(world: &mut World) -> Box<dyn Command<P>> {
Box::new(Self {
state: SystemState::new(world),
view: world.query(),
entity: world.query(),
}
})
}
}

impl<P: PhaseItem, C: RenderCommand<P>> Command<P> for RenderCommandState<P, C> {
/// Prepares the render command to be used. This is called once and only once before the phase
/// begins. There may be zero or more `draw` calls following a call to this function.
fn prepare(&mut self, world: &'_ World) {
self.view.update_archetypes(world);
self.entity.update_archetypes(world);
}

/// Fetches the ECS parameters for the wrapped [`RenderCommand`] and then,
/// the phase item is rendered using this command.
fn render<'w>(
&mut self,
world: &'w World,
Expand All @@ -187,24 +253,41 @@ struct RenderCommandsInternal<P: PhaseItem> {
}

impl<P: PhaseItem> RenderCommandsInternal<P> {
fn get_id<T: 'static>(&self) -> Option<RenderCommandId> {
self.indices.get(&TypeId::of::<T>()).copied()
fn get_id<C: RenderCommand<P>>(&self) -> Option<RenderCommandId> {
self.indices.get(&TypeId::of::<C>()).copied()
}

fn id<T: 'static>(&self) -> RenderCommandId {
self.get_id::<T>().unwrap_or_else(|| {
fn id<C: RenderCommand<P>>(&self) -> RenderCommandId {
self.get_id::<C>().unwrap_or_else(|| {
panic!(
"Draw function {} not found for {}",
std::any::type_name::<T>(),
"Render command {} not found for {}",
std::any::type_name::<C>(),
std::any::type_name::<P>()
)
})
}

fn add<T: 'static, C: Command<P>>(&mut self, render_command: C) -> RenderCommandId {
fn render<'w>(
&mut self,
world: &'w World,
render_phase: &RenderPhase<P>,
render_pass: &mut TrackedRenderPass<'w>,
view: Entity,
) {
for render_command in &mut self.render_commands {
render_command.prepare(world);
}

for item in &render_phase.items {
let render_command = &mut self.render_commands[item.render_command().0 as usize];
render_command.render(world, render_pass, view, item);
}
}

fn add<C: RenderCommand<P>>(&mut self, render_command: Box<dyn Command<P>>) -> RenderCommandId {
let id = RenderCommandId(self.render_commands.len() as u32);
self.render_commands.push(Box::new(render_command));
self.indices.insert(TypeId::of::<T>(), id);
self.render_commands.push(render_command);
self.indices.insert(TypeId::of::<C>(), id);
id
}
}

0 comments on commit 570d23c

Please sign in to comment.