1use 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 #[repr(C)]
45 #[derive(Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
46 pub struct PointCloudBatchFlags : u32 {
47 const FLAG_ENABLE_SHADING = 0b0001;
49
50 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 #[repr(C, packed)]
63 #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
64 pub struct PositionRadius {
65 pub pos: glam::Vec3,
66
67 pub radius: Size, }
70 static_assertions::assert_eq_size!(PositionRadius, glam::Vec4);
71
72 impl PositionRadius {
73 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 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 #[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 #[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, 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#[derive(Clone)]
128struct PointCloudBatch {
129 bind_group: GpuBindGroup,
130 vertex_range: Range<u32>,
131 active_phases: EnumSet<DrawPhase>,
132}
133
134#[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 for (batch_idx, batch) in self.batches.iter().enumerate() {
155 collector.add_drawable(
156 batch.active_phases,
157 DrawDataDrawable {
158 distance_sort_key: f32::MAX,
160 secondary_sort_key: 0.0,
161 draw_data_payload: batch_idx as _,
162 },
163 );
164 }
165 }
166}
167
168pub struct PointCloudBatchInfo {
170 pub label: Label,
171
172 pub world_from_obj: glam::Affine3A,
177
178 pub flags: PointCloudBatchFlags,
180
181 pub point_count: u32,
185
186 pub overall_outline_mask_ids: OutlineMaskPreference,
188
189 pub additional_outline_mask_ids_vertex_ranges: Vec<(Range<u32>, OutlineMaskPreference)>,
197
198 pub picking_object_id: PickingLayerObjectId,
200
201 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 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 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 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 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 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 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 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 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}