Path: blob/main/crates/bevy_render/src/render_phase/draw.rs
9356 views
use bevy_material::labels::DrawFunctionId;12use crate::render_phase::{PhaseItem, TrackedRenderPass};3use bevy_app::{App, SubApp};4use bevy_ecs::{5entity::Entity,6query::{QueryEntityError, QueryState, ROQueryItem, ReadOnlyQueryData},7resource::Resource,8system::{ReadOnlySystemParam, SystemParam, SystemParamItem, SystemState},9world::World,10};11use bevy_utils::TypeIdMap;12use core::{any::TypeId, fmt::Debug};13use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};14use thiserror::Error;15use variadics_please::all_tuples;1617/// A draw function used to draw [`PhaseItem`]s.18///19/// The draw function can retrieve and query the required ECS data from the render world.20///21/// This trait can either be implemented directly or implicitly composed out of multiple modular22/// [`RenderCommand`]s. For more details and an example see the [`RenderCommand`] documentation.23pub trait Draw<P: PhaseItem>: Send + Sync + 'static {24/// Prepares the draw function to be used. This is called once and only once before the phase25/// begins. There may be zero or more [`draw`](Draw::draw) calls following a call to this function.26/// Implementing this is optional.27#[expect(28unused_variables,29reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion."30)]31fn prepare(&mut self, world: &'_ World) {}3233/// Draws a [`PhaseItem`] by issuing zero or more `draw` calls via the [`TrackedRenderPass`].34fn draw<'w>(35&mut self,36world: &'w World,37pass: &mut TrackedRenderPass<'w>,38view: Entity,39item: &P,40) -> Result<(), DrawError>;41}4243#[derive(Error, Debug, PartialEq, Eq)]44pub enum DrawError {45#[error("Failed to execute render command {0:?}")]46RenderCommandFailure(&'static str),47#[error("Failed to get execute view query")]48InvalidViewQuery,49#[error("View entity not found")]50ViewEntityNotFound,51}5253/// Stores all [`Draw`] functions for the [`PhaseItem`] type.54///55/// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s.56pub struct DrawFunctionsInternal<P: PhaseItem> {57pub draw_functions: Vec<Box<dyn Draw<P>>>,58pub indices: TypeIdMap<DrawFunctionId>,59}6061impl<P: PhaseItem> DrawFunctionsInternal<P> {62/// Prepares all draw function. This is called once and only once before the phase begins.63pub fn prepare(&mut self, world: &World) {64for function in &mut self.draw_functions {65function.prepare(world);66}67}6869/// Adds the [`Draw`] function and maps it to its own type.70pub fn add<T: Draw<P>>(&mut self, draw_function: T) -> DrawFunctionId {71self.add_with::<T, T>(draw_function)72}7374/// Adds the [`Draw`] function and maps it to the type `T`75pub fn add_with<T: 'static, D: Draw<P>>(&mut self, draw_function: D) -> DrawFunctionId {76let id = DrawFunctionId(self.draw_functions.len().try_into().unwrap());77self.draw_functions.push(Box::new(draw_function));78self.indices.insert(TypeId::of::<T>(), id);79id80}8182/// Retrieves the [`Draw`] function corresponding to the `id` mutably.83pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw<P>> {84self.draw_functions.get_mut(id.0 as usize).map(|f| &mut **f)85}8687/// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`.88pub fn get_id<T: 'static>(&self) -> Option<DrawFunctionId> {89self.indices.get(&TypeId::of::<T>()).copied()90}9192/// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`.93///94/// Fallible wrapper for [`Self::get_id()`]95///96/// ## Panics97/// If the id doesn't exist, this function will panic.98pub fn id<T: 'static>(&self) -> DrawFunctionId {99self.get_id::<T>().unwrap_or_else(|| {100panic!(101"Draw function {} not found for {}",102core::any::type_name::<T>(),103core::any::type_name::<P>()104)105})106}107}108109/// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock.110///111/// To access them the [`DrawFunctions::read`] and [`DrawFunctions::write`] methods are used.112#[derive(Resource)]113pub struct DrawFunctions<P: PhaseItem> {114internal: RwLock<DrawFunctionsInternal<P>>,115}116117impl<P: PhaseItem> Default for DrawFunctions<P> {118fn default() -> Self {119Self {120internal: RwLock::new(DrawFunctionsInternal {121draw_functions: Vec::new(),122indices: Default::default(),123}),124}125}126}127128impl<P: PhaseItem> DrawFunctions<P> {129/// Accesses the draw functions in read mode.130pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal<P>> {131self.internal.read().unwrap_or_else(PoisonError::into_inner)132}133134/// Accesses the draw functions in write mode.135pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal<P>> {136self.internal137.write()138.unwrap_or_else(PoisonError::into_inner)139}140}141142/// [`RenderCommand`]s are modular standardized pieces of render logic that can be composed into143/// [`Draw`] functions.144///145/// To turn a stateless render command into a usable draw function it has to be wrapped by a146/// [`RenderCommandState`].147/// This is done automatically when registering a render command as a [`Draw`] function via the148/// [`AddRenderCommand::add_render_command`] method.149///150/// Compared to the draw function the required ECS data is fetched automatically151/// (by the [`RenderCommandState`]) from the render world.152/// Therefore the three types [`Param`](RenderCommand::Param),153/// [`ViewQuery`](RenderCommand::ViewQuery) and154/// [`ItemQuery`](RenderCommand::ItemQuery) are used.155/// They specify which information is required to execute the render command.156///157/// Multiple render commands can be combined together by wrapping them in a tuple.158///159/// # Example160///161/// The `DrawMaterial` draw function is created from the following render command162/// tuple. Const generics are used to set specific bind group locations:163///164/// ```165/// # use bevy_render::render_phase::SetItemPipeline;166/// # struct SetMeshViewBindGroup<const N: usize>;167/// # struct SetMeshViewBindingArrayBindGroup<const N: usize>;168/// # struct SetMeshBindGroup<const N: usize>;169/// # struct SetMaterialBindGroup<M, const N: usize>(std::marker::PhantomData<M>);170/// # struct DrawMesh;171/// pub type DrawMaterial<M> = (172/// SetItemPipeline,173/// SetMeshViewBindGroup<0>,174/// SetMeshViewBindingArrayBindGroup<1>,175/// SetMeshBindGroup<2>,176/// SetMaterialBindGroup<M, 3>,177/// DrawMesh,178/// );179/// ```180pub trait RenderCommand<P: PhaseItem> {181/// Specifies the general ECS data (e.g. resources) required by [`RenderCommand::render`].182///183/// When fetching resources, note that, due to lifetime limitations of the `Deref` trait,184/// [`SRes::into_inner`] must be called on each [`SRes`] reference in the185/// [`RenderCommand::render`] method, instead of being automatically dereferenced as is the186/// case in normal `systems`.187///188/// All parameters have to be read only.189///190/// [`SRes`]: bevy_ecs::system::lifetimeless::SRes191/// [`SRes::into_inner`]: bevy_ecs::system::lifetimeless::SRes::into_inner192type Param: SystemParam + 'static;193/// Specifies the ECS data of the view entity required by [`RenderCommand::render`].194///195/// The view entity refers to the camera, or shadow-casting light, etc. from which the phase196/// item will be rendered from.197/// All components have to be accessed read only.198type ViewQuery: ReadOnlyQueryData;199/// Specifies the ECS data of the item entity required by [`RenderCommand::render`].200///201/// The item is the entity that will be rendered for the corresponding view.202/// All components have to be accessed read only.203///204/// For efficiency reasons, Bevy doesn't always extract entities to the205/// render world; for instance, entities that simply consist of meshes are206/// often not extracted. If the entity doesn't exist in the render world,207/// the supplied query data will be `None`.208type ItemQuery: ReadOnlyQueryData;209210/// Renders a [`PhaseItem`] by recording commands (e.g. setting pipelines, binding bind groups,211/// issuing draw calls, etc.) via the [`TrackedRenderPass`].212fn render<'w>(213item: &P,214view: ROQueryItem<'w, '_, Self::ViewQuery>,215entity: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,216param: SystemParamItem<'w, '_, Self::Param>,217pass: &mut TrackedRenderPass<'w>,218) -> RenderCommandResult;219}220221/// The result of a [`RenderCommand`].222#[derive(Debug)]223pub enum RenderCommandResult {224Success,225Skip,226Failure(&'static str),227}228229macro_rules! render_command_tuple_impl {230($(#[$meta:meta])* $(($name: ident, $view: ident, $entity: ident)),*) => {231$(#[$meta])*232impl<P: PhaseItem, $($name: RenderCommand<P>),*> RenderCommand<P> for ($($name,)*) {233type Param = ($($name::Param,)*);234type ViewQuery = ($($name::ViewQuery,)*);235type ItemQuery = ($($name::ItemQuery,)*);236237#[expect(238clippy::allow_attributes,239reason = "We are in a macro; as such, `non_snake_case` may not always lint."240)]241#[allow(242non_snake_case,243reason = "Parameter and variable names are provided by the macro invocation, not by us."244)]245fn render<'w>(246_item: &P,247($($view,)*): ROQueryItem<'w, '_, Self::ViewQuery>,248maybe_entities: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,249($($name,)*): SystemParamItem<'w, '_, Self::Param>,250_pass: &mut TrackedRenderPass<'w>,251) -> RenderCommandResult {252match maybe_entities {253None => {254$(255match $name::render(_item, $view, None, $name, _pass) {256RenderCommandResult::Skip => return RenderCommandResult::Skip,257RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason),258_ => {},259}260)*261}262Some(($($entity,)*)) => {263$(264match $name::render(_item, $view, Some($entity), $name, _pass) {265RenderCommandResult::Skip => return RenderCommandResult::Skip,266RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason),267_ => {},268}269)*270}271}272RenderCommandResult::Success273}274}275};276}277278all_tuples!(279#[doc(fake_variadic)]280render_command_tuple_impl,2810,28215,283C,284V,285E286);287288/// Wraps a [`RenderCommand`] into a state so that it can be used as a [`Draw`] function.289///290/// The [`RenderCommand::Param`], [`RenderCommand::ViewQuery`] and291/// [`RenderCommand::ItemQuery`] are fetched from the ECS and passed to the command.292pub struct RenderCommandState<P: PhaseItem + 'static, C: RenderCommand<P>> {293state: SystemState<C::Param>,294view: QueryState<C::ViewQuery>,295entity: QueryState<C::ItemQuery>,296}297298impl<P: PhaseItem, C: RenderCommand<P>> RenderCommandState<P, C> {299/// Creates a new [`RenderCommandState`] for the [`RenderCommand`].300pub fn new(world: &mut World) -> Self {301Self {302state: SystemState::new(world),303view: world.query(),304entity: world.query(),305}306}307}308309impl<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static> Draw<P> for RenderCommandState<P, C>310where311C::Param: ReadOnlySystemParam,312{313/// Prepares the render command to be used. This is called once and only once before the phase314/// begins. There may be zero or more [`draw`](RenderCommandState::draw) calls following a call to this function.315fn prepare(&mut self, world: &'_ World) {316self.view.update_archetypes(world);317self.entity.update_archetypes(world);318}319320/// Fetches the ECS parameters for the wrapped [`RenderCommand`] and then renders it.321fn draw<'w>(322&mut self,323world: &'w World,324pass: &mut TrackedRenderPass<'w>,325view: Entity,326item: &P,327) -> Result<(), DrawError> {328let param = self.state.get(world);329let view = match self.view.get_manual(world, view) {330Ok(view) => view,331Err(err) => match err {332QueryEntityError::NotSpawned(_) => return Err(DrawError::ViewEntityNotFound),333QueryEntityError::QueryDoesNotMatch(_, _)334| QueryEntityError::AliasedMutability(_) => {335return Err(DrawError::InvalidViewQuery)336}337},338};339340let entity = self.entity.get_manual(world, item.entity()).ok();341match C::render(item, view, entity, param, pass) {342RenderCommandResult::Success | RenderCommandResult::Skip => Ok(()),343RenderCommandResult::Failure(reason) => Err(DrawError::RenderCommandFailure(reason)),344}345}346}347348/// Registers a [`RenderCommand`] as a [`Draw`] function.349/// They are stored inside the [`DrawFunctions`] resource of the app.350pub trait AddRenderCommand {351/// Adds the [`RenderCommand`] for the specified render phase to the app.352fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(353&mut self,354) -> &mut Self355where356C::Param: ReadOnlySystemParam;357}358359impl AddRenderCommand for SubApp {360fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(361&mut self,362) -> &mut Self363where364C::Param: ReadOnlySystemParam,365{366let draw_function = RenderCommandState::<P, C>::new(self.world_mut());367let draw_functions = self368.world()369.get_resource::<DrawFunctions<P>>()370.unwrap_or_else(|| {371panic!(372"DrawFunctions<{}> must be added to the world as a resource \373before adding render commands to it",374core::any::type_name::<P>(),375);376});377draw_functions.write().add_with::<C, _>(draw_function);378self379}380}381382impl AddRenderCommand for App {383fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(384&mut self,385) -> &mut Self386where387C::Param: ReadOnlySystemParam,388{389SubApp::add_render_command::<P, C>(self.main_mut());390self391}392}393394395