Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui_render/src/ui_material_pipeline.rs
6596 views
1
use crate::ui_material::{MaterialNode, UiMaterial, UiMaterialKey};
2
use crate::*;
3
use bevy_asset::*;
4
use bevy_ecs::{
5
prelude::{Component, With},
6
query::ROQueryItem,
7
system::{
8
lifetimeless::{Read, SRes},
9
*,
10
},
11
};
12
use bevy_image::BevyDefault as _;
13
use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
14
use bevy_mesh::VertexBufferLayout;
15
use bevy_render::{
16
globals::{GlobalsBuffer, GlobalsUniform},
17
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
18
render_phase::*,
19
render_resource::{binding_types::uniform_buffer, *},
20
renderer::{RenderDevice, RenderQueue},
21
sync_world::{MainEntity, TemporaryRenderEntity},
22
view::*,
23
Extract, ExtractSchedule, Render, RenderSystems,
24
};
25
use bevy_render::{RenderApp, RenderStartup};
26
use bevy_shader::{load_shader_library, Shader, ShaderRef};
27
use bevy_sprite::BorderRect;
28
use bevy_utils::default;
29
use bytemuck::{Pod, Zeroable};
30
use core::{hash::Hash, marker::PhantomData, ops::Range};
31
32
/// Adds the necessary ECS resources and render logic to enable rendering entities using the given
33
/// [`UiMaterial`] asset type (which includes [`UiMaterial`] types).
34
pub struct UiMaterialPlugin<M: UiMaterial>(PhantomData<M>);
35
36
impl<M: UiMaterial> Default for UiMaterialPlugin<M> {
37
fn default() -> Self {
38
Self(Default::default())
39
}
40
}
41
42
impl<M: UiMaterial> Plugin for UiMaterialPlugin<M>
43
where
44
M::Data: PartialEq + Eq + Hash + Clone,
45
{
46
fn build(&self, app: &mut App) {
47
load_shader_library!(app, "ui_vertex_output.wgsl");
48
49
embedded_asset!(app, "ui_material.wgsl");
50
51
app.init_asset::<M>()
52
.register_type::<MaterialNode<M>>()
53
.add_plugins(RenderAssetPlugin::<PreparedUiMaterial<M>>::default());
54
55
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
56
render_app
57
.add_render_command::<TransparentUi, DrawUiMaterial<M>>()
58
.init_resource::<ExtractedUiMaterialNodes<M>>()
59
.init_resource::<UiMaterialMeta<M>>()
60
.init_resource::<SpecializedRenderPipelines<UiMaterialPipeline<M>>>()
61
.add_systems(RenderStartup, init_ui_material_pipeline::<M>)
62
.add_systems(
63
ExtractSchedule,
64
extract_ui_material_nodes::<M>.in_set(RenderUiSystems::ExtractBackgrounds),
65
)
66
.add_systems(
67
Render,
68
(
69
queue_ui_material_nodes::<M>.in_set(RenderSystems::Queue),
70
prepare_uimaterial_nodes::<M>.in_set(RenderSystems::PrepareBindGroups),
71
),
72
);
73
}
74
}
75
}
76
77
#[derive(Resource)]
78
pub struct UiMaterialMeta<M: UiMaterial> {
79
vertices: RawBufferVec<UiMaterialVertex>,
80
view_bind_group: Option<BindGroup>,
81
marker: PhantomData<M>,
82
}
83
84
impl<M: UiMaterial> Default for UiMaterialMeta<M> {
85
fn default() -> Self {
86
Self {
87
vertices: RawBufferVec::new(BufferUsages::VERTEX),
88
view_bind_group: Default::default(),
89
marker: PhantomData,
90
}
91
}
92
}
93
94
#[repr(C)]
95
#[derive(Copy, Clone, Pod, Zeroable)]
96
pub struct UiMaterialVertex {
97
pub position: [f32; 3],
98
pub uv: [f32; 2],
99
pub size: [f32; 2],
100
pub border: [f32; 4],
101
pub radius: [f32; 4],
102
}
103
104
// in this [`UiMaterialPipeline`] there is (currently) no batching going on.
105
// Therefore the [`UiMaterialBatch`] is more akin to a draw call.
106
#[derive(Component)]
107
pub struct UiMaterialBatch<M: UiMaterial> {
108
/// The range of vertices inside the [`UiMaterialMeta`]
109
pub range: Range<u32>,
110
pub material: AssetId<M>,
111
}
112
113
/// Render pipeline data for a given [`UiMaterial`]
114
#[derive(Resource)]
115
pub struct UiMaterialPipeline<M: UiMaterial> {
116
pub ui_layout: BindGroupLayout,
117
pub view_layout: BindGroupLayout,
118
pub vertex_shader: Handle<Shader>,
119
pub fragment_shader: Handle<Shader>,
120
marker: PhantomData<M>,
121
}
122
123
impl<M: UiMaterial> SpecializedRenderPipeline for UiMaterialPipeline<M>
124
where
125
M::Data: PartialEq + Eq + Hash + Clone,
126
{
127
type Key = UiMaterialKey<M>;
128
129
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
130
let vertex_layout = VertexBufferLayout::from_vertex_formats(
131
VertexStepMode::Vertex,
132
vec![
133
// position
134
VertexFormat::Float32x3,
135
// uv
136
VertexFormat::Float32x2,
137
// size
138
VertexFormat::Float32x2,
139
// border widths
140
VertexFormat::Float32x4,
141
// border radius
142
VertexFormat::Float32x4,
143
],
144
);
145
let shader_defs = Vec::new();
146
147
let mut descriptor = RenderPipelineDescriptor {
148
vertex: VertexState {
149
shader: self.vertex_shader.clone(),
150
shader_defs: shader_defs.clone(),
151
buffers: vec![vertex_layout],
152
..default()
153
},
154
fragment: Some(FragmentState {
155
shader: self.fragment_shader.clone(),
156
shader_defs,
157
targets: vec![Some(ColorTargetState {
158
format: if key.hdr {
159
ViewTarget::TEXTURE_FORMAT_HDR
160
} else {
161
TextureFormat::bevy_default()
162
},
163
blend: Some(BlendState::ALPHA_BLENDING),
164
write_mask: ColorWrites::ALL,
165
})],
166
..default()
167
}),
168
label: Some("ui_material_pipeline".into()),
169
..default()
170
};
171
172
descriptor.layout = vec![self.view_layout.clone(), self.ui_layout.clone()];
173
174
M::specialize(&mut descriptor, key);
175
176
descriptor
177
}
178
}
179
180
pub fn init_ui_material_pipeline<M: UiMaterial>(
181
mut commands: Commands,
182
render_device: Res<RenderDevice>,
183
asset_server: Res<AssetServer>,
184
) {
185
let ui_layout = M::bind_group_layout(&render_device);
186
187
let view_layout = render_device.create_bind_group_layout(
188
"ui_view_layout",
189
&BindGroupLayoutEntries::sequential(
190
ShaderStages::VERTEX_FRAGMENT,
191
(
192
uniform_buffer::<ViewUniform>(true),
193
uniform_buffer::<GlobalsUniform>(false),
194
),
195
),
196
);
197
198
let load_default = || load_embedded_asset!(asset_server.as_ref(), "ui_material.wgsl");
199
200
commands.insert_resource(UiMaterialPipeline::<M> {
201
ui_layout,
202
view_layout,
203
vertex_shader: match M::vertex_shader() {
204
ShaderRef::Default => load_default(),
205
ShaderRef::Handle(handle) => handle,
206
ShaderRef::Path(path) => asset_server.load(path),
207
},
208
fragment_shader: match M::fragment_shader() {
209
ShaderRef::Default => load_default(),
210
ShaderRef::Handle(handle) => handle,
211
ShaderRef::Path(path) => asset_server.load(path),
212
},
213
marker: PhantomData,
214
});
215
}
216
217
pub type DrawUiMaterial<M> = (
218
SetItemPipeline,
219
SetMatUiViewBindGroup<M, 0>,
220
SetUiMaterialBindGroup<M, 1>,
221
DrawUiMaterialNode<M>,
222
);
223
224
pub struct SetMatUiViewBindGroup<M: UiMaterial, const I: usize>(PhantomData<M>);
225
impl<P: PhaseItem, M: UiMaterial, const I: usize> RenderCommand<P> for SetMatUiViewBindGroup<M, I> {
226
type Param = SRes<UiMaterialMeta<M>>;
227
type ViewQuery = Read<ViewUniformOffset>;
228
type ItemQuery = ();
229
230
fn render<'w>(
231
_item: &P,
232
view_uniform: &'w ViewUniformOffset,
233
_entity: Option<()>,
234
ui_meta: SystemParamItem<'w, '_, Self::Param>,
235
pass: &mut TrackedRenderPass<'w>,
236
) -> RenderCommandResult {
237
pass.set_bind_group(
238
I,
239
ui_meta.into_inner().view_bind_group.as_ref().unwrap(),
240
&[view_uniform.offset],
241
);
242
RenderCommandResult::Success
243
}
244
}
245
246
pub struct SetUiMaterialBindGroup<M: UiMaterial, const I: usize>(PhantomData<M>);
247
impl<P: PhaseItem, M: UiMaterial, const I: usize> RenderCommand<P>
248
for SetUiMaterialBindGroup<M, I>
249
{
250
type Param = SRes<RenderAssets<PreparedUiMaterial<M>>>;
251
type ViewQuery = ();
252
type ItemQuery = Read<UiMaterialBatch<M>>;
253
254
fn render<'w>(
255
_item: &P,
256
_view: (),
257
material_handle: Option<ROQueryItem<'_, '_, Self::ItemQuery>>,
258
materials: SystemParamItem<'w, '_, Self::Param>,
259
pass: &mut TrackedRenderPass<'w>,
260
) -> RenderCommandResult {
261
let Some(material_handle) = material_handle else {
262
return RenderCommandResult::Skip;
263
};
264
let Some(material) = materials.into_inner().get(material_handle.material) else {
265
return RenderCommandResult::Skip;
266
};
267
pass.set_bind_group(I, &material.bind_group, &[]);
268
RenderCommandResult::Success
269
}
270
}
271
272
pub struct DrawUiMaterialNode<M>(PhantomData<M>);
273
impl<P: PhaseItem, M: UiMaterial> RenderCommand<P> for DrawUiMaterialNode<M> {
274
type Param = SRes<UiMaterialMeta<M>>;
275
type ViewQuery = ();
276
type ItemQuery = Read<UiMaterialBatch<M>>;
277
278
#[inline]
279
fn render<'w>(
280
_item: &P,
281
_view: (),
282
batch: Option<&'w UiMaterialBatch<M>>,
283
ui_meta: SystemParamItem<'w, '_, Self::Param>,
284
pass: &mut TrackedRenderPass<'w>,
285
) -> RenderCommandResult {
286
let Some(batch) = batch else {
287
return RenderCommandResult::Skip;
288
};
289
290
pass.set_vertex_buffer(0, ui_meta.into_inner().vertices.buffer().unwrap().slice(..));
291
pass.draw(batch.range.clone(), 0..1);
292
RenderCommandResult::Success
293
}
294
}
295
296
pub struct ExtractedUiMaterialNode<M: UiMaterial> {
297
pub stack_index: u32,
298
pub transform: Affine2,
299
pub rect: Rect,
300
pub border: BorderRect,
301
pub border_radius: [f32; 4],
302
pub material: AssetId<M>,
303
pub clip: Option<Rect>,
304
// Camera to render this UI node to. By the time it is extracted,
305
// it is defaulted to a single camera if only one exists.
306
// Nodes with ambiguous camera will be ignored.
307
pub extracted_camera_entity: Entity,
308
pub main_entity: MainEntity,
309
pub render_entity: Entity,
310
}
311
312
#[derive(Resource)]
313
pub struct ExtractedUiMaterialNodes<M: UiMaterial> {
314
pub uinodes: Vec<ExtractedUiMaterialNode<M>>,
315
}
316
317
impl<M: UiMaterial> Default for ExtractedUiMaterialNodes<M> {
318
fn default() -> Self {
319
Self {
320
uinodes: Default::default(),
321
}
322
}
323
}
324
325
pub fn extract_ui_material_nodes<M: UiMaterial>(
326
mut commands: Commands,
327
mut extracted_uinodes: ResMut<ExtractedUiMaterialNodes<M>>,
328
materials: Extract<Res<Assets<M>>>,
329
uinode_query: Extract<
330
Query<(
331
Entity,
332
&ComputedNode,
333
&UiGlobalTransform,
334
&MaterialNode<M>,
335
&InheritedVisibility,
336
Option<&CalculatedClip>,
337
&ComputedUiTargetCamera,
338
)>,
339
>,
340
camera_map: Extract<UiCameraMap>,
341
) {
342
let mut camera_mapper = camera_map.get_mapper();
343
344
for (entity, computed_node, transform, handle, inherited_visibility, clip, camera) in
345
uinode_query.iter()
346
{
347
// skip invisible nodes
348
if !inherited_visibility.get() || computed_node.is_empty() {
349
continue;
350
}
351
352
// Skip loading materials
353
if !materials.contains(handle) {
354
continue;
355
}
356
357
let Some(extracted_camera_entity) = camera_mapper.map(camera) else {
358
continue;
359
};
360
361
extracted_uinodes.uinodes.push(ExtractedUiMaterialNode {
362
render_entity: commands.spawn(TemporaryRenderEntity).id(),
363
stack_index: computed_node.stack_index,
364
transform: transform.into(),
365
material: handle.id(),
366
rect: Rect {
367
min: Vec2::ZERO,
368
max: computed_node.size(),
369
},
370
border: computed_node.border(),
371
border_radius: computed_node.border_radius().into(),
372
clip: clip.map(|clip| clip.clip),
373
extracted_camera_entity,
374
main_entity: entity.into(),
375
});
376
}
377
}
378
379
pub fn prepare_uimaterial_nodes<M: UiMaterial>(
380
mut commands: Commands,
381
render_device: Res<RenderDevice>,
382
render_queue: Res<RenderQueue>,
383
mut ui_meta: ResMut<UiMaterialMeta<M>>,
384
mut extracted_uinodes: ResMut<ExtractedUiMaterialNodes<M>>,
385
view_uniforms: Res<ViewUniforms>,
386
globals_buffer: Res<GlobalsBuffer>,
387
ui_material_pipeline: Res<UiMaterialPipeline<M>>,
388
mut phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
389
mut previous_len: Local<usize>,
390
) {
391
if let (Some(view_binding), Some(globals_binding)) = (
392
view_uniforms.uniforms.binding(),
393
globals_buffer.buffer.binding(),
394
) {
395
let mut batches: Vec<(Entity, UiMaterialBatch<M>)> = Vec::with_capacity(*previous_len);
396
397
ui_meta.vertices.clear();
398
ui_meta.view_bind_group = Some(render_device.create_bind_group(
399
"ui_material_view_bind_group",
400
&ui_material_pipeline.view_layout,
401
&BindGroupEntries::sequential((view_binding, globals_binding)),
402
));
403
let mut index = 0;
404
405
for ui_phase in phases.values_mut() {
406
let mut batch_item_index = 0;
407
let mut batch_shader_handle = AssetId::invalid();
408
409
for item_index in 0..ui_phase.items.len() {
410
let item = &mut ui_phase.items[item_index];
411
if let Some(extracted_uinode) = extracted_uinodes
412
.uinodes
413
.get(item.index)
414
.filter(|n| item.entity() == n.render_entity)
415
{
416
let mut existing_batch = batches
417
.last_mut()
418
.filter(|_| batch_shader_handle == extracted_uinode.material);
419
420
if existing_batch.is_none() {
421
batch_item_index = item_index;
422
batch_shader_handle = extracted_uinode.material;
423
424
let new_batch = UiMaterialBatch {
425
range: index..index,
426
material: extracted_uinode.material,
427
};
428
429
batches.push((item.entity(), new_batch));
430
431
existing_batch = batches.last_mut();
432
}
433
434
let uinode_rect = extracted_uinode.rect;
435
436
let rect_size = uinode_rect.size();
437
438
let positions = QUAD_VERTEX_POSITIONS.map(|pos| {
439
extracted_uinode
440
.transform
441
.transform_point2(pos * rect_size)
442
.extend(1.0)
443
});
444
445
let positions_diff = if let Some(clip) = extracted_uinode.clip {
446
[
447
Vec2::new(
448
f32::max(clip.min.x - positions[0].x, 0.),
449
f32::max(clip.min.y - positions[0].y, 0.),
450
),
451
Vec2::new(
452
f32::min(clip.max.x - positions[1].x, 0.),
453
f32::max(clip.min.y - positions[1].y, 0.),
454
),
455
Vec2::new(
456
f32::min(clip.max.x - positions[2].x, 0.),
457
f32::min(clip.max.y - positions[2].y, 0.),
458
),
459
Vec2::new(
460
f32::max(clip.min.x - positions[3].x, 0.),
461
f32::min(clip.max.y - positions[3].y, 0.),
462
),
463
]
464
} else {
465
[Vec2::ZERO; 4]
466
};
467
468
let positions_clipped = [
469
positions[0] + positions_diff[0].extend(0.),
470
positions[1] + positions_diff[1].extend(0.),
471
positions[2] + positions_diff[2].extend(0.),
472
positions[3] + positions_diff[3].extend(0.),
473
];
474
475
let transformed_rect_size =
476
extracted_uinode.transform.transform_vector2(rect_size);
477
478
// Don't try to cull nodes that have a rotation
479
// In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
480
// In those two cases, the culling check can proceed normally as corners will be on
481
// horizontal / vertical lines
482
// For all other angles, bypass the culling check
483
// This does not properly handles all rotations on all axis
484
if extracted_uinode.transform.x_axis[1] == 0.0 {
485
// Cull nodes that are completely clipped
486
if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x
487
|| positions_diff[1].y - positions_diff[2].y >= transformed_rect_size.y
488
{
489
continue;
490
}
491
}
492
let uvs = [
493
Vec2::new(
494
uinode_rect.min.x + positions_diff[0].x,
495
uinode_rect.min.y + positions_diff[0].y,
496
),
497
Vec2::new(
498
uinode_rect.max.x + positions_diff[1].x,
499
uinode_rect.min.y + positions_diff[1].y,
500
),
501
Vec2::new(
502
uinode_rect.max.x + positions_diff[2].x,
503
uinode_rect.max.y + positions_diff[2].y,
504
),
505
Vec2::new(
506
uinode_rect.min.x + positions_diff[3].x,
507
uinode_rect.max.y + positions_diff[3].y,
508
),
509
]
510
.map(|pos| pos / uinode_rect.max);
511
512
for i in QUAD_INDICES {
513
ui_meta.vertices.push(UiMaterialVertex {
514
position: positions_clipped[i].into(),
515
uv: uvs[i].into(),
516
size: extracted_uinode.rect.size().into(),
517
radius: extracted_uinode.border_radius,
518
border: [
519
extracted_uinode.border.left,
520
extracted_uinode.border.top,
521
extracted_uinode.border.right,
522
extracted_uinode.border.bottom,
523
],
524
});
525
}
526
527
index += QUAD_INDICES.len() as u32;
528
existing_batch.unwrap().1.range.end = index;
529
ui_phase.items[batch_item_index].batch_range_mut().end += 1;
530
} else {
531
batch_shader_handle = AssetId::invalid();
532
}
533
}
534
}
535
ui_meta.vertices.write_buffer(&render_device, &render_queue);
536
*previous_len = batches.len();
537
commands.try_insert_batch(batches);
538
}
539
extracted_uinodes.uinodes.clear();
540
}
541
542
pub struct PreparedUiMaterial<T: UiMaterial> {
543
pub bindings: BindingResources,
544
pub bind_group: BindGroup,
545
pub key: T::Data,
546
}
547
548
impl<M: UiMaterial> RenderAsset for PreparedUiMaterial<M> {
549
type SourceAsset = M;
550
551
type Param = (SRes<RenderDevice>, SRes<UiMaterialPipeline<M>>, M::Param);
552
553
fn prepare_asset(
554
material: Self::SourceAsset,
555
_: AssetId<Self::SourceAsset>,
556
(render_device, pipeline, material_param): &mut SystemParamItem<Self::Param>,
557
_: Option<&Self>,
558
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
559
let bind_group_data = material.bind_group_data();
560
match material.as_bind_group(&pipeline.ui_layout, render_device, material_param) {
561
Ok(prepared) => Ok(PreparedUiMaterial {
562
bindings: prepared.bindings,
563
bind_group: prepared.bind_group,
564
key: bind_group_data,
565
}),
566
Err(AsBindGroupError::RetryNextUpdate) => {
567
Err(PrepareAssetError::RetryNextUpdate(material))
568
}
569
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
570
}
571
}
572
}
573
574
pub fn queue_ui_material_nodes<M: UiMaterial>(
575
extracted_uinodes: Res<ExtractedUiMaterialNodes<M>>,
576
draw_functions: Res<DrawFunctions<TransparentUi>>,
577
ui_material_pipeline: Res<UiMaterialPipeline<M>>,
578
mut pipelines: ResMut<SpecializedRenderPipelines<UiMaterialPipeline<M>>>,
579
pipeline_cache: Res<PipelineCache>,
580
render_materials: Res<RenderAssets<PreparedUiMaterial<M>>>,
581
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<TransparentUi>>,
582
mut render_views: Query<&UiCameraView, With<ExtractedView>>,
583
camera_views: Query<&ExtractedView>,
584
) where
585
M::Data: PartialEq + Eq + Hash + Clone,
586
{
587
let draw_function = draw_functions.read().id::<DrawUiMaterial<M>>();
588
589
for (index, extracted_uinode) in extracted_uinodes.uinodes.iter().enumerate() {
590
let Some(material) = render_materials.get(extracted_uinode.material) else {
591
continue;
592
};
593
594
let Ok(default_camera_view) =
595
render_views.get_mut(extracted_uinode.extracted_camera_entity)
596
else {
597
continue;
598
};
599
600
let Ok(view) = camera_views.get(default_camera_view.0) else {
601
continue;
602
};
603
604
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
605
else {
606
continue;
607
};
608
609
let pipeline = pipelines.specialize(
610
&pipeline_cache,
611
&ui_material_pipeline,
612
UiMaterialKey {
613
hdr: view.hdr,
614
bind_group_data: material.key.clone(),
615
},
616
);
617
if transparent_phase.items.capacity() < extracted_uinodes.uinodes.len() {
618
transparent_phase.items.reserve_exact(
619
extracted_uinodes.uinodes.len() - transparent_phase.items.capacity(),
620
);
621
}
622
transparent_phase.add(TransparentUi {
623
draw_function,
624
pipeline,
625
entity: (extracted_uinode.render_entity, extracted_uinode.main_entity),
626
sort_key: FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::MATERIAL),
627
batch_range: 0..0,
628
extra_index: PhaseItemExtraIndex::None,
629
index,
630
indexed: false,
631
});
632
}
633
}
634
635