diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 0ba66415c35c8..5456c7208accc 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -11,7 +11,6 @@ pub mod extract_resource; pub mod globals; pub mod mesh; pub mod primitives; -pub mod rangefinder; pub mod render_asset; pub mod render_graph; pub mod render_phase; diff --git a/crates/bevy_render/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs index d0a60d529d57c..d94cf3be7cd5a 100644 --- a/crates/bevy_render/src/render_phase/draw.rs +++ b/crates/bevy_render/src/render_phase/draw.rs @@ -1,34 +1,30 @@ -use crate::{ - render_phase::TrackedRenderPass, - render_resource::{CachedRenderPipelineId, PipelineCache}, -}; +use crate::render_phase::{PhaseItem, TrackedRenderPass}; use bevy_app::App; use bevy_ecs::{ all_tuples, entity::Entity, query::{QueryState, ROQueryItem, ReadOnlyWorldQuery}, - system::{ - lifetimeless::SRes, ReadOnlySystemParam, Resource, SystemParam, SystemParamItem, - SystemState, - }, + system::{ReadOnlySystemParam, Resource, SystemParam, SystemParamItem, SystemState}, world::World, }; use bevy_utils::HashMap; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use std::{any::TypeId, fmt::Debug, hash::Hash, ops::Range}; +use std::{any::TypeId, fmt::Debug, hash::Hash}; -/// A draw function which is used to draw a specific [`PhaseItem`]. +/// A draw function used to draw [`PhaseItem`]s. +/// +/// The draw function can retrieve and query the required ECS data from the render world. /// -/// They are the general form of drawing items, whereas [`RenderCommands`](RenderCommand) -/// are more modular. +/// This trait can either be implemented directly or implicitly composed out of multiple modular +/// [`RenderCommand`]s. For more details and an example see the [`RenderCommand`] documentation. pub trait Draw: Send + Sync + 'static { /// Prepares the draw function 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.. + /// begins. There may be zero or more `draw` calls following a call to this function. /// Implementing this is optional. #[allow(unused_variables)] fn prepare(&mut self, world: &'_ World) {} - /// Draws the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`]. + /// Draws a [`PhaseItem`] by issuing zero or more `draw` calls via the [`TrackedRenderPass`]. fn draw<'w>( &mut self, world: &'w World, @@ -38,64 +34,33 @@ pub trait Draw: Send + Sync + 'static { ); } -/// An item which will be drawn to the screen. A phase item should be queued up for rendering -/// during the [`RenderStage::Queue`](crate::RenderStage::Queue) stage. -/// Afterwards it will be sorted and rendered automatically in the -/// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) stage and -/// [`RenderStage::Render`](crate::RenderStage::Render) stage, respectively. -pub trait PhaseItem: Sized + Send + Sync + 'static { - /// The type used for ordering the items. The smallest values are drawn first. - type SortKey: Ord; - fn entity(&self) -> Entity; - - /// Determines the order in which the items are drawn during the corresponding [`RenderPhase`](super::RenderPhase). - fn sort_key(&self) -> Self::SortKey; - /// Specifies the [`Draw`] function used to render the item. - fn draw_function(&self) -> DrawFunctionId; - - /// Sorts a slice of phase items into render order. Generally if the same type - /// implements [`BatchedPhaseItem`], this should use a stable sort like [`slice::sort_by_key`]. - /// In almost all other cases, this should not be altered from the default, - /// which uses a unstable sort, as this provides the best balance of CPU and GPU - /// performance. - /// - /// Implementers can optionally not sort the list at all. This is generally advisable if and - /// only if the renderer supports a depth prepass, which is by default not supported by - /// the rest of Bevy's first party rendering crates. Even then, this may have a negative - /// impact on GPU-side performance due to overdraw. - /// - /// It's advised to always profile for performance changes when changing this implementation. - #[inline] - fn sort(items: &mut [Self]) { - items.sort_unstable_by_key(|item| item.sort_key()); - } -} - // TODO: make this generic? /// An identifier for a [`Draw`] function stored in [`DrawFunctions`]. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct DrawFunctionId(u32); -/// Stores all draw functions for the [`PhaseItem`] type. -/// For retrieval they are associated with their [`TypeId`]. +/// Stores all [`Draw`] functions for the [`PhaseItem`] type. +/// +/// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s. pub struct DrawFunctionsInternal { pub draw_functions: Vec>>, pub indices: HashMap, } impl DrawFunctionsInternal

{ + /// Prepares all draw function. This is called once and only once before the phase begins. pub fn prepare(&mut self, world: &World) { for function in &mut self.draw_functions { function.prepare(world); } } - /// Adds the [`Draw`] function and associates it to its own type. + /// Adds the [`Draw`] function and maps it to its own type. pub fn add>(&mut self, draw_function: T) -> DrawFunctionId { self.add_with::(draw_function) } - /// Adds the [`Draw`] function and associates it to the type `T` + /// Adds the [`Draw`] function and maps it to the type `T` pub fn add_with>(&mut self, draw_function: D) -> DrawFunctionId { let id = DrawFunctionId(self.draw_functions.len().try_into().unwrap()); self.draw_functions.push(Box::new(draw_function)); @@ -118,7 +83,7 @@ impl DrawFunctionsInternal

{ /// Fallible wrapper for [`Self::get_id()`] /// /// ## Panics - /// If the id doesn't exist it will panic + /// If the id doesn't exist, this function will panic. pub fn id(&self) -> DrawFunctionId { self.get_id::().unwrap_or_else(|| { panic!( @@ -131,6 +96,7 @@ impl DrawFunctionsInternal

{ } /// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock. +/// /// To access them the [`DrawFunctions::read`] and [`DrawFunctions::write`] methods are used. #[derive(Resource)] pub struct DrawFunctions { @@ -160,15 +126,26 @@ impl DrawFunctions

{ } } -/// [`RenderCommand`] is a trait that runs an ECS query and produces one or more -/// [`TrackedRenderPass`] calls. Types implementing this trait can be composed (as tuples). +/// [`RenderCommand`]s are modular standardized pieces of render logic that can be composed into +/// [`Draw`] functions. /// -/// They can be registered as a [`Draw`] function via the +/// To turn a stateless render command into a usable draw function it has to be wrapped by a +/// [`RenderCommandState`]. +/// This is done automatically when registering a render command as a [`Draw`] function via the /// [`AddRenderCommand::add_render_command`] method. /// +/// Compared to the draw function the required ECS data is fetched automatically +/// (by the [`RenderCommandState`]) from the render world. +/// Therefore the three types [`Param`](RenderCommand::Param), +/// [`ViewWorldQuery`](RenderCommand::ViewWorldQuery) and +/// [`ItemWorldQuery`](RenderCommand::ItemWorldQuery) are used. +/// They specify which information is required to execute the render command. +/// +/// Multiple render commands can be combined together by wrapping them in a tuple. +/// /// # Example /// The `DrawPbr` draw function is created from the following render command -/// tuple. Const generics are used to set specific bind group locations: +/// tuple. Const generics are used to set specific bind group locations: /// /// ```ignore /// pub type DrawPbr = ( @@ -180,13 +157,24 @@ impl DrawFunctions

{ /// ); /// ``` pub trait RenderCommand { - /// Specifies all ECS data required by [`RenderCommand::render`]. + /// Specifies the general ECS data (e.g. resources) required by [`RenderCommand::render`]. + /// /// All parameters have to be read only. type Param: SystemParam + 'static; + /// Specifies the ECS data of the view entity required by [`RenderCommand::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 the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`]. + /// Renders a [`PhaseItem`] by recording commands (e.g. setting pipelines, binding bind groups, + /// issuing draw calls, etc.) via the [`TrackedRenderPass`]. fn render<'w>( item: &P, view: ROQueryItem<'w, Self::ViewWorldQuery>, @@ -196,86 +184,12 @@ pub trait RenderCommand { ) -> RenderCommandResult; } +/// The result of a [`RenderCommand`]. pub enum RenderCommandResult { Success, Failure, } -pub trait CachedRenderPipelinePhaseItem: PhaseItem { - fn cached_pipeline(&self) -> CachedRenderPipelineId; -} - -/// A [`PhaseItem`] that can be batched dynamically. -/// -/// Batching is an optimization that regroups multiple items in the same vertex buffer -/// to render them in a single draw call. -/// -/// If this is implemented on a type, the implementation of [`PhaseItem::sort`] should -/// be changed to implement a stable sort, or incorrect/suboptimal batching may result. -pub trait BatchedPhaseItem: PhaseItem { - /// Range in the vertex buffer of this item - fn batch_range(&self) -> &Option>; - - /// Range in the vertex buffer of this item - fn batch_range_mut(&mut self) -> &mut Option>; - - /// Batches another item within this item if they are compatible. - /// Items can be batched together if they have the same entity, and consecutive ranges. - /// If batching is successful, the `other` item should be discarded from the render pass. - #[inline] - fn add_to_batch(&mut self, other: &Self) -> BatchResult { - let self_entity = self.entity(); - if let (Some(self_batch_range), Some(other_batch_range)) = ( - self.batch_range_mut().as_mut(), - other.batch_range().as_ref(), - ) { - // If the items are compatible, join their range into `self` - if self_entity == other.entity() { - if self_batch_range.end == other_batch_range.start { - self_batch_range.end = other_batch_range.end; - return BatchResult::Success; - } else if self_batch_range.start == other_batch_range.end { - self_batch_range.start = other_batch_range.start; - return BatchResult::Success; - } - } - } - BatchResult::IncompatibleItems - } -} - -pub enum BatchResult { - /// The `other` item was batched into `self` - Success, - /// `self` and `other` cannot be batched together - IncompatibleItems, -} - -pub struct SetItemPipeline; -impl RenderCommand

for SetItemPipeline { - type Param = SRes; - type ViewWorldQuery = (); - type ItemWorldQuery = (); - #[inline] - fn render<'w>( - item: &P, - _view: (), - _entity: (), - pipeline_cache: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - if let Some(pipeline) = pipeline_cache - .into_inner() - .get_render_pipeline(item.cached_pipeline()) - { - pass.set_render_pipeline(pipeline); - RenderCommandResult::Success - } else { - RenderCommandResult::Failure - } - } -} - macro_rules! render_command_tuple_impl { ($(($name: ident, $view: ident, $entity: ident)),*) => { impl),*> RenderCommand

for ($($name,)*) { @@ -303,7 +217,9 @@ macro_rules! render_command_tuple_impl { all_tuples!(render_command_tuple_impl, 0, 15, C, V, E); /// Wraps a [`RenderCommand`] into a state so that it can be used as a [`Draw`] function. -/// Therefore the [`RenderCommand::Param`] is queried from the ECS and passed to the command. +/// +/// The [`RenderCommand::Param`], [`RenderCommand::ViewWorldQuery`] and +/// [`RenderCommand::ItemWorldQuery`] are fetched from the ECS and passed to the command. pub struct RenderCommandState> { state: SystemState, view: QueryState, @@ -311,6 +227,7 @@ pub struct RenderCommandState> { } impl> RenderCommandState { + /// Creates a new [`RenderCommandState`] for the [`RenderCommand`]. pub fn new(world: &mut World) -> Self { Self { state: SystemState::new(world), @@ -324,12 +241,14 @@ impl + Send + Sync + 'static> Draw

for Rend where C::Param: ReadOnlySystemParam, { + /// 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); } - /// Prepares the ECS parameters for the wrapped [`RenderCommand`] and then renders it. + /// Fetches the ECS parameters for the wrapped [`RenderCommand`] and then renders it. fn draw<'w>( &mut self, world: &'w World, @@ -340,6 +259,7 @@ where let param = self.state.get(world); let view = self.view.get_manual(world, view).unwrap(); let entity = self.entity.get_manual(world, item.entity()).unwrap(); + // TODO: handle/log `RenderCommand` failure C::render(item, view, entity, param, pass); } } diff --git a/crates/bevy_render/src/render_phase/draw_state.rs b/crates/bevy_render/src/render_phase/draw_state.rs index 6d6795fe9a936..f228e46ef2690 100644 --- a/crates/bevy_render/src/render_phase/draw_state.rs +++ b/crates/bevy_render/src/render_phase/draw_state.rs @@ -12,7 +12,11 @@ use std::ops::Range; use wgpu::{IndexFormat, RenderPass}; use wgpu_hal::{MAX_BIND_GROUPS, MAX_VERTEX_BUFFERS}; -/// Tracks the current [`TrackedRenderPass`] state to ensure draw calls are valid. +/// Tracks the state of a [`TrackedRenderPass`]. +/// +/// This is used to skip redundant operations on the [`TrackedRenderPass`] (e.g. setting an already +/// set pipeline, binding an already bound bind group). These operations can otherwise be fairly +/// costly due to IO to the GPU, so deduplicating these calls results in a speedup. #[derive(Debug, Default)] struct DrawState { pipeline: Option, @@ -22,6 +26,21 @@ struct DrawState { } impl DrawState { + /// Marks the `pipeline` as bound. + pub fn set_pipeline(&mut self, pipeline: RenderPipelineId) { + // TODO: do these need to be cleared? + // self.bind_groups.clear(); + // self.vertex_buffers.clear(); + // self.index_buffer = None; + self.pipeline = Some(pipeline); + } + + /// Checks, whether the `pipeline` is already bound. + pub fn is_pipeline_set(&self, pipeline: RenderPipelineId) -> bool { + self.pipeline == Some(pipeline) + } + + /// Marks the `bind_group` as bound to the `index`. pub fn set_bind_group( &mut self, index: usize, @@ -34,6 +53,7 @@ impl DrawState { group.1.extend(dynamic_indices); } + /// Checks, whether the `bind_group` is already bound to the `index`. pub fn is_bind_group_set( &self, index: usize, @@ -47,10 +67,12 @@ impl DrawState { } } + /// Marks the vertex `buffer` as bound to the `index`. pub fn set_vertex_buffer(&mut self, index: usize, buffer: BufferId, offset: u64) { self.vertex_buffers[index] = Some((buffer, offset)); } + /// Checks, whether the vertex `buffer` is already bound to the `index`. pub fn is_vertex_buffer_set(&self, index: usize, buffer: BufferId, offset: u64) -> bool { if let Some(current) = self.vertex_buffers.get(index) { *current == Some((buffer, offset)) @@ -59,10 +81,12 @@ impl DrawState { } } + /// Marks the index `buffer` as bound. pub fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat) { self.index_buffer = Some((buffer, offset, index_format)); } + /// Checks, whether the index `buffer` is already bound. pub fn is_index_buffer_set( &self, buffer: BufferId, @@ -71,22 +95,11 @@ impl DrawState { ) -> bool { self.index_buffer == Some((buffer, offset, index_format)) } - - pub fn is_pipeline_set(&self, pipeline: RenderPipelineId) -> bool { - self.pipeline == Some(pipeline) - } - - pub fn set_pipeline(&mut self, pipeline: RenderPipelineId) { - // TODO: do these need to be cleared? - // self.bind_groups.clear(); - // self.vertex_buffers.clear(); - // self.index_buffer = None; - self.pipeline = Some(pipeline); - } } -/// A [`RenderPass`], which tracks the current pipeline state to ensure all draw calls are valid. -/// It is used to set the current [`RenderPipeline`], [`BindGroups`](BindGroup) and buffers. +/// A [`RenderPass`], which tracks the current pipeline state to skip redundant operations. +/// +/// It is used to set the current [`RenderPipeline`], [`BindGroup`]s and [`Buffer`]s. /// After all requirements are specified, draw calls can be issued. pub struct TrackedRenderPass<'a> { pass: RenderPass<'a>, @@ -121,8 +134,13 @@ impl<'a> TrackedRenderPass<'a> { self.state.set_pipeline(pipeline.id()); } - /// Sets the active [`BindGroup`] for a given bind group index. The bind group layout in the - /// active pipeline when any `draw()` function is called must match the layout of this `bind group`. + /// Sets the active bind group for a given bind group index. The bind group layout + /// in the active pipeline when any `draw()` function is called must match the layout of + /// this bind group. + /// + /// If the bind group have dynamic offsets, provide them in binding order. + /// These offsets have to be aligned to [`WgpuLimits::min_uniform_buffer_offset_alignment`](crate::settings::WgpuLimits::min_uniform_buffer_offset_alignment) + /// or [`WgpuLimits::min_storage_buffer_offset_alignment`](crate::settings::WgpuLimits::min_storage_buffer_offset_alignment) appropriately. pub fn set_bind_group( &mut self, index: usize, @@ -156,11 +174,14 @@ impl<'a> TrackedRenderPass<'a> { /// Assign a vertex buffer to a slot. /// - /// Subsequent calls to [`TrackedRenderPass::draw`] and [`TrackedRenderPass::draw_indexed`] - /// will use the buffer referenced by `buffer_slice` as one of the source vertex buffer(s). + /// Subsequent calls to [`draw`] and [`draw_indexed`] on this + /// [`RenderPass`] will use `buffer` as one of the source vertex buffers. /// /// The `slot_index` refers to the index of the matching descriptor in /// [`VertexState::buffers`](crate::render_resource::VertexState::buffers). + /// + /// [`draw`]: TrackedRenderPass::draw + /// [`draw_indexed`]: TrackedRenderPass::draw_indexed pub fn set_vertex_buffer(&mut self, slot_index: usize, buffer_slice: BufferSlice<'a>) { let offset = buffer_slice.offset(); if self @@ -237,7 +258,8 @@ impl<'a> TrackedRenderPass<'a> { self.pass.draw_indexed(indices, base_vertex, instances); } - /// Draws primitives from the active vertex buffer(s) based on the contents of the `indirect_buffer`. + /// Draws primitives from the active vertex buffer(s) based on the contents of the + /// `indirect_buffer`. /// /// The active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. /// @@ -261,8 +283,8 @@ impl<'a> TrackedRenderPass<'a> { /// Draws indexed primitives using the active index buffer and the active vertex buffers, /// based on the contents of the `indirect_buffer`. /// - /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the active - /// vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. + /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the + /// active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. /// /// The structure expected in `indirect_buffer` is the following: /// @@ -287,8 +309,8 @@ impl<'a> TrackedRenderPass<'a> { .draw_indexed_indirect(indirect_buffer, indirect_offset); } - /// Dispatches multiple draw calls from the active vertex buffer(s) based on the contents of the `indirect_buffer`. - /// `count` draw calls are issued. + /// Dispatches multiple draw calls from the active vertex buffer(s) based on the contents of the + /// `indirect_buffer`.`count` draw calls are issued. /// /// The active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. /// @@ -320,11 +342,13 @@ impl<'a> TrackedRenderPass<'a> { .multi_draw_indirect(indirect_buffer, indirect_offset, count); } - /// Dispatches multiple draw calls from the active vertex buffer(s) based on the contents of the `indirect_buffer`. + /// Dispatches multiple draw calls from the active vertex buffer(s) based on the contents of + /// the `indirect_buffer`. /// The count buffer is read to determine how many draws to issue. /// - /// The indirect buffer must be long enough to account for `max_count` draws, however only `count` elements - /// will be read, where `count` is the value read from `count_buffer` capped at `max_count`. + /// The indirect buffer must be long enough to account for `max_count` draws, however only + /// `count` elements will be read, where `count` is the value read from `count_buffer` capped + /// at `max_count`. /// /// The active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. /// @@ -368,8 +392,8 @@ impl<'a> TrackedRenderPass<'a> { /// Dispatches multiple draw calls from the active index buffer and the active vertex buffers, /// based on the contents of the `indirect_buffer`. `count` draw calls are issued. /// - /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the active - /// vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. + /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the + /// active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. /// /// `indirect_buffer` should contain `count` tightly packed elements of the following structure: /// @@ -401,13 +425,15 @@ impl<'a> TrackedRenderPass<'a> { } /// Dispatches multiple draw calls from the active index buffer and the active vertex buffers, - /// based on the contents of the `indirect_buffer`. The count buffer is read to determine how many draws to issue. + /// based on the contents of the `indirect_buffer`. + /// The count buffer is read to determine how many draws to issue. /// - /// The indirect buffer must be long enough to account for `max_count` draws, however only `count` elements - /// will be read, where `count` is the value read from `count_buffer` capped at `max_count`. + /// The indirect buffer must be long enough to account for `max_count` draws, however only + /// `count` elements will be read, where `count` is the value read from `count_buffer` capped + /// at `max_count`. /// - /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the active - /// vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. + /// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the + /// active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`]. /// /// `indirect_buffer` should contain `count` tightly packed elements of the following structure: /// @@ -501,7 +527,7 @@ impl<'a> TrackedRenderPass<'a> { .set_viewport(x, y, width, height, min_depth, max_depth); } - /// Set the rendering viewport to the given [`Camera`](crate::camera::Viewport) [`Viewport`]. + /// Set the rendering viewport to the given camera [`Viewport`]. /// /// Subsequent draw calls will be projected into that viewport. pub fn set_camera_viewport(&mut self, viewport: &Viewport) { @@ -565,6 +591,9 @@ impl<'a> TrackedRenderPass<'a> { self.pass.pop_debug_group(); } + /// Sets the blend color as used by some of the blending modes. + /// + /// Subsequent blending tests will test against this value. pub fn set_blend_constant(&mut self, color: Color) { trace!("set blend constant: {:?}", color); self.pass.set_blend_constant(wgpu::Color::from(color)); diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index b0b502422b928..54226a5d3b1d6 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -1,14 +1,55 @@ +//! The modular rendering abstraction responsible for queuing, preparing, sorting and drawing +//! entities as part of separate render phases. +//! +//! In Bevy each view (camera, or shadow-casting light, etc.) has one or multiple [`RenderPhase`]s +//! (e.g. opaque, transparent, shadow, etc). +//! They are used to queue entities for rendering. +//! Multiple phases might be required due to different sorting/batching behaviours +//! (e.g. opaque: front to back, transparent: back to front) or because one phase depends on +//! the rendered texture of the previous phase (e.g. for screen-space reflections). +//! +//! To draw an entity, a corresponding [`PhaseItem`] has to be added to one or multiple of these +//! render phases for each view that it is visible in. +//! This must be done in the [`RenderStage::Queue`](crate::RenderStage::Queue). +//! After that the render phase sorts them in the +//! [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort). +//! Finally the items are rendered using a single [`TrackedRenderPass`], during the +//! [`RenderStage::Render`](crate::RenderStage::Render). +//! +//! Therefore each phase item is assigned a [`Draw`] function. +//! 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, +//! for the corresponding item. +//! +//! The [`Draw`] function trait can either be implemented directly or such a function can be +//! created by composing multiple [`RenderCommand`]s. + mod draw; mod draw_state; +mod rangefinder; -use bevy_ecs::entity::Entity; pub use draw::*; pub use draw_state::*; +pub use rangefinder::*; -use bevy_ecs::prelude::{Component, Query}; -use bevy_ecs::world::World; +use crate::render_resource::{CachedRenderPipelineId, PipelineCache}; +use bevy_ecs::{ + prelude::*, + system::{lifetimeless::SRes, SystemParamItem}, +}; +use std::ops::Range; -/// A resource to collect and sort draw requests for specific [`PhaseItems`](PhaseItem). +/// A collection of all rendering instructions, 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. +/// They are used to queue entities for rendering. +/// Multiple phases might be required due to different sorting/batching behaviours +/// (e.g. opaque: front to back, transparent: back to front) or because one phase depends on +/// the rendered texture of the previous phase (e.g. for screen-space reflections). +/// All [`PhaseItem`]s are then rendered using a single [`TrackedRenderPass`]. +/// The render pass might be reused for multiple phases to reduce GPU overhead. #[derive(Component)] pub struct RenderPhase { pub items: Vec, @@ -27,11 +68,12 @@ impl RenderPhase { self.items.push(item); } - /// Sorts all of its [`PhaseItems`](PhaseItem). + /// Sorts all of its [`PhaseItem`]s. pub fn sort(&mut self) { I::sort(&mut self.items); } + /// Renders all of its [`PhaseItem`]s using their corresponding draw functions. pub fn render<'w>( &self, render_pass: &mut TrackedRenderPass<'w>, @@ -76,7 +118,138 @@ impl RenderPhase { } } -/// This system sorts all [`RenderPhases`](RenderPhase) for the [`PhaseItem`] type. +/// An item (entity of the render world) which will be drawn to a texture or the screen, +/// as part of a [`RenderPhase`]. +/// +/// The data required for rendering an entity is extracted from the main world in the +/// [`RenderStage::Extract`](crate::RenderStage::Extract). +/// Then it has to be queued up for rendering during the +/// [`RenderStage::Queue`](crate::RenderStage::Queue), by adding a corresponding phase item to +/// a render phase. +/// Afterwards it will be sorted and rendered automatically in the +/// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) and +/// [`RenderStage::Render`](crate::RenderStage::Render), respectively. +pub trait PhaseItem: Sized + Send + Sync + 'static { + /// The type used for ordering the items. The smallest values are drawn first. + /// This order can be calculated using the [`ViewRangefinder3d`], + /// based on the view-space `Z` value of the corresponding view matrix. + type SortKey: Ord; + + /// The corresponding entity that will be drawn. + /// + /// This is used to fetch the render data of the entity, required by the draw function, + /// from the render world . + fn entity(&self) -> Entity; + + /// Determines the order in which the items are drawn. + fn sort_key(&self) -> Self::SortKey; + + /// Specifies the [`Draw`] function used to render the item. + fn draw_function(&self) -> DrawFunctionId; + + /// Sorts a slice of phase items into render order. Generally if the same type + /// implements [`BatchedPhaseItem`], this should use a stable sort like [`slice::sort_by_key`]. + /// In almost all other cases, this should not be altered from the default, + /// which uses a unstable sort, as this provides the best balance of CPU and GPU + /// performance. + /// + /// Implementers can optionally not sort the list at all. This is generally advisable if and + /// only if the renderer supports a depth prepass, which is by default not supported by + /// the rest of Bevy's first party rendering crates. Even then, this may have a negative + /// impact on GPU-side performance due to overdraw. + /// + /// It's advised to always profile for performance changes when changing this implementation. + #[inline] + fn sort(items: &mut [Self]) { + items.sort_unstable_by_key(|item| item.sort_key()); + } +} + +/// A [`PhaseItem`] item, that automatically sets the appropriate render pipeline, +/// cached in the [`PipelineCache`]. +/// +/// You can use the [`SetItemPipeline`] render command to set the pipeline for this item. +pub trait CachedRenderPipelinePhaseItem: PhaseItem { + /// The id of the render pipeline, cached in the [`PipelineCache`], that will be used to draw + /// this phase item. + fn cached_pipeline(&self) -> CachedRenderPipelineId; +} + +/// A [`RenderCommand`] that sets the pipeline for the [`CachedRenderPipelinePhaseItem`]. +pub struct SetItemPipeline; + +impl RenderCommand

for SetItemPipeline { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = (); + #[inline] + fn render<'w>( + item: &P, + _view: (), + _entity: (), + pipeline_cache: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + if let Some(pipeline) = pipeline_cache + .into_inner() + .get_render_pipeline(item.cached_pipeline()) + { + pass.set_render_pipeline(pipeline); + RenderCommandResult::Success + } else { + RenderCommandResult::Failure + } + } +} + +/// A [`PhaseItem`] that can be batched dynamically. +/// +/// Batching is an optimization that regroups multiple items in the same vertex buffer +/// to render them in a single draw call. +/// +/// If this is implemented on a type, the implementation of [`PhaseItem::sort`] should +/// be changed to implement a stable sort, or incorrect/suboptimal batching may result. +pub trait BatchedPhaseItem: PhaseItem { + /// Range in the vertex buffer of this item. + fn batch_range(&self) -> &Option>; + + /// Range in the vertex buffer of this item. + fn batch_range_mut(&mut self) -> &mut Option>; + + /// Batches another item within this item if they are compatible. + /// Items can be batched together if they have the same entity, and consecutive ranges. + /// If batching is successful, the `other` item should be discarded from the render pass. + #[inline] + fn add_to_batch(&mut self, other: &Self) -> BatchResult { + let self_entity = self.entity(); + if let (Some(self_batch_range), Some(other_batch_range)) = ( + self.batch_range_mut().as_mut(), + other.batch_range().as_ref(), + ) { + // If the items are compatible, join their range into `self` + if self_entity == other.entity() { + if self_batch_range.end == other_batch_range.start { + self_batch_range.end = other_batch_range.end; + return BatchResult::Success; + } else if self_batch_range.start == other_batch_range.end { + self_batch_range.start = other_batch_range.start; + return BatchResult::Success; + } + } + } + BatchResult::IncompatibleItems + } +} + +/// The result of a batching operation. +pub enum BatchResult { + /// The `other` item was batched into `self` + Success, + /// `self` and `other` cannot be batched together + IncompatibleItems, +} + +/// This system sorts the [`PhaseItem`]s of all [`RenderPhase`]s of this type. pub fn sort_phase_system(mut render_phases: Query<&mut RenderPhase>) { for mut phase in &mut render_phases { phase.sort(); @@ -92,11 +265,9 @@ pub fn batch_phase_system(mut render_phases: Query<&mut Ren #[cfg(test)] mod tests { - use std::ops::Range; - - use bevy_ecs::entity::Entity; - use super::*; + use bevy_ecs::entity::Entity; + use std::ops::Range; #[test] fn batching() { @@ -108,7 +279,7 @@ mod tests { impl PhaseItem for TestPhaseItem { type SortKey = (); - fn entity(&self) -> bevy_ecs::entity::Entity { + fn entity(&self) -> Entity { self.entity } @@ -119,11 +290,11 @@ mod tests { } } impl BatchedPhaseItem for TestPhaseItem { - fn batch_range(&self) -> &Option> { + fn batch_range(&self) -> &Option> { &self.batch_range } - fn batch_range_mut(&mut self) -> &mut Option> { + fn batch_range_mut(&mut self) -> &mut Option> { &mut self.batch_range } } diff --git a/crates/bevy_render/src/rangefinder.rs b/crates/bevy_render/src/render_phase/rangefinder.rs similarity index 89% rename from crates/bevy_render/src/rangefinder.rs rename to crates/bevy_render/src/render_phase/rangefinder.rs index c12ab3af16b11..797782b9ccf66 100644 --- a/crates/bevy_render/src/rangefinder.rs +++ b/crates/bevy_render/src/render_phase/rangefinder.rs @@ -6,15 +6,16 @@ pub struct ViewRangefinder3d { } impl ViewRangefinder3d { - /// Creates a 3D rangefinder for a view matrix + /// Creates a 3D rangefinder for a view matrix. pub fn from_view_matrix(view_matrix: &Mat4) -> ViewRangefinder3d { let inverse_view_matrix = view_matrix.inverse(); + ViewRangefinder3d { inverse_view_row_2: inverse_view_matrix.row(2), } } - /// Calculates the distance, or view-space `Z` value, for a transform + /// Calculates the distance, or view-space `Z` value, for the given `transform`. #[inline] pub fn distance(&self, transform: &Mat4) -> f32 { // NOTE: row 2 of the inverse view matrix dotted with column 3 of the model matrix diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index bc0c12b023135..3bb6479c86e89 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -8,8 +8,8 @@ use crate::{ camera::ExtractedCamera, extract_resource::{ExtractResource, ExtractResourcePlugin}, prelude::Image, - rangefinder::ViewRangefinder3d, render_asset::RenderAssets, + render_phase::ViewRangefinder3d, render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, texture::{BevyDefault, TextureCache},