1
1
use crate :: {
2
- render_phase:: TrackedRenderPass ,
2
+ render_phase:: { PhaseItem , TrackedRenderPass } ,
3
3
render_resource:: { CachedRenderPipelineId , PipelineCache } ,
4
4
} ;
5
5
use bevy_app:: App ;
@@ -15,20 +15,22 @@ use bevy_ecs::{
15
15
} ;
16
16
use bevy_utils:: HashMap ;
17
17
use parking_lot:: { RwLock , RwLockReadGuard , RwLockWriteGuard } ;
18
- use std:: { any:: TypeId , fmt:: Debug , hash:: Hash , ops :: Range } ;
18
+ use std:: { any:: TypeId , fmt:: Debug , hash:: Hash } ;
19
19
20
- /// A draw function which is used to draw a specific [`PhaseItem`].
20
+ // Todo: consider renaming this to `DrawFunction`
21
+ /// A draw function used to draw [`PhaseItem`]s.
21
22
///
22
- /// They are the general form of drawing items, whereas [`RenderCommands`](RenderCommand)
23
- /// are more modular.
23
+ /// Therefore the draw function can retrieve and query the required ECS data from the render world.
24
+ ///
25
+ /// This trait can either be implemented directly or implicitly composed out of multiple modular [`RenderCommand`]s.
24
26
pub trait Draw < P : PhaseItem > : Send + Sync + ' static {
25
27
/// Prepares the draw function to be used. This is called once and only once before the phase
26
28
/// begins. There may be zero or more `draw` calls following a call to this function..
27
29
/// Implementing this is optional.
28
30
#[ allow( unused_variables) ]
29
31
fn prepare ( & mut self , world : & ' _ World ) { }
30
32
31
- /// Draws the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
33
+ /// Draws a [`PhaseItem`] by issuing one or more draw calls via the [`TrackedRenderPass`].
32
34
fn draw < ' w > (
33
35
& mut self ,
34
36
world : & ' w World ,
@@ -38,64 +40,33 @@ pub trait Draw<P: PhaseItem>: Send + Sync + 'static {
38
40
) ;
39
41
}
40
42
41
- /// An item which will be drawn to the screen. A phase item should be queued up for rendering
42
- /// during the [`RenderStage::Queue`](crate::RenderStage::Queue) stage.
43
- /// Afterwards it will be sorted and rendered automatically in the
44
- /// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) stage and
45
- /// [`RenderStage::Render`](crate::RenderStage::Render) stage, respectively.
46
- pub trait PhaseItem : Sized + Send + Sync + ' static {
47
- /// The type used for ordering the items. The smallest values are drawn first.
48
- type SortKey : Ord ;
49
- fn entity ( & self ) -> Entity ;
50
-
51
- /// Determines the order in which the items are drawn during the corresponding [`RenderPhase`](super::RenderPhase).
52
- fn sort_key ( & self ) -> Self :: SortKey ;
53
- /// Specifies the [`Draw`] function used to render the item.
54
- fn draw_function ( & self ) -> DrawFunctionId ;
55
-
56
- /// Sorts a slice of phase items into render order. Generally if the same type
57
- /// implements [`BatchedPhaseItem`], this should use a stable sort like [`slice::sort_by_key`].
58
- /// In almost all other cases, this should not be altered from the default,
59
- /// which uses a unstable sort, as this provides the best balance of CPU and GPU
60
- /// performance.
61
- ///
62
- /// Implementers can optionally not sort the list at all. This is generally advisable if and
63
- /// only if the renderer supports a depth prepass, which is by default not supported by
64
- /// the rest of Bevy's first party rendering crates. Even then, this may have a negative
65
- /// impact on GPU-side performance due to overdraw.
66
- ///
67
- /// It's advised to always profile for performance changes when changing this implementation.
68
- #[ inline]
69
- fn sort ( items : & mut [ Self ] ) {
70
- items. sort_unstable_by_key ( |item| item. sort_key ( ) ) ;
71
- }
72
- }
73
-
74
43
// TODO: make this generic?
75
44
/// An identifier for a [`Draw`] function stored in [`DrawFunctions`].
76
45
#[ derive( Copy , Clone , Debug , Eq , PartialEq , Hash ) ]
77
46
pub struct DrawFunctionId ( u32 ) ;
78
47
79
- /// Stores all draw functions for the [`PhaseItem`] type.
80
- /// For retrieval they are associated with their [`TypeId`].
48
+ /// Stores all [`Draw`] functions for the [`PhaseItem`] type.
49
+ ///
50
+ /// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s.
81
51
pub struct DrawFunctionsInternal < P : PhaseItem > {
82
52
pub draw_functions : Vec < Box < dyn Draw < P > > > ,
83
53
pub indices : HashMap < TypeId , DrawFunctionId > ,
84
54
}
85
55
86
56
impl < P : PhaseItem > DrawFunctionsInternal < P > {
57
+ // Todo: add comment
87
58
pub fn prepare ( & mut self , world : & World ) {
88
59
for function in & mut self . draw_functions {
89
60
function. prepare ( world) ;
90
61
}
91
62
}
92
63
93
- /// Adds the [`Draw`] function and associates it to its own type.
64
+ /// Adds the [`Draw`] function and maps it to its own type.
94
65
pub fn add < T : Draw < P > > ( & mut self , draw_function : T ) -> DrawFunctionId {
95
66
self . add_with :: < T , T > ( draw_function)
96
67
}
97
68
98
- /// Adds the [`Draw`] function and associates it to the type `T`
69
+ /// Adds the [`Draw`] function and maps it to the type `T`
99
70
pub fn add_with < T : ' static , D : Draw < P > > ( & mut self , draw_function : D ) -> DrawFunctionId {
100
71
let id = DrawFunctionId ( self . draw_functions . len ( ) . try_into ( ) . unwrap ( ) ) ;
101
72
self . draw_functions . push ( Box :: new ( draw_function) ) ;
@@ -118,7 +89,7 @@ impl<P: PhaseItem> DrawFunctionsInternal<P> {
118
89
/// Fallible wrapper for [`Self::get_id()`]
119
90
///
120
91
/// ## Panics
121
- /// If the id doesn't exist it will panic
92
+ /// If the id doesn't exist, this function will panic.
122
93
pub fn id < T : ' static > ( & self ) -> DrawFunctionId {
123
94
self . get_id :: < T > ( ) . unwrap_or_else ( || {
124
95
panic ! (
@@ -131,6 +102,7 @@ impl<P: PhaseItem> DrawFunctionsInternal<P> {
131
102
}
132
103
133
104
/// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock.
105
+ ///
134
106
/// To access them the [`DrawFunctions::read`] and [`DrawFunctions::write`] methods are used.
135
107
#[ derive( Resource ) ]
136
108
pub struct DrawFunctions < P : PhaseItem > {
@@ -160,15 +132,20 @@ impl<P: PhaseItem> DrawFunctions<P> {
160
132
}
161
133
}
162
134
163
- /// [`RenderCommand`] is a trait that runs an ECS query and produces one or more
164
- /// [`TrackedRenderPass`] calls. Types implementing this trait can be composed (as tuples).
135
+ /// [`RenderCommand`]s are modular standardized pieces of render logic that can be composed into [`Draw`] functions.
165
136
///
166
- /// They can be registered as a [`Draw`] function via the
167
- /// [`AddRenderCommand::add_render_command`] method.
137
+ /// To turn a stateless render command into a usable draw function it has to be wrapped by a [`RenderCommandState`].
138
+ /// This is done automatically when registering a render command as a [`Draw`] function via the [`AddRenderCommand::add_render_command`] method.
139
+ ///
140
+ /// Compared to the draw function the required ECS data is fetched automatically (by the [`RenderCommandState`]) from the render world.
141
+ /// Therefore the three types [`Param`](RenderCommand::Param), [`ViewWorldQuery`](RenderCommand::ViewWorldQuery) and [`WorldQuery`](RenderCommand::WorldQuery) are used.
142
+ /// They specify which information is required to execute the render command.
143
+ ///
144
+ /// Multiple render commands can be combined together by wrapping them in a tuple.
168
145
///
169
146
/// # Example
170
147
/// The `DrawPbr` draw function is created from the following render command
171
- /// tuple. Const generics are used to set specific bind group locations:
148
+ /// tuple. Const generics are used to set specific bind group locations:
172
149
///
173
150
/// ```ignore
174
151
/// pub type DrawPbr = (
@@ -183,10 +160,12 @@ pub trait RenderCommand<P: PhaseItem> {
183
160
/// Specifies all ECS data required by [`RenderCommand::render`].
184
161
/// All parameters have to be read only.
185
162
type Param : SystemParam + ' static ;
163
+ // Todo: add comment
186
164
type ViewWorldQuery : ReadOnlyWorldQuery ;
165
+ // Todo: add comment
187
166
type ItemWorldQuery : ReadOnlyWorldQuery ;
188
167
189
- /// Renders the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
168
+ /// Renders a [`PhaseItem`] by recording commands (e.g. setting pipelines, binding bind groups, issuing draw calls, etc.) via the [`TrackedRenderPass`].
190
169
fn render < ' w > (
191
170
item : & P ,
192
171
view : ROQueryItem < ' w , Self :: ViewWorldQuery > ,
@@ -196,86 +175,12 @@ pub trait RenderCommand<P: PhaseItem> {
196
175
) -> RenderCommandResult ;
197
176
}
198
177
178
+ /// The result of a [`RenderCommand`].
199
179
pub enum RenderCommandResult {
200
180
Success ,
201
181
Failure ,
202
182
}
203
183
204
- pub trait CachedRenderPipelinePhaseItem : PhaseItem {
205
- fn cached_pipeline ( & self ) -> CachedRenderPipelineId ;
206
- }
207
-
208
- /// A [`PhaseItem`] that can be batched dynamically.
209
- ///
210
- /// Batching is an optimization that regroups multiple items in the same vertex buffer
211
- /// to render them in a single draw call.
212
- ///
213
- /// If this is implemented on a type, the implementation of [`PhaseItem::sort`] should
214
- /// be changed to implement a stable sort, or incorrect/suboptimal batching may result.
215
- pub trait BatchedPhaseItem : PhaseItem {
216
- /// Range in the vertex buffer of this item
217
- fn batch_range ( & self ) -> & Option < Range < u32 > > ;
218
-
219
- /// Range in the vertex buffer of this item
220
- fn batch_range_mut ( & mut self ) -> & mut Option < Range < u32 > > ;
221
-
222
- /// Batches another item within this item if they are compatible.
223
- /// Items can be batched together if they have the same entity, and consecutive ranges.
224
- /// If batching is successful, the `other` item should be discarded from the render pass.
225
- #[ inline]
226
- fn add_to_batch ( & mut self , other : & Self ) -> BatchResult {
227
- let self_entity = self . entity ( ) ;
228
- if let ( Some ( self_batch_range) , Some ( other_batch_range) ) = (
229
- self . batch_range_mut ( ) . as_mut ( ) ,
230
- other. batch_range ( ) . as_ref ( ) ,
231
- ) {
232
- // If the items are compatible, join their range into `self`
233
- if self_entity == other. entity ( ) {
234
- if self_batch_range. end == other_batch_range. start {
235
- self_batch_range. end = other_batch_range. end ;
236
- return BatchResult :: Success ;
237
- } else if self_batch_range. start == other_batch_range. end {
238
- self_batch_range. start = other_batch_range. start ;
239
- return BatchResult :: Success ;
240
- }
241
- }
242
- }
243
- BatchResult :: IncompatibleItems
244
- }
245
- }
246
-
247
- pub enum BatchResult {
248
- /// The `other` item was batched into `self`
249
- Success ,
250
- /// `self` and `other` cannot be batched together
251
- IncompatibleItems ,
252
- }
253
-
254
- pub struct SetItemPipeline ;
255
- impl < P : CachedRenderPipelinePhaseItem > RenderCommand < P > for SetItemPipeline {
256
- type Param = SRes < PipelineCache > ;
257
- type ViewWorldQuery = ( ) ;
258
- type ItemWorldQuery = ( ) ;
259
- #[ inline]
260
- fn render < ' w > (
261
- item : & P ,
262
- _view : ( ) ,
263
- _entity : ( ) ,
264
- pipeline_cache : SystemParamItem < ' w , ' _ , Self :: Param > ,
265
- pass : & mut TrackedRenderPass < ' w > ,
266
- ) -> RenderCommandResult {
267
- if let Some ( pipeline) = pipeline_cache
268
- . into_inner ( )
269
- . get_render_pipeline ( item. cached_pipeline ( ) )
270
- {
271
- pass. set_render_pipeline ( pipeline) ;
272
- RenderCommandResult :: Success
273
- } else {
274
- RenderCommandResult :: Failure
275
- }
276
- }
277
- }
278
-
279
184
macro_rules! render_command_tuple_impl {
280
185
( $( ( $name: ident, $view: ident, $entity: ident) ) ,* ) => {
281
186
impl <P : PhaseItem , $( $name: RenderCommand <P >) ,* > RenderCommand <P > for ( $( $name, ) * ) {
@@ -303,14 +208,15 @@ macro_rules! render_command_tuple_impl {
303
208
all_tuples ! ( render_command_tuple_impl, 0 , 15 , C , V , E ) ;
304
209
305
210
/// Wraps a [`RenderCommand`] into a state so that it can be used as a [`Draw`] function.
306
- /// Therefore the [`RenderCommand::Param`] is queried from the ECS and passed to the command.
211
+ /// Therefore the [`RenderCommand::Param`], [`RenderCommand::ViewWorldQuery`] and [`RenderCommand::WorldQuery`] are queried from the ECS and passed to the command.
307
212
pub struct RenderCommandState < P : PhaseItem + ' static , C : RenderCommand < P > > {
308
213
state : SystemState < C :: Param > ,
309
214
view : QueryState < C :: ViewWorldQuery > ,
310
215
entity : QueryState < C :: ItemWorldQuery > ,
311
216
}
312
217
313
218
impl < P : PhaseItem , C : RenderCommand < P > > RenderCommandState < P , C > {
219
+ /// Creates a new [`RenderCommandState`] for the [`RenderCommand`].
314
220
pub fn new ( world : & mut World ) -> Self {
315
221
Self {
316
222
state : SystemState :: new ( world) ,
@@ -324,12 +230,13 @@ impl<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static> Draw<P> for Rend
324
230
where
325
231
C :: Param : ReadOnlySystemParam ,
326
232
{
233
+ // Todo: add comment
327
234
fn prepare ( & mut self , world : & ' _ World ) {
328
235
self . view . update_archetypes ( world) ;
329
236
self . entity . update_archetypes ( world) ;
330
237
}
331
238
332
- /// Prepares the ECS parameters for the wrapped [`RenderCommand`] and then renders it.
239
+ /// Fetches the ECS parameters for the wrapped [`RenderCommand`] and then renders it.
333
240
fn draw < ' w > (
334
241
& mut self ,
335
242
world : & ' w World ,
@@ -340,6 +247,7 @@ where
340
247
let param = self . state . get ( world) ;
341
248
let view = self . view . get_manual ( world, view) . unwrap ( ) ;
342
249
let entity = self . entity . get_manual ( world, item. entity ( ) ) . unwrap ( ) ;
250
+ // Todo: handle/log `RenderCommand` failure
343
251
C :: render ( item, view, entity, param, pass) ;
344
252
}
345
253
}
@@ -377,3 +285,36 @@ impl AddRenderCommand for App {
377
285
self
378
286
}
379
287
}
288
+
289
+ // Todo: If this is always needed, combine this with PhaseItem?
290
+ pub trait CachedRenderPipelinePhaseItem : PhaseItem {
291
+ fn cached_pipeline ( & self ) -> CachedRenderPipelineId ;
292
+ }
293
+
294
+ /// A [`RenderCommand`] that sets the pipeline for the `[PhaseItem]`.
295
+ pub struct SetItemPipeline ;
296
+
297
+ impl < P : CachedRenderPipelinePhaseItem > RenderCommand < P > for SetItemPipeline {
298
+ type Param = SRes < PipelineCache > ;
299
+ type ViewWorldQuery = ( ) ;
300
+ type WorldQuery = ( ) ;
301
+
302
+ #[ inline]
303
+ fn render < ' w > (
304
+ item : & P ,
305
+ _view : ( ) ,
306
+ _entity : ( ) ,
307
+ pipeline_cache : SystemParamItem < ' w , ' _ , Self :: Param > ,
308
+ pass : & mut TrackedRenderPass < ' w > ,
309
+ ) -> RenderCommandResult {
310
+ if let Some ( pipeline) = pipeline_cache
311
+ . into_inner ( )
312
+ . get_render_pipeline ( item. cached_pipeline ( ) )
313
+ {
314
+ pass. set_render_pipeline ( pipeline) ;
315
+ RenderCommandResult :: Success
316
+ } else {
317
+ RenderCommandResult :: Failure
318
+ }
319
+ }
320
+ }
0 commit comments