bevy_render/render_phase/
draw.rs1use crate::render_phase::{PhaseItem, TrackedRenderPass};
2use bevy_app::{App, SubApp};
3use bevy_ecs::{
4 entity::Entity,
5 query::{QueryEntityError, QueryState, ROQueryItem, ReadOnlyQueryData},
6 resource::Resource,
7 system::{ReadOnlySystemParam, SystemParam, SystemParamItem, SystemState},
8 world::World,
9};
10use bevy_utils::TypeIdMap;
11use core::{any::TypeId, fmt::Debug, hash::Hash};
12use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
13use thiserror::Error;
14use variadics_please::all_tuples;
15
16pub trait Draw<P: PhaseItem>: Send + Sync + 'static {
23 #[expect(
27 unused_variables,
28 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."
29 )]
30 fn prepare(&mut self, world: &'_ World) {}
31
32 fn draw<'w>(
34 &mut self,
35 world: &'w World,
36 pass: &mut TrackedRenderPass<'w>,
37 view: Entity,
38 item: &P,
39 ) -> Result<(), DrawError>;
40}
41
42#[derive(Error, Debug, PartialEq, Eq)]
43pub enum DrawError {
44 #[error("Failed to execute render command {0:?}")]
45 RenderCommandFailure(&'static str),
46 #[error("Failed to get execute view query")]
47 InvalidViewQuery,
48 #[error("View entity not found")]
49 ViewEntityNotFound,
50}
51
52#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
55pub struct DrawFunctionId(u32);
56
57pub struct DrawFunctionsInternal<P: PhaseItem> {
61 pub draw_functions: Vec<Box<dyn Draw<P>>>,
62 pub indices: TypeIdMap<DrawFunctionId>,
63}
64
65impl<P: PhaseItem> DrawFunctionsInternal<P> {
66 pub fn prepare(&mut self, world: &World) {
68 for function in &mut self.draw_functions {
69 function.prepare(world);
70 }
71 }
72
73 pub fn add<T: Draw<P>>(&mut self, draw_function: T) -> DrawFunctionId {
75 self.add_with::<T, T>(draw_function)
76 }
77
78 pub fn add_with<T: 'static, D: Draw<P>>(&mut self, draw_function: D) -> DrawFunctionId {
80 let id = DrawFunctionId(self.draw_functions.len().try_into().unwrap());
81 self.draw_functions.push(Box::new(draw_function));
82 self.indices.insert(TypeId::of::<T>(), id);
83 id
84 }
85
86 pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw<P>> {
88 self.draw_functions.get_mut(id.0 as usize).map(|f| &mut **f)
89 }
90
91 pub fn get_id<T: 'static>(&self) -> Option<DrawFunctionId> {
93 self.indices.get(&TypeId::of::<T>()).copied()
94 }
95
96 pub fn id<T: 'static>(&self) -> DrawFunctionId {
103 self.get_id::<T>().unwrap_or_else(|| {
104 panic!(
105 "Draw function {} not found for {}",
106 core::any::type_name::<T>(),
107 core::any::type_name::<P>()
108 )
109 })
110 }
111}
112
113#[derive(Resource)]
117pub struct DrawFunctions<P: PhaseItem> {
118 internal: RwLock<DrawFunctionsInternal<P>>,
119}
120
121impl<P: PhaseItem> Default for DrawFunctions<P> {
122 fn default() -> Self {
123 Self {
124 internal: RwLock::new(DrawFunctionsInternal {
125 draw_functions: Vec::new(),
126 indices: Default::default(),
127 }),
128 }
129 }
130}
131
132impl<P: PhaseItem> DrawFunctions<P> {
133 pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal<P>> {
135 self.internal.read().unwrap_or_else(PoisonError::into_inner)
136 }
137
138 pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal<P>> {
140 self.internal
141 .write()
142 .unwrap_or_else(PoisonError::into_inner)
143 }
144}
145
146pub trait RenderCommand<P: PhaseItem> {
185 type Param: SystemParam + 'static;
197 type ViewQuery: ReadOnlyQueryData;
203 type ItemQuery: ReadOnlyQueryData;
213
214 fn render<'w>(
217 item: &P,
218 view: ROQueryItem<'w, '_, Self::ViewQuery>,
219 entity: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
220 param: SystemParamItem<'w, '_, Self::Param>,
221 pass: &mut TrackedRenderPass<'w>,
222 ) -> RenderCommandResult;
223}
224
225#[derive(Debug)]
227pub enum RenderCommandResult {
228 Success,
229 Skip,
230 Failure(&'static str),
231}
232
233macro_rules! render_command_tuple_impl {
234 ($(#[$meta:meta])* $(($name: ident, $view: ident, $entity: ident)),*) => {
235 $(#[$meta])*
236 impl<P: PhaseItem, $($name: RenderCommand<P>),*> RenderCommand<P> for ($($name,)*) {
237 type Param = ($($name::Param,)*);
238 type ViewQuery = ($($name::ViewQuery,)*);
239 type ItemQuery = ($($name::ItemQuery,)*);
240
241 #[expect(
242 clippy::allow_attributes,
243 reason = "We are in a macro; as such, `non_snake_case` may not always lint."
244 )]
245 #[allow(
246 non_snake_case,
247 reason = "Parameter and variable names are provided by the macro invocation, not by us."
248 )]
249 fn render<'w>(
250 _item: &P,
251 ($($view,)*): ROQueryItem<'w, '_, Self::ViewQuery>,
252 maybe_entities: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
253 ($($name,)*): SystemParamItem<'w, '_, Self::Param>,
254 _pass: &mut TrackedRenderPass<'w>,
255 ) -> RenderCommandResult {
256 match maybe_entities {
257 None => {
258 $(
259 match $name::render(_item, $view, None, $name, _pass) {
260 RenderCommandResult::Skip => return RenderCommandResult::Skip,
261 RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason),
262 _ => {},
263 }
264 )*
265 }
266 Some(($($entity,)*)) => {
267 $(
268 match $name::render(_item, $view, Some($entity), $name, _pass) {
269 RenderCommandResult::Skip => return RenderCommandResult::Skip,
270 RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason),
271 _ => {},
272 }
273 )*
274 }
275 }
276 RenderCommandResult::Success
277 }
278 }
279 };
280}
281
282all_tuples!(
283 #[doc(fake_variadic)]
284 render_command_tuple_impl,
285 0,
286 15,
287 C,
288 V,
289 E
290);
291
292pub struct RenderCommandState<P: PhaseItem + 'static, C: RenderCommand<P>> {
297 state: SystemState<C::Param>,
298 view: QueryState<C::ViewQuery>,
299 entity: QueryState<C::ItemQuery>,
300}
301
302impl<P: PhaseItem, C: RenderCommand<P>> RenderCommandState<P, C> {
303 pub fn new(world: &mut World) -> Self {
305 Self {
306 state: SystemState::new(world),
307 view: world.query(),
308 entity: world.query(),
309 }
310 }
311}
312
313impl<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static> Draw<P> for RenderCommandState<P, C>
314where
315 C::Param: ReadOnlySystemParam,
316{
317 fn prepare(&mut self, world: &'_ World) {
320 self.view.update_archetypes(world);
321 self.entity.update_archetypes(world);
322 }
323
324 fn draw<'w>(
326 &mut self,
327 world: &'w World,
328 pass: &mut TrackedRenderPass<'w>,
329 view: Entity,
330 item: &P,
331 ) -> Result<(), DrawError> {
332 let param = self.state.get(world);
333 let view = match self.view.get_manual(world, view) {
334 Ok(view) => view,
335 Err(err) => match err {
336 QueryEntityError::EntityDoesNotExist(_) => {
337 return Err(DrawError::ViewEntityNotFound)
338 }
339 QueryEntityError::QueryDoesNotMatch(_, _)
340 | QueryEntityError::AliasedMutability(_) => {
341 return Err(DrawError::InvalidViewQuery)
342 }
343 },
344 };
345
346 let entity = self.entity.get_manual(world, item.entity()).ok();
347 match C::render(item, view, entity, param, pass) {
348 RenderCommandResult::Success | RenderCommandResult::Skip => Ok(()),
349 RenderCommandResult::Failure(reason) => Err(DrawError::RenderCommandFailure(reason)),
350 }
351 }
352}
353
354pub trait AddRenderCommand {
357 fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
359 &mut self,
360 ) -> &mut Self
361 where
362 C::Param: ReadOnlySystemParam;
363}
364
365impl AddRenderCommand for SubApp {
366 fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
367 &mut self,
368 ) -> &mut Self
369 where
370 C::Param: ReadOnlySystemParam,
371 {
372 let draw_function = RenderCommandState::<P, C>::new(self.world_mut());
373 let draw_functions = self
374 .world()
375 .get_resource::<DrawFunctions<P>>()
376 .unwrap_or_else(|| {
377 panic!(
378 "DrawFunctions<{}> must be added to the world as a resource \
379 before adding render commands to it",
380 core::any::type_name::<P>(),
381 );
382 });
383 draw_functions.write().add_with::<C, _>(draw_function);
384 self
385 }
386}
387
388impl AddRenderCommand for App {
389 fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
390 &mut self,
391 ) -> &mut Self
392 where
393 C::Param: ReadOnlySystemParam,
394 {
395 SubApp::add_render_command::<P, C>(self.main_mut());
396 self
397 }
398}