diff --git a/crates/re_renderer/examples/2d.rs b/crates/re_renderer/examples/2d.rs index 194731a3785b..b171d3cb8a44 100644 --- a/crates/re_renderer/examples/2d.rs +++ b/crates/re_renderer/examples/2d.rs @@ -67,7 +67,7 @@ impl framework::Example for Render2D { splits[0].resolution_in_pixel[1] as f32, ); - let mut line_strip_builder = LineStripSeriesBuilder::<()>::new(re_ctx); + let mut line_strip_builder = LineStripSeriesBuilder::new(re_ctx); // Blue rect outline around the bottom right quarter. { @@ -182,7 +182,7 @@ impl framework::Example for Render2D { } } - let line_strip_draw_data = line_strip_builder.to_draw_data(re_ctx); + let line_strip_draw_data = line_strip_builder.to_draw_data(re_ctx).unwrap(); let point_draw_data = point_cloud_builder.to_draw_data(re_ctx).unwrap(); let image_scale = 4.0; diff --git a/crates/re_renderer/examples/depth_cloud.rs b/crates/re_renderer/examples/depth_cloud.rs index f37b3b2167ed..38fa0e252622 100644 --- a/crates/re_renderer/examples/depth_cloud.rs +++ b/crates/re_renderer/examples/depth_cloud.rs @@ -312,7 +312,7 @@ impl framework::Example for RenderDepthClouds { let world_from_model = rotation * translation_center * scale; let frame_draw_data = { - let mut builder = LineStripSeriesBuilder::<()>::new(re_ctx); + let mut builder = LineStripSeriesBuilder::new(re_ctx); { let mut line_batch = builder.batch("frame").world_from_obj(world_from_model); line_batch.add_box_outline(glam::Affine3A::from_scale_rotation_translation( @@ -321,7 +321,7 @@ impl framework::Example for RenderDepthClouds { glam::Vec3::ONE * 0.5, )); } - builder.to_draw_data(re_ctx) + builder.to_draw_data(re_ctx).unwrap() }; let image_draw_data = RectangleDrawData::new( diff --git a/crates/re_renderer/examples/multiview.rs b/crates/re_renderer/examples/multiview.rs index efdd88fa2749..35d234152b3b 100644 --- a/crates/re_renderer/examples/multiview.rs +++ b/crates/re_renderer/examples/multiview.rs @@ -84,7 +84,7 @@ fn build_lines(re_ctx: &mut RenderContext, seconds_since_startup: f32) -> LineDr // Calculate some points that look nice for an animated line. let lorenz_points = lorenz_points(seconds_since_startup); - let mut builder = LineStripSeriesBuilder::<()>::new(re_ctx); + let mut builder = LineStripSeriesBuilder::new(re_ctx); { let mut batch = builder.batch("lines without transform"); @@ -125,7 +125,7 @@ fn build_lines(re_ctx: &mut RenderContext, seconds_since_startup: f32) -> LineDr .radius(Size::new_scene(0.1)) .flags(LineStripFlags::CAP_END_TRIANGLE); - builder.to_draw_data(re_ctx) + builder.to_draw_data(re_ctx).unwrap() } enum CameraControl { diff --git a/crates/re_renderer/shader/lines.wgsl b/crates/re_renderer/shader/lines.wgsl index ffa727b4fdb4..c8812e8112df 100644 --- a/crates/re_renderer/shader/lines.wgsl +++ b/crates/re_renderer/shader/lines.wgsl @@ -10,6 +10,8 @@ var line_strip_texture: texture_2d; @group(1) @binding(1) var position_data_texture: texture_2d; +@group(1) @binding(2) +var picking_instance_id_texture: texture_2d; struct DrawDataUniformBuffer { radius_boost_in_ui_points: f32, @@ -19,12 +21,13 @@ struct DrawDataUniformBuffer { // if we wouldn't add padding here, which isn't available on WebGL. _padding: Vec4, }; -@group(1) @binding(2) +@group(1) @binding(3) var draw_data: DrawDataUniformBuffer; struct BatchUniformBuffer { world_from_obj: Mat4, outline_mask_ids: UVec2, + picking_layer_object_id: UVec2, }; @group(2) @binding(0) var batch: BatchUniformBuffer; @@ -32,8 +35,8 @@ var batch: BatchUniformBuffer; // textureLoad needs i32 right now, so we use that with all sizes & indices to avoid casts // https://github.com/gfx-rs/naga/issues/1997 -const LINESTRIP_TEXTURE_SIZE: i32 = 512; -const POSITION_DATA_TEXTURE_SIZE: i32 = 256; +const POSITION_TEXTURE_SIZE: i32 = 512; +const LINE_STRIP_TEXTURE_SIZE: i32 = 256; // Flags // See lines.rs#LineStripFlags @@ -69,6 +72,9 @@ struct VertexOut { @location(5) @interpolate(flat) fragment_flags: u32, + + @location(6) @interpolate(flat) + picking_instance_id: UVec2, }; struct LineStripData { @@ -76,13 +82,15 @@ struct LineStripData { unresolved_radius: f32, stippling: f32, flags: u32, + picking_instance_id: UVec2, } // Read and unpack line strip data at a given location fn read_strip_data(idx: u32) -> LineStripData { // can be u32 once https://github.com/gfx-rs/naga/issues/1997 is solved let idx = i32(idx); - var raw_data = textureLoad(position_data_texture, IVec2(idx % POSITION_DATA_TEXTURE_SIZE, idx / POSITION_DATA_TEXTURE_SIZE), 0).xy; + let coord = IVec2(idx % LINE_STRIP_TEXTURE_SIZE, idx / LINE_STRIP_TEXTURE_SIZE); + var raw_data = textureLoad(position_data_texture, coord, 0).xy; var data: LineStripData; data.color = linear_from_srgba(unpack4x8unorm_workaround(raw_data.x)); @@ -91,6 +99,7 @@ fn read_strip_data(idx: u32) -> LineStripData { data.unresolved_radius = unpack2x16float(raw_data.y).y; data.flags = ((raw_data.y >> 8u) & 0xFFu); data.stippling = f32((raw_data.y >> 16u) & 0xFFu) * (1.0 / 255.0); + data.picking_instance_id = textureLoad(picking_instance_id_texture, coord, 0).rg; return data; } @@ -103,7 +112,7 @@ struct PositionData { fn read_position_data(idx: u32) -> PositionData { // can be u32 once https://github.com/gfx-rs/naga/issues/1997 is solved let idx = i32(idx); - var raw_data = textureLoad(line_strip_texture, IVec2(idx % LINESTRIP_TEXTURE_SIZE, idx / LINESTRIP_TEXTURE_SIZE), 0); + var raw_data = textureLoad(line_strip_texture, IVec2(idx % POSITION_TEXTURE_SIZE, idx / POSITION_TEXTURE_SIZE), 0); var data: PositionData; let pos_4d = batch.world_from_obj * Vec4(raw_data.xyz, 1.0); @@ -262,6 +271,7 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut { out.active_radius = active_radius; out.fragment_flags = strip_data.flags & (NO_COLOR_GRADIENT | (u32(is_cap_triangle) * select(CAP_START_ROUND, CAP_END_ROUND, is_right_triangle))); + out.picking_instance_id = strip_data.picking_instance_id; return out; } @@ -305,7 +315,7 @@ fn fs_main_picking_layer(in: VertexOut) -> @location(0) UVec4 { if coverage < 0.5 { discard; } - return UVec4(0u, 0u, 0u, 0u); // TODO(andreas): Implement picking layer id pass-through. + return UVec4(batch.picking_layer_object_id, in.picking_instance_id); } @fragment diff --git a/crates/re_renderer/src/line_strip_builder.rs b/crates/re_renderer/src/line_strip_builder.rs index 2c273491d563..9fee0afdb964 100644 --- a/crates/re_renderer/src/line_strip_builder.rs +++ b/crates/re_renderer/src/line_strip_builder.rs @@ -1,8 +1,12 @@ use std::ops::Range; use crate::{ - renderer::{LineBatchInfo, LineDrawData, LineStripFlags, LineStripInfo, LineVertex}, - Color32, DebugLabel, OutlineMaskPreference, RenderContext, Size, + allocator::CpuWriteGpuReadBuffer, + renderer::{ + LineBatchInfo, LineDrawData, LineDrawDataError, LineStripFlags, LineStripInfo, LineVertex, + }, + Color32, DebugLabel, OutlineMaskPreference, PickingLayerInstanceId, PickingLayerObjectId, + RenderContext, Size, }; /// Builder for a vector of line strips, making it easy to create [`crate::renderer::LineDrawData`]. @@ -11,31 +15,39 @@ use crate::{ /// of writing to a GPU readable memory location. /// This will require some ahead of time size limit, but should be feasible. /// But before that we first need to sort out cpu->gpu transfers better by providing staging buffers. -pub struct LineStripSeriesBuilder { +pub struct LineStripSeriesBuilder { pub vertices: Vec, - // Number of elements in strips and strip_user_data should be equal at all times. + pub batches: Vec, + pub strips: Vec, - pub strip_user_data: Vec, - pub batches: Vec, + /// Buffer for picking instance id - every strip gets its own instance id. + /// Therefore, there need to be always as many picking instance ids as there are strips. + pub(crate) picking_instance_ids_buffer: CpuWriteGpuReadBuffer, pub(crate) radius_boost_in_ui_points_for_outlines: f32, } -impl LineStripSeriesBuilder -where - PerStripUserData: Default + Copy, -{ - // TODO(andreas): ctx not yet needed since we don't write to GPU yet, but soon needed. - pub fn new(_ctx: &RenderContext) -> Self { +impl LineStripSeriesBuilder { + pub fn new(ctx: &RenderContext) -> Self { const RESERVE_SIZE: usize = 512; + // TODO(andreas): Be more resourceful about the size allocated here. Typically we know in advance! + let picking_instance_ids_buffer = ctx + .cpu_write_gpu_read_belt + .lock() + .allocate::( + &ctx.device, + &ctx.gpu_resources.buffers, + LineDrawData::MAX_NUM_STRIPS, + ); + Self { vertices: Vec::with_capacity(RESERVE_SIZE * 2), strips: Vec::with_capacity(RESERVE_SIZE), - strip_user_data: Vec::with_capacity(RESERVE_SIZE), batches: Vec::with_capacity(16), + picking_instance_ids_buffer, radius_boost_in_ui_points_for_outlines: 0.0, } } @@ -50,16 +62,14 @@ where } /// Start of a new batch. - pub fn batch( - &mut self, - label: impl Into, - ) -> LineBatchBuilder<'_, PerStripUserData> { + pub fn batch(&mut self, label: impl Into) -> LineBatchBuilder<'_> { self.batches.push(LineBatchInfo { label: label.into(), world_from_obj: glam::Mat4::IDENTITY, line_vertex_count: 0, overall_outline_mask_ids: OutlineMaskPreference::NONE, additional_outline_mask_ids_vertex_ranges: Vec::new(), + picking_object_id: PickingLayerObjectId::default(), }); LineBatchBuilder(self) @@ -84,44 +94,11 @@ where } /// Finalizes the builder and returns a line draw data with all the lines added so far. - pub fn to_draw_data(&self, ctx: &mut crate::context::RenderContext) -> LineDrawData { - LineDrawData::new( - ctx, - &self.vertices, - &self.strips, - &self.batches, - self.radius_boost_in_ui_points_for_outlines, - ) - .unwrap() - } - - /// Iterates over all line strips batches together with their strips and their respective vertices. - pub fn iter_strips_with_vertices( - &self, - ) -> impl Iterator< - Item = ( - (&LineStripInfo, &PerStripUserData), - impl Iterator, - ), - > { - let mut cumulative_offset = 0; - self.strips - .iter() - .zip(self.strip_user_data.iter()) - .enumerate() - .map(move |(strip_index, strip)| { - (strip, { - let offset = cumulative_offset; - let strip_index = strip_index as u32; - let vertex_iterator = self - .vertices - .iter() - .skip(offset) - .take_while(move |v| v.strip_index == strip_index); - cumulative_offset += vertex_iterator.clone().count(); - vertex_iterator - }) - }) + pub fn to_draw_data( + self, + ctx: &mut crate::context::RenderContext, + ) -> Result { + LineDrawData::new(ctx, self) } pub fn is_empty(&self) -> bool { @@ -129,9 +106,9 @@ where } } -pub struct LineBatchBuilder<'a, PerStripUserData>(&'a mut LineStripSeriesBuilder); +pub struct LineBatchBuilder<'a>(&'a mut LineStripSeriesBuilder); -impl<'a, PerStripUserData> Drop for LineBatchBuilder<'a, PerStripUserData> { +impl<'a> Drop for LineBatchBuilder<'a> { fn drop(&mut self) { // Remove batch again if it wasn't actually used. if self.0.batches.last().unwrap().line_vertex_count == 0 { @@ -140,10 +117,7 @@ impl<'a, PerStripUserData> Drop for LineBatchBuilder<'a, PerStripUserData> { } } -impl<'a, PerStripUserData> LineBatchBuilder<'a, PerStripUserData> -where - PerStripUserData: Default + Copy, -{ +impl<'a> LineBatchBuilder<'a> { #[inline] fn batch_mut(&mut self) -> &mut LineBatchInfo { self.0 @@ -176,11 +150,14 @@ where self } + /// Sets the picking object id for every element in the batch. + pub fn picking_object_id(mut self, picking_object_id: PickingLayerObjectId) -> Self { + self.batch_mut().picking_object_id = picking_object_id; + self + } + /// Adds a 3D series of line connected points. - pub fn add_strip( - &mut self, - points: impl Iterator, - ) -> LineStripBuilder<'_, PerStripUserData> { + pub fn add_strip(&mut self, points: impl Iterator) -> LineStripBuilder<'_> { let old_strip_count = self.0.strips.len(); let old_vertex_count = self.0.vertices.len(); let strip_index = old_strip_count as _; @@ -188,14 +165,13 @@ where self.add_vertices(points, strip_index); let new_vertex_count = self.0.vertices.len(); - debug_assert_eq!(self.0.strips.len(), self.0.strip_user_data.len()); self.0.strips.push(LineStripInfo::default()); - self.0.strip_user_data.push(PerStripUserData::default()); let new_strip_count = self.0.strips.len(); LineStripBuilder { builder: self.0, outline_mask_ids: OutlineMaskPreference::NONE, + picking_instance_id: PickingLayerInstanceId::default(), vertex_range: old_vertex_count..new_vertex_count, strip_range: old_strip_count..new_strip_count, } @@ -203,11 +179,7 @@ where /// Adds a single 3D line segment connecting two points. #[inline] - pub fn add_segment( - &mut self, - a: glam::Vec3, - b: glam::Vec3, - ) -> LineStripBuilder<'_, PerStripUserData> { + pub fn add_segment(&mut self, a: glam::Vec3, b: glam::Vec3) -> LineStripBuilder<'_> { self.add_strip([a, b].into_iter()) } @@ -215,7 +187,12 @@ where pub fn add_segments( &mut self, segments: impl Iterator, - ) -> LineStripBuilder<'_, PerStripUserData> { + ) -> LineStripBuilder<'_> { + debug_assert_eq!( + self.0.strips.len(), + self.0.picking_instance_ids_buffer.num_written() + ); + let old_strip_count = self.0.strips.len(); let old_vertex_count = self.0.vertices.len(); let mut strip_index = old_strip_count as u32; @@ -230,18 +207,15 @@ where let new_vertex_count = self.0.vertices.len(); let num_strips_added = strip_index as usize - old_strip_count; - debug_assert_eq!(self.0.strips.len(), self.0.strip_user_data.len()); self.0 .strips .extend(std::iter::repeat(LineStripInfo::default()).take(num_strips_added)); - self.0 - .strip_user_data - .extend(std::iter::repeat(PerStripUserData::default()).take(num_strips_added)); let new_strip_count = self.0.strips.len(); LineStripBuilder { builder: self.0, outline_mask_ids: OutlineMaskPreference::NONE, + picking_instance_id: PickingLayerInstanceId::default(), vertex_range: old_vertex_count..new_vertex_count, strip_range: old_strip_count..new_strip_count, } @@ -252,10 +226,7 @@ where /// Internally adds 12 line segments with rounded line heads. /// Disables color gradient since we don't support gradients in this setup yet (i.e. enabling them does not look good) #[inline] - pub fn add_box_outline( - &mut self, - transform: glam::Affine3A, - ) -> LineStripBuilder<'_, PerStripUserData> { + pub fn add_box_outline(&mut self, transform: glam::Affine3A) -> LineStripBuilder<'_> { let corners = [ transform.transform_point3(glam::vec3(-0.5, -0.5, -0.5)), transform.transform_point3(glam::vec3(-0.5, -0.5, 0.5)), @@ -305,7 +276,7 @@ where top_left_corner: glam::Vec3, extent_u: glam::Vec3, extent_v: glam::Vec3, - ) -> LineStripBuilder<'_, PerStripUserData> { + ) -> LineStripBuilder<'_> { self.add_segments( [ (top_left_corner, top_left_corner + extent_u), @@ -337,17 +308,13 @@ where pub fn add_strip_2d( &mut self, points: impl Iterator, - ) -> LineStripBuilder<'_, PerStripUserData> { + ) -> LineStripBuilder<'_> { self.add_strip(points.map(|p| p.extend(0.0))) } /// Adds a single 2D line segment connecting two points. Uses autogenerated depth value. #[inline] - pub fn add_segment_2d( - &mut self, - a: glam::Vec2, - b: glam::Vec2, - ) -> LineStripBuilder<'_, PerStripUserData> { + pub fn add_segment_2d(&mut self, a: glam::Vec2, b: glam::Vec2) -> LineStripBuilder<'_> { self.add_strip_2d([a, b].into_iter()) } @@ -358,7 +325,7 @@ where pub fn add_segments_2d( &mut self, segments: impl Iterator, - ) -> LineStripBuilder<'_, PerStripUserData> { + ) -> LineStripBuilder<'_> { self.add_segments(segments.map(|(a, b)| (a.extend(0.0), b.extend(0.0)))) } @@ -372,7 +339,7 @@ where top_left_corner: glam::Vec2, extent_u: glam::Vec2, extent_v: glam::Vec2, - ) -> LineStripBuilder<'_, PerStripUserData> { + ) -> LineStripBuilder<'_> { self.add_rectangle_outline( top_left_corner.extend(0.0), extent_u.extend(0.0), @@ -389,7 +356,7 @@ where &mut self, min: glam::Vec2, max: glam::Vec2, - ) -> LineStripBuilder<'_, PerStripUserData> { + ) -> LineStripBuilder<'_> { self.add_rectangle_outline( min.extend(0.0), glam::Vec3::X * (max.x - min.x), @@ -398,17 +365,15 @@ where } } -pub struct LineStripBuilder<'a, PerStripUserData> { - builder: &'a mut LineStripSeriesBuilder, +pub struct LineStripBuilder<'a> { + builder: &'a mut LineStripSeriesBuilder, outline_mask_ids: OutlineMaskPreference, + picking_instance_id: PickingLayerInstanceId, vertex_range: Range, strip_range: Range, } -impl<'a, PerStripUserData> LineStripBuilder<'a, PerStripUserData> -where - PerStripUserData: Clone, -{ +impl<'a> LineStripBuilder<'a> { #[inline] pub fn radius(self, radius: Size) -> Self { for strip in self.builder.strips[self.strip_range.clone()].iter_mut() { @@ -433,6 +398,11 @@ where self } + pub fn picking_instance_id(mut self, instance_id: PickingLayerInstanceId) -> Self { + self.picking_instance_id = instance_id; + self + } + /// Sets an individual outline mask ids. /// Note that this has a relatively high performance impact. #[inline] @@ -440,20 +410,9 @@ where self.outline_mask_ids = outline_mask_ids; self } - - /// Adds user data for every strip this builder adds. - /// - /// User data is currently not available on the GPU. - #[inline] - pub fn user_data(self, user_data: PerStripUserData) -> Self { - for d in self.builder.strip_user_data[self.strip_range.clone()].iter_mut() { - *d = user_data.clone(); - } - self - } } -impl<'a, PerStripUserData> Drop for LineStripBuilder<'a, PerStripUserData> { +impl<'a> Drop for LineStripBuilder<'a> { fn drop(&mut self) { if self.outline_mask_ids.is_some() { self.builder @@ -466,5 +425,8 @@ impl<'a, PerStripUserData> Drop for LineStripBuilder<'a, PerStripUserData> { self.outline_mask_ids, )); } + self.builder + .picking_instance_ids_buffer + .extend(std::iter::repeat(self.picking_instance_id).take(self.strip_range.len())); } } diff --git a/crates/re_renderer/src/renderer/lines.rs b/crates/re_renderer/src/renderer/lines.rs index 180398633b47..cf1222acfe1b 100644 --- a/crates/re_renderer/src/renderer/lines.rs +++ b/crates/re_renderer/src/renderer/lines.rs @@ -124,7 +124,8 @@ use crate::{ BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle, GpuRenderPipelineHandle, PipelineLayoutDesc, PoolError, RenderPipelineDesc, TextureDesc, }, - Color32, DebugLabel, OutlineMaskPreference, PickingLayerProcessor, + Color32, DebugLabel, LineStripSeriesBuilder, OutlineMaskPreference, PickingLayerObjectId, + PickingLayerProcessor, }; use super::{ @@ -135,7 +136,7 @@ use super::{ pub mod gpu_data { // Don't use `wgsl_buffer_types` since none of this data goes into a buffer, so its alignment rules don't apply. - use crate::{size::SizeHalf, wgpu_buffer_types, Color32}; + use crate::{size::SizeHalf, wgpu_buffer_types, Color32, PickingLayerObjectId}; use super::LineStripFlags; @@ -173,7 +174,8 @@ pub mod gpu_data { #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] pub struct BatchUniformBuffer { pub world_from_obj: wgpu_buffer_types::Mat4, - pub outline_mask_ids: wgpu_buffer_types::UVec2RowPadded, + pub outline_mask_ids: wgpu_buffer_types::UVec2, + pub picking_object_id: PickingLayerObjectId, pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 5], } @@ -259,6 +261,9 @@ pub struct LineBatchInfo { /// This feature is meant for a limited number of "extra selections" /// If an overall mask is defined as well, the per-vertex-range masks is overwriting the overall mask. pub additional_outline_mask_ids_vertex_ranges: Vec<(Range, OutlineMaskPreference)>, + + /// Picking object id that applies for the entire batch. + pub picking_object_id: PickingLayerObjectId, } /// Style information for a line strip. @@ -321,11 +326,7 @@ impl LineDrawData { /// If no batches are passed, all lines are assumed to be in a single batch with identity transform. pub fn new( ctx: &mut RenderContext, - // TODO(andreas): Take LineBuilder directly - vertices: &[gpu_data::LineVertex], - strips: &[LineStripInfo], - batches: &[LineBatchInfo], - radius_boost_in_ui_points_for_outlines: f32, + line_builder: LineStripSeriesBuilder, ) -> Result { let mut renderers = ctx.renderers.write(); let line_renderer = renderers.get_or_create::<_, LineRenderer>( @@ -335,7 +336,7 @@ impl LineDrawData { &mut ctx.resolver, ); - if strips.is_empty() { + if line_builder.strips.is_empty() { return Ok(LineDrawData { bind_group_all_lines: None, bind_group_all_lines_outline_mask: None, @@ -343,15 +344,23 @@ impl LineDrawData { }); } - let fallback_batches = [LineBatchInfo { - world_from_obj: glam::Mat4::IDENTITY, - label: "LineDrawData::fallback_batch".into(), - line_vertex_count: vertices.len() as _, - overall_outline_mask_ids: OutlineMaskPreference::NONE, - additional_outline_mask_ids_vertex_ranges: Vec::new(), - }]; + let LineStripSeriesBuilder { + vertices, + batches, + strips, + mut picking_instance_ids_buffer, + radius_boost_in_ui_points_for_outlines, + } = line_builder; + let batches = if batches.is_empty() { - &fallback_batches + vec![LineBatchInfo { + world_from_obj: glam::Mat4::IDENTITY, + label: "LineDrawData::fallback_batch".into(), + line_vertex_count: vertices.len() as _, + overall_outline_mask_ids: OutlineMaskPreference::NONE, + picking_object_id: PickingLayerObjectId::default(), + additional_outline_mask_ids_vertex_ranges: Vec::new(), + }] } else { batches }; @@ -373,7 +382,7 @@ impl LineDrawData { See also https://github.com/rerun-io/rerun/issues/957", Self::MAX_NUM_VERTICES, vertices.len() ); &vertices[..Self::MAX_NUM_VERTICES] } else { - vertices + &vertices[..] }; let strips = if strips.len() > Self::MAX_NUM_STRIPS { re_log::error_once!("Reached maximum number of supported line strips. Clamping down to {}, passed were {}. This may lead to rendering artifacts. @@ -387,7 +396,7 @@ impl LineDrawData { { return Err(LineDrawDataError::InvalidStripIndex); } - strips + &strips[..] }; let num_strips = strips.len() as u32; @@ -396,116 +405,152 @@ impl LineDrawData { // TODO(andreas): We want a "stack allocation" here that lives for one frame. // Note also that this doesn't protect against sharing the same texture with several LineDrawData! - let position_data_texture = ctx.gpu_resources.textures.alloc( - &ctx.device, - &TextureDesc { - label: "LineDrawData::position_data_texture".into(), - size: wgpu::Extent3d { - width: POSITION_TEXTURE_SIZE, - height: POSITION_TEXTURE_SIZE, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba32Float, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + let position_data_texture_desc = TextureDesc { + label: "LineDrawData::position_data_texture".into(), + size: wgpu::Extent3d { + width: POSITION_TEXTURE_SIZE, + height: POSITION_TEXTURE_SIZE, + depth_or_array_layers: 1, }, - ); - let line_strip_texture = ctx.gpu_resources.textures.alloc( + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba32Float, + usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + }; + let position_data_texture = ctx + .gpu_resources + .textures + .alloc(&ctx.device, &position_data_texture_desc); + + let line_strip_texture_desc = TextureDesc { + label: "LineDrawData::line_strip_texture".into(), + size: wgpu::Extent3d { + width: LINE_STRIP_TEXTURE_SIZE, + height: LINE_STRIP_TEXTURE_SIZE, + depth_or_array_layers: 1, + }, + format: wgpu::TextureFormat::Rg32Uint, + ..position_data_texture_desc + }; + let line_strip_texture = ctx + .gpu_resources + .textures + .alloc(&ctx.device, &line_strip_texture_desc); + let picking_instance_id_texture = ctx.gpu_resources.textures.alloc( &ctx.device, &TextureDesc { - label: "LineDrawData::line_strip_texture".into(), - size: wgpu::Extent3d { - width: LINE_STRIP_TEXTURE_SIZE, - height: LINE_STRIP_TEXTURE_SIZE, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, + label: "LineDrawData::picking_instance_id_texture".into(), format: wgpu::TextureFormat::Rg32Uint, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, + ..line_strip_texture_desc }, ); - // TODO(andreas): We want a staging-belt(-like) mechanism to upload data instead of the queue. - // These staging buffers would be provided by the belt. - // To make the data upload simpler (and have it be done in one go), we always update full rows of each of our textures - let mut position_data_staging = - Vec::with_capacity(wgpu::util::align_to(num_segments, POSITION_TEXTURE_SIZE) as usize); - // sentinel at the beginning to facilitate caps. - position_data_staging.push(LineVertex { - position: glam::vec3(f32::MAX, f32::MAX, f32::MAX), - strip_index: u32::MAX, - }); - position_data_staging.extend(vertices.iter()); - // placeholder at the end to facilitate caps. - position_data_staging.push(LineVertex { - position: glam::vec3(f32::MAX, f32::MAX, f32::MAX), - strip_index: u32::MAX, - }); - position_data_staging.extend(std::iter::repeat(gpu_data::LineVertex::zeroed()).take( - (wgpu::util::align_to(num_segments, POSITION_TEXTURE_SIZE) - num_segments) as usize, - )); - - let mut line_strip_info_staging = - Vec::with_capacity(wgpu::util::align_to(num_strips, LINE_STRIP_TEXTURE_SIZE) as usize); - line_strip_info_staging.extend(strips.iter().map(|line_strip| { - gpu_data::LineStripInfo { - color: line_strip.color, - radius: line_strip.radius.into(), - stippling: 0, //(line_strip.stippling.clamp(0.0, 1.0) * 255.0) as u8, - flags: line_strip.flags, - } - })); - line_strip_info_staging.extend(std::iter::repeat(gpu_data::LineStripInfo::zeroed()).take( - (wgpu::util::align_to(num_strips, LINE_STRIP_TEXTURE_SIZE) - num_strips) as usize, - )); - - // Upload data from staging buffers to gpu. - ctx.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &position_data_texture.texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - bytemuck::cast_slice(&position_data_staging), - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new( - POSITION_TEXTURE_SIZE * std::mem::size_of::() as u32, - ), - rows_per_image: None, - }, - wgpu::Extent3d { - width: POSITION_TEXTURE_SIZE, - height: (num_segments + POSITION_TEXTURE_SIZE - 1) / POSITION_TEXTURE_SIZE, - depth_or_array_layers: 1, - }, - ); - ctx.queue.write_texture( - wgpu::ImageCopyTexture { - texture: &line_strip_texture.texture, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }, - bytemuck::cast_slice(&line_strip_info_staging), - wgpu::ImageDataLayout { - offset: 0, - bytes_per_row: NonZeroU32::new( - LINE_STRIP_TEXTURE_SIZE * std::mem::size_of::() as u32, - ), - rows_per_image: None, - }, - wgpu::Extent3d { + // Upload position data. + { + // To make the data upload simpler (and have it be done in one go), we always update full rows of each of our textures + let mut position_data_staging = Vec::with_capacity(wgpu::util::align_to( + num_segments, + POSITION_TEXTURE_SIZE, + ) as usize); + // sentinel at the beginning to facilitate caps. + position_data_staging.push(LineVertex { + position: glam::vec3(f32::MAX, f32::MAX, f32::MAX), + strip_index: u32::MAX, + }); + position_data_staging.extend(vertices.iter()); + // placeholder at the end to facilitate caps. + position_data_staging.push(LineVertex { + position: glam::vec3(f32::MAX, f32::MAX, f32::MAX), + strip_index: u32::MAX, + }); + position_data_staging.extend(std::iter::repeat(gpu_data::LineVertex::zeroed()).take( + (wgpu::util::align_to(num_segments, POSITION_TEXTURE_SIZE) - num_segments) as usize, + )); + + // TODO(andreas): Use staging belt here. + ctx.queue.write_texture( + wgpu::ImageCopyTexture { + texture: &position_data_texture.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + bytemuck::cast_slice(&position_data_staging), + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new( + POSITION_TEXTURE_SIZE * std::mem::size_of::() as u32, + ), + rows_per_image: None, + }, + wgpu::Extent3d { + width: POSITION_TEXTURE_SIZE, + height: (num_segments + POSITION_TEXTURE_SIZE - 1) / POSITION_TEXTURE_SIZE, + depth_or_array_layers: 1, + }, + ); + } + + // Upload strip data. + { + let mut line_strip_info_staging = Vec::with_capacity(wgpu::util::align_to( + num_strips, + LINE_STRIP_TEXTURE_SIZE, + ) as usize); + line_strip_info_staging.extend(strips.iter().map(|line_strip| { + gpu_data::LineStripInfo { + color: line_strip.color, + radius: line_strip.radius.into(), + stippling: 0, //(line_strip.stippling.clamp(0.0, 1.0) * 255.0) as u8, + flags: line_strip.flags, + } + })); + let num_strips_padding = + (wgpu::util::align_to(num_strips, LINE_STRIP_TEXTURE_SIZE) - num_strips) as usize; + line_strip_info_staging.extend( + std::iter::repeat(gpu_data::LineStripInfo::zeroed()).take(num_strips_padding), + ); + + let strip_texture_extent = wgpu::Extent3d { width: LINE_STRIP_TEXTURE_SIZE, height: (num_strips + LINE_STRIP_TEXTURE_SIZE - 1) / LINE_STRIP_TEXTURE_SIZE, depth_or_array_layers: 1, - }, - ); + }; + + // TODO(andreas): Use staging belt here. + ctx.queue.write_texture( + wgpu::ImageCopyTexture { + texture: &line_strip_texture.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + bytemuck::cast_slice(&line_strip_info_staging), + wgpu::ImageDataLayout { + offset: 0, + bytes_per_row: NonZeroU32::new( + LINE_STRIP_TEXTURE_SIZE + * std::mem::size_of::() as u32, + ), + rows_per_image: None, + }, + strip_texture_extent, + ); + + picking_instance_ids_buffer + .extend(std::iter::repeat(Default::default()).take(num_strips_padding)); + picking_instance_ids_buffer.copy_to_texture2d( + ctx.active_frame.before_view_builder_encoder.lock().get(), + wgpu::ImageCopyTexture { + texture: &picking_instance_id_texture.texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }, + glam::uvec2(strip_texture_extent.width, strip_texture_extent.height), + ); + } let draw_data_uniform_buffer_bindings = create_and_fill_uniform_buffer_batch( ctx, @@ -530,6 +575,7 @@ impl LineDrawData { entries: smallvec![ BindGroupEntry::DefaultTextureView(position_data_texture.handle), BindGroupEntry::DefaultTextureView(line_strip_texture.handle), + BindGroupEntry::DefaultTextureView(picking_instance_id_texture.handle), draw_data_uniform_buffer_bindings[0].clone(), ], layout: line_renderer.bind_group_layout_all_lines, @@ -543,6 +589,7 @@ impl LineDrawData { entries: smallvec![ BindGroupEntry::DefaultTextureView(position_data_texture.handle), BindGroupEntry::DefaultTextureView(line_strip_texture.handle), + BindGroupEntry::DefaultTextureView(picking_instance_id_texture.handle), draw_data_uniform_buffer_bindings[1].clone(), ], layout: line_renderer.bind_group_layout_all_lines, @@ -564,6 +611,7 @@ impl LineDrawData { .0 .unwrap_or_default() .into(), + picking_object_id: batch_info.picking_object_id, end_padding: Default::default(), }), ); @@ -583,6 +631,7 @@ impl LineDrawData { .map(|(_, mask)| gpu_data::BatchUniformBuffer { world_from_obj: batch_info.world_from_obj.into(), outline_mask_ids: mask.0.unwrap_or_default().into(), + picking_object_id: batch_info.picking_object_id, end_padding: Default::default(), }) }) @@ -598,7 +647,7 @@ impl LineDrawData { let line_vertex_range_end = (start_vertex_for_next_batch + batch_info.line_vertex_count) .min(Self::MAX_NUM_VERTICES as u32); - let mut active_phases = enum_set![DrawPhase::Opaque]; + let mut active_phases = enum_set![DrawPhase::Opaque | DrawPhase::PickingLayer]; // Does the entire batch participate in the outline mask phase? if batch_info.overall_outline_mask_ids.is_some() { active_phases.insert(DrawPhase::OutlineMask); @@ -724,6 +773,16 @@ impl Renderer for LineRenderer { 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, diff --git a/crates/re_renderer/src/renderer/mod.rs b/crates/re_renderer/src/renderer/mod.rs index 7eb9714b7a2e..614edc879711 100644 --- a/crates/re_renderer/src/renderer/mod.rs +++ b/crates/re_renderer/src/renderer/mod.rs @@ -2,7 +2,10 @@ mod generic_skybox; pub use generic_skybox::GenericSkyboxDrawData; mod lines; -pub use lines::{gpu_data::LineVertex, LineBatchInfo, LineDrawData, LineStripFlags, LineStripInfo}; +pub use lines::{ + gpu_data::LineVertex, LineBatchInfo, LineDrawData, LineDrawDataError, LineStripFlags, + LineStripInfo, +}; mod point_cloud; pub use point_cloud::{ diff --git a/crates/re_renderer/src/renderer/point_cloud.rs b/crates/re_renderer/src/renderer/point_cloud.rs index 4d484fcff225..639db5a17ceb 100644 --- a/crates/re_renderer/src/renderer/point_cloud.rs +++ b/crates/re_renderer/src/renderer/point_cloud.rs @@ -269,7 +269,7 @@ impl PointCloudDrawData { let picking_instance_id_texture = ctx.gpu_resources.textures.alloc( &ctx.device, &TextureDesc { - label: "PointCloudDrawData::picking_layer_instance_id_texture".into(), + label: "PointCloudDrawData::picking_instance_id_texture".into(), format: wgpu::TextureFormat::Rg32Uint, ..position_data_texture_desc }, diff --git a/crates/re_viewer/src/ui/space_view.rs b/crates/re_viewer/src/ui/space_view.rs index 39ac2923d478..ade8ab69aa3e 100644 --- a/crates/re_viewer/src/ui/space_view.rs +++ b/crates/re_viewer/src/ui/space_view.rs @@ -1,5 +1,5 @@ use re_arrow_store::Timeline; -use re_data_store::{EntityPath, EntityTree, InstancePath, TimeInt}; +use re_data_store::{EntityPath, EntityPropertyMap, EntityTree, InstancePath, TimeInt}; use re_renderer::{GpuReadbackIdentifier, ScreenshotProcessor}; use crate::{ @@ -249,8 +249,15 @@ impl SpaceView { self.view_state .state_spatial .update_object_property_heuristics(ctx, &mut self.data_blueprint); - self.view_state - .ui_spatial(ctx, ui, &self.space_path, scene, self.id, highlights); + self.view_state.ui_spatial( + ctx, + ui, + &self.space_path, + scene, + self.id, + highlights, + self.data_blueprint.data_blueprints_projected(), + ); } ViewCategory::Tensor => { @@ -333,10 +340,18 @@ impl ViewState { scene: view_spatial::SceneSpatial, space_view_id: SpaceViewId, highlights: &SpaceViewHighlights, + entity_properties: &EntityPropertyMap, ) { ui.vertical(|ui| { - self.state_spatial - .view_spatial(ctx, ui, space, scene, space_view_id, highlights); + self.state_spatial.view_spatial( + ctx, + ui, + space, + scene, + space_view_id, + highlights, + entity_properties, + ); }); } diff --git a/crates/re_viewer/src/ui/view_spatial/scene/mod.rs b/crates/re_viewer/src/ui/view_spatial/scene/mod.rs index a3f9b3fc7e72..6ef4ef4cb50c 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/mod.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/mod.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use ahash::HashMap; use re_data_store::{EntityPath, InstancePathHash}; use re_log_types::{ - component_types::{ClassId, KeypointId, Tensor}, + component_types::{ClassId, InstanceKey, KeypointId, Tensor}, MeshId, }; use re_renderer::{Color32, OutlineMaskPreference, Size}; @@ -21,7 +21,7 @@ mod picking; mod primitives; mod scene_part; -pub use self::picking::{AdditionalPickingInfo, PickingContext, PickingRayHit, PickingResult}; +pub use self::picking::{PickingContext, PickingHitType, PickingRayHit, PickingResult}; pub use self::primitives::SceneSpatialPrimitives; use scene_part::ScenePart; @@ -122,17 +122,6 @@ pub struct SceneSpatial { pub space_cameras: Vec, } -fn instance_path_hash_if_interactive( - entity_path: &EntityPath, - interactive: bool, -) -> InstancePathHash { - if interactive { - InstancePathHash::entity_splat(entity_path) - } else { - InstancePathHash::NONE - } -} - pub type Keypoints = HashMap<(ClassId, i64), HashMap>; impl SceneSpatial { @@ -192,12 +181,13 @@ impl SceneSpatial { entity_path: &re_data_store::EntityPath, keypoints: Keypoints, annotations: &Arc, - interactive: bool, ) { // Generate keypoint connections if any. - let instance_path_hash = instance_path_hash_if_interactive(entity_path, interactive); - - let mut line_batch = self.primitives.line_strips.batch("keypoint connections"); + let mut line_batch = self + .primitives + .line_strips + .batch("keypoint connections") + .picking_object_id(re_renderer::PickingLayerObjectId(entity_path.hash64())); for ((class_id, _time), keypoints_in_class) in keypoints { let Some(class_description) = annotations.context.class_map.get(&class_id) else { @@ -221,7 +211,8 @@ impl SceneSpatial { .add_segment(*a, *b) .radius(Size::AUTO) .color(color) - .user_data(instance_path_hash); + // Select the entire object when clicking any of the lines. + .picking_instance_id(re_renderer::PickingLayerInstanceId(InstanceKey::SPLAT.0)); } } } diff --git a/crates/re_viewer/src/ui/view_spatial/scene/picking.rs b/crates/re_viewer/src/ui/view_spatial/scene/picking.rs index d74d80ef1ee1..59996c8e803c 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/picking.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/picking.rs @@ -1,22 +1,16 @@ //! Handles picking in 2D & 3D spaces. -use itertools::Itertools as _; - use re_data_store::InstancePathHash; use re_renderer::PickingLayerProcessor; use super::{SceneSpatialPrimitives, SceneSpatialUiData}; use crate::{ - math::{line_segment_distance_sq_to_point_2d, ray_closest_t_line_segment}, misc::instance_hash_conversions::instance_path_hash_from_picking_layer_id, ui::view_spatial::eye::Eye, }; #[derive(Clone)] -pub enum AdditionalPickingInfo { - /// No additional picking information. - None, - +pub enum PickingHitType { /// The hit was a textured rect at the given uv coordinates (ranging from 0 to 1) TexturedRect(glam::Vec2), @@ -34,104 +28,28 @@ pub struct PickingRayHit { /// The ray hit position may not actually be on this entity, as we allow snapping to closest entity! pub instance_path_hash: InstancePathHash, - /// Where along the picking ray the hit occurred. - pub ray_t: f32, + /// Where the ray hit the entity. + pub space_position: glam::Vec3, pub depth_offset: re_renderer::DepthOffset, /// Any additional information about the picking hit. - pub info: AdditionalPickingInfo, -} - -impl PickingRayHit { - fn from_instance_and_t(instance_path_hash: InstancePathHash, t: f32) -> Self { - Self { - instance_path_hash, - ray_t: t, - info: AdditionalPickingInfo::None, - depth_offset: 0, - } - } - - pub fn space_position(&self, ray_in_world: &macaw::Ray3) -> glam::Vec3 { - ray_in_world.origin + ray_in_world.dir * self.ray_t - } + pub hit_type: PickingHitType, } #[derive(Clone)] pub struct PickingResult { - /// Picking ray hit for an opaque object (if any). - pub opaque_hit: Option, - - /// Picking ray hits for transparent objects, sorted from far to near. - /// If there is an opaque hit, all of them are in front of the opaque hit. - pub transparent_hits: Vec, + /// Picking ray hits. NOT sorted by distance but rather by source of picking. + /// + /// Typically there is only one hit, but there might be several if there are transparent objects + /// or "aggressive" objects like 2D images which we always want to pick, even if they're in the background. + /// (This is very useful for 2D scenes and so far we keep this behavior in 3D for simplicity) + pub hits: Vec, } impl PickingResult { - /// Iterates over all hits from far to close. - pub fn iter_hits(&self) -> impl Iterator { - self.opaque_hit.iter().chain(self.transparent_hits.iter()) - } - - pub fn space_position(&self, ray_in_world: &macaw::Ray3) -> Option { - self.opaque_hit - .as_ref() - .or_else(|| self.transparent_hits.last()) - .map(|hit| hit.space_position(ray_in_world)) - } -} - -const RAY_T_EPSILON: f32 = f32::EPSILON; - -/// State used to build up picking results. -struct PickingState { - closest_opaque_side_ui_dist_sq: f32, - closest_opaque_pick: PickingRayHit, - transparent_hits: Vec, -} - -impl PickingState { - fn check_hit(&mut self, side_ui_dist_sq: f32, ray_hit: PickingRayHit, transparent: bool) { - let gap_to_closest_opaque = self.closest_opaque_pick.ray_t - ray_hit.ray_t; - - // Use depth offset if very close to each other in relative distance. - if gap_to_closest_opaque.abs() - < self.closest_opaque_pick.ray_t.max(ray_hit.ray_t) * RAY_T_EPSILON - { - if ray_hit.depth_offset < self.closest_opaque_pick.depth_offset { - return; - } - } else if gap_to_closest_opaque < 0.0 { - return; - } - - if side_ui_dist_sq <= self.closest_opaque_side_ui_dist_sq { - if transparent { - self.transparent_hits.push(ray_hit); - } else { - self.closest_opaque_pick = ray_hit; - self.closest_opaque_side_ui_dist_sq = side_ui_dist_sq; - } - } - } - - fn sort_and_remove_hidden_transparent(&mut self) { - // Sort from far to close - self.transparent_hits - .sort_by(|a, b| b.ray_t.partial_cmp(&a.ray_t).unwrap()); - - // Delete subset that is behind opaque hit. - if self.closest_opaque_pick.ray_t.is_finite() { - let mut num_hidden = 0; - for (i, transparent_hit) in self.transparent_hits.iter().enumerate() { - if transparent_hit.ray_t <= self.closest_opaque_pick.ray_t { - break; - } - num_hidden = i + 1; - } - self.transparent_hits.drain(0..num_hidden); - } + pub fn space_position(&self) -> Option { + self.hits.last().map(|hit| hit.space_position) } } @@ -150,12 +68,6 @@ pub struct PickingContext { /// The picking ray used. Given in the coordinates of the space the picking is performed in. pub ray_in_world: macaw::Ray3, - - /// Transformation from ui coordinates to world coordinates. - ui_from_world: glam::Mat4, - - /// Multiply with this to convert to pixels from points. - pixels_from_points: f32, } impl PickingContext { @@ -180,9 +92,7 @@ impl PickingContext { pointer_in_space2d, pointer_in_pixel: glam::vec2(pointer_in_pixel.x, pointer_in_pixel.y), pointer_in_ui: glam::vec2(pointer_in_ui.x, pointer_in_ui.y), - ui_from_world: eye.ui_from_world(*space2d_from_ui.to()), ray_in_world: eye.picking_ray(*space2d_from_ui.to(), pointer_in_space2d), - pixels_from_points, } } @@ -197,69 +107,38 @@ impl PickingContext { ) -> PickingResult { crate::profile_function!(); - let max_side_ui_dist_sq = Self::UI_INTERACTION_RADIUS * Self::UI_INTERACTION_RADIUS; + let mut hits = Vec::new(); - let mut state = PickingState { - closest_opaque_side_ui_dist_sq: max_side_ui_dist_sq, - closest_opaque_pick: PickingRayHit { - instance_path_hash: InstancePathHash::NONE, - ray_t: f32::INFINITY, - info: AdditionalPickingInfo::None, - depth_offset: 0, - }, - // Combined, sorted (and partially "hidden") by opaque results later. - transparent_hits: Vec::new(), - }; - - let SceneSpatialPrimitives { - bounding_box: _, - textured_rectangles, - textured_rectangles_ids, - line_strips, - points: _, - meshes: _, - depth_clouds: _, // no picking for depth clouds yet - any_outlines: _, - } = primitives; - - // GPU based picking. - picking_gpu( + // Start with gpu based picking as baseline. This is our prime source of picking information. + hits.extend(picking_gpu( render_ctx, gpu_readback_identifier, - &mut state, self, previous_picking_result, - ); + )); - picking_lines(self, &mut state, line_strips); - picking_textured_rects( + // We also never throw away any textured rects, even if they're behind other objects. + let mut rect_hits = picking_textured_rects( self, - &mut state, - textured_rectangles, - textured_rectangles_ids, + &primitives.textured_rectangles, + &primitives.textured_rectangles_ids, ); - picking_ui_rects(self, &mut state, ui_data); + rect_hits.sort_by(|a, b| b.depth_offset.cmp(&a.depth_offset)); + hits.extend(rect_hits); - state.sort_and_remove_hidden_transparent(); + // UI rects are overlaid on top, but we don't let them hide other picking results either. + hits.extend(picking_ui_rects(self, ui_data)); - PickingResult { - opaque_hit: state - .closest_opaque_pick - .instance_path_hash - .is_some() - .then_some(state.closest_opaque_pick), - transparent_hits: state.transparent_hits, - } + PickingResult { hits } } } fn picking_gpu( render_ctx: &re_renderer::RenderContext, gpu_readback_identifier: u64, - state: &mut PickingState, context: &PickingContext, previous_picking_result: &Option, -) { +) -> Option { crate::profile_function!(); // Only look at newest available result, discard everything else. @@ -305,97 +184,45 @@ fn picking_gpu( } if picked_id == re_renderer::PickingLayerId::default() { // Nothing found. - return; + return None; } - let ui_distance_sq = picked_on_picking_rect.distance_squared(pointer_on_picking_rect) - / (context.pixels_from_points * context.pixels_from_points); let picked_world_position = gpu_picking_result.picked_world_position(picked_on_picking_rect.as_uvec2()); - state.check_hit( - ui_distance_sq, - PickingRayHit { - instance_path_hash: instance_path_hash_from_picking_layer_id(picked_id), - // TODO(andreas): Once this is the primary path we should not awkwardly reconstruct the ray_t here. It's not entirely correct either! - ray_t: picked_world_position.distance(context.ray_in_world.origin), - depth_offset: 0, - info: AdditionalPickingInfo::GpuPickingResult, - }, - false, - ); + + Some(PickingRayHit { + instance_path_hash: instance_path_hash_from_picking_layer_id(picked_id), + space_position: picked_world_position, + depth_offset: 1, + hit_type: PickingHitType::GpuPickingResult, + }) } else { // It is possible that some frames we don't get a picking result and the frame after we get several. // We need to cache the last picking result and use it until we get a new one or the mouse leaves the screen. // (Andreas: On my mac this *actually* happens in very simple scenes, I get occasional frames with 0 and then with 2 picking results!) - if let Some(PickingResult { - opaque_hit: Some(previous_opaque_hit), - .. - }) = previous_picking_result - { - if matches!( - previous_opaque_hit.info, - AdditionalPickingInfo::GpuPickingResult - ) { - state.closest_opaque_pick = previous_opaque_hit.clone(); - } - } - } -} - -fn picking_lines( - context: &PickingContext, - state: &mut PickingState, - line_strips: &re_renderer::LineStripSeriesBuilder, -) { - crate::profile_function!(); - - for (batch, vertices) in line_strips.iter_vertices_by_batch() { - // For getting the closest point we could transform the mouse ray into the "batch space". - // However, we want to determine the closest point in *screen space*, meaning that we need to project all points. - let ui_from_batch = context.ui_from_world * batch.world_from_obj; - - for (start, end) in vertices.tuple_windows() { - // Skip unconnected tuples. - if start.strip_index != end.strip_index { - continue; - } - - let instance_hash = line_strips.strip_user_data[start.strip_index as usize]; - if instance_hash.is_none() { - continue; - } - - // TODO(emilk): take line segment radius into account - let a = ui_from_batch.project_point3(start.position); - let b = ui_from_batch.project_point3(end.position); - let side_ui_dist_sq = line_segment_distance_sq_to_point_2d( - [a.truncate(), b.truncate()], - context.pointer_in_space2d, - ); - - if side_ui_dist_sq < state.closest_opaque_side_ui_dist_sq { - let start_world = batch.world_from_obj.transform_point3(start.position); - let end_world = batch.world_from_obj.transform_point3(end.position); - let t = ray_closest_t_line_segment(&context.ray_in_world, [start_world, end_world]); - - state.check_hit( - side_ui_dist_sq, - PickingRayHit::from_instance_and_t(instance_hash, t), - false, - ); + if let Some(PickingResult { hits }) = previous_picking_result { + for previous_opaque_hit in hits.iter() { + if matches!( + previous_opaque_hit.hit_type, + PickingHitType::GpuPickingResult + ) { + return Some(previous_opaque_hit.clone()); + } } } + None } } fn picking_textured_rects( context: &PickingContext, - state: &mut PickingState, textured_rectangles: &[re_renderer::renderer::TexturedRect], textured_rectangles_ids: &[InstancePathHash], -) { +) -> Vec { crate::profile_function!(); + let mut hits = Vec::new(); + for (rect, id) in textured_rectangles .iter() .zip(textured_rectangles_ids.iter()) @@ -415,42 +242,41 @@ fn picking_textured_rects( if !intersect { continue; } - let intersection_world = context.ray_in_world.origin + context.ray_in_world.dir * t; + let intersection_world = context.ray_in_world.point_along(t); let dir_from_rect_top_left = intersection_world - rect.top_left_corner_position; let u = dir_from_rect_top_left.dot(rect.extent_u) / rect.extent_u.length_squared(); let v = dir_from_rect_top_left.dot(rect.extent_v) / rect.extent_v.length_squared(); if (0.0..=1.0).contains(&u) && (0.0..=1.0).contains(&v) { - let picking_hit = PickingRayHit { + hits.push(PickingRayHit { instance_path_hash: *id, - ray_t: t, - info: AdditionalPickingInfo::TexturedRect(glam::vec2(u, v)), + space_position: intersection_world, + hit_type: PickingHitType::TexturedRect(glam::vec2(u, v)), depth_offset: rect.depth_offset, - }; - state.check_hit(0.0, picking_hit, rect.multiplicative_tint.a() < 1.0); + }); } } + + hits } fn picking_ui_rects( context: &PickingContext, - state: &mut PickingState, ui_data: &SceneSpatialUiData, -) { +) -> Option { crate::profile_function!(); let egui_pos = egui::pos2(context.pointer_in_space2d.x, context.pointer_in_space2d.y); for (bbox, instance_hash) in &ui_data.pickable_ui_rects { - let side_ui_dist_sq = bbox.distance_sq_to_pos(egui_pos); - state.check_hit( - side_ui_dist_sq, - PickingRayHit { + if bbox.contains(egui_pos) { + // Handle only a single ui rectangle (exit right away, ignore potential overlaps) + return Some(PickingRayHit { instance_path_hash: *instance_hash, - ray_t: 0.0, - info: AdditionalPickingInfo::GuiOverlay, + space_position: context.ray_in_world.origin, + hit_type: PickingHitType::GuiOverlay, depth_offset: 0, - }, - false, - ); + }); + } } + None } diff --git a/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs b/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs index c495496b3c9a..d8748a6bb36b 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs @@ -12,7 +12,7 @@ use super::MeshSource; /// Primitives sent off to `re_renderer`. /// (Some meta information still relevant to ui setup as well) /// -/// TODO(andreas): Right now we're using `re_renderer` data structures for reading (bounding box & picking). +/// TODO(andreas): Right now we're using `re_renderer` data structures for reading (bounding box). /// In the future, this will be more limited as we're going to gpu staging data as soon as possible /// which is very slow to read. See [#594](https://github.com/rerun-io/rerun/pull/594) pub struct SceneSpatialPrimitives { @@ -24,7 +24,7 @@ pub struct SceneSpatialPrimitives { pub textured_rectangles_ids: Vec, pub textured_rectangles: Vec, - pub line_strips: LineStripSeriesBuilder, + pub line_strips: LineStripSeriesBuilder, pub points: PointCloudBuilder, pub meshes: Vec, pub depth_clouds: DepthClouds, @@ -177,7 +177,13 @@ impl SceneSpatialPrimitives { let line_radius = re_renderer::Size::new_scene(axis_length * 0.05); let origin = transform.translation(); - let mut line_batch = self.line_strips.batch("origin axis"); + let picking_layer_id = picking_layer_id_from_instance_path_hash(instance_path_hash); + + let mut line_batch = self + .line_strips + .batch("origin axis") + .picking_object_id(picking_layer_id.object); + line_batch .add_segment( origin, @@ -186,7 +192,7 @@ impl SceneSpatialPrimitives { .radius(line_radius) .color(AXIS_COLOR_X) .flags(LineStripFlags::CAP_END_TRIANGLE | LineStripFlags::CAP_START_ROUND) - .user_data(instance_path_hash); + .picking_instance_id(picking_layer_id.instance); line_batch .add_segment( origin, @@ -195,7 +201,7 @@ impl SceneSpatialPrimitives { .radius(line_radius) .color(AXIS_COLOR_Y) .flags(LineStripFlags::CAP_END_TRIANGLE | LineStripFlags::CAP_START_ROUND) - .user_data(instance_path_hash); + .picking_instance_id(picking_layer_id.instance); line_batch .add_segment( origin, @@ -204,6 +210,6 @@ impl SceneSpatialPrimitives { .radius(line_radius) .color(AXIS_COLOR_Z) .flags(LineStripFlags::CAP_END_TRIANGLE | LineStripFlags::CAP_START_ROUND) - .user_data(instance_path_hash); + .picking_instance_id(picking_layer_id.instance); } } diff --git a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/arrows3d.rs b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/arrows3d.rs index eb1cecb63b26..f40ca9025684 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/arrows3d.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/arrows3d.rs @@ -1,5 +1,5 @@ use glam::Mat4; -use re_data_store::{EntityPath, EntityProperties}; +use re_data_store::EntityPath; use re_log_types::{ component_types::{ColorRGBA, InstanceKey, Label, Radius}, Arrow3D, Component, @@ -12,16 +12,13 @@ use crate::{ ui::{scene::SceneQuery, view_spatial::SceneSpatial, DefaultColor}, }; -use super::{instance_path_hash_for_picking, ScenePart}; +use super::{instance_key_to_picking_id, ScenePart}; pub struct Arrows3DPart; impl Arrows3DPart { - #[allow(clippy::too_many_arguments)] fn process_entity_view( scene: &mut SceneSpatial, - _query: &SceneQuery<'_>, - props: &EntityProperties, entity_view: &EntityView, ent_path: &EntityPath, world_from_obj: Mat4, @@ -39,21 +36,14 @@ impl Arrows3DPart { .line_strips .batch("arrows") .world_from_obj(world_from_obj) - .outline_mask_ids(entity_highlight.overall); + .outline_mask_ids(entity_highlight.overall) + .picking_object_id(re_renderer::PickingLayerObjectId(ent_path.hash64())); let visitor = |instance_key: InstanceKey, arrow: Arrow3D, color: Option, radius: Option, _label: Option