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