use std::num::NonZeroU64;
use std::ops::Range;
use std::sync::Arc;
use enumset::{EnumSet, enum_set};
use parking_lot::Mutex;
use smallvec::smallvec;
use super::{DrawData, DrawError, RenderContext, Renderer};
use crate::allocator::create_and_fill_uniform_buffer_batch;
use crate::draw_phases::{
DrawPhase, OutlineMaskProcessor, PickingLayerObjectId, PickingLayerProcessor,
};
use crate::renderer::{DrawDataDrawable, DrawInstruction, DrawableCollectionViewInfo};
use crate::transparent_sort::{
SortOrderCache, SortedDrawable, SortedDrawables, TransparentSort,
build_back_to_front_lookup_texture,
};
use crate::view_builder::ViewBuilder;
use crate::wgpu_resources::{
BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle,
GpuRenderPipelineHandle, GpuRenderPipelinePoolAccessor, PipelineLayoutDesc, RenderPipelineDesc,
};
use crate::{
DrawableCollector, GaussianSplatBuilder, Label, OutlineMaskPreference, include_shader_module,
};
const FLAG_ENABLE_INDEX_LOOKUP: u32 = 1;
pub mod gpu_data {
use crate::draw_phases::PickingLayerObjectId;
use crate::wgpu_buffer_types;
#[repr(C, packed)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GaussianPositionScaleX {
pub pos: glam::Vec3,
pub scale_x: f32,
}
static_assertions::assert_eq_size!(GaussianPositionScaleX, glam::Vec4);
#[repr(C, packed)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GaussianRotation {
pub quat_xyzw: [f32; 4],
}
static_assertions::assert_eq_size!(GaussianRotation, glam::Vec4);
#[repr(C, packed)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GaussianScaleYZ {
pub scale_y: f32,
pub scale_z: f32,
}
static_assertions::assert_eq_size!(GaussianScaleYZ, glam::Vec2);
#[repr(C, packed)]
#[derive(Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GaussianShCoefficient {
pub rgb_unused: [half::f16; 4],
}
static_assertions::assert_eq_size!(GaussianShCoefficient, u64);
impl re_byte_size::SizeBytes for GaussianShCoefficient {
const IS_POD: bool = true;
#[inline]
fn heap_size_bytes(&self) -> u64 {
0
}
}
impl GaussianShCoefficient {
#[inline]
pub fn from_rgb([r, g, b]: [half::f16; 3]) -> Self {
Self {
rgb_unused: [r, g, b, half::f16::ZERO],
}
}
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct BatchUniformBuffer {
pub world_from_obj: wgpu_buffer_types::Mat4,
pub obj_from_world: wgpu_buffer_types::Mat4,
pub flags: u32, pub first_gaussian_index: u32,
pub sh_num_coefficients: u32,
pub sh_first_texel: u32,
pub outline_mask_ids: wgpu_buffer_types::UVec2,
pub picking_object_id: PickingLayerObjectId,
pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 10],
}
}
bitflags::bitflags! {
#[repr(C)]
#[derive(Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GaussianSplatBatchFlags : u32 {
const FLAG_HAS_SH_COEFFICIENTS = 0b0010;
}
}
pub const SH_TEXELS_PER_GAUSSIAN: usize = 15;
const VERTICES_PER_GAUSSIAN: u32 = 6;
#[derive(Clone)]
struct GaussianSplatBatch {
bind_group: GpuBindGroup,
vertex_range: Range<u32>,
active_phases: EnumSet<DrawPhase>,
center_world_position: glam::Vec3,
sort: Option<TransparentSort>,
}
#[derive(Clone)]
pub struct GaussianSplatDrawData {
bind_group_all_gaussians: Option<GpuBindGroup>,
batches: Vec<GaussianSplatBatch>,
drawables: Arc<Mutex<SortedDrawables>>,
}
impl DrawData for GaussianSplatDrawData {
type Renderer = GaussianSplatRenderer;
fn collect_drawables(
&self,
view_info: &DrawableCollectionViewInfo,
collector: &mut DrawableCollector<'_>,
) {
let lookup_bind_group_layout = collector
.render_ctx()
.renderer::<GaussianSplatRenderer>()
.bind_group_layout_lookup;
for (batch_index, batch) in self.batches.iter().enumerate() {
let lookup_bind_group = if let Some(sort) = &batch.sort {
let render_ctx = collector.render_ctx();
let Some(lookup_texture) =
build_back_to_front_lookup_texture(render_ctx, sort, view_info)
else {
continue;
};
Some(render_ctx.gpu_resources.bind_groups.alloc(
&render_ctx.device,
&render_ctx.gpu_resources,
&BindGroupDesc {
label: "GaussianSplatDrawData::lookup_bind_group".into(),
entries: smallvec![BindGroupEntry::DefaultTextureView(
lookup_texture.handle
)],
layout: lookup_bind_group_layout,
},
))
} else {
None
};
let frame_index = collector.render_ctx().active_frame.frame_index;
let drawable_index = self.drawables.lock().push_for_frame(
frame_index,
SortedDrawable {
batch_index,
lookup_bind_group,
},
);
collector.add_drawable(
batch.active_phases,
DrawDataDrawable::from_world_position(
view_info,
batch.center_world_position.into(),
drawable_index as _,
),
);
}
}
}
pub struct GaussianSplatBatchInfo {
pub label: Label,
pub world_from_obj: glam::Affine3A,
pub flags: GaussianSplatBatchFlags,
pub sh_num_coefficients: u32,
pub gaussian_count: u32,
pub object_space_bounding_box: macaw::BoundingBox,
pub overall_outline_mask_ids: OutlineMaskPreference,
pub additional_outline_mask_ids_vertex_ranges: Vec<(Range<u32>, OutlineMaskPreference)>,
pub picking_object_id: PickingLayerObjectId,
pub sort_positions: Option<Vec<glam::Vec3>>,
pub sort_order_cache: Option<SortOrderCache>,
}
impl Default for GaussianSplatBatchInfo {
#[inline]
fn default() -> Self {
Self {
label: Label::default(),
world_from_obj: glam::Affine3A::IDENTITY,
flags: GaussianSplatBatchFlags::empty(),
sh_num_coefficients: 0,
gaussian_count: 0,
object_space_bounding_box: macaw::BoundingBox::nothing(),
overall_outline_mask_ids: OutlineMaskPreference::NONE,
additional_outline_mask_ids_vertex_ranges: Vec::new(),
picking_object_id: Default::default(),
sort_positions: None,
sort_order_cache: None,
}
}
}
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum GaussianSplatDrawDataError {
#[error("Failed to transfer data to the GPU: {0}")]
FailedTransferringDataToGpu(#[from] crate::allocator::CpuWriteGpuReadError),
}
impl GaussianSplatDrawData {
pub fn new(builder: GaussianSplatBuilder<'_>) -> Result<Self, GaussianSplatDrawDataError> {
re_tracing::profile_function!();
let GaussianSplatBuilder {
ctx,
position_scale_x_buffer,
rotation_buffer,
scale_yz_buffer,
sh_buffer,
color_buffer,
picking_instance_ids_buffer,
batches,
} = builder;
let renderer = ctx.renderer::<GaussianSplatRenderer>();
let batches = batches.as_slice();
if position_scale_x_buffer.is_empty() {
return Ok(Self {
bind_group_all_gaussians: None,
batches: Vec::new(),
drawables: Arc::new(Mutex::new(SortedDrawables::default())),
});
}
let num_gaussians = position_scale_x_buffer.len();
let fallback_batches = [GaussianSplatBatchInfo {
label: "fallback_batches".into(),
gaussian_count: num_gaussians as _,
..Default::default()
}];
let batches = if batches.is_empty() {
&fallback_batches
} else {
batches
};
let position_scale_x_texture = position_scale_x_buffer.finish(
wgpu::TextureFormat::Rgba32Float,
"GaussianSplatDrawData::position_scale_x_texture",
)?;
let rotation_texture = rotation_buffer.finish(
wgpu::TextureFormat::Rgba32Float,
"GaussianSplatDrawData::rotation_texture",
)?;
let scale_yz_texture = scale_yz_buffer.finish(
wgpu::TextureFormat::Rg32Float,
"GaussianSplatDrawData::scale_yz_texture",
)?;
let sh_texture_handle = if sh_buffer.is_empty() {
ctx.texture_manager_2d.zeroed_texture_float().handle
} else {
sh_buffer
.finish(
wgpu::TextureFormat::Rgba16Float,
"GaussianSplatDrawData::sh_texture",
)?
.handle
};
let color_texture = color_buffer.finish(
wgpu::TextureFormat::Rgba8UnormSrgb,
"GaussianSplatDrawData::color_texture",
)?;
let picking_instance_id_texture = picking_instance_ids_buffer.finish(
wgpu::TextureFormat::Rg32Uint,
"GaussianSplatDrawData::picking_instance_id_texture",
)?;
let bind_group_all_gaussians = ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label: "GaussianSplatDrawData::bind_group_all_gaussians".into(),
entries: smallvec![
BindGroupEntry::DefaultTextureView(position_scale_x_texture.handle),
BindGroupEntry::DefaultTextureView(rotation_texture.handle),
BindGroupEntry::DefaultTextureView(scale_yz_texture.handle),
BindGroupEntry::DefaultTextureView(sh_texture_handle),
BindGroupEntry::DefaultTextureView(color_texture.handle),
BindGroupEntry::DefaultTextureView(picking_instance_id_texture.handle),
],
layout: renderer.bind_group_layout_all_gaussians,
},
);
let mut batches_internal = Vec::with_capacity(batches.len());
{
let make_batch_uniform_buffer =
|batch_info: &GaussianSplatBatchInfo,
outline_mask_ids: OutlineMaskPreference,
flags: u32,
first_gaussian_index: u32,
sh_first_texel: u32| {
gpu_data::BatchUniformBuffer {
world_from_obj: batch_info.world_from_obj.into(),
obj_from_world: batch_info.world_from_obj.inverse().into(),
flags: flags | batch_info.flags.bits(),
first_gaussian_index,
sh_num_coefficients: batch_info.sh_num_coefficients,
sh_first_texel,
outline_mask_ids: outline_mask_ids.0.unwrap_or_default().into(),
picking_object_id: batch_info.picking_object_id,
end_padding: Default::default(),
}
};
let batch_is_sorted = |batch_info: &GaussianSplatBatchInfo| {
batch_info
.sort_positions
.as_ref()
.is_some_and(|p| !p.is_empty())
};
let batch_offsets = batches
.iter()
.scan(
(0, 0),
|(first_gaussian_index, first_sh_texel), batch_info| {
let offsets = (*first_gaussian_index, *first_sh_texel);
*first_gaussian_index += batch_info.gaussian_count;
*first_sh_texel +=
batch_info.gaussian_count * batch_info.sh_num_coefficients;
Some(offsets)
},
)
.collect::<Vec<_>>();
let uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
ctx,
"gaussian batch uniform buffers".into(),
std::iter::zip(batches, &batch_offsets)
.map(|(batch_info, &(first_gaussian_index, sh_first_texel))| {
let flags = if batch_is_sorted(batch_info) {
FLAG_ENABLE_INDEX_LOOKUP
} else {
0
};
make_batch_uniform_buffer(
batch_info,
batch_info.overall_outline_mask_ids,
flags,
first_gaussian_index,
sh_first_texel,
)
})
.collect::<Vec<_>>()
.into_iter(),
);
let mut uniform_buffer_bindings_mask_only_batches =
create_and_fill_uniform_buffer_batch(
ctx,
"gaussian batch uniform buffers - mask only".into(),
std::iter::zip(batches, &batch_offsets)
.flat_map(|(batch_info, &(first_gaussian_index, sh_first_texel))| {
batch_info
.additional_outline_mask_ids_vertex_ranges
.iter()
.map(move |(_, mask)| {
make_batch_uniform_buffer(
batch_info,
*mask,
0,
first_gaussian_index,
sh_first_texel,
)
})
})
.collect::<Vec<_>>()
.into_iter(),
)
.into_iter();
let mut start_gaussian_for_next_batch = 0;
for (batch_info, uniform_buffer_binding) in
std::iter::zip(batches, uniform_buffer_bindings)
{
re_tracing::profile_scope!("batch");
let gaussian_range_end = start_gaussian_for_next_batch + batch_info.gaussian_count;
let mut active_phases = DrawPhase::Transparent | DrawPhase::PickingLayer;
if batch_info.overall_outline_mask_ids.is_some() {
active_phases.insert(DrawPhase::OutlineMask);
}
let object_bbox = if batch_info.object_space_bounding_box.is_finite()
&& batch_info.object_space_bounding_box.is_something()
{
batch_info.object_space_bounding_box
} else {
batch_info
.sort_positions
.as_ref()
.map(|p| crate::util::bounding_box_from_points(p.iter().copied()))
.unwrap_or_else(macaw::BoundingBox::nothing)
};
let object_center = if object_bbox.is_finite() {
object_bbox.center()
} else {
glam::Vec3::ZERO
};
let center_world_position =
batch_info.world_from_obj.transform_point3(object_center);
let sort =
batch_info
.sort_positions
.as_ref()
.map(|obj_positions| TransparentSort {
object_positions: Arc::new(obj_positions.clone()),
object_from_world: batch_info.world_from_obj.inverse(),
sort_order_cache: batch_info.sort_order_cache.clone(),
});
batches_internal.push(renderer.create_gaussian_splat_batch(
ctx,
batch_info.label.clone(),
uniform_buffer_binding,
start_gaussian_for_next_batch..gaussian_range_end,
active_phases,
center_world_position,
sort,
));
for (range, _) in &batch_info.additional_outline_mask_ids_vertex_ranges {
let range = (range.start + start_gaussian_for_next_batch)
..(range.end + start_gaussian_for_next_batch);
batches_internal.push(renderer.create_gaussian_splat_batch(
ctx,
format!("{:?} outline-only {:?}", batch_info.label, range).into(),
uniform_buffer_bindings_mask_only_batches.next().unwrap(),
range.clone(),
enum_set![DrawPhase::OutlineMask],
center_world_position,
None,
));
}
start_gaussian_for_next_batch = gaussian_range_end;
if start_gaussian_for_next_batch >= num_gaussians as u32 {
break;
}
}
}
Ok(Self {
bind_group_all_gaussians: Some(bind_group_all_gaussians),
batches: batches_internal,
drawables: Arc::new(Mutex::new(SortedDrawables::default())),
})
}
}
pub struct GaussianSplatRenderer {
render_pipeline_color: GpuRenderPipelineHandle,
render_pipeline_picking_layer: GpuRenderPipelineHandle,
render_pipeline_outline_mask: GpuRenderPipelineHandle,
bind_group_layout_all_gaussians: GpuBindGroupLayoutHandle,
bind_group_layout_batch: GpuBindGroupLayoutHandle,
bind_group_layout_lookup: GpuBindGroupLayoutHandle,
dummy_lookup_bind_group: GpuBindGroup,
}
impl GaussianSplatRenderer {
fn create_gaussian_splat_batch(
&self,
ctx: &RenderContext,
label: Label,
uniform_buffer_binding: BindGroupEntry,
gaussian_range: Range<u32>,
active_phases: EnumSet<DrawPhase>,
center_world_position: glam::Vec3,
sort: Option<TransparentSort>,
) -> GaussianSplatBatch {
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,
},
);
GaussianSplatBatch {
bind_group,
vertex_range: (gaussian_range.start * VERTICES_PER_GAUSSIAN)
..(gaussian_range.end * VERTICES_PER_GAUSSIAN),
active_phases,
center_world_position,
sort,
}
}
}
impl Renderer for GaussianSplatRenderer {
type RendererDrawData = GaussianSplatDrawData;
fn create_renderer(ctx: &RenderContext) -> Self {
re_tracing::profile_function!();
let render_pipelines = &ctx.gpu_resources.render_pipelines;
let float_texture_entry = |binding| wgpu::BindGroupLayoutEntry {
binding,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: false },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
};
let bind_group_layout_all_gaussians = ctx.gpu_resources.bind_group_layouts.get_or_create(
&ctx.device,
&BindGroupLayoutDesc {
label: "GaussianSplatRenderer::bind_group_layout_all_gaussians".into(),
entries: vec![
float_texture_entry(0), float_texture_entry(1), float_texture_entry(2), float_texture_entry(3), float_texture_entry(4), wgpu::BindGroupLayoutEntry {
binding: 5,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Uint,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
],
},
);
let bind_group_layout_batch = ctx.gpu_resources.bind_group_layouts.get_or_create(
&ctx.device,
&BindGroupLayoutDesc {
label: "GaussianSplatRenderer::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 bind_group_layout_lookup = ctx.gpu_resources.bind_group_layouts.get_or_create(
&ctx.device,
&BindGroupLayoutDesc {
label: "GaussianSplatRenderer::bind_group_layout_lookup".into(),
entries: vec![wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Uint,
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
}],
},
);
let dummy_lookup_bind_group = ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label: "GaussianSplatRenderer::dummy_lookup_bind_group".into(),
entries: smallvec![BindGroupEntry::DefaultTextureView(
ctx.texture_manager_2d.zeroed_texture_uint().handle,
)],
layout: bind_group_layout_lookup,
},
);
let pipeline_layout = ctx.gpu_resources.pipeline_layouts.get_or_create(
ctx,
&PipelineLayoutDesc {
label: "GaussianSplatRenderer::pipeline_layout".into(),
entries: vec![
ctx.global_bindings.layout,
bind_group_layout_all_gaussians,
bind_group_layout_batch,
bind_group_layout_lookup,
],
},
);
let shader_module_desc = include_shader_module!("../../shader/gaussian_splat.wgsl");
let shader_module = ctx
.gpu_resources
.shader_modules
.get_or_create(ctx, &shader_module_desc);
let render_pipeline_desc_color = RenderPipelineDesc {
label: "GaussianSplatRenderer::render_pipeline_color".into(),
pipeline_layout,
vertex_entrypoint: "vs_main".into(),
vertex_handle: shader_module,
fragment_entrypoint: "fs_main".into(),
fragment_handle: shader_module,
vertex_buffers: smallvec![],
render_targets: smallvec![Some(wgpu::ColorTargetState {
format: ViewBuilder::MAIN_TARGET_COLOR_FORMAT,
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..Default::default()
},
depth_stencil: Some(ViewBuilder::MAIN_TARGET_DEFAULT_DEPTH_STATE_NO_WRITE),
multisample: ViewBuilder::main_target_default_msaa_state(ctx.render_config(), false),
};
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: "GaussianSplatRenderer::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: "GaussianSplatRenderer::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.device_caps().tier),
..render_pipeline_desc_color
},
);
Self {
render_pipeline_color,
render_pipeline_picking_layer,
render_pipeline_outline_mask,
bind_group_layout_all_gaussians,
bind_group_layout_batch,
bind_group_layout_lookup,
dummy_lookup_bind_group,
}
}
fn draw(
&self,
render_pipelines: &GpuRenderPipelinePoolAccessor<'_>,
phase: DrawPhase,
pass: &mut wgpu::RenderPass<'_>,
draw_instructions: &[DrawInstruction<'_, Self::RendererDrawData>],
) -> Result<(), DrawError> {
let pipeline_handle = match phase {
DrawPhase::OutlineMask => self.render_pipeline_outline_mask,
DrawPhase::Transparent => self.render_pipeline_color,
DrawPhase::PickingLayer => self.render_pipeline_picking_layer,
_ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
};
let pipeline = render_pipelines.get(pipeline_handle)?;
pass.set_pipeline(pipeline);
for DrawInstruction {
draw_data,
drawables,
} in draw_instructions
{
let Some(bind_group_all_gaussians) = &draw_data.bind_group_all_gaussians else {
re_log::debug_panic!(
"Gaussian data bind group was not set despite being submitted for drawing."
);
continue;
};
pass.set_bind_group(1, bind_group_all_gaussians, &[]);
let gaussian_drawables = draw_data.drawables.lock();
for drawable in *drawables {
let gaussian_drawable = gaussian_drawables.get(drawable.draw_data_payload as usize);
let batch = &draw_data.batches[gaussian_drawable.batch_index];
let lookup_bind_group = gaussian_drawable
.lookup_bind_group
.as_ref()
.unwrap_or(&self.dummy_lookup_bind_group);
pass.set_bind_group(2, &batch.bind_group, &[]);
pass.set_bind_group(3, lookup_bind_group, &[]);
pass.draw(batch.vertex_range.clone(), 0..1);
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::view_builder::{
Projection, TargetConfiguration, ViewBuilder, ViewPickingConfiguration,
};
use crate::{
PickingLayerInstanceId, PickingLayerProcessor, RectInt, Rgba, Rgba32Unmul, ViewBuilderId,
draw_phases::PickingLayerObjectId,
};
#[test]
fn picking_layer_reports_object_and_instance_ids() {
let mut ctx = RenderContext::new_test();
let object_id = PickingLayerObjectId(0x1234_5678_9abc_def0);
let instance_id = PickingLayerInstanceId(7);
let resolution = [64_u32, 64];
let readback_identifier = 42;
ctx.begin_frame();
let mut view_builder = ViewBuilder::new(
&ctx,
TargetConfiguration {
name: "gaussian_picking".into(),
resolution_in_pixel: resolution,
view_from_world: macaw::IsoTransform::look_at_rh(
glam::Vec3::new(0.0, 0.0, 3.0),
glam::Vec3::ZERO,
glam::Vec3::Y,
)
.expect("valid camera"),
projection_from_view: Projection::Perspective {
vertical_fov: 50.0_f32.to_radians(),
near_plane_distance: 0.01,
aspect_ratio: 1.0,
},
picking_config: Some(ViewPickingConfiguration {
picking_rect: RectInt {
min: glam::IVec2::ZERO,
extent: glam::UVec2::new(resolution[0], resolution[1]),
},
readback_identifier,
show_debug_view: false,
}),
..Default::default()
},
ViewBuilderId::new(0),
)
.expect("failed to create view builder");
let mut builder = GaussianSplatBuilder::new(&ctx);
builder
.batch("test")
.picking_object_id(object_id)
.add_gaussians(
&[glam::Vec3::ZERO],
&[glam::Vec3::splat(0.5)],
&[glam::Quat::IDENTITY],
&[Rgba32Unmul::WHITE],
&[],
0,
&[instance_id],
);
view_builder.queue_draw(&ctx, builder.into_draw_data().expect("draw data"));
let command_buffer = view_builder.draw(&ctx, Rgba::BLACK).expect("draw");
ctx.before_submit();
ctx.queue.submit([command_buffer]);
ctx.device
.poll(wgpu::PollType::Wait {
submission_index: None,
timeout: Some(std::time::Duration::from_secs(10)),
})
.expect("gpu wait");
let mut result = None;
for _ in 0..10 {
ctx.begin_frame();
result = PickingLayerProcessor::readback_result(&ctx, readback_identifier);
if result.is_some() {
break;
}
}
let result = result.expect("no picking readback received");
let center = glam::UVec2::new(resolution[0] / 2, resolution[1] / 2);
let picked = result.picked_id(center);
assert_eq!(picked.object, object_id, "wrong picking object id");
assert_eq!(picked.instance, instance_id, "wrong picking instance id");
}
}