use std::{num::NonZeroU64, ops::Range};
use crate::{
allocator::create_and_fill_uniform_buffer_batch,
draw_phases::{DrawPhase, OutlineMaskProcessor, PickingLayerObjectId, PickingLayerProcessor},
include_shader_module,
wgpu_resources::GpuRenderPipelinePoolAccessor,
DebugLabel, DepthOffset, OutlineMaskPreference, PointCloudBuilder,
};
use bitflags::bitflags;
use enumset::{enum_set, EnumSet};
use itertools::Itertools as _;
use smallvec::smallvec;
use crate::{
view_builder::ViewBuilder,
wgpu_resources::{
BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle,
GpuRenderPipelineHandle, PipelineLayoutDesc, RenderPipelineDesc,
},
};
use super::{DrawData, DrawError, RenderContext, Renderer};
bitflags! {
#[repr(C)]
#[derive(Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PointCloudBatchFlags : u32 {
const FLAG_ENABLE_SHADING = 0b0001;
const FLAG_DRAW_AS_CIRCLES = 0b0010;
}
}
pub mod gpu_data {
use crate::{draw_phases::PickingLayerObjectId, wgpu_buffer_types, Size};
#[repr(C, packed)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct PositionRadius {
pub pos: glam::Vec3,
pub radius: Size, }
static_assertions::assert_eq_size!(PositionRadius, glam::Vec4);
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct DrawDataUniformBuffer {
pub radius_boost_in_ui_points: wgpu_buffer_types::F32RowPadded,
pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 1],
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct BatchUniformBuffer {
pub world_from_obj: wgpu_buffer_types::Mat4,
pub flags: u32, pub depth_offset: f32,
pub _row_padding: [f32; 2],
pub outline_mask_ids: wgpu_buffer_types::UVec2,
pub picking_object_id: PickingLayerObjectId,
pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 6],
}
}
#[derive(Clone)]
struct PointCloudBatch {
bind_group: GpuBindGroup,
vertex_range: Range<u32>,
active_phases: EnumSet<DrawPhase>,
}
#[derive(Clone)]
pub struct PointCloudDrawData {
bind_group_all_points: Option<GpuBindGroup>,
bind_group_all_points_outline_mask: Option<GpuBindGroup>,
batches: Vec<PointCloudBatch>,
}
impl DrawData for PointCloudDrawData {
type Renderer = PointCloudRenderer;
}
pub struct PointCloudBatchInfo {
pub label: DebugLabel,
pub world_from_obj: glam::Affine3A,
pub flags: PointCloudBatchFlags,
pub point_count: u32,
pub overall_outline_mask_ids: OutlineMaskPreference,
pub additional_outline_mask_ids_vertex_ranges: Vec<(Range<u32>, OutlineMaskPreference)>,
pub picking_object_id: PickingLayerObjectId,
pub depth_offset: DepthOffset,
}
impl Default for PointCloudBatchInfo {
#[inline]
fn default() -> Self {
Self {
label: DebugLabel::default(),
world_from_obj: glam::Affine3A::IDENTITY,
flags: PointCloudBatchFlags::FLAG_ENABLE_SHADING,
point_count: 0,
overall_outline_mask_ids: OutlineMaskPreference::NONE,
additional_outline_mask_ids_vertex_ranges: Vec::new(),
picking_object_id: Default::default(),
depth_offset: 0,
}
}
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum PointCloudDrawDataError {
#[error("Failed to transfer data to the GPU: {0}")]
FailedTransferringDataToGpu(#[from] crate::allocator::CpuWriteGpuReadError),
}
impl PointCloudDrawData {
pub fn new(builder: PointCloudBuilder<'_>) -> Result<Self, PointCloudDrawDataError> {
re_tracing::profile_function!();
let PointCloudBuilder {
ctx,
position_radius_buffer: vertices_buffer,
color_buffer,
picking_instance_ids_buffer,
batches,
radius_boost_in_ui_points_for_outlines,
} = builder;
let point_renderer = ctx.renderer::<PointCloudRenderer>();
let batches = batches.as_slice();
if vertices_buffer.is_empty() {
return Ok(Self {
bind_group_all_points: None,
bind_group_all_points_outline_mask: None,
batches: Vec::new(),
});
}
let num_vertices = vertices_buffer.len();
let fallback_batches = [PointCloudBatchInfo {
label: "fallback_batches".into(),
world_from_obj: glam::Affine3A::IDENTITY,
flags: PointCloudBatchFlags::empty(),
point_count: num_vertices as _,
overall_outline_mask_ids: OutlineMaskPreference::NONE,
additional_outline_mask_ids_vertex_ranges: Vec::new(),
picking_object_id: Default::default(),
depth_offset: 0,
}];
let batches = if batches.is_empty() {
&fallback_batches
} else {
batches
};
let position_data_texture = vertices_buffer.finish(
wgpu::TextureFormat::Rgba32Float,
"PointCloudDrawData::position_data_texture",
)?;
let color_texture = color_buffer.finish(
wgpu::TextureFormat::Rgba8UnormSrgb,
"PointCloudDrawData::color_texture",
)?;
let picking_instance_id_texture = picking_instance_ids_buffer.finish(
wgpu::TextureFormat::Rg32Uint,
"PointCloudDrawData::picking_instance_id_texture",
)?;
let draw_data_uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
ctx,
"PointCloudDrawData::DrawDataUniformBuffer".into(),
[
gpu_data::DrawDataUniformBuffer {
radius_boost_in_ui_points: 0.0.into(),
end_padding: Default::default(),
},
gpu_data::DrawDataUniformBuffer {
radius_boost_in_ui_points: radius_boost_in_ui_points_for_outlines.into(),
end_padding: Default::default(),
},
]
.into_iter(),
);
let (draw_data_uniform_buffer_bindings_normal, draw_data_uniform_buffer_bindings_outline) =
draw_data_uniform_buffer_bindings
.into_iter()
.collect_tuple()
.unwrap();
let mk_bind_group = |label, draw_data_uniform_buffer_binding| {
ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label,
entries: smallvec![
BindGroupEntry::DefaultTextureView(position_data_texture.handle),
BindGroupEntry::DefaultTextureView(color_texture.handle),
BindGroupEntry::DefaultTextureView(picking_instance_id_texture.handle),
draw_data_uniform_buffer_binding,
],
layout: point_renderer.bind_group_layout_all_points,
},
)
};
let bind_group_all_points = mk_bind_group(
"PointCloudDrawData::bind_group_all_points".into(),
draw_data_uniform_buffer_bindings_normal,
);
let bind_group_all_points_outline_mask = mk_bind_group(
"PointCloudDrawData::bind_group_all_points_outline_mask".into(),
draw_data_uniform_buffer_bindings_outline,
);
let mut batches_internal = Vec::with_capacity(batches.len());
{
let uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
ctx,
"point batch uniform buffers".into(),
batches
.iter()
.map(|batch_info| gpu_data::BatchUniformBuffer {
world_from_obj: batch_info.world_from_obj.into(),
flags: batch_info.flags.bits(),
outline_mask_ids: batch_info
.overall_outline_mask_ids
.0
.unwrap_or_default()
.into(),
picking_object_id: batch_info.picking_object_id,
depth_offset: batch_info.depth_offset as f32,
_row_padding: [0.0, 0.0],
end_padding: Default::default(),
}),
);
let mut uniform_buffer_bindings_mask_only_batches =
create_and_fill_uniform_buffer_batch(
ctx,
"lines batch uniform buffers - mask only".into(),
batches
.iter()
.flat_map(|batch_info| {
batch_info
.additional_outline_mask_ids_vertex_ranges
.iter()
.map(|(_, mask)| gpu_data::BatchUniformBuffer {
world_from_obj: batch_info.world_from_obj.into(),
flags: batch_info.flags.bits(),
outline_mask_ids: mask.0.unwrap_or_default().into(),
picking_object_id: batch_info.picking_object_id,
depth_offset: batch_info.depth_offset as f32,
_row_padding: [0.0, 0.0],
end_padding: Default::default(),
})
})
.collect::<Vec<_>>()
.into_iter(),
)
.into_iter();
let mut start_point_for_next_batch = 0;
for (batch_info, uniform_buffer_binding) in
batches.iter().zip(uniform_buffer_bindings.into_iter())
{
let point_vertex_range_end = start_point_for_next_batch + batch_info.point_count;
let mut active_phases = enum_set![DrawPhase::Opaque | DrawPhase::PickingLayer];
if batch_info.overall_outline_mask_ids.is_some() {
active_phases.insert(DrawPhase::OutlineMask);
}
batches_internal.push(point_renderer.create_point_cloud_batch(
ctx,
batch_info.label.clone(),
uniform_buffer_binding,
start_point_for_next_batch..point_vertex_range_end,
active_phases,
));
for (range, _) in &batch_info.additional_outline_mask_ids_vertex_ranges {
let range = (range.start + start_point_for_next_batch)
..(range.end + start_point_for_next_batch);
batches_internal.push(point_renderer.create_point_cloud_batch(
ctx,
format!("{:?} strip-only {:?}", batch_info.label, range).into(),
uniform_buffer_bindings_mask_only_batches.next().unwrap(),
range.clone(),
enum_set![DrawPhase::OutlineMask],
));
}
start_point_for_next_batch = point_vertex_range_end;
if start_point_for_next_batch >= num_vertices as u32 {
break;
}
}
}
Ok(Self {
bind_group_all_points: Some(bind_group_all_points),
bind_group_all_points_outline_mask: Some(bind_group_all_points_outline_mask),
batches: batches_internal,
})
}
}
pub struct PointCloudRenderer {
render_pipeline_color: GpuRenderPipelineHandle,
render_pipeline_picking_layer: GpuRenderPipelineHandle,
render_pipeline_outline_mask: GpuRenderPipelineHandle,
bind_group_layout_all_points: GpuBindGroupLayoutHandle,
bind_group_layout_batch: GpuBindGroupLayoutHandle,
}
impl PointCloudRenderer {
fn create_point_cloud_batch(
&self,
ctx: &RenderContext,
label: DebugLabel,
uniform_buffer_binding: BindGroupEntry,
vertex_range: Range<u32>,
active_phases: EnumSet<DrawPhase>,
) -> PointCloudBatch {
let bind_group = ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label,
entries: smallvec![uniform_buffer_binding],
layout: self.bind_group_layout_batch,
},
);
PointCloudBatch {
bind_group,
vertex_range: (vertex_range.start * 6)..(vertex_range.end * 6),
active_phases,
}
}
}
impl Renderer for PointCloudRenderer {
type RendererDrawData = PointCloudDrawData;
fn participated_phases() -> &'static [DrawPhase] {
&[
DrawPhase::OutlineMask,
DrawPhase::Opaque,
DrawPhase::PickingLayer,
]
}
fn create_renderer(ctx: &RenderContext) -> Self {
re_tracing::profile_function!();
let render_pipelines = &ctx.gpu_resources.render_pipelines;
let bind_group_layout_all_points = ctx.gpu_resources.bind_group_layouts.get_or_create(
&ctx.device,
&BindGroupLayoutDesc {
label: "PointCloudRenderer::bind_group_layout_all_points".into(),
entries: vec![
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Uint,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(std::mem::size_of::<
gpu_data::DrawDataUniformBuffer,
>() as _),
},
count: None,
},
],
},
);
let bind_group_layout_batch = ctx.gpu_resources.bind_group_layouts.get_or_create(
&ctx.device,
&BindGroupLayoutDesc {
label: "PointCloudRenderer::bind_group_layout_batch".into(),
entries: vec![wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(std::mem::size_of::<
gpu_data::BatchUniformBuffer,
>() as _),
},
count: None,
}],
},
);
let pipeline_layout = ctx.gpu_resources.pipeline_layouts.get_or_create(
ctx,
&PipelineLayoutDesc {
label: "PointCloudRenderer::pipeline_layout".into(),
entries: vec![
ctx.global_bindings.layout,
bind_group_layout_all_points,
bind_group_layout_batch,
],
},
);
let shader_module_desc = include_shader_module!("../../shader/point_cloud.wgsl");
let shader_module = ctx
.gpu_resources
.shader_modules
.get_or_create(ctx, &shader_module_desc);
let mut shader_module_desc_vertex = shader_module_desc.clone();
shader_module_desc_vertex.extra_workaround_replacements =
vec![("fwidth(".to_owned(), "f32(".to_owned())];
let shader_module_vertex = ctx
.gpu_resources
.shader_modules
.get_or_create(ctx, &shader_module_desc_vertex);
let render_pipeline_desc_color = RenderPipelineDesc {
label: "PointCloudRenderer::render_pipeline_color".into(),
pipeline_layout,
vertex_entrypoint: "vs_main".into(),
vertex_handle: shader_module_vertex,
fragment_entrypoint: "fs_main".into(),
fragment_handle: shader_module,
vertex_buffers: smallvec![],
render_targets: smallvec![Some(ViewBuilder::MAIN_TARGET_ALPHA_TO_COVERAGE_COLOR_STATE)],
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: ViewBuilder::MAIN_TARGET_DEFAULT_DEPTH_STATE,
multisample: wgpu::MultisampleState {
alpha_to_coverage_enabled: true,
..ViewBuilder::MAIN_TARGET_DEFAULT_MSAA_STATE
},
};
let render_pipeline_color =
render_pipelines.get_or_create(ctx, &render_pipeline_desc_color);
let render_pipeline_picking_layer = render_pipelines.get_or_create(
ctx,
&RenderPipelineDesc {
label: "PointCloudRenderer::render_pipeline_picking_layer".into(),
fragment_entrypoint: "fs_main_picking_layer".into(),
render_targets: smallvec![Some(PickingLayerProcessor::PICKING_LAYER_FORMAT.into())],
depth_stencil: PickingLayerProcessor::PICKING_LAYER_DEPTH_STATE,
multisample: PickingLayerProcessor::PICKING_LAYER_MSAA_STATE,
..render_pipeline_desc_color.clone()
},
);
let render_pipeline_outline_mask = render_pipelines.get_or_create(
ctx,
&RenderPipelineDesc {
label: "PointCloudRenderer::render_pipeline_outline_mask".into(),
fragment_entrypoint: "fs_main_outline_mask".into(),
render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())],
depth_stencil: OutlineMaskProcessor::MASK_DEPTH_STATE,
multisample: OutlineMaskProcessor::mask_default_msaa_state(&ctx.config.device_caps),
..render_pipeline_desc_color
},
);
Self {
render_pipeline_color,
render_pipeline_picking_layer,
render_pipeline_outline_mask,
bind_group_layout_all_points,
bind_group_layout_batch,
}
}
fn draw(
&self,
render_pipelines: &GpuRenderPipelinePoolAccessor<'_>,
phase: DrawPhase,
pass: &mut wgpu::RenderPass<'_>,
draw_data: &Self::RendererDrawData,
) -> Result<(), DrawError> {
let (pipeline_handle, bind_group_all_points) = match phase {
DrawPhase::OutlineMask => (
self.render_pipeline_outline_mask,
&draw_data.bind_group_all_points_outline_mask,
),
DrawPhase::Opaque => (self.render_pipeline_color, &draw_data.bind_group_all_points),
DrawPhase::PickingLayer => (
self.render_pipeline_picking_layer,
&draw_data.bind_group_all_points,
),
_ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
};
let Some(bind_group_all_points) = bind_group_all_points else {
return Ok(()); };
let pipeline = render_pipelines.get(pipeline_handle)?;
pass.set_pipeline(pipeline);
pass.set_bind_group(1, bind_group_all_points, &[]);
for batch in &draw_data.batches {
if batch.active_phases.contains(phase) {
pass.set_bind_group(2, &batch.bind_group, &[]);
pass.draw(batch.vertex_range.clone(), 0..1);
}
}
Ok(())
}
}