1use std::{num::NonZeroU64, ops::Range};
79
80use bitflags::bitflags;
81use enumset::{EnumSet, enum_set};
82use re_tracing::profile_function;
83use smallvec::smallvec;
84
85use crate::{
86 DebugLabel, DepthOffset, DrawableCollector, LineDrawableBuilder, OutlineMaskPreference,
87 PickingLayerObjectId, PickingLayerProcessor,
88 allocator::create_and_fill_uniform_buffer_batch,
89 draw_phases::{DrawPhase, OutlineMaskProcessor},
90 include_shader_module,
91 renderer::{DrawDataDrawable, DrawInstruction, DrawableCollectionViewInfo},
92 view_builder::ViewBuilder,
93 wgpu_resources::{
94 BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle,
95 GpuRenderPipelineHandle, GpuRenderPipelinePoolAccessor, PipelineLayoutDesc, PoolError,
96 RenderPipelineDesc,
97 },
98};
99
100use super::{DrawData, DrawError, RenderContext, Renderer};
101
102pub mod gpu_data {
103 use crate::{
106 Color32, PickingLayerObjectId, UnalignedColor32, size::SizeHalf, wgpu_buffer_types,
107 };
108
109 use super::LineStripFlags;
110
111 #[repr(C, packed)]
112 #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
113 pub struct LineVertex {
114 pub position: glam::Vec3,
115 pub strip_index: u32,
118 }
119 static_assertions::assert_eq_size!(LineVertex, glam::Vec4);
121
122 impl LineVertex {
123 pub const SENTINEL: Self = Self {
125 position: glam::vec3(f32::MAX, f32::MAX, f32::MAX),
126 strip_index: u32::MAX,
127 };
128
129 pub const NUM_SENTINEL_VERTICES: usize = 2;
131 }
132
133 #[repr(C, packed)]
134 #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
135 pub struct LineStripInfo {
136 pub color: UnalignedColor32, pub stippling: u8,
139 pub flags: LineStripFlags,
140 pub radius: SizeHalf,
141 }
142 static_assertions::assert_eq_size!(LineStripInfo, [u32; 2]);
143
144 impl Default for LineStripInfo {
145 fn default() -> Self {
146 Self {
147 radius: crate::Size::new_ui_points(1.5).into(),
148 color: Color32::WHITE.into(),
149 stippling: 0,
150 flags: LineStripFlags::empty(),
151 }
152 }
153 }
154
155 #[repr(C)]
157 #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
158 pub struct DrawDataUniformBuffer {
159 pub radius_boost_in_ui_points: wgpu_buffer_types::F32RowPadded,
160 pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 1],
161 }
162
163 #[repr(C)]
165 #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
166 pub struct BatchUniformBuffer {
167 pub world_from_obj: wgpu_buffer_types::Mat4,
168 pub outline_mask_ids: wgpu_buffer_types::UVec2,
169 pub picking_object_id: PickingLayerObjectId,
170
171 pub depth_offset: f32,
172 pub triangle_cap_length_factor: f32,
173 pub triangle_cap_width_factor: f32,
174 pub _padding: f32,
175
176 pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 6],
177 }
178}
179
180#[derive(Clone)]
182struct LineStripBatch {
183 bind_group: GpuBindGroup,
184 vertex_range: Range<u32>,
185 active_phases: EnumSet<DrawPhase>,
186}
187
188#[derive(Clone)]
191pub struct LineDrawData {
192 bind_group_all_lines: Option<GpuBindGroup>,
193 bind_group_all_lines_outline_mask: Option<GpuBindGroup>,
194 batches: Vec<LineStripBatch>,
195}
196
197impl DrawData for LineDrawData {
198 type Renderer = LineRenderer;
199
200 fn collect_drawables(
201 &self,
202 _view_info: &DrawableCollectionViewInfo,
203 collector: &mut DrawableCollector<'_>,
204 ) {
205 for (batch_idx, batch) in self.batches.iter().enumerate() {
209 collector.add_drawable(
210 batch.active_phases,
211 DrawDataDrawable {
212 distance_sort_key: f32::MAX,
214 draw_data_payload: batch_idx as _,
215 },
216 );
217 }
218 }
219}
220
221bitflags! {
222 #[repr(C)]
226 #[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
227 pub struct LineStripFlags : u8 {
228 const FLAG_CAP_END_TRIANGLE = 0b0000_0001;
230
231 const FLAG_CAP_END_ROUND = 0b0000_0010;
233
234 const FLAG_CAP_END_EXTEND_OUTWARDS = 0b0000_0100;
237
238 const FLAG_CAP_START_TRIANGLE = 0b0000_1000;
240
241 const FLAG_CAP_START_ROUND = 0b0001_0000;
243
244 const FLAG_CAP_START_EXTEND_OUTWARDS = 0b0010_0000;
247
248 const FLAG_COLOR_GRADIENT = 0b0100_0000;
252
253 const FLAG_FORCE_ORTHO_SPANNING = 0b1000_0000;
261
262 const FLAGS_OUTWARD_EXTENDING_ROUND_CAPS =
264 LineStripFlags::FLAG_CAP_START_ROUND.bits() |
265 LineStripFlags::FLAG_CAP_END_ROUND.bits() |
266 LineStripFlags::FLAG_CAP_START_EXTEND_OUTWARDS.bits() |
267 LineStripFlags::FLAG_CAP_END_EXTEND_OUTWARDS.bits();
268 }
269}
270
271pub struct LineBatchInfo {
273 pub label: DebugLabel,
274
275 pub world_from_obj: glam::Affine3A,
280
281 pub line_vertex_count: u32,
286
287 pub overall_outline_mask_ids: OutlineMaskPreference,
289
290 pub additional_outline_mask_ids_vertex_ranges: Vec<(Range<u32>, OutlineMaskPreference)>,
298
299 pub picking_object_id: PickingLayerObjectId,
301
302 pub depth_offset: DepthOffset,
304
305 pub triangle_cap_length_factor: f32,
310
311 pub triangle_cap_width_factor: f32,
316}
317
318impl Default for LineBatchInfo {
319 fn default() -> Self {
320 Self {
321 label: "unknown_line_batch".into(),
322 world_from_obj: glam::Affine3A::IDENTITY,
323 line_vertex_count: 0,
324 overall_outline_mask_ids: OutlineMaskPreference::NONE,
325 additional_outline_mask_ids_vertex_ranges: Vec::new(),
326 picking_object_id: PickingLayerObjectId::default(),
327 depth_offset: 0,
328 triangle_cap_length_factor: 4.0,
329 triangle_cap_width_factor: 2.0,
330 }
331 }
332}
333
334#[derive(thiserror::Error, Debug, PartialEq, Eq)]
335pub enum LineDrawDataError {
336 #[error("Line vertex refers to unknown line strip.")]
337 InvalidStripIndex,
338
339 #[error(transparent)]
340 PoolError(#[from] PoolError),
341
342 #[error(transparent)]
343 FailedTransferringDataToGpu(#[from] crate::allocator::CpuWriteGpuReadError),
344
345 #[error(transparent)]
346 DataTextureSourceWriteError(#[from] crate::allocator::DataTextureSourceWriteError),
347}
348
349impl LineDrawData {
350 pub fn new(line_builder: LineDrawableBuilder<'_>) -> Result<Self, LineDrawDataError> {
357 let LineDrawableBuilder {
358 ctx,
359 vertices_buffer,
360 batches,
361 strips_buffer,
362 picking_instance_ids_buffer,
363 radius_boost_in_ui_points_for_outlines,
364 } = line_builder;
365
366 let line_renderer = ctx.renderer::<LineRenderer>();
367
368 if strips_buffer.is_empty() || vertices_buffer.is_empty() {
369 return Ok(Self {
370 bind_group_all_lines: None,
371 bind_group_all_lines_outline_mask: None,
372 batches: Vec::new(),
373 });
374 }
375
376 let batches = if batches.is_empty() {
377 vec![LineBatchInfo {
378 label: "LineDrawData::fallback_batch".into(),
379 line_vertex_count: vertices_buffer.len() as _,
380 ..Default::default()
381 }]
382 } else {
383 batches
384 };
385
386 const NUM_SENTINEL_VERTICES: usize = 2;
387
388 let max_texture_dimension_2d = ctx.device.limits().max_texture_dimension_2d;
389 let max_num_texels = max_texture_dimension_2d as usize * max_texture_dimension_2d as usize;
390 let max_num_vertices = max_num_texels - NUM_SENTINEL_VERTICES;
391
392 let position_texture = vertices_buffer.finish(
393 wgpu::TextureFormat::Rgba32Float,
394 "LineDrawData::position_texture",
395 )?;
396 let strip_data_texture = strips_buffer.finish(
397 wgpu::TextureFormat::Rg32Uint,
398 "LineDrawData::strip_data_texture",
399 )?;
400 let picking_instance_id_texture = picking_instance_ids_buffer.finish(
401 wgpu::TextureFormat::Rg32Uint,
402 "LineDrawData::picking_instance_id_texture",
403 )?;
404
405 let draw_data_uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
406 ctx,
407 "LineDrawData::DrawDataUniformBuffer".into(),
408 [
409 gpu_data::DrawDataUniformBuffer {
410 radius_boost_in_ui_points: 0.0.into(),
411 end_padding: Default::default(),
412 },
413 gpu_data::DrawDataUniformBuffer {
414 radius_boost_in_ui_points: radius_boost_in_ui_points_for_outlines.into(),
415 end_padding: Default::default(),
416 },
417 ]
418 .into_iter(),
419 );
420 let bind_group_all_lines = ctx.gpu_resources.bind_groups.alloc(
421 &ctx.device,
422 &ctx.gpu_resources,
423 &BindGroupDesc {
424 label: "LineDrawData::bind_group_all_lines".into(),
425 entries: smallvec![
426 BindGroupEntry::DefaultTextureView(position_texture.handle),
427 BindGroupEntry::DefaultTextureView(strip_data_texture.handle),
428 BindGroupEntry::DefaultTextureView(picking_instance_id_texture.handle),
429 draw_data_uniform_buffer_bindings[0].clone(),
430 ],
431 layout: line_renderer.bind_group_layout_all_lines,
432 },
433 );
434 let bind_group_all_lines_outline_mask = ctx.gpu_resources.bind_groups.alloc(
435 &ctx.device,
436 &ctx.gpu_resources,
437 &BindGroupDesc {
438 label: "LineDrawData::bind_group_all_lines_outline_mask".into(),
439 entries: smallvec![
440 BindGroupEntry::DefaultTextureView(position_texture.handle),
441 BindGroupEntry::DefaultTextureView(strip_data_texture.handle),
442 BindGroupEntry::DefaultTextureView(picking_instance_id_texture.handle),
443 draw_data_uniform_buffer_bindings[1].clone(),
444 ],
445 layout: line_renderer.bind_group_layout_all_lines,
446 },
447 );
448
449 let mut batches_internal = Vec::with_capacity(batches.len());
451 {
452 fn uniforms_from_batch_info(
453 batch_info: &LineBatchInfo,
454 outline_mask_ids: [u8; 2],
455 ) -> gpu_data::BatchUniformBuffer {
456 gpu_data::BatchUniformBuffer {
457 world_from_obj: batch_info.world_from_obj.into(),
458 outline_mask_ids: outline_mask_ids.into(),
459 picking_object_id: batch_info.picking_object_id,
460 depth_offset: batch_info.depth_offset as f32,
461 triangle_cap_length_factor: batch_info.triangle_cap_length_factor,
462 triangle_cap_width_factor: batch_info.triangle_cap_width_factor,
463 _padding: 0.0,
464 end_padding: Default::default(),
465 }
466 }
467
468 let uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
469 ctx,
470 "lines batch uniform buffers".into(),
471 batches.iter().map(|batch_info| {
472 uniforms_from_batch_info(
473 batch_info,
474 batch_info.overall_outline_mask_ids.0.unwrap_or_default(),
475 )
476 }),
477 );
478
479 let mut uniform_buffer_bindings_mask_only_batches =
482 create_and_fill_uniform_buffer_batch(
483 ctx,
484 "lines batch uniform buffers - mask only".into(),
485 batches
486 .iter()
487 .flat_map(|batch_info| {
488 batch_info
489 .additional_outline_mask_ids_vertex_ranges
490 .iter()
491 .map(|(_, mask)| {
492 uniforms_from_batch_info(batch_info, mask.0.unwrap_or_default())
493 })
494 })
495 .collect::<Vec<_>>()
496 .into_iter(),
497 )
498 .into_iter();
499
500 let mut start_vertex_for_next_batch = 0;
501 for (batch_info, uniform_buffer_binding) in
502 batches.iter().zip(uniform_buffer_bindings.into_iter())
503 {
504 let line_vertex_range_end = (start_vertex_for_next_batch
505 + batch_info.line_vertex_count)
506 .min(max_num_vertices as u32);
507 let mut active_phases = enum_set![DrawPhase::Opaque | DrawPhase::PickingLayer];
508 if batch_info.overall_outline_mask_ids.is_some() {
510 active_phases.insert(DrawPhase::OutlineMask);
511 }
512
513 batches_internal.push(line_renderer.create_linestrip_batch(
514 ctx,
515 batch_info.label.clone(),
516 uniform_buffer_binding,
517 start_vertex_for_next_batch..line_vertex_range_end,
518 active_phases,
519 ));
520
521 for (range, _) in &batch_info.additional_outline_mask_ids_vertex_ranges {
522 batches_internal.push(line_renderer.create_linestrip_batch(
523 ctx,
524 format!("{} strip-only {range:?}", batch_info.label).into(),
525 uniform_buffer_bindings_mask_only_batches.next().unwrap(),
526 range.clone(),
527 enum_set![DrawPhase::OutlineMask],
528 ));
529 }
530
531 start_vertex_for_next_batch = line_vertex_range_end;
532 }
533 }
534
535 Ok(Self {
536 bind_group_all_lines: Some(bind_group_all_lines),
537 bind_group_all_lines_outline_mask: Some(bind_group_all_lines_outline_mask),
538 batches: batches_internal,
539 })
540 }
541}
542
543pub struct LineRenderer {
544 render_pipeline_color: GpuRenderPipelineHandle,
545 render_pipeline_picking_layer: GpuRenderPipelineHandle,
546 render_pipeline_outline_mask: GpuRenderPipelineHandle,
547 bind_group_layout_all_lines: GpuBindGroupLayoutHandle,
548 bind_group_layout_batch: GpuBindGroupLayoutHandle,
549}
550
551impl LineRenderer {
552 fn create_linestrip_batch(
553 &self,
554 ctx: &RenderContext,
555 label: DebugLabel,
556 uniform_buffer_binding: BindGroupEntry,
557 line_vertex_range: Range<u32>,
558 active_phases: EnumSet<DrawPhase>,
559 ) -> LineStripBatch {
560 let bind_group = ctx.gpu_resources.bind_groups.alloc(
563 &ctx.device,
564 &ctx.gpu_resources,
565 &BindGroupDesc {
566 label,
567 entries: smallvec![uniform_buffer_binding],
568 layout: self.bind_group_layout_batch,
569 },
570 );
571
572 LineStripBatch {
573 bind_group,
574 vertex_range: (line_vertex_range.start * 6)..(line_vertex_range.end * 6),
578 active_phases,
579 }
580 }
581}
582
583impl Renderer for LineRenderer {
584 type RendererDrawData = LineDrawData;
585
586 fn create_renderer(ctx: &RenderContext) -> Self {
587 profile_function!();
588
589 let render_pipelines = &ctx.gpu_resources.render_pipelines;
590
591 let bind_group_layout_all_lines = ctx.gpu_resources.bind_group_layouts.get_or_create(
592 &ctx.device,
593 &BindGroupLayoutDesc {
594 label: "LineRenderer::bind_group_layout_all_lines".into(),
595 entries: vec![
596 wgpu::BindGroupLayoutEntry {
597 binding: 0,
598 visibility: wgpu::ShaderStages::VERTEX,
599 ty: wgpu::BindingType::Texture {
600 sample_type: wgpu::TextureSampleType::Float { filterable: false },
601 view_dimension: wgpu::TextureViewDimension::D2,
602 multisampled: false,
603 },
604 count: None,
605 },
606 wgpu::BindGroupLayoutEntry {
607 binding: 1,
608 visibility: wgpu::ShaderStages::VERTEX,
609 ty: wgpu::BindingType::Texture {
610 sample_type: wgpu::TextureSampleType::Uint,
611 view_dimension: wgpu::TextureViewDimension::D2,
612 multisampled: false,
613 },
614 count: None,
615 },
616 wgpu::BindGroupLayoutEntry {
617 binding: 2,
618 visibility: wgpu::ShaderStages::VERTEX,
619 ty: wgpu::BindingType::Texture {
620 sample_type: wgpu::TextureSampleType::Uint,
621 view_dimension: wgpu::TextureViewDimension::D2,
622 multisampled: false,
623 },
624 count: None,
625 },
626 wgpu::BindGroupLayoutEntry {
627 binding: 3,
628 visibility: wgpu::ShaderStages::VERTEX,
629 ty: wgpu::BindingType::Buffer {
630 ty: wgpu::BufferBindingType::Uniform,
631 has_dynamic_offset: false,
632 min_binding_size: NonZeroU64::new(std::mem::size_of::<
633 gpu_data::DrawDataUniformBuffer,
634 >() as _),
635 },
636 count: None,
637 },
638 ],
639 },
640 );
641
642 let bind_group_layout_batch = ctx.gpu_resources.bind_group_layouts.get_or_create(
643 &ctx.device,
644 &BindGroupLayoutDesc {
645 label: "LineRenderer::bind_group_layout_batch".into(),
646 entries: vec![wgpu::BindGroupLayoutEntry {
647 binding: 0,
648 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
649 ty: wgpu::BindingType::Buffer {
650 ty: wgpu::BufferBindingType::Uniform,
651 has_dynamic_offset: false,
652 min_binding_size: NonZeroU64::new(std::mem::size_of::<
653 gpu_data::BatchUniformBuffer,
654 >() as _),
655 },
656 count: None,
657 }],
658 },
659 );
660
661 let pipeline_layout = ctx.gpu_resources.pipeline_layouts.get_or_create(
662 ctx,
663 &PipelineLayoutDesc {
664 label: "LineRenderer::pipeline_layout".into(),
665 entries: vec![
666 ctx.global_bindings.layout,
667 bind_group_layout_all_lines,
668 bind_group_layout_batch,
669 ],
670 },
671 );
672
673 let shader_module = ctx
674 .gpu_resources
675 .shader_modules
676 .get_or_create(ctx, &include_shader_module!("../../shader/lines.wgsl"));
677
678 let render_pipeline_desc_color = RenderPipelineDesc {
679 label: "LineRenderer::render_pipeline_color".into(),
680 pipeline_layout,
681 vertex_entrypoint: "vs_main".into(),
682 vertex_handle: shader_module,
683 fragment_entrypoint: "fs_main".into(),
684 fragment_handle: shader_module,
685 vertex_buffers: smallvec![],
686 render_targets: smallvec![Some(ViewBuilder::MAIN_TARGET_ALPHA_TO_COVERAGE_COLOR_STATE)],
687 primitive: wgpu::PrimitiveState {
688 topology: wgpu::PrimitiveTopology::TriangleList,
689 ..Default::default()
690 },
691 depth_stencil: Some(ViewBuilder::MAIN_TARGET_DEFAULT_DEPTH_STATE),
692 multisample: ViewBuilder::main_target_default_msaa_state(ctx.render_config(), true),
693 };
694 let render_pipeline_color =
695 render_pipelines.get_or_create(ctx, &render_pipeline_desc_color);
696 let render_pipeline_picking_layer = render_pipelines.get_or_create(
697 ctx,
698 &RenderPipelineDesc {
699 label: "LineRenderer::render_pipeline_picking_layer".into(),
700 fragment_entrypoint: "fs_main_picking_layer".into(),
701 render_targets: smallvec![Some(PickingLayerProcessor::PICKING_LAYER_FORMAT.into())],
702 depth_stencil: PickingLayerProcessor::PICKING_LAYER_DEPTH_STATE,
703 multisample: PickingLayerProcessor::PICKING_LAYER_MSAA_STATE,
704 ..render_pipeline_desc_color.clone()
705 },
706 );
707 let render_pipeline_outline_mask = render_pipelines.get_or_create(
708 ctx,
709 &RenderPipelineDesc {
710 label: "LineRenderer::render_pipeline_outline_mask".into(),
711 pipeline_layout,
712 vertex_entrypoint: "vs_main".into(),
713 vertex_handle: shader_module,
714 fragment_entrypoint: "fs_main_outline_mask".into(),
715 fragment_handle: shader_module,
716 vertex_buffers: smallvec![],
717 render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())],
718 primitive: wgpu::PrimitiveState {
719 topology: wgpu::PrimitiveTopology::TriangleList,
720 ..Default::default()
721 },
722 depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE,
723 multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier),
725 },
726 );
727
728 Self {
729 render_pipeline_color,
730 render_pipeline_picking_layer,
731 render_pipeline_outline_mask,
732 bind_group_layout_all_lines,
733 bind_group_layout_batch,
734 }
735 }
736
737 fn draw(
738 &self,
739 render_pipelines: &GpuRenderPipelinePoolAccessor<'_>,
740 phase: DrawPhase,
741 pass: &mut wgpu::RenderPass<'_>,
742 draw_instructions: &[DrawInstruction<'_, Self::RendererDrawData>],
743 ) -> Result<(), DrawError> {
744 let pipeline_handle = match phase {
745 DrawPhase::OutlineMask => self.render_pipeline_outline_mask,
746 DrawPhase::Opaque => self.render_pipeline_color,
747 DrawPhase::PickingLayer => self.render_pipeline_picking_layer,
748 _ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
749 };
750
751 let pipeline = render_pipelines.get(pipeline_handle)?;
752 pass.set_pipeline(pipeline);
753
754 for DrawInstruction {
755 draw_data,
756 drawables,
757 } in draw_instructions
758 {
759 let bind_group_draw_data = match phase {
760 DrawPhase::OutlineMask => &draw_data.bind_group_all_lines_outline_mask,
761 DrawPhase::Opaque | DrawPhase::PickingLayer => &draw_data.bind_group_all_lines,
762 _ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
763 };
764 let Some(bind_group_draw_data) = bind_group_draw_data else {
765 debug_assert!(
766 false,
767 "Line data bind group for draw phase {phase:?} was not set despite being submitted for drawing."
768 );
769 continue;
770 };
771 pass.set_bind_group(1, bind_group_draw_data, &[]);
772
773 for drawable in *drawables {
774 let batch = &draw_data.batches[drawable.draw_data_payload as usize];
775 pass.set_bind_group(2, &batch.bind_group, &[]);
776 pass.draw(batch.vertex_range.clone(), 0..1);
777 }
778 }
779
780 Ok(())
781 }
782}
783
784#[cfg(test)]
785mod tests {
786 use crate::{Rgba, view_builder::TargetConfiguration};
787
788 use super::*;
789
790 #[test]
792 fn empty_strips() {
793 re_log::setup_logging();
794 re_log::PanicOnWarnScope::new();
795
796 RenderContext::new_test().execute_test_frame(|ctx| {
797 let mut view = ViewBuilder::new(ctx, TargetConfiguration::default()).unwrap();
798
799 let empty = LineDrawableBuilder::new(ctx);
800 view.queue_draw(ctx, empty.into_draw_data().unwrap());
801
802 let mut empty_batch = LineDrawableBuilder::new(ctx);
806 empty_batch
807 .batch("empty batch")
808 .add_strip(std::iter::empty());
809 view.queue_draw(ctx, empty_batch.into_draw_data().unwrap());
810
811 let mut empty_batch_between_non_empty = LineDrawableBuilder::new(ctx);
812 empty_batch_between_non_empty
813 .batch("non-empty batch")
814 .add_strip([glam::Vec3::ZERO, glam::Vec3::ZERO].into_iter());
815 empty_batch_between_non_empty
816 .batch("empty batch")
817 .add_strip(std::iter::empty());
818 empty_batch_between_non_empty
819 .batch("non-empty batch")
820 .add_strip([glam::Vec3::ZERO, glam::Vec3::ZERO].into_iter());
821 view.queue_draw(ctx, empty_batch_between_non_empty.into_draw_data().unwrap());
822
823 [view.draw(ctx, Rgba::BLACK).unwrap()]
824 });
825 }
826}