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#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)]
11pub enum GizmoRenderSystems {
12 #[cfg(feature = "bevy_sprite_render")]
14 QueueLineGizmos2d,
15 #[cfg(feature = "bevy_pbr")]
17 QueueLineGizmos3d,
18}
19
20pub mod retained;
21
22#[cfg(feature = "bevy_sprite_render")]
23mod pipeline_2d;
24#[cfg(feature = "bevy_pbr")]
25mod pipeline_3d;
26
27use bevy_app::{App, Plugin};
28use bevy_ecs::{
29 resource::Resource,
30 schedule::{IntoScheduleConfigs, SystemSet},
31 system::Res,
32};
33
34use {bevy_gizmos::config::GizmoMeshConfig, bevy_mesh::VertexBufferLayout};
35
36use {
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
64use bevy_render::render_resource::{
65 BindGroupLayoutDescriptor, PipelineCache, VertexAttribute, VertexStepMode,
66};
67
68use bevy_gizmos::{
69 config::{GizmoConfigStore, GizmoLineJoint},
70 GizmoAsset, GizmoHandles,
71};
72
73#[derive(Default)]
77pub struct GizmoRenderPlugin;
78
79impl 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
118fn 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
132fn 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 #[cfg_attr(
151 not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
152 expect(
153 unused_variables,
154 reason = "`handle` is unused when bevy_pbr and bevy_sprite_render are both disabled."
155 )
156 )]
157 let Some(handle) = handle
158 else {
159 continue;
160 };
161
162 let joints_resolution = if let GizmoLineJoint::Round(resolution) = config.line.joints {
163 resolution
164 } else {
165 0
166 };
167
168 let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
169 gap_scale,
170 line_scale,
171 } = config.line.style
172 {
173 if gap_scale <= 0.0 {
174 once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero."));
175 }
176 if line_scale <= 0.0 {
177 once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero."));
178 }
179 (gap_scale, line_scale)
180 } else {
181 (1.0, 1.0)
182 };
183
184 commands.spawn((
185 LineGizmoUniform {
186 world_from_local: Affine3::from(&Affine3A::IDENTITY).to_transpose(),
187 line_width: config.line.width,
188 depth_bias: config.depth_bias,
189 joints_resolution,
190 gap_scale,
191 line_scale,
192 #[cfg(feature = "webgl")]
193 _padding: Default::default(),
194 },
195 #[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite_render"))]
196 GizmoMeshConfig {
197 line_perspective: config.line.perspective,
198 line_style: config.line.style,
199 line_joints: config.line.joints,
200 render_layers: config.render_layers.clone(),
201 handle: handle.clone(),
202 },
203 MainEntity::from(Entity::PLACEHOLDER),
206 TemporaryRenderEntity,
207 ));
208 }
209}
210
211#[derive(Component, ShaderType, Clone, Copy)]
212struct LineGizmoUniform {
213 world_from_local: [Vec4; 3],
214 line_width: f32,
215 depth_bias: f32,
216 joints_resolution: u32,
218 gap_scale: f32,
220 line_scale: f32,
221 #[cfg(feature = "webgl")]
223 _padding: bevy_math::Vec3,
224}
225
226#[cfg_attr(
227 not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
228 expect(
229 dead_code,
230 reason = "fields are unused when bevy_pbr and bevy_sprite_render are both disabled."
231 )
232)]
233#[derive(Debug, Clone)]
234struct GpuLineGizmo {
235 list_position_buffer: Buffer,
236 list_color_buffer: Buffer,
237 list_vertex_count: u32,
238 strip_position_buffer: Buffer,
239 strip_color_buffer: Buffer,
240 strip_vertex_count: u32,
241}
242
243impl RenderAsset for GpuLineGizmo {
244 type SourceAsset = GizmoAsset;
245 type Param = SRes<RenderDevice>;
246
247 fn prepare_asset(
248 gizmo: Self::SourceAsset,
249 _: AssetId<Self::SourceAsset>,
250 render_device: &mut SystemParamItem<Self::Param>,
251 _: Option<&Self>,
252 ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
253 let list_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
254 usage: BufferUsages::VERTEX,
255 label: Some("LineGizmo Position Buffer"),
256 contents: cast_slice(&gizmo.buffer().list_positions),
257 });
258
259 let list_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
260 usage: BufferUsages::VERTEX,
261 label: Some("LineGizmo Color Buffer"),
262 contents: cast_slice(&gizmo.buffer().list_colors),
263 });
264
265 let strip_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
266 usage: BufferUsages::VERTEX,
267 label: Some("LineGizmo Strip Position Buffer"),
268 contents: cast_slice(&gizmo.buffer().strip_positions),
269 });
270
271 let strip_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
272 usage: BufferUsages::VERTEX,
273 label: Some("LineGizmo Strip Color Buffer"),
274 contents: cast_slice(&gizmo.buffer().strip_colors),
275 });
276
277 Ok(GpuLineGizmo {
278 list_position_buffer,
279 list_color_buffer,
280 list_vertex_count: gizmo.buffer().list_positions.len() as u32,
281 strip_position_buffer,
282 strip_color_buffer,
283 strip_vertex_count: gizmo.buffer().strip_positions.len() as u32,
284 })
285 }
286}
287
288#[derive(Resource)]
289struct LineGizmoUniformBindgroupLayout {
290 layout: BindGroupLayoutDescriptor,
291}
292
293#[cfg_attr(
294 not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
295 expect(
296 dead_code,
297 reason = "fields are unused when bevy_pbr and bevy_sprite_render are both disabled."
298 )
299)]
300#[derive(Resource)]
301struct LineGizmoUniformBindgroup {
302 bindgroup: BindGroup,
303}
304
305fn prepare_line_gizmo_bind_group(
306 mut commands: Commands,
307 line_gizmo_uniform_layout: Res<LineGizmoUniformBindgroupLayout>,
308 render_device: Res<RenderDevice>,
309 pipeline_cache: Res<PipelineCache>,
310 line_gizmo_uniforms: Res<ComponentUniforms<LineGizmoUniform>>,
311) {
312 if let Some(binding) = line_gizmo_uniforms.uniforms().binding() {
313 commands.insert_resource(LineGizmoUniformBindgroup {
314 bindgroup: render_device.create_bind_group(
315 "LineGizmoUniform bindgroup",
316 &pipeline_cache.get_bind_group_layout(&line_gizmo_uniform_layout.layout),
317 &BindGroupEntries::single(binding),
318 ),
319 });
320 }
321}
322
323#[cfg_attr(
324 not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
325 expect(
326 dead_code,
327 reason = "struct is not constructed when bevy_pbr and bevy_sprite_render are both disabled."
328 )
329)]
330struct SetLineGizmoBindGroup<const I: usize>;
331
332impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetLineGizmoBindGroup<I> {
333 type Param = SRes<LineGizmoUniformBindgroup>;
334 type ViewQuery = ();
335 type ItemQuery = Read<DynamicUniformIndex<LineGizmoUniform>>;
336
337 #[inline]
338 fn render<'w>(
339 _item: &P,
340 _view: ROQueryItem<'w, '_, Self::ViewQuery>,
341 uniform_index: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
342 bind_group: SystemParamItem<'w, '_, Self::Param>,
343 pass: &mut TrackedRenderPass<'w>,
344 ) -> RenderCommandResult {
345 let Some(uniform_index) = uniform_index else {
346 return RenderCommandResult::Skip;
347 };
348 pass.set_bind_group(
349 I,
350 &bind_group.into_inner().bindgroup,
351 &[uniform_index.index()],
352 );
353 RenderCommandResult::Success
354 }
355}
356
357#[cfg_attr(
358 not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
359 expect(
360 dead_code,
361 reason = "struct is not constructed when bevy_pbr and bevy_sprite_render are both disabled."
362 )
363)]
364struct DrawLineGizmo<const STRIP: bool>;
365
366impl<P: PhaseItem, const STRIP: bool> RenderCommand<P> for DrawLineGizmo<STRIP> {
367 type Param = SRes<RenderAssets<GpuLineGizmo>>;
368 type ViewQuery = ();
369 type ItemQuery = Read<GizmoMeshConfig>;
370
371 #[inline]
372 fn render<'w>(
373 _item: &P,
374 _view: ROQueryItem<'w, '_, Self::ViewQuery>,
375 config: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
376 line_gizmos: SystemParamItem<'w, '_, Self::Param>,
377 pass: &mut TrackedRenderPass<'w>,
378 ) -> RenderCommandResult {
379 let Some(config) = config else {
380 return RenderCommandResult::Skip;
381 };
382 let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
383 return RenderCommandResult::Skip;
384 };
385
386 let vertex_count = if STRIP {
387 line_gizmo.strip_vertex_count
388 } else {
389 line_gizmo.list_vertex_count
390 };
391
392 if vertex_count < 2 {
393 return RenderCommandResult::Success;
394 }
395
396 let instances = if STRIP {
397 let item_size = VertexFormat::Float32x3.size();
398 let buffer_size = line_gizmo.strip_position_buffer.size() - item_size;
399
400 pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size));
401 pass.set_vertex_buffer(1, line_gizmo.strip_position_buffer.slice(item_size..));
402
403 let item_size = VertexFormat::Float32x4.size();
404 let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
405
406 pass.set_vertex_buffer(2, line_gizmo.strip_color_buffer.slice(..buffer_size));
407 pass.set_vertex_buffer(3, line_gizmo.strip_color_buffer.slice(item_size..));
408
409 vertex_count - 1
410 } else {
411 pass.set_vertex_buffer(0, line_gizmo.list_position_buffer.slice(..));
412 pass.set_vertex_buffer(1, line_gizmo.list_color_buffer.slice(..));
413
414 vertex_count / 2
415 };
416
417 pass.draw(0..6, 0..instances);
418
419 RenderCommandResult::Success
420 }
421}
422
423#[cfg_attr(
424 not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
425 expect(
426 dead_code,
427 reason = "struct is not constructed when bevy_pbr and bevy_sprite_render are both disabled."
428 )
429)]
430struct DrawLineJointGizmo;
431
432impl<P: PhaseItem> RenderCommand<P> for DrawLineJointGizmo {
433 type Param = SRes<RenderAssets<GpuLineGizmo>>;
434 type ViewQuery = ();
435 type ItemQuery = Read<GizmoMeshConfig>;
436
437 #[inline]
438 fn render<'w>(
439 _item: &P,
440 _view: ROQueryItem<'w, '_, Self::ViewQuery>,
441 config: Option<ROQueryItem<'w, '_, Self::ItemQuery>>,
442 line_gizmos: SystemParamItem<'w, '_, Self::Param>,
443 pass: &mut TrackedRenderPass<'w>,
444 ) -> RenderCommandResult {
445 let Some(config) = config else {
446 return RenderCommandResult::Skip;
447 };
448 let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
449 return RenderCommandResult::Skip;
450 };
451
452 if line_gizmo.strip_vertex_count <= 2 {
453 return RenderCommandResult::Success;
454 };
455
456 if config.line_joints == GizmoLineJoint::None {
457 return RenderCommandResult::Success;
458 };
459
460 let instances = {
461 let item_size = VertexFormat::Float32x3.size();
462 let buffer_size_a = line_gizmo.strip_position_buffer.size() - item_size * 2;
464 pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size_a));
465 let buffer_size_b = line_gizmo.strip_position_buffer.size() - item_size;
467 pass.set_vertex_buffer(
468 1,
469 line_gizmo
470 .strip_position_buffer
471 .slice(item_size..buffer_size_b),
472 );
473 pass.set_vertex_buffer(2, line_gizmo.strip_position_buffer.slice(item_size * 2..));
475
476 let item_size = VertexFormat::Float32x4.size();
478 let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
479 pass.set_vertex_buffer(
481 3,
482 line_gizmo.strip_color_buffer.slice(item_size..buffer_size),
483 );
484
485 line_gizmo.strip_vertex_count - 2
486 };
487
488 let vertices = match config.line_joints {
489 GizmoLineJoint::None => unreachable!(),
490 GizmoLineJoint::Miter => 6,
491 GizmoLineJoint::Round(resolution) => resolution * 3,
492 GizmoLineJoint::Bevel => 3,
493 };
494
495 pass.draw(0..vertices, 0..instances);
496
497 RenderCommandResult::Success
498 }
499}
500
501#[cfg_attr(
502 not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
503 expect(
504 dead_code,
505 reason = "function is unused when bevy_pbr and bevy_sprite_render are both disabled."
506 )
507)]
508fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
509 use VertexFormat::*;
510 let mut position_layout = VertexBufferLayout {
511 array_stride: Float32x3.size(),
512 step_mode: VertexStepMode::Instance,
513 attributes: vec![VertexAttribute {
514 format: Float32x3,
515 offset: 0,
516 shader_location: 0,
517 }],
518 };
519
520 let mut color_layout = VertexBufferLayout {
521 array_stride: Float32x4.size(),
522 step_mode: VertexStepMode::Instance,
523 attributes: vec![VertexAttribute {
524 format: Float32x4,
525 offset: 0,
526 shader_location: 2,
527 }],
528 };
529
530 if strip {
531 vec![
532 position_layout.clone(),
533 {
534 position_layout.attributes[0].shader_location = 1;
535 position_layout
536 },
537 color_layout.clone(),
538 {
539 color_layout.attributes[0].shader_location = 3;
540 color_layout
541 },
542 ]
543 } else {
544 position_layout.array_stride *= 2;
545 position_layout.attributes.push(VertexAttribute {
546 format: Float32x3,
547 offset: Float32x3.size(),
548 shader_location: 1,
549 });
550
551 color_layout.array_stride *= 2;
552 color_layout.attributes.push(VertexAttribute {
553 format: Float32x4,
554 offset: Float32x4.size(),
555 shader_location: 3,
556 });
557
558 vec![position_layout, color_layout]
559 }
560}
561
562#[cfg_attr(
563 not(any(feature = "bevy_pbr", feature = "bevy_sprite_render")),
564 expect(
565 dead_code,
566 reason = "function is unused when bevy_pbr and bevy_sprite_render are both disabled."
567 )
568)]
569fn line_joint_gizmo_vertex_buffer_layouts() -> Vec<VertexBufferLayout> {
570 use VertexFormat::*;
571 let mut position_layout = VertexBufferLayout {
572 array_stride: Float32x3.size(),
573 step_mode: VertexStepMode::Instance,
574 attributes: vec![VertexAttribute {
575 format: Float32x3,
576 offset: 0,
577 shader_location: 0,
578 }],
579 };
580
581 let color_layout = VertexBufferLayout {
582 array_stride: Float32x4.size(),
583 step_mode: VertexStepMode::Instance,
584 attributes: vec![VertexAttribute {
585 format: Float32x4,
586 offset: 0,
587 shader_location: 3,
588 }],
589 };
590
591 vec![
592 position_layout.clone(),
593 {
594 position_layout.attributes[0].shader_location = 1;
595 position_layout.clone()
596 },
597 {
598 position_layout.attributes[0].shader_location = 2;
599 position_layout
600 },
601 color_layout.clone(),
602 ]
603}