Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_gizmos_render/src/lib.rs
9351 views
1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![doc(
3
html_logo_url = "https://bevy.org/assets/icon.png",
4
html_favicon_url = "https://bevy.org/assets/icon.png"
5
)]
6
7
//! This crate renders `bevy_gizmos` with `bevy_render`.
8
9
/// System set label for the systems handling the rendering of gizmos.
10
#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)]
11
pub enum GizmoRenderSystems {
12
/// Adds gizmos to the [`Transparent2d`](bevy_core_pipeline::core_2d::Transparent2d) render phase
13
#[cfg(feature = "bevy_sprite_render")]
14
QueueLineGizmos2d,
15
/// Adds gizmos to the [`Transparent3d`](bevy_core_pipeline::core_3d::Transparent3d) render phase
16
#[cfg(feature = "bevy_pbr")]
17
QueueLineGizmos3d,
18
}
19
20
pub mod retained;
21
22
#[cfg(feature = "bevy_sprite_render")]
23
mod pipeline_2d;
24
#[cfg(feature = "bevy_pbr")]
25
mod pipeline_3d;
26
27
use bevy_app::{App, Plugin};
28
use bevy_ecs::{
29
resource::Resource,
30
schedule::{IntoScheduleConfigs, SystemSet},
31
system::Res,
32
};
33
use bevy_math::Affine3Ext;
34
35
use {bevy_gizmos::config::GizmoMeshConfig, bevy_mesh::VertexBufferLayout};
36
37
use {
38
crate::retained::extract_linegizmos,
39
bevy_asset::AssetId,
40
bevy_ecs::{
41
component::Component,
42
entity::Entity,
43
query::ROQueryItem,
44
system::{
45
lifetimeless::{Read, SRes},
46
Commands, SystemParamItem,
47
},
48
},
49
bevy_math::{Affine3, Affine3A, Vec4},
50
bevy_render::{
51
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
52
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
53
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
54
render_resource::{
55
binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayoutEntries,
56
Buffer, BufferInitDescriptor, BufferUsages, ShaderStages, ShaderType, VertexFormat,
57
},
58
renderer::RenderDevice,
59
sync_world::{MainEntity, TemporaryRenderEntity},
60
Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,
61
},
62
bytemuck::cast_slice,
63
};
64
65
use bevy_render::render_resource::{
66
BindGroupLayoutDescriptor, PipelineCache, VertexAttribute, VertexStepMode,
67
};
68
69
use bevy_gizmos::{
70
config::{GizmoConfigStore, GizmoLineJoint},
71
GizmoAsset, GizmoHandles,
72
};
73
74
/// A [`Plugin`] that provides an immediate mode drawing api for visual debugging.
75
///
76
/// Requires to be loaded after [`PbrPlugin`](bevy_pbr::PbrPlugin) or [`SpriteRenderPlugin`](bevy_sprite_render::SpriteRenderPlugin).
77
#[derive(Default)]
78
pub struct GizmoRenderPlugin;
79
80
impl Plugin for GizmoRenderPlugin {
81
fn build(&self, app: &mut App) {
82
{
83
use bevy_asset::embedded_asset;
84
embedded_asset!(app, "lines.wgsl");
85
embedded_asset!(app, "line_joints.wgsl");
86
}
87
88
app.add_plugins(UniformComponentPlugin::<LineGizmoUniform>::default())
89
.add_plugins(RenderAssetPlugin::<GpuLineGizmo>::default());
90
91
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
92
render_app.add_systems(RenderStartup, init_line_gizmo_uniform_bind_group_layout);
93
94
render_app.add_systems(
95
Render,
96
prepare_line_gizmo_bind_group.in_set(RenderSystems::PrepareBindGroups),
97
);
98
99
render_app.add_systems(ExtractSchedule, (extract_gizmo_data, extract_linegizmos));
100
101
#[cfg(feature = "bevy_sprite_render")]
102
if app.is_plugin_added::<bevy_sprite_render::SpriteRenderPlugin>() {
103
app.add_plugins(pipeline_2d::LineGizmo2dPlugin);
104
} else {
105
tracing::warn!("bevy_sprite_render feature is enabled but bevy_sprite_render::SpriteRenderPlugin was not detected. Are you sure you loaded GizmoPlugin after SpriteRenderPlugin?");
106
}
107
#[cfg(feature = "bevy_pbr")]
108
if app.is_plugin_added::<bevy_pbr::PbrPlugin>() {
109
app.add_plugins(pipeline_3d::LineGizmo3dPlugin);
110
} else {
111
tracing::warn!("bevy_pbr feature is enabled but bevy_pbr::PbrPlugin was not detected. Are you sure you loaded GizmoPlugin after PbrPlugin?");
112
}
113
} else {
114
tracing::warn!("bevy_render feature is enabled but RenderApp was not detected. Are you sure you loaded GizmoPlugin after RenderPlugin?");
115
}
116
}
117
}
118
119
fn init_line_gizmo_uniform_bind_group_layout(mut commands: Commands) {
120
let line_layout = BindGroupLayoutDescriptor::new(
121
"LineGizmoUniform layout",
122
&BindGroupLayoutEntries::single(
123
ShaderStages::VERTEX,
124
uniform_buffer::<LineGizmoUniform>(true),
125
),
126
);
127
128
commands.insert_resource(LineGizmoUniformBindgroupLayout {
129
layout: line_layout,
130
});
131
}
132
133
fn extract_gizmo_data(
134
mut commands: Commands,
135
handles: Extract<Res<GizmoHandles>>,
136
config: Extract<Res<GizmoConfigStore>>,
137
) {
138
use bevy_gizmos::config::GizmoLineStyle;
139
use bevy_utils::once;
140
use tracing::warn;
141
142
for (group_type_id, handle) in handles.handles() {
143
let Some((config, _)) = config.get_config_dyn(group_type_id) else {
144
continue;
145
};
146
147
if !config.enabled {
148
continue;
149
}
150
151
#[cfg_attr(
152
not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
153
expect(
154
unused_variables,
155
reason = "`handle` is unused when bevy_pbr and bevy_sprite_render are both disabled."
156
)
157
)]
158
let Some(handle) = handle
159
else {
160
continue;
161
};
162
163
let joints_resolution = if let GizmoLineJoint::Round(resolution) = config.line.joints {
164
resolution
165
} else {
166
0
167
};
168
169
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
170
gap_scale,
171
line_scale,
172
} = config.line.style
173
{
174
if gap_scale <= 0.0 {
175
once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero."));
176
}
177
if line_scale <= 0.0 {
178
once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero."));
179
}
180
(gap_scale, line_scale)
181
} else {
182
(1.0, 1.0)
183
};
184
185
commands.spawn((
186
LineGizmoUniform {
187
world_from_local: Affine3::from(Affine3A::IDENTITY).to_transpose(),
188
line_width: config.line.width,
189
depth_bias: config.depth_bias,
190
joints_resolution,
191
gap_scale,
192
line_scale,
193
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
194
_webgl2_padding: Default::default(),
195
},
196
#[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite_render"))]
197
GizmoMeshConfig {
198
line_perspective: config.line.perspective,
199
line_style: config.line.style,
200
line_joints: config.line.joints,
201
render_layers: config.render_layers.clone(),
202
handle: handle.clone(),
203
},
204
// The immediate mode API does not have a main world entity to refer to,
205
// but we do need MainEntity on this render entity for the systems to find it.
206
MainEntity::from(Entity::PLACEHOLDER),
207
TemporaryRenderEntity,
208
));
209
}
210
}
211
212
#[derive(Component, ShaderType, Clone, Copy)]
213
struct LineGizmoUniform {
214
world_from_local: [Vec4; 3],
215
line_width: f32,
216
depth_bias: f32,
217
// Only used by gizmo line t if the current configs `line_joints` is set to `GizmoLineJoint::Round(_)`
218
joints_resolution: u32,
219
// Only used if the current configs `line_style` is set to `GizmoLineStyle::Dashed{_}`
220
gap_scale: f32,
221
line_scale: f32,
222
/// WebGL2 structs must be 16 byte aligned.
223
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
224
_webgl2_padding: bevy_math::Vec3,
225
}
226
227
#[cfg_attr(
228
not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
229
expect(
230
dead_code,
231
reason = "fields are unused when bevy_pbr and bevy_sprite_render are both disabled."
232
)
233
)]
234
#[derive(Debug, Clone)]
235
struct GpuLineGizmo {
236
list_position_buffer: Buffer,
237
list_color_buffer: Buffer,
238
list_vertex_count: u32,
239
strip_position_buffer: Buffer,
240
strip_color_buffer: Buffer,
241
strip_vertex_count: u32,
242
}
243
244
impl RenderAsset for GpuLineGizmo {
245
type SourceAsset = GizmoAsset;
246
type Param = SRes<RenderDevice>;
247
248
fn prepare_asset(
249
gizmo: Self::SourceAsset,
250
_: AssetId<Self::SourceAsset>,
251
render_device: &mut SystemParamItem<Self::Param>,
252
_: Option<&Self>,
253
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
254
let list_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
255
usage: BufferUsages::VERTEX,
256
label: Some("LineGizmo Position Buffer"),
257
contents: cast_slice(&gizmo.buffer().list_positions),
258
});
259
260
let list_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
261
usage: BufferUsages::VERTEX,
262
label: Some("LineGizmo Color Buffer"),
263
contents: cast_slice(&gizmo.buffer().list_colors),
264
});
265
266
let strip_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
267
usage: BufferUsages::VERTEX,
268
label: Some("LineGizmo Strip Position Buffer"),
269
contents: cast_slice(&gizmo.buffer().strip_positions),
270
});
271
272
let strip_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
273
usage: BufferUsages::VERTEX,
274
label: Some("LineGizmo Strip Color Buffer"),
275
contents: cast_slice(&gizmo.buffer().strip_colors),
276
});
277
278
Ok(GpuLineGizmo {
279
list_position_buffer,
280
list_color_buffer,
281
list_vertex_count: gizmo.buffer().list_positions.len() as u32,
282
strip_position_buffer,
283
strip_color_buffer,
284
strip_vertex_count: gizmo.buffer().strip_positions.len() as u32,
285
})
286
}
287
}
288
289
#[derive(Resource)]
290
struct LineGizmoUniformBindgroupLayout {
291
layout: BindGroupLayoutDescriptor,
292
}
293
294
#[cfg_attr(
295
not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
296
expect(
297
dead_code,
298
reason = "fields are unused when bevy_pbr and bevy_sprite_render are both disabled."
299
)
300
)]
301
#[derive(Resource)]
302
struct LineGizmoUniformBindgroup {
303
bindgroup: BindGroup,
304
}
305
306
fn prepare_line_gizmo_bind_group(
307
mut commands: Commands,
308
line_gizmo_uniform_layout: Res<LineGizmoUniformBindgroupLayout>,
309
render_device: Res<RenderDevice>,
310
pipeline_cache: Res<PipelineCache>,
311
line_gizmo_uniforms: Res<ComponentUniforms<LineGizmoUniform>>,
312
) {
313
if let Some(binding) = line_gizmo_uniforms.uniforms().binding() {
314
commands.insert_resource(LineGizmoUniformBindgroup {
315
bindgroup: render_device.create_bind_group(
316
"LineGizmoUniform bindgroup",
317
&pipeline_cache.get_bind_group_layout(&line_gizmo_uniform_layout.layout),
318
&BindGroupEntries::single(binding),
319
),
320
});
321
}
322
}
323
324
#[cfg_attr(
325
not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
326
expect(
327
dead_code,
328
reason = "struct is not constructed when bevy_pbr and bevy_sprite_render are both disabled."
329
)
330
)]
331
struct SetLineGizmoBindGroup<const I: usize>;
332
333
impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetLineGizmoBindGroup<I> {
334
type Param = SRes<LineGizmoUniformBindgroup>;
335
type ViewQuery = ();
336
type ItemQuery = Read<DynamicUniformIndex<LineGizmoUniform>>;
337
338
#[inline]
339
fn render<'w>(
340
_item: &P,
341
_view: ROQueryItem<'w, '_, Self::ViewQuery>,
342
uniform_index: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
343
bind_group: SystemParamItem<'w, '_, Self::Param>,
344
pass: &mut TrackedRenderPass<'w>,
345
) -> RenderCommandResult {
346
let Some(uniform_index) = uniform_index else {
347
return RenderCommandResult::Skip;
348
};
349
pass.set_bind_group(
350
I,
351
&bind_group.into_inner().bindgroup,
352
&[uniform_index.index()],
353
);
354
RenderCommandResult::Success
355
}
356
}
357
358
#[cfg_attr(
359
not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
360
expect(
361
dead_code,
362
reason = "struct is not constructed when bevy_pbr and bevy_sprite_render are both disabled."
363
)
364
)]
365
struct DrawLineGizmo<const STRIP: bool>;
366
367
impl<P: PhaseItem, const STRIP: bool> RenderCommand<P> for DrawLineGizmo<STRIP> {
368
type Param = SRes<RenderAssets<GpuLineGizmo>>;
369
type ViewQuery = ();
370
type ItemQuery = Read<GizmoMeshConfig>;
371
372
#[inline]
373
fn render<'w>(
374
_item: &P,
375
_view: ROQueryItem<'w, '_, Self::ViewQuery>,
376
config: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
377
line_gizmos: SystemParamItem<'w, '_, Self::Param>,
378
pass: &mut TrackedRenderPass<'w>,
379
) -> RenderCommandResult {
380
let Some(config) = config else {
381
return RenderCommandResult::Skip;
382
};
383
let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
384
return RenderCommandResult::Skip;
385
};
386
387
let vertex_count = if STRIP {
388
line_gizmo.strip_vertex_count
389
} else {
390
line_gizmo.list_vertex_count
391
};
392
393
if vertex_count < 2 {
394
return RenderCommandResult::Success;
395
}
396
397
let instances = if STRIP {
398
let item_size = VertexFormat::Float32x3.size();
399
let buffer_size = line_gizmo.strip_position_buffer.size() - item_size;
400
401
pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size));
402
pass.set_vertex_buffer(1, line_gizmo.strip_position_buffer.slice(item_size..));
403
404
let item_size = VertexFormat::Float32x4.size();
405
let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
406
407
pass.set_vertex_buffer(2, line_gizmo.strip_color_buffer.slice(..buffer_size));
408
pass.set_vertex_buffer(3, line_gizmo.strip_color_buffer.slice(item_size..));
409
410
vertex_count - 1
411
} else {
412
pass.set_vertex_buffer(0, line_gizmo.list_position_buffer.slice(..));
413
pass.set_vertex_buffer(1, line_gizmo.list_color_buffer.slice(..));
414
415
vertex_count / 2
416
};
417
418
pass.draw(0..6, 0..instances);
419
420
RenderCommandResult::Success
421
}
422
}
423
424
#[cfg_attr(
425
not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
426
expect(
427
dead_code,
428
reason = "struct is not constructed when bevy_pbr and bevy_sprite_render are both disabled."
429
)
430
)]
431
struct DrawLineJointGizmo;
432
433
impl<P: PhaseItem> RenderCommand<P> for DrawLineJointGizmo {
434
type Param = SRes<RenderAssets<GpuLineGizmo>>;
435
type ViewQuery = ();
436
type ItemQuery = Read<GizmoMeshConfig>;
437
438
#[inline]
439
fn render<'w>(
440
_item: &P,
441
_view: ROQueryItem<'w, '_, Self::ViewQuery>,
442
config: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
443
line_gizmos: SystemParamItem<'w, '_, Self::Param>,
444
pass: &mut TrackedRenderPass<'w>,
445
) -> RenderCommandResult {
446
let Some(config) = config else {
447
return RenderCommandResult::Skip;
448
};
449
let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
450
return RenderCommandResult::Skip;
451
};
452
453
if line_gizmo.strip_vertex_count <= 2 {
454
return RenderCommandResult::Success;
455
};
456
457
if config.line_joints == GizmoLineJoint::None {
458
return RenderCommandResult::Success;
459
};
460
461
let instances = {
462
let item_size = VertexFormat::Float32x3.size();
463
// position_a
464
let buffer_size_a = line_gizmo.strip_position_buffer.size() - item_size * 2;
465
pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size_a));
466
// position_b
467
let buffer_size_b = line_gizmo.strip_position_buffer.size() - item_size;
468
pass.set_vertex_buffer(
469
1,
470
line_gizmo
471
.strip_position_buffer
472
.slice(item_size..buffer_size_b),
473
);
474
// position_c
475
pass.set_vertex_buffer(2, line_gizmo.strip_position_buffer.slice(item_size * 2..));
476
477
// color
478
let item_size = VertexFormat::Float32x4.size();
479
let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
480
// This corresponds to the color of position_b, hence starts from `item_size`
481
pass.set_vertex_buffer(
482
3,
483
line_gizmo.strip_color_buffer.slice(item_size..buffer_size),
484
);
485
486
line_gizmo.strip_vertex_count - 2
487
};
488
489
let vertices = match config.line_joints {
490
GizmoLineJoint::None => unreachable!(),
491
GizmoLineJoint::Miter => 6,
492
GizmoLineJoint::Round(resolution) => resolution * 3,
493
GizmoLineJoint::Bevel => 3,
494
};
495
496
pass.draw(0..vertices, 0..instances);
497
498
RenderCommandResult::Success
499
}
500
}
501
502
#[cfg_attr(
503
not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
504
expect(
505
dead_code,
506
reason = "function is unused when bevy_pbr and bevy_sprite_render are both disabled."
507
)
508
)]
509
fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
510
use VertexFormat::*;
511
let mut position_layout = VertexBufferLayout {
512
array_stride: Float32x3.size(),
513
step_mode: VertexStepMode::Instance,
514
attributes: vec![VertexAttribute {
515
format: Float32x3,
516
offset: 0,
517
shader_location: 0,
518
}],
519
};
520
521
let mut color_layout = VertexBufferLayout {
522
array_stride: Float32x4.size(),
523
step_mode: VertexStepMode::Instance,
524
attributes: vec![VertexAttribute {
525
format: Float32x4,
526
offset: 0,
527
shader_location: 2,
528
}],
529
};
530
531
if strip {
532
vec![
533
position_layout.clone(),
534
{
535
position_layout.attributes[0].shader_location = 1;
536
position_layout
537
},
538
color_layout.clone(),
539
{
540
color_layout.attributes[0].shader_location = 3;
541
color_layout
542
},
543
]
544
} else {
545
position_layout.array_stride *= 2;
546
position_layout.attributes.push(VertexAttribute {
547
format: Float32x3,
548
offset: Float32x3.size(),
549
shader_location: 1,
550
});
551
552
color_layout.array_stride *= 2;
553
color_layout.attributes.push(VertexAttribute {
554
format: Float32x4,
555
offset: Float32x4.size(),
556
shader_location: 3,
557
});
558
559
vec![position_layout, color_layout]
560
}
561
}
562
563
#[cfg_attr(
564
not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
565
expect(
566
dead_code,
567
reason = "function is unused when bevy_pbr and bevy_sprite_render are both disabled."
568
)
569
)]
570
fn line_joint_gizmo_vertex_buffer_layouts() -> Vec<VertexBufferLayout> {
571
use VertexFormat::*;
572
let mut position_layout = VertexBufferLayout {
573
array_stride: Float32x3.size(),
574
step_mode: VertexStepMode::Instance,
575
attributes: vec![VertexAttribute {
576
format: Float32x3,
577
offset: 0,
578
shader_location: 0,
579
}],
580
};
581
582
let color_layout = VertexBufferLayout {
583
array_stride: Float32x4.size(),
584
step_mode: VertexStepMode::Instance,
585
attributes: vec![VertexAttribute {
586
format: Float32x4,
587
offset: 0,
588
shader_location: 3,
589
}],
590
};
591
592
vec![
593
position_layout.clone(),
594
{
595
position_layout.attributes[0].shader_location = 1;
596
position_layout.clone()
597
},
598
{
599
position_layout.attributes[0].shader_location = 2;
600
position_layout
601
},
602
color_layout.clone(),
603
]
604
}
605
606