Skip to main content

bevy_render/render_phase/
draw.rs

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