From 76de9f940786698ec7a33ecbfe916acbc9e2f33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kurt=20K=C3=BChnert?= Date: Thu, 12 Jan 2023 15:11:58 +0000 Subject: [PATCH] Improve render phase documentation (#7016) # Objective The documentation of the bevy_render crate is still pretty incomplete. This PR follows up on #6885 and improves the documentation of the `render_phase` module. This module contains one of our most important rendering abstractions and the current documentation is pretty confusing. This PR tries to clarify what all of these pieces are for and how they work together to form bevy`s modular rendering logic. ## Solution ### Code Reformating - I have moved the `rangefinder` into the `render_phase` module since it is only used there. - I have moved the `PhaseItem` (and the `BatchedPhaseItem`) from `render_phase::draw` over to `render_phase::mod`. This does not change the public-facing API since they are reexported anyway, but this change makes the relation between `RenderPhase` and `PhaseItem` clear and easier to discover. ### Documentation - revised all documentation in the `render_phase` module - added a module-level explanation of how `RenderPhase`s, `RenderPass`es, `PhaseItem`s, `Draw` functions, and `RenderCommands` relate to each other and how they are used --- ## Changelog - The `rangefinder` module has been moved into the `render_phase` module. ## Migration Guide - The `rangefinder` module has been moved into the `render_phase` module. ```rust //old use bevy::render::rangefinder::*; // new use bevy::render::render_phase::rangefinder::*; ``` --- crates/bevy_render/src/lib.rs | 1 - crates/bevy_render/src/render_phase/draw.rs | 190 +++++------------ .../src/render_phase/draw_state.rs | 99 +++++---- crates/bevy_render/src/render_phase/mod.rs | 197 ++++++++++++++++-- .../src/{ => render_phase}/rangefinder.rs | 5 +- crates/bevy_render/src/view/mod.rs | 2 +- 6 files changed, 307 insertions(+), 187 deletions(-) rename crates/bevy_render/src/{ => render_phase}/rangefinder.rs (89%) 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},