Skip to main content

re_renderer/renderer/
point_cloud.rs

1//! Point renderer for efficient rendering of point clouds.
2//!
3//!
4//! How it works:
5//! =================
6//! Points are rendered as quads and stenciled out by a fragment shader.
7//! Quad spanning happens in the vertex shader, uploaded are only the data for the actual points (no vertex buffer!).
8//!
9//! Like with the `super::lines::LineRenderer`, we're rendering as all quads in a single triangle list draw call.
10//! (Rationale for this can be found in the [`crate::renderer::lines`]'s documentation)
11//!
12//! For WebGL compatibility, data is uploaded as textures. Color is stored in a separate srgb texture, meaning
13//! that srgb->linear conversion happens on texture load.
14//!
15
16use std::num::NonZeroU64;
17use std::ops::Range;
18
19use bitflags::bitflags;
20use enumset::{EnumSet, enum_set};
21use itertools::Itertools as _;
22use smallvec::smallvec;
23
24use super::{DrawData, DrawError, RenderContext, Renderer};
25use crate::allocator::create_and_fill_uniform_buffer_batch;
26use crate::draw_phases::{
27    DrawPhase, OutlineMaskProcessor, PickingLayerObjectId, PickingLayerProcessor,
28};
29use crate::renderer::{DrawDataDrawable, DrawInstruction, DrawableCollectionViewInfo};
30use crate::view_builder::ViewBuilder;
31use crate::wgpu_resources::{
32    BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle,
33    GpuRenderPipelineHandle, GpuRenderPipelinePoolAccessor, PipelineLayoutDesc, RenderPipelineDesc,
34};
35use crate::{
36    DepthOffset, DrawableCollector, Label, OutlineMaskPreference, PointCloudBuilder,
37    include_shader_module,
38};
39
40bitflags! {
41    /// Property flags for a point batch
42    ///
43    /// Needs to be kept in sync with `point_cloud.wgsl`
44    #[repr(C)]
45    #[derive(Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
46    pub struct PointCloudBatchFlags : u32 {
47        /// If true, we shade all points in the batch like spheres.
48        const FLAG_ENABLE_SHADING = 0b0001;
49
50        /// If true, draw 2D camera facing circles instead of spheres.
51        const FLAG_DRAW_AS_CIRCLES = 0b0010;
52    }
53}
54
55pub mod gpu_data {
56    use crate::draw_phases::PickingLayerObjectId;
57    use crate::{Size, wgpu_buffer_types};
58
59    // Don't use `wgsl_buffer_types` since this data doesn't go into a buffer, so alignment rules don't apply like on buffers..
60
61    /// Position and radius.
62    #[repr(C, packed)]
63    #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
64    pub struct PositionRadius {
65        pub pos: glam::Vec3,
66
67        /// Radius of the point in world space
68        pub radius: Size, // Might use a f16 here to free memory for more data!
69    }
70    static_assertions::assert_eq_size!(PositionRadius, glam::Vec4);
71
72    impl PositionRadius {
73        /// If there are fewer radii than positions,
74        /// the last radius will be repeated for the remaining positions
75        /// (clamp to edge).
76        pub fn from_many(positions: &[glam::Vec3], radii: &[Size]) -> Vec<Self> {
77            use itertools::izip;
78
79            re_tracing::profile_function_if!(10_0000 < positions.len());
80            if positions.len() == radii.len() {
81                // Optimize common-case with simpler iterators.
82                re_tracing::profile_scope_if!(10_000 < positions.len(), "zipped");
83                izip!(positions.iter().copied(), radii.iter().copied())
84                    .map(|(pos, radius)| Self { pos, radius })
85                    .collect()
86            } else {
87                re_tracing::profile_scope_if!(10_000 < positions.len(), "extended-radius");
88                izip!(
89                    positions.iter().copied(),
90                    std::iter::chain(
91                        radii.iter().copied(),
92                        std::iter::repeat(*radii.last().unwrap_or(&Size::ONE_UI_POINT))
93                    )
94                )
95                .map(|(pos, radius)| Self { pos, radius })
96                .collect()
97            }
98        }
99    }
100
101    /// Uniform buffer that changes once per draw data rendering.
102    #[repr(C)]
103    #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
104    pub struct DrawDataUniformBuffer {
105        pub radius_boost_in_ui_points: wgpu_buffer_types::F32RowPadded,
106        pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 1],
107    }
108
109    /// Uniform buffer that changes for every batch of points.
110    #[repr(C)]
111    #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
112    pub struct BatchUniformBuffer {
113        pub world_from_obj: wgpu_buffer_types::Mat4,
114
115        pub flags: u32, // PointCloudBatchFlags
116        pub depth_offset: f32,
117        pub _row_padding: [f32; 2],
118
119        pub outline_mask_ids: wgpu_buffer_types::UVec2,
120        pub picking_object_id: PickingLayerObjectId,
121
122        pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 6],
123    }
124}
125
126/// Internal, ready to draw representation of [`PointCloudBatchInfo`]
127#[derive(Clone)]
128struct PointCloudBatch {
129    bind_group: GpuBindGroup,
130    vertex_range: Range<u32>,
131    active_phases: EnumSet<DrawPhase>,
132}
133
134/// A point cloud drawing operation.
135/// Expected to be recreated every frame.
136#[derive(Clone)]
137pub struct PointCloudDrawData {
138    bind_group_all_points: Option<GpuBindGroup>,
139    bind_group_all_points_outline_mask: Option<GpuBindGroup>,
140    batches: Vec<PointCloudBatch>,
141}
142
143impl DrawData for PointCloudDrawData {
144    type Renderer = PointCloudRenderer;
145
146    fn collect_drawables(
147        &self,
148        _view_info: &DrawableCollectionViewInfo,
149        collector: &mut DrawableCollector<'_>,
150    ) {
151        // TODO(#1611): transparency, split drawables for some semblance of transparency ordering.
152        // TODO(#1025, #4787): Better handling of 2D objects, use per-2D layer sorting instead of depth offsets.
153
154        for (batch_idx, batch) in self.batches.iter().enumerate() {
155            collector.add_drawable(
156                batch.active_phases,
157                DrawDataDrawable {
158                    // TODO(andreas): Don't have distance information yet. For now just always draw points last since they're quite expensive.
159                    distance_sort_key: f32::MAX,
160                    secondary_sort_key: 0.0,
161                    draw_data_payload: batch_idx as _,
162                },
163            );
164        }
165    }
166}
167
168/// Data that is valid for a batch of point cloud points.
169pub struct PointCloudBatchInfo {
170    pub label: Label,
171
172    /// Transformation applies to point positions
173    ///
174    /// TODO(andreas): We don't apply scaling to the radius yet. Need to pass a scaling factor like this in
175    /// `let scale = Mat3::from(world_from_obj).determinant().abs().cbrt()`
176    pub world_from_obj: glam::Affine3A,
177
178    /// Additional properties of this point cloud batch.
179    pub flags: PointCloudBatchFlags,
180
181    /// Number of points covered by this batch.
182    ///
183    /// The batch will start with the next point after the one the previous batch ended with.
184    pub point_count: u32,
185
186    /// Optional outline mask setting for the entire batch.
187    pub overall_outline_mask_ids: OutlineMaskPreference,
188
189    /// Defines an outline mask for an individual vertex ranges.
190    ///
191    /// Vertex ranges are relative within the current batch.
192    ///
193    /// Having many of these individual outline masks can be slow as they require each their own uniform buffer & draw call.
194    /// This feature is meant for a limited number of "extra selections"
195    /// If an overall mask is defined as well, the per-point-range masks is overwriting the overall mask.
196    pub additional_outline_mask_ids_vertex_ranges: Vec<(Range<u32>, OutlineMaskPreference)>,
197
198    /// Picking object id that applies for the entire batch.
199    pub picking_object_id: PickingLayerObjectId,
200
201    /// Depth offset applied after projection.
202    pub depth_offset: DepthOffset,
203}
204
205impl Default for PointCloudBatchInfo {
206    #[inline]
207    fn default() -> Self {
208        Self {
209            label: Label::default(),
210            world_from_obj: glam::Affine3A::IDENTITY,
211            flags: PointCloudBatchFlags::FLAG_ENABLE_SHADING,
212            point_count: 0,
213            overall_outline_mask_ids: OutlineMaskPreference::NONE,
214            additional_outline_mask_ids_vertex_ranges: Vec::new(),
215            picking_object_id: Default::default(),
216            depth_offset: 0,
217        }
218    }
219}
220
221#[derive(thiserror::Error, Debug, PartialEq, Eq)]
222pub enum PointCloudDrawDataError {
223    #[error("Failed to transfer data to the GPU: {0}")]
224    FailedTransferringDataToGpu(#[from] crate::allocator::CpuWriteGpuReadError),
225}
226
227impl PointCloudDrawData {
228    /// Transforms and uploads point cloud data to be consumed by gpu.
229    ///
230    /// Try to bundle all points into a single draw data instance whenever possible.
231    /// Number of vertices and colors has to be equal.
232    ///
233    /// If no batches are passed, all points are assumed to be in a single batch with identity transform.
234    pub fn new(builder: PointCloudBuilder<'_>) -> Result<Self, PointCloudDrawDataError> {
235        re_tracing::profile_function!();
236
237        let PointCloudBuilder {
238            ctx,
239            position_radius_buffer: vertices_buffer,
240            color_buffer,
241            picking_instance_ids_buffer,
242            batches,
243            radius_boost_in_ui_points_for_outlines,
244        } = builder;
245
246        let point_renderer = ctx.renderer::<PointCloudRenderer>();
247        let batches = batches.as_slice();
248
249        if vertices_buffer.is_empty() {
250            return Ok(Self {
251                bind_group_all_points: None,
252                bind_group_all_points_outline_mask: None,
253                batches: Vec::new(),
254            });
255        }
256
257        let num_vertices = vertices_buffer.len();
258
259        let fallback_batches = [PointCloudBatchInfo {
260            label: "fallback_batches".into(),
261            world_from_obj: glam::Affine3A::IDENTITY,
262            flags: PointCloudBatchFlags::empty(),
263            point_count: num_vertices as _,
264            overall_outline_mask_ids: OutlineMaskPreference::NONE,
265            additional_outline_mask_ids_vertex_ranges: Vec::new(),
266            picking_object_id: Default::default(),
267            depth_offset: 0,
268        }];
269        let batches = if batches.is_empty() {
270            &fallback_batches
271        } else {
272            batches
273        };
274
275        let position_data_texture = vertices_buffer.finish(
276            wgpu::TextureFormat::Rgba32Float,
277            "PointCloudDrawData::position_data_texture",
278        )?;
279        let color_texture = color_buffer.finish(
280            wgpu::TextureFormat::Rgba8UnormSrgb,
281            "PointCloudDrawData::color_texture",
282        )?;
283        let picking_instance_id_texture = picking_instance_ids_buffer.finish(
284            wgpu::TextureFormat::Rg32Uint,
285            "PointCloudDrawData::picking_instance_id_texture",
286        )?;
287
288        let draw_data_uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
289            ctx,
290            "PointCloudDrawData::DrawDataUniformBuffer".into(),
291            [
292                gpu_data::DrawDataUniformBuffer {
293                    radius_boost_in_ui_points: 0.0.into(),
294                    end_padding: Default::default(),
295                },
296                gpu_data::DrawDataUniformBuffer {
297                    radius_boost_in_ui_points: radius_boost_in_ui_points_for_outlines.into(),
298                    end_padding: Default::default(),
299                },
300            ]
301            .into_iter(),
302        );
303        let (draw_data_uniform_buffer_bindings_normal, draw_data_uniform_buffer_bindings_outline) =
304            draw_data_uniform_buffer_bindings
305                .into_iter()
306                .collect_tuple()
307                .unwrap();
308
309        let mk_bind_group = |label, draw_data_uniform_buffer_binding| {
310            ctx.gpu_resources.bind_groups.alloc(
311                &ctx.device,
312                &ctx.gpu_resources,
313                &BindGroupDesc {
314                    label,
315                    entries: smallvec![
316                        BindGroupEntry::DefaultTextureView(position_data_texture.handle),
317                        BindGroupEntry::DefaultTextureView(color_texture.handle),
318                        BindGroupEntry::DefaultTextureView(picking_instance_id_texture.handle),
319                        draw_data_uniform_buffer_binding,
320                    ],
321                    layout: point_renderer.bind_group_layout_all_points,
322                },
323            )
324        };
325
326        let bind_group_all_points = mk_bind_group(
327            "PointCloudDrawData::bind_group_all_points".into(),
328            draw_data_uniform_buffer_bindings_normal,
329        );
330        let bind_group_all_points_outline_mask = mk_bind_group(
331            "PointCloudDrawData::bind_group_all_points_outline_mask".into(),
332            draw_data_uniform_buffer_bindings_outline,
333        );
334
335        // Process batches
336        let mut batches_internal = Vec::with_capacity(batches.len());
337        {
338            let uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
339                ctx,
340                "point batch uniform buffers".into(),
341                batches
342                    .iter()
343                    .map(|batch_info| gpu_data::BatchUniformBuffer {
344                        world_from_obj: batch_info.world_from_obj.into(),
345                        flags: batch_info.flags.bits(),
346                        outline_mask_ids: batch_info
347                            .overall_outline_mask_ids
348                            .0
349                            .unwrap_or_default()
350                            .into(),
351                        picking_object_id: batch_info.picking_object_id,
352                        depth_offset: batch_info.depth_offset as f32,
353
354                        _row_padding: [0.0, 0.0],
355                        end_padding: Default::default(),
356                    }),
357            );
358
359            // Generate additional "micro batches" for each point range that has a unique outline setting.
360            // This is fairly costly if there's many, but easy and low-overhead if there's only few, which is usually what we expect!
361            let mut uniform_buffer_bindings_mask_only_batches =
362                create_and_fill_uniform_buffer_batch(
363                    ctx,
364                    "lines batch uniform buffers - mask only".into(),
365                    batches
366                        .iter()
367                        .flat_map(|batch_info| {
368                            batch_info
369                                .additional_outline_mask_ids_vertex_ranges
370                                .iter()
371                                .map(|(_, mask)| gpu_data::BatchUniformBuffer {
372                                    world_from_obj: batch_info.world_from_obj.into(),
373                                    flags: batch_info.flags.bits(),
374                                    outline_mask_ids: mask.0.unwrap_or_default().into(),
375                                    picking_object_id: batch_info.picking_object_id,
376                                    depth_offset: batch_info.depth_offset as f32,
377
378                                    _row_padding: [0.0, 0.0],
379                                    end_padding: Default::default(),
380                                })
381                        })
382                        .collect::<Vec<_>>()
383                        .into_iter(),
384                )
385                .into_iter();
386
387            let mut start_point_for_next_batch = 0;
388            for (batch_info, uniform_buffer_binding) in
389                std::iter::zip(batches, uniform_buffer_bindings)
390            {
391                let point_vertex_range_end = start_point_for_next_batch + batch_info.point_count;
392                let mut active_phases = enum_set![DrawPhase::Opaque | DrawPhase::PickingLayer];
393                // Does the entire batch participate in the outline mask phase?
394                if batch_info.overall_outline_mask_ids.is_some() {
395                    active_phases.insert(DrawPhase::OutlineMask);
396                }
397
398                batches_internal.push(point_renderer.create_point_cloud_batch(
399                    ctx,
400                    batch_info.label.clone(),
401                    uniform_buffer_binding,
402                    start_point_for_next_batch..point_vertex_range_end,
403                    active_phases,
404                ));
405
406                for (range, _) in &batch_info.additional_outline_mask_ids_vertex_ranges {
407                    let range = (range.start + start_point_for_next_batch)
408                        ..(range.end + start_point_for_next_batch);
409                    batches_internal.push(point_renderer.create_point_cloud_batch(
410                        ctx,
411                        format!("{:?} strip-only {:?}", batch_info.label, range).into(),
412                        uniform_buffer_bindings_mask_only_batches.next().unwrap(),
413                        range.clone(),
414                        enum_set![DrawPhase::OutlineMask],
415                    ));
416                }
417
418                start_point_for_next_batch = point_vertex_range_end;
419
420                // Should happen only if the number of vertices was clamped.
421                if start_point_for_next_batch >= num_vertices as u32 {
422                    break;
423                }
424            }
425        }
426
427        Ok(Self {
428            bind_group_all_points: Some(bind_group_all_points),
429            bind_group_all_points_outline_mask: Some(bind_group_all_points_outline_mask),
430            batches: batches_internal,
431        })
432    }
433}
434
435pub struct PointCloudRenderer {
436    render_pipeline_color: GpuRenderPipelineHandle,
437    render_pipeline_picking_layer: GpuRenderPipelineHandle,
438    render_pipeline_outline_mask: GpuRenderPipelineHandle,
439    bind_group_layout_all_points: GpuBindGroupLayoutHandle,
440    bind_group_layout_batch: GpuBindGroupLayoutHandle,
441}
442
443impl PointCloudRenderer {
444    fn create_point_cloud_batch(
445        &self,
446        ctx: &RenderContext,
447        label: Label,
448        uniform_buffer_binding: BindGroupEntry,
449        vertex_range: Range<u32>,
450        active_phases: EnumSet<DrawPhase>,
451    ) -> PointCloudBatch {
452        // TODO(andreas): There should be only a single bindgroup with dynamic indices for all batches.
453        //                  (each batch would then know which dynamic indices to use in the bindgroup)
454        let bind_group = ctx.gpu_resources.bind_groups.alloc(
455            &ctx.device,
456            &ctx.gpu_resources,
457            &BindGroupDesc {
458                label,
459                entries: smallvec![uniform_buffer_binding],
460                layout: self.bind_group_layout_batch,
461            },
462        );
463
464        PointCloudBatch {
465            bind_group,
466            vertex_range: (vertex_range.start * 6)..(vertex_range.end * 6),
467            active_phases,
468        }
469    }
470}
471
472impl Renderer for PointCloudRenderer {
473    type RendererDrawData = PointCloudDrawData;
474
475    fn create_renderer(ctx: &RenderContext) -> Self {
476        re_tracing::profile_function!();
477
478        let render_pipelines = &ctx.gpu_resources.render_pipelines;
479
480        let bind_group_layout_all_points = ctx.gpu_resources.bind_group_layouts.get_or_create(
481            &ctx.device,
482            &BindGroupLayoutDesc {
483                label: "PointCloudRenderer::bind_group_layout_all_points".into(),
484                entries: vec![
485                    wgpu::BindGroupLayoutEntry {
486                        binding: 0,
487                        visibility: wgpu::ShaderStages::VERTEX,
488                        ty: wgpu::BindingType::Texture {
489                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
490                            view_dimension: wgpu::TextureViewDimension::D2,
491                            multisampled: false,
492                        },
493                        count: None,
494                    },
495                    wgpu::BindGroupLayoutEntry {
496                        binding: 1,
497                        visibility: wgpu::ShaderStages::VERTEX,
498                        ty: wgpu::BindingType::Texture {
499                            sample_type: wgpu::TextureSampleType::Float { filterable: false },
500                            view_dimension: wgpu::TextureViewDimension::D2,
501                            multisampled: false,
502                        },
503                        count: None,
504                    },
505                    wgpu::BindGroupLayoutEntry {
506                        binding: 2,
507                        visibility: wgpu::ShaderStages::VERTEX,
508                        ty: wgpu::BindingType::Texture {
509                            sample_type: wgpu::TextureSampleType::Uint,
510                            view_dimension: wgpu::TextureViewDimension::D2,
511                            multisampled: false,
512                        },
513                        count: None,
514                    },
515                    wgpu::BindGroupLayoutEntry {
516                        binding: 3,
517                        visibility: wgpu::ShaderStages::VERTEX,
518                        ty: wgpu::BindingType::Buffer {
519                            ty: wgpu::BufferBindingType::Uniform,
520                            has_dynamic_offset: false,
521                            min_binding_size: NonZeroU64::new(std::mem::size_of::<
522                                gpu_data::DrawDataUniformBuffer,
523                            >() as _),
524                        },
525                        count: None,
526                    },
527                ],
528            },
529        );
530
531        let bind_group_layout_batch = ctx.gpu_resources.bind_group_layouts.get_or_create(
532            &ctx.device,
533            &BindGroupLayoutDesc {
534                label: "PointCloudRenderer::bind_group_layout_batch".into(),
535                entries: vec![wgpu::BindGroupLayoutEntry {
536                    binding: 0,
537                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
538                    ty: wgpu::BindingType::Buffer {
539                        ty: wgpu::BufferBindingType::Uniform,
540                        has_dynamic_offset: false,
541                        min_binding_size: NonZeroU64::new(std::mem::size_of::<
542                            gpu_data::BatchUniformBuffer,
543                        >() as _),
544                    },
545                    count: None,
546                }],
547            },
548        );
549
550        let pipeline_layout = ctx.gpu_resources.pipeline_layouts.get_or_create(
551            ctx,
552            &PipelineLayoutDesc {
553                label: "PointCloudRenderer::pipeline_layout".into(),
554                entries: vec![
555                    ctx.global_bindings.layout,
556                    bind_group_layout_all_points,
557                    bind_group_layout_batch,
558                ],
559            },
560        );
561
562        let shader_module_desc = include_shader_module!("../../shader/point_cloud.wgsl");
563        let shader_module = ctx
564            .gpu_resources
565            .shader_modules
566            .get_or_create(ctx, &shader_module_desc);
567
568        let render_pipeline_desc_color = RenderPipelineDesc {
569            label: "PointCloudRenderer::render_pipeline_color".into(),
570            pipeline_layout,
571            vertex_entrypoint: "vs_main".into(),
572            vertex_handle: shader_module,
573            fragment_entrypoint: "fs_main".into(),
574            fragment_handle: shader_module,
575            vertex_buffers: smallvec![],
576            render_targets: smallvec![Some(ViewBuilder::MAIN_TARGET_ALPHA_TO_COVERAGE_COLOR_STATE)],
577            primitive: wgpu::PrimitiveState {
578                topology: wgpu::PrimitiveTopology::TriangleList,
579                ..Default::default()
580            },
581            depth_stencil: Some(ViewBuilder::MAIN_TARGET_DEFAULT_DEPTH_STATE),
582            // We discard pixels to do the round cutout, therefore we need to calculate our own sampling mask.
583            multisample: ViewBuilder::main_target_default_msaa_state(ctx.render_config(), true),
584        };
585        let render_pipeline_color =
586            render_pipelines.get_or_create(ctx, &render_pipeline_desc_color);
587        let render_pipeline_picking_layer = render_pipelines.get_or_create(
588            ctx,
589            &RenderPipelineDesc {
590                label: "PointCloudRenderer::render_pipeline_picking_layer".into(),
591                fragment_entrypoint: "fs_main_picking_layer".into(),
592                render_targets: smallvec![Some(PickingLayerProcessor::PICKING_LAYER_FORMAT.into())],
593                depth_stencil: PickingLayerProcessor::PICKING_LAYER_DEPTH_STATE,
594                multisample: PickingLayerProcessor::PICKING_LAYER_MSAA_STATE,
595                ..render_pipeline_desc_color.clone()
596            },
597        );
598        let render_pipeline_outline_mask = render_pipelines.get_or_create(
599            ctx,
600            &RenderPipelineDesc {
601                label: "PointCloudRenderer::render_pipeline_outline_mask".into(),
602                fragment_entrypoint: "fs_main_outline_mask".into(),
603                render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())],
604                depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE,
605                // Alpha to coverage doesn't work with the mask integer target.
606                multisample: OutlineMaskProcessor::mask_default_msaa_state(ctx.device_caps().tier),
607                ..render_pipeline_desc_color
608            },
609        );
610
611        Self {
612            render_pipeline_color,
613            render_pipeline_picking_layer,
614            render_pipeline_outline_mask,
615            bind_group_layout_all_points,
616            bind_group_layout_batch,
617        }
618    }
619
620    fn draw(
621        &self,
622        render_pipelines: &GpuRenderPipelinePoolAccessor<'_>,
623        phase: DrawPhase,
624        pass: &mut wgpu::RenderPass<'_>,
625        draw_instructions: &[DrawInstruction<'_, Self::RendererDrawData>],
626    ) -> Result<(), DrawError> {
627        let pipeline_handle = match phase {
628            DrawPhase::OutlineMask => self.render_pipeline_outline_mask,
629            DrawPhase::Opaque => self.render_pipeline_color,
630            DrawPhase::PickingLayer => self.render_pipeline_picking_layer,
631            _ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
632        };
633        let pipeline = render_pipelines.get(pipeline_handle)?;
634
635        pass.set_pipeline(pipeline);
636
637        for DrawInstruction {
638            draw_data,
639            drawables,
640        } in draw_instructions
641        {
642            let bind_group_all_points = match phase {
643                DrawPhase::OutlineMask => &draw_data.bind_group_all_points_outline_mask,
644                DrawPhase::Opaque | DrawPhase::PickingLayer => &draw_data.bind_group_all_points,
645                _ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
646            };
647            let Some(bind_group_all_points) = bind_group_all_points else {
648                re_log::debug_panic!(
649                    "Point data bind group for draw phase {phase:?} was not set despite being submitted for drawing."
650                );
651                continue;
652            };
653            pass.set_bind_group(1, bind_group_all_points, &[]);
654
655            for drawable in *drawables {
656                let batch = &draw_data.batches[drawable.draw_data_payload as usize];
657                pass.set_bind_group(2, &batch.bind_group, &[]);
658                pass.draw(batch.vertex_range.clone(), 0..1);
659            }
660        }
661
662        Ok(())
663    }
664}