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