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> {
183 type Param: SystemParam + 'static;
195 type ViewQuery: ReadOnlyQueryData;
201 type ItemQuery: ReadOnlyQueryData;
211
212 fn render<'w>(
215 item: &P,
216 view: ROQueryItem<'w, Self::ViewQuery>,
217 entity: Option<ROQueryItem<'w, Self::ItemQuery>>,
218 param: SystemParamItem<'w, '_, Self::Param>,
219 pass: &mut TrackedRenderPass<'w>,
220 ) -> RenderCommandResult;
221}
222
223#[derive(Debug)]
225pub enum RenderCommandResult {
226 Success,
227 Skip,
228 Failure(&'static str),
229}
230
231macro_rules! render_command_tuple_impl {
232 ($(#[$meta:meta])* $(($name: ident, $view: ident, $entity: ident)),*) => {
233 $(#[$meta])*
234 impl<P: PhaseItem, $($name: RenderCommand<P>),*> RenderCommand<P> for ($($name,)*) {
235 type Param = ($($name::Param,)*);
236 type ViewQuery = ($($name::ViewQuery,)*);
237 type ItemQuery = ($($name::ItemQuery,)*);
238
239 #[expect(
240 clippy::allow_attributes,
241 reason = "We are in a macro; as such, `non_snake_case` may not always lint."
242 )]
243 #[allow(
244 non_snake_case,
245 reason = "Parameter and variable names are provided by the macro invocation, not by us."
246 )]
247 fn render<'w>(
248 _item: &P,
249 ($($view,)*): ROQueryItem<'w, Self::ViewQuery>,
250 maybe_entities: Option<ROQueryItem<'w, Self::ItemQuery>>,
251 ($($name,)*): SystemParamItem<'w, '_, Self::Param>,
252 _pass: &mut TrackedRenderPass<'w>,
253 ) -> RenderCommandResult {
254 match maybe_entities {
255 None => {
256 $(
257 match $name::render(_item, $view, None, $name, _pass) {
258 RenderCommandResult::Skip => return RenderCommandResult::Skip,
259 RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason),
260 _ => {},
261 }
262 )*
263 }
264 Some(($($entity,)*)) => {
265 $(
266 match $name::render(_item, $view, Some($entity), $name, _pass) {
267 RenderCommandResult::Skip => return RenderCommandResult::Skip,
268 RenderCommandResult::Failure(reason) => return RenderCommandResult::Failure(reason),
269 _ => {},
270 }
271 )*
272 }
273 }
274 RenderCommandResult::Success
275 }
276 }
277 };
278}
279
280all_tuples!(
281 #[doc(fake_variadic)]
282 render_command_tuple_impl,
283 0,
284 15,
285 C,
286 V,
287 E
288);
289
290pub struct RenderCommandState<P: PhaseItem + 'static, C: RenderCommand<P>> {
295 state: SystemState<C::Param>,
296 view: QueryState<C::ViewQuery>,
297 entity: QueryState<C::ItemQuery>,
298}
299
300impl<P: PhaseItem, C: RenderCommand<P>> RenderCommandState<P, C> {
301 pub fn new(world: &mut World) -> Self {
303 Self {
304 state: SystemState::new(world),
305 view: world.query(),
306 entity: world.query(),
307 }
308 }
309}
310
311impl<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static> Draw<P> for RenderCommandState<P, C>
312where
313 C::Param: ReadOnlySystemParam,
314{
315 fn prepare(&mut self, world: &'_ World) {
318 self.state.update_archetypes(world);
319 self.view.update_archetypes(world);
320 self.entity.update_archetypes(world);
321 }
322
323 fn draw<'w>(
325 &mut self,
326 world: &'w World,
327 pass: &mut TrackedRenderPass<'w>,
328 view: Entity,
329 item: &P,
330 ) -> Result<(), DrawError> {
331 let param = self.state.get_manual(world);
332 let view = match self.view.get_manual(world, view) {
333 Ok(view) => view,
334 Err(err) => match err {
335 QueryEntityError::EntityDoesNotExist(_) => {
336 return Err(DrawError::ViewEntityNotFound)
337 }
338 QueryEntityError::QueryDoesNotMatch(_, _)
339 | QueryEntityError::AliasedMutability(_) => {
340 return Err(DrawError::InvalidViewQuery)
341 }
342 },
343 };
344
345 let entity = self.entity.get_manual(world, item.entity()).ok();
346 match C::render(item, view, entity, param, pass) {
347 RenderCommandResult::Success | RenderCommandResult::Skip => Ok(()),
348 RenderCommandResult::Failure(reason) => Err(DrawError::RenderCommandFailure(reason)),
349 }
350 }
351}
352
353pub trait AddRenderCommand {
356 fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
358 &mut self,
359 ) -> &mut Self
360 where
361 C::Param: ReadOnlySystemParam;
362}
363
364impl AddRenderCommand for SubApp {
365 fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
366 &mut self,
367 ) -> &mut Self
368 where
369 C::Param: ReadOnlySystemParam,
370 {
371 let draw_function = RenderCommandState::<P, C>::new(self.world_mut());
372 let draw_functions = self
373 .world()
374 .get_resource::<DrawFunctions<P>>()
375 .unwrap_or_else(|| {
376 panic!(
377 "DrawFunctions<{}> must be added to the world as a resource \
378 before adding render commands to it",
379 core::any::type_name::<P>(),
380 );
381 });
382 draw_functions.write().add_with::<C, _>(draw_function);
383 self
384 }
385}
386
387impl AddRenderCommand for App {
388 fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
389 &mut self,
390 ) -> &mut Self
391 where
392 C::Param: ReadOnlySystemParam,
393 {
394 SubApp::add_render_command::<P, C>(self.main_mut());
395 self
396 }
397}