diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index 48ca8a0eb3f6aa..e0a3d5bbc886f5 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -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, @@ -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. @@ -72,7 +72,7 @@ impl RenderPhase

{ 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>, diff --git a/crates/bevy_render/src/render_phase/render_command.rs b/crates/bevy_render/src/render_phase/render_command.rs index 9eb71b65256e91..de02589a3e2e72 100644 --- a/crates/bevy_render/src/render_phase/render_command.rs +++ b/crates/bevy_render/src/render_phase/render_command.rs @@ -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: 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>, @@ -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 { internal: RwLock>, @@ -76,19 +127,22 @@ impl Default for RenderCommands

{ } impl RenderCommands

{ - pub fn get_id(&self) -> Option { - self.internal.read().get_id::() + /// 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>(&self) -> Option { + self.internal.read().get_id::() } - pub fn id(&self) -> RenderCommandId { - self.internal.read().id::() - } - - fn add>(&self, render_command: C) -> RenderCommandId { - let mut internal = self.internal.write(); - internal.add::(render_command) + /// Retrieves the id of the corresponding [`RenderCommand`]. + pub fn id>(&self) -> RenderCommandId { + self.internal.read().id::() } + /// Renders all items of the `render_phase` using their corresponding [`RenderCommand`]. pub(crate) fn render<'w>( &self, world: &'w World, @@ -96,26 +150,28 @@ impl RenderCommands

{ 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>(&self, render_command: Box>) -> RenderCommandId { + self.internal.write().add::(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>(&mut self) -> &mut Self; } impl AddRenderCommand for App { fn add_render_command>(&mut self) -> &mut Self { - let render_command = RenderCommandState::::new(&mut self.world); + let render_command = RenderCommandState::::initialize(&mut self.world); let render_commands = self .world .get_resource::>() @@ -126,14 +182,14 @@ impl AddRenderCommand for App { std::any::type_name::

(), ); }); - render_commands.add::(render_command); + render_commands.add::(render_command); self } } +// TODO: can we get rid of this trait entirely? trait Command: Send + Sync + 'static { - #[allow(unused_variables)] - fn prepare(&mut self, world: &World) {} + fn prepare(&mut self, world: &World); fn render<'w>( &mut self, @@ -144,6 +200,11 @@ trait Command: 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> { state: SystemState, view: QueryState, @@ -151,21 +212,26 @@ struct RenderCommandState> { } impl> RenderCommandState { - pub fn new(world: &mut World) -> Self { - Self { + /// Creates a new [`RenderCommandState`] for the [`RenderCommand`]. + pub fn initialize(world: &mut World) -> Box> { + Box::new(Self { state: SystemState::new(world), view: world.query(), entity: world.query(), - } + }) } } impl> Command

for RenderCommandState { + /// 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, @@ -187,24 +253,41 @@ struct RenderCommandsInternal { } impl RenderCommandsInternal

{ - fn get_id(&self) -> Option { - self.indices.get(&TypeId::of::()).copied() + fn get_id>(&self) -> Option { + self.indices.get(&TypeId::of::()).copied() } - fn id(&self) -> RenderCommandId { - self.get_id::().unwrap_or_else(|| { + fn id>(&self) -> RenderCommandId { + self.get_id::().unwrap_or_else(|| { panic!( - "Draw function {} not found for {}", - std::any::type_name::(), + "Render command {} not found for {}", + std::any::type_name::(), std::any::type_name::

() ) }) } - fn add>(&mut self, render_command: C) -> RenderCommandId { + fn render<'w>( + &mut self, + world: &'w World, + render_phase: &RenderPhase

, + 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>(&mut self, render_command: Box>) -> RenderCommandId { let id = RenderCommandId(self.render_commands.len() as u32); - self.render_commands.push(Box::new(render_command)); - self.indices.insert(TypeId::of::(), id); + self.render_commands.push(render_command); + self.indices.insert(TypeId::of::(), id); id } }