diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 62624d0e0bd86..286f91fc6b26b 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -29,4 +29,4 @@ jobs: with: mode: minimum count: 1 - labels: "πŸ“Š analytics, πŸͺ³ bug, πŸ§‘β€πŸ’» dev experience, πŸ“– documentation, πŸ’¬ discussion, examples, πŸ“‰ performance, 🐍 python API, ⛃ re_datastore, πŸ“Ί re_viewer, πŸ”Ί re_renderer, β›΄ release, πŸ¦€ rust SDK, πŸ”¨ testing, ui, πŸ•ΈοΈ web" + labels: "πŸ“Š analytics, πŸͺ³ bug, πŸ§‘β€πŸ’» dev experience, πŸ“– documentation, πŸ’¬ discussion, examples, πŸ“‰ performance, 🐍 python API, ⛃ re_datastore, πŸ“Ί re_viewer, πŸ”Ί re_renderer, 🚜 refactor, β›΄ release, πŸ¦€ rust SDK, πŸ”¨ testing, ui, πŸ•ΈοΈ web" diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 80c2f14c10051..60e5292fa8662 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -216,6 +216,17 @@ jobs: - name: Run tests run: cd rerun_py/tests && pytest + - name: Install requriements for e2e test + run: | + pip install -r examples/python/api_demo/requirements.txt + pip install -r examples/python/car/requirements.txt + pip install -r examples/python/multithreading/requirements.txt + pip install -r examples/python/plots/requirements.txt + pip install -r examples/python/text_logging/requirements.txt + + - name: Run e2e test + run: scripts/run_python_e2e_test.py --no-build # rerun-sdk is already built and installed + - name: Unpack the wheel shell: bash run: | diff --git a/Cargo.lock b/Cargo.lock index 7e2ef38c3da1f..587df51f948fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4315,6 +4315,7 @@ dependencies = [ "re_analytics", "re_build_build_info", "re_build_info", + "re_data_store", "re_format", "re_log", "re_log_encoding", diff --git a/Cargo.toml b/Cargo.toml index 0623b83473945..6a433c6954eee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,8 @@ wgpu-hal = { version = "0.15.4", default-features = false } [profile.dev] -opt-level = 1 # Make debug builds run faster +opt-level = 1 # Make debug builds run faster +panic = "abort" # This leads to better optimizations and smaller binaries (and is the default in Wasm anyways). # Optimize all dependencies even in debug builds (does not affect workspace packages): [profile.dev.package."*"] @@ -99,6 +100,7 @@ opt-level = 2 [profile.release] # debug = true # good for profilers +panic = "abort" # This leads to better optimizations and smaller binaries (and is the default in Wasm anyways). [profile.bench] debug = true diff --git a/clippy.toml b/clippy.toml index c72661614556f..4da41d009fbca 100644 --- a/clippy.toml +++ b/clippy.toml @@ -27,6 +27,8 @@ disallowed-methods = [ "std::thread::spawn", # Use `std::thread::Builder` and name the thread "sha1::Digest::new", # SHA1 is cryptographically broken + + "std::panic::catch_unwind", # We compile with `panic = "abort"` ] # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names diff --git a/crates/re_log/src/lib.rs b/crates/re_log/src/lib.rs index 68f77b420b09b..653451a374054 100644 --- a/crates/re_log/src/lib.rs +++ b/crates/re_log/src/lib.rs @@ -35,6 +35,11 @@ pub use { setup::*, }; +/// Re-exports of other crates. +pub mod external { + pub use log; +} + /// Never log anything less serious than a `WARN` from these crates. const CRATES_AT_WARN_LEVEL: [&str; 3] = [ // wgpu crates spam a lot on info level, which is really annoying diff --git a/crates/re_log_types/src/component_types/mod.rs b/crates/re_log_types/src/component_types/mod.rs index 9867a3282b296..1ea83dd557abe 100644 --- a/crates/re_log_types/src/component_types/mod.rs +++ b/crates/re_log_types/src/component_types/mod.rs @@ -61,7 +61,7 @@ pub use size::Size3D; #[cfg(feature = "image")] pub use tensor::TensorImageError; pub use tensor::{ - Tensor, TensorCastError, TensorData, TensorDataMeaning, TensorDimension, TensorId, TensorTrait, + Tensor, TensorCastError, TensorData, TensorDataMeaning, TensorDimension, TensorId, }; pub use text_entry::TextEntry; pub use transform::{Pinhole, Rigid3, Transform}; diff --git a/crates/re_log_types/src/component_types/point.rs b/crates/re_log_types/src/component_types/point.rs index 764aa96b8b760..934a69b076fc1 100644 --- a/crates/re_log_types/src/component_types/point.rs +++ b/crates/re_log_types/src/component_types/point.rs @@ -64,6 +64,14 @@ impl From for glam::Vec2 { } } +#[cfg(feature = "glam")] +impl From for glam::Vec3 { + #[inline] + fn from(pt: Point2D) -> Self { + Self::new(pt.x, pt.y, 0.0) + } +} + /// A point in 3D space. /// /// ``` diff --git a/crates/re_log_types/src/component_types/tensor.rs b/crates/re_log_types/src/component_types/tensor.rs index 18dccea0399ce..19118545eb52a 100644 --- a/crates/re_log_types/src/component_types/tensor.rs +++ b/crates/re_log_types/src/component_types/tensor.rs @@ -9,18 +9,6 @@ use crate::{TensorDataType, TensorElement}; use super::arrow_convert_shims::BinaryBuffer; -pub trait TensorTrait { - fn id(&self) -> TensorId; - fn shape(&self) -> &[TensorDimension]; - fn num_dim(&self) -> usize; - fn is_shaped_like_an_image(&self) -> bool; - fn is_vector(&self) -> bool; - fn meaning(&self) -> TensorDataMeaning; - fn get(&self, index: &[u64]) -> Option; - fn dtype(&self) -> TensorDataType; - fn size_in_bytes(&self) -> usize; -} - // ---------------------------------------------------------------------------- /// A unique id per [`Tensor`]. @@ -365,23 +353,23 @@ pub struct Tensor { pub meter: Option, } -impl TensorTrait for Tensor { +impl Tensor { #[inline] - fn id(&self) -> TensorId { + pub fn id(&self) -> TensorId { self.tensor_id } #[inline] - fn shape(&self) -> &[TensorDimension] { + pub fn shape(&self) -> &[TensorDimension] { self.shape.as_slice() } #[inline] - fn num_dim(&self) -> usize { + pub fn num_dim(&self) -> usize { self.shape.len() } - fn is_shaped_like_an_image(&self) -> bool { + pub fn is_shaped_like_an_image(&self) -> bool { self.num_dim() == 2 || self.num_dim() == 3 && { matches!( @@ -393,17 +381,17 @@ impl TensorTrait for Tensor { } #[inline] - fn is_vector(&self) -> bool { + pub fn is_vector(&self) -> bool { let shape = &self.shape; shape.len() == 1 || { shape.len() == 2 && (shape[0].size == 1 || shape[1].size == 1) } } #[inline] - fn meaning(&self) -> TensorDataMeaning { + pub fn meaning(&self) -> TensorDataMeaning { self.meaning } - fn get(&self, index: &[u64]) -> Option { + pub fn get(&self, index: &[u64]) -> Option { let mut stride: usize = 1; let mut offset: usize = 0; for (TensorDimension { size, .. }, index) in self.shape.iter().zip(index).rev() { @@ -429,11 +417,11 @@ impl TensorTrait for Tensor { } } - fn dtype(&self) -> TensorDataType { + pub fn dtype(&self) -> TensorDataType { self.data.dtype() } - fn size_in_bytes(&self) -> usize { + pub fn size_in_bytes(&self) -> usize { self.data.size_in_bytes() } } diff --git a/crates/re_renderer/examples/2d.rs b/crates/re_renderer/examples/2d.rs index cb4c25035645e..a9ea6e7e56383 100644 --- a/crates/re_renderer/examples/2d.rs +++ b/crates/re_renderer/examples/2d.rs @@ -149,7 +149,7 @@ impl framework::Example for Render2D { // Moving the windows to a high dpi screen makes the second one bigger. // Also, it looks different under perspective projection. // The third point is automatic thickness which is determined by the point renderer implementation. - let mut point_cloud_builder = PointCloudBuilder::<()>::new(re_ctx); + let mut point_cloud_builder = PointCloudBuilder::new(re_ctx); point_cloud_builder .batch("points") .add_points_2d( diff --git a/crates/re_renderer/examples/depth_cloud.rs b/crates/re_renderer/examples/depth_cloud.rs index fb6a18821ace0..ce80f17594c54 100644 --- a/crates/re_renderer/examples/depth_cloud.rs +++ b/crates/re_renderer/examples/depth_cloud.rs @@ -98,7 +98,7 @@ impl RenderDepthClouds { }) .multiunzip(); - let mut builder = PointCloudBuilder::<()>::new(re_ctx); + let mut builder = PointCloudBuilder::new(re_ctx); builder .batch("backprojected point cloud") .add_points(num_points as _, points.into_iter()) diff --git a/crates/re_renderer/examples/framework.rs b/crates/re_renderer/examples/framework.rs index 65d16fa3cf30a..47e0b42a32129 100644 --- a/crates/re_renderer/examples/framework.rs +++ b/crates/re_renderer/examples/framework.rs @@ -210,10 +210,10 @@ impl Application { Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. - } => self.example.on_cursor_moved(glam::uvec2( - position.x.round() as u32, - position.y.round() as u32, - )), + } => self + .example + // Don't round the position: The entire range from 0 to excluding 1 should fall into pixel coordinate 0! + .on_cursor_moved(glam::uvec2(position.x as u32, position.y as u32)), Event::WindowEvent { event: WindowEvent::ScaleFactorChanged { diff --git a/crates/re_renderer/examples/multiview.rs b/crates/re_renderer/examples/multiview.rs index 24c76d4c9d5f4..35cb61cd23691 100644 --- a/crates/re_renderer/examples/multiview.rs +++ b/crates/re_renderer/examples/multiview.rs @@ -316,7 +316,7 @@ impl Example for Multiview { let skybox = GenericSkyboxDrawData::new(re_ctx); let lines = build_lines(re_ctx, seconds_since_startup); - let mut builder = PointCloudBuilder::<()>::new(re_ctx); + let mut builder = PointCloudBuilder::new(re_ctx); builder .batch("Random Points") .world_from_obj(glam::Mat4::from_rotation_x(seconds_since_startup)) diff --git a/crates/re_renderer/examples/picking.rs b/crates/re_renderer/examples/picking.rs index 5344e044d26d0..f304b241ed1ad 100644 --- a/crates/re_renderer/examples/picking.rs +++ b/crates/re_renderer/examples/picking.rs @@ -157,7 +157,7 @@ impl framework::Example for Picking { .schedule_picking_rect(re_ctx, picking_rect, READBACK_IDENTIFIER, (), false) .unwrap(); - let mut point_builder = PointCloudBuilder::<()>::new(re_ctx); + let mut point_builder = PointCloudBuilder::new(re_ctx); for (i, point_set) in self.point_sets.iter().enumerate() { point_builder .batch(format!("Random Points {i}")) diff --git a/crates/re_renderer/src/draw_phases/picking_layer.rs b/crates/re_renderer/src/draw_phases/picking_layer.rs index 69b125529b2de..dc5cf38f033f3 100644 --- a/crates/re_renderer/src/draw_phases/picking_layer.rs +++ b/crates/re_renderer/src/draw_phases/picking_layer.rs @@ -224,7 +224,7 @@ impl PickingLayerProcessor { DepthReadbackWorkaround::new(ctx, picking_rect.extent, picking_depth_target.handle) }); - let rect_min = picking_rect.top_left_corner.as_vec2(); + let rect_min = picking_rect.left_top.as_vec2(); let rect_max = rect_min + picking_rect.extent.as_vec2(); let screen_resolution = screen_resolution.as_vec2(); // y axis is flipped in NDC, therefore we need to flip the y axis of the rect. @@ -232,10 +232,10 @@ impl PickingLayerProcessor { pixel_coord_to_ndc(glam::vec2(rect_min.x, rect_max.y), screen_resolution); let rect_max_ndc = pixel_coord_to_ndc(glam::vec2(rect_max.x, rect_min.y), screen_resolution); - let rect_center_ndc = (rect_min_ndc + rect_max_ndc) * 0.5; - let cropped_projection_from_projection = - glam::Mat4::from_scale(2.0 / (rect_max_ndc - rect_min_ndc).extend(1.0)) - * glam::Mat4::from_translation(-rect_center_ndc.extend(0.0)); + let scale = 2.0 / (rect_max_ndc - rect_min_ndc); + let translation = -0.5 * (rect_min_ndc + rect_max_ndc); + let cropped_projection_from_projection = glam::Mat4::from_scale(scale.extend(1.0)) + * glam::Mat4::from_translation(translation.extend(0.0)); // Setup frame uniform buffer let previous_projection_from_world: glam::Mat4 = diff --git a/crates/re_renderer/src/point_cloud_builder.rs b/crates/re_renderer/src/point_cloud_builder.rs index 84ef0e0187b9f..d32435e66e236 100644 --- a/crates/re_renderer/src/point_cloud_builder.rs +++ b/crates/re_renderer/src/point_cloud_builder.rs @@ -9,23 +9,19 @@ use crate::{ }; /// Builder for point clouds, making it easy to create [`crate::renderer::PointCloudDrawData`]. -pub struct PointCloudBuilder { - // Size of `point`/color`/`per_point_user_data` must be equal. +pub struct PointCloudBuilder { + // Size of `point`/color` must be equal. pub vertices: Vec, pub(crate) color_buffer: CpuWriteGpuReadBuffer, pub(crate) picking_instance_ids_buffer: CpuWriteGpuReadBuffer, - pub user_data: Vec, pub(crate) batches: Vec, pub(crate) radius_boost_in_ui_points_for_outlines: f32, } -impl PointCloudBuilder -where - PerPointUserData: Default + Copy, -{ +impl PointCloudBuilder { pub fn new(ctx: &RenderContext) -> Self { const RESERVE_SIZE: usize = 512; @@ -48,7 +44,6 @@ where vertices: Vec::with_capacity(RESERVE_SIZE), color_buffer, picking_instance_ids_buffer, - user_data: Vec::with_capacity(RESERVE_SIZE), batches: Vec::with_capacity(16), radius_boost_in_ui_points_for_outlines: 0.0, } @@ -65,10 +60,7 @@ where /// Start of a new batch. #[inline] - pub fn batch( - &mut self, - label: impl Into, - ) -> PointCloudBatchBuilder<'_, PerPointUserData> { + pub fn batch(&mut self, label: impl Into) -> PointCloudBatchBuilder<'_> { self.batches.push(PointCloudBatchInfo { label: label.into(), world_from_obj: glam::Mat4::IDENTITY, @@ -105,30 +97,6 @@ where }) } - // Iterate over all batches, yielding the batch info and a point vertex iterator zipped with its user data. - pub fn iter_vertices_and_userdata_by_batch( - &self, - ) -> impl Iterator< - Item = ( - &PointCloudBatchInfo, - impl Iterator, - ), - > { - let mut vertex_offset = 0; - self.batches.iter().map(move |batch| { - let out = ( - batch, - self.vertices - .iter() - .zip(self.user_data.iter()) - .skip(vertex_offset) - .take(batch.point_count as usize), - ); - vertex_offset += batch.point_count as usize; - out - }) - } - /// Finalizes the builder and returns a point cloud draw data with all the points added so far. pub fn to_draw_data( self, @@ -138,16 +106,9 @@ where } } -pub struct PointCloudBatchBuilder<'a, PerPointUserData>( - &'a mut PointCloudBuilder, -) -where - PerPointUserData: Default + Copy; +pub struct PointCloudBatchBuilder<'a>(&'a mut PointCloudBuilder); -impl<'a, PerPointUserData> Drop for PointCloudBatchBuilder<'a, PerPointUserData> -where - PerPointUserData: Default + Copy, -{ +impl<'a> Drop for PointCloudBatchBuilder<'a> { fn drop(&mut self) { // Remove batch again if it wasn't actually used. if self.0.batches.last().unwrap().point_count == 0 { @@ -157,10 +118,7 @@ where } } -impl<'a, PerPointUserData> PointCloudBatchBuilder<'a, PerPointUserData> -where - PerPointUserData: Default + Copy, -{ +impl<'a> PointCloudBatchBuilder<'a> { #[inline] fn batch_mut(&mut self) -> &mut PointCloudBatchInfo { self.0 @@ -200,13 +158,6 @@ where self.0.vertices.len() - self.0.picking_instance_ids_buffer.num_written(), )); } - - if self.0.user_data.len() < self.0.vertices.len() { - self.0.user_data.extend( - std::iter::repeat(PerPointUserData::default()) - .take(self.0.vertices.len() - self.0.user_data.len()), - ); - } } #[inline] @@ -222,7 +173,7 @@ where &mut self, size_hint: usize, positions: impl Iterator, - ) -> PointsBuilder<'_, PerPointUserData> { + ) -> PointsBuilder<'_> { // TODO(jleibs): Figure out if we can plumb-through proper support for `Iterator::size_hints()` // or potentially make `FixedSizedIterator` work correctly. This should be possible size the // underlying arrow structures are of known-size, but carries some complexity with the amount of @@ -232,7 +183,6 @@ where self.extend_defaults(); debug_assert_eq!(self.0.vertices.len(), self.0.color_buffer.num_written()); - debug_assert_eq!(self.0.vertices.len(), self.0.user_data.len()); let old_size = self.0.vertices.len(); @@ -245,8 +195,6 @@ where let num_points = self.0.vertices.len() - old_size; self.batch_mut().point_count += num_points as u32; - self.0.user_data.reserve(num_points); - let new_range = old_size..self.0.vertices.len(); let max_points = self.0.vertices.len(); @@ -256,7 +204,6 @@ where max_points, colors: &mut self.0.color_buffer, picking_instance_ids: &mut self.0.picking_instance_ids_buffer, - user_data: &mut self.0.user_data, additional_outline_mask_ids: &mut self .0 .batches @@ -267,36 +214,6 @@ where } } - #[inline] - pub fn add_point(&mut self, position: glam::Vec3) -> PointBuilder<'_, PerPointUserData> { - self.extend_defaults(); - - debug_assert_eq!(self.0.vertices.len(), self.0.color_buffer.num_written()); - debug_assert_eq!(self.0.vertices.len(), self.0.user_data.len()); - - let vertex_index = self.0.vertices.len() as u32; - self.0.vertices.push(PointCloudVertex { - position, - radius: Size::AUTO, - }); - self.0.user_data.push(Default::default()); - self.batch_mut().point_count += 1; - - PointBuilder { - vertex: self.0.vertices.last_mut().unwrap(), - color: &mut self.0.color_buffer, - user_data: self.0.user_data.last_mut().unwrap(), - vertex_index, - additional_outline_mask_ids: &mut self - .0 - .batches - .last_mut() - .unwrap() - .additional_outline_mask_ids_vertex_ranges, - outline_mask_id: OutlineMaskPreference::NONE, - } - } - /// Adds several 2D points. Uses an autogenerated depth value, the same for all points passed. /// /// Params: @@ -308,16 +225,10 @@ where &mut self, size_hint: usize, positions: impl Iterator, - ) -> PointsBuilder<'_, PerPointUserData> { + ) -> PointsBuilder<'_> { self.add_points(size_hint, positions.map(|p| p.extend(0.0))) } - /// Adds a single 2D point. Uses an autogenerated depth value. - #[inline] - pub fn add_point_2d(&mut self, position: glam::Vec2) -> PointBuilder<'_, PerPointUserData> { - self.add_point(position.extend(0.0)) - } - /// Set flags for this batch. pub fn flags(mut self, flags: PointCloudBatchFlags) -> Self { self.batch_mut().flags = flags; @@ -330,73 +241,17 @@ where } } -// TODO(andreas): Should remove single-point builder, practically this never makes sense as we're almost always dealing with arrays of points. -pub struct PointBuilder<'a, PerPointUserData> { - vertex: &'a mut PointCloudVertex, - color: &'a mut CpuWriteGpuReadBuffer, - user_data: &'a mut PerPointUserData, - vertex_index: u32, - additional_outline_mask_ids: &'a mut Vec<(std::ops::Range, OutlineMaskPreference)>, - outline_mask_id: OutlineMaskPreference, -} - -impl<'a, PerPointUserData> PointBuilder<'a, PerPointUserData> -where - PerPointUserData: Clone, -{ - #[inline] - pub fn radius(self, radius: Size) -> Self { - self.vertex.radius = radius; - self - } - - /// This mustn't call this more than once. - #[inline] - pub fn color(self, color: Color32) -> Self { - self.color.push(color); - self - } - - pub fn user_data(self, data: PerPointUserData) -> Self { - *self.user_data = data; - self - } - - /// Pushes additional outline mask ids for this point - /// - /// Prefer the `overall_outline_mask_ids` setting to set the outline mask ids for the entire batch whenever possible! - pub fn outline_mask_id(mut self, outline_mask_id: OutlineMaskPreference) -> Self { - self.outline_mask_id = outline_mask_id; - self - } -} - -impl<'a, PerPointUserData> Drop for PointBuilder<'a, PerPointUserData> { - fn drop(&mut self) { - if self.outline_mask_id.is_some() { - self.additional_outline_mask_ids.push(( - self.vertex_index..self.vertex_index + 1, - self.outline_mask_id, - )); - } - } -} - -pub struct PointsBuilder<'a, PerPointUserData> { +pub struct PointsBuilder<'a> { // Vertices is a slice, which radii will update vertices: &'a mut [PointCloudVertex], max_points: usize, colors: &'a mut CpuWriteGpuReadBuffer, picking_instance_ids: &'a mut CpuWriteGpuReadBuffer, - user_data: &'a mut Vec, additional_outline_mask_ids: &'a mut Vec<(std::ops::Range, OutlineMaskPreference)>, start_vertex_index: u32, } -impl<'a, PerPointUserData> PointsBuilder<'a, PerPointUserData> -where - PerPointUserData: Clone, -{ +impl<'a> PointsBuilder<'a> { /// Assigns radii to all points. /// /// This mustn't call this more than once. @@ -440,19 +295,6 @@ where self } - /// Assigns user data for all points in this builder. - /// - /// This mustn't call this more than once. - /// - /// User data is currently not available on the GPU. - #[inline] - pub fn user_data(self, data: impl Iterator) -> Self { - crate::profile_function!(); - self.user_data - .extend(data.take(self.max_points - self.user_data.len())); - self - } - /// Pushes additional outline mask ids for a specific range of points. /// The range is relative to this builder's range, not the entire batch. /// diff --git a/crates/re_renderer/src/rect.rs b/crates/re_renderer/src/rect.rs index 60c48ea82ae41..8b70e81ac357e 100644 --- a/crates/re_renderer/src/rect.rs +++ b/crates/re_renderer/src/rect.rs @@ -4,7 +4,7 @@ #[derive(Clone, Copy, Debug)] pub struct IntRect { /// The top left corner of the rectangle. - pub top_left_corner: glam::IVec2, + pub left_top: glam::IVec2, /// The size of the rectangle. pub extent: glam::UVec2, @@ -14,23 +14,23 @@ impl IntRect { #[inline] pub fn from_middle_and_extent(middle: glam::IVec2, size: glam::UVec2) -> Self { Self { - top_left_corner: middle - size.as_ivec2() / 2, + left_top: middle - size.as_ivec2() / 2, extent: size, } } #[inline] - pub fn width(&self) -> u32 { + pub fn width(self) -> u32 { self.extent.x } #[inline] - pub fn height(&self) -> u32 { - self.extent.x + pub fn height(self) -> u32 { + self.extent.y } #[inline] - pub fn wgpu_extent(&self) -> wgpu::Extent3d { + pub fn wgpu_extent(self) -> wgpu::Extent3d { wgpu::Extent3d { width: self.extent.x, height: self.extent.y, diff --git a/crates/re_renderer/src/renderer/debug_overlay.rs b/crates/re_renderer/src/renderer/debug_overlay.rs index 276f8fc413dac..6e615cd4a7106 100644 --- a/crates/re_renderer/src/renderer/debug_overlay.rs +++ b/crates/re_renderer/src/renderer/debug_overlay.rs @@ -93,7 +93,7 @@ impl DebugOverlayDrawData { "DebugOverlayDrawData".into(), gpu_data::DebugOverlayUniformBuffer { screen_resolution: screen_resolution.as_vec2().into(), - position_in_pixel: overlay_rect.top_left_corner.as_vec2().into(), + position_in_pixel: overlay_rect.left_top.as_vec2().into(), extent_in_pixel: overlay_rect.extent.as_vec2().into(), mode: mode as u32, _padding: 0, diff --git a/crates/re_renderer/src/renderer/point_cloud.rs b/crates/re_renderer/src/renderer/point_cloud.rs index 1e9bfb77a578a..15059d82fd04d 100644 --- a/crates/re_renderer/src/renderer/point_cloud.rs +++ b/crates/re_renderer/src/renderer/point_cloud.rs @@ -173,9 +173,9 @@ impl PointCloudDrawData { /// Number of vertices and colors has to be equal. /// /// If no batches are passed, all points are assumed to be in a single batch with identity transform. - pub fn new( + pub fn new( ctx: &mut RenderContext, - mut builder: PointCloudBuilder, + mut builder: PointCloudBuilder, ) -> Result { crate::profile_function!(); diff --git a/crates/re_sdk/src/lib.rs b/crates/re_sdk/src/lib.rs index 709412d6601fb..ce3f2baf50094 100644 --- a/crates/re_sdk/src/lib.rs +++ b/crates/re_sdk/src/lib.rs @@ -75,8 +75,7 @@ pub mod components { EncodedMesh3D, InstanceKey, KeypointId, Label, LineStrip2D, LineStrip3D, Mat3x3, Mesh3D, MeshFormat, MeshId, Pinhole, Point2D, Point3D, Quaternion, Radius, RawMesh3D, Rect2D, Rigid3, Scalar, ScalarPlotProps, Size3D, Tensor, TensorData, TensorDataMeaning, - TensorDimension, TensorId, TensorTrait, TextEntry, Transform, Vec2D, Vec3D, Vec4D, - ViewCoordinates, + TensorDimension, TensorId, TextEntry, Transform, Vec2D, Vec3D, Vec4D, ViewCoordinates, }; } diff --git a/crates/re_sdk_comms/src/server.rs b/crates/re_sdk_comms/src/server.rs index fd1ceca50ea07..75f766d40b7c1 100644 --- a/crates/re_sdk_comms/src/server.rs +++ b/crates/re_sdk_comms/src/server.rs @@ -158,7 +158,8 @@ async fn run_client( let msg = crate::decode_log_msg(&packet)?; if matches!(msg, LogMsg::Goodbye(_)) { - re_log::debug!("Client sent goodbye message."); + re_log::debug!("Received goodbye message."); + tx.send(msg)?; return Ok(()); } diff --git a/crates/re_tensor_ops/tests/tensor_tests.rs b/crates/re_tensor_ops/tests/tensor_tests.rs index f13893c3f58eb..c04cd4ebad983 100644 --- a/crates/re_tensor_ops/tests/tensor_tests.rs +++ b/crates/re_tensor_ops/tests/tensor_tests.rs @@ -1,5 +1,5 @@ use re_log_types::component_types::{ - Tensor, TensorCastError, TensorData, TensorDataMeaning, TensorDimension, TensorId, TensorTrait, + Tensor, TensorCastError, TensorData, TensorDataMeaning, TensorDimension, TensorId, }; #[test] diff --git a/crates/re_viewer/src/app.rs b/crates/re_viewer/src/app.rs index 897901b79e436..b001c12fbbefb 100644 --- a/crates/re_viewer/src/app.rs +++ b/crates/re_viewer/src/app.rs @@ -44,6 +44,7 @@ enum TimeControlCommand { #[derive(Clone, Copy, Default)] pub struct StartupOptions { pub memory_limit: re_memory::MemoryLimit, + pub persist_state: bool, } // ---------------------------------------------------------------------------- @@ -118,9 +119,13 @@ impl App { ); } - let state: AppState = storage - .and_then(|storage| eframe::get_value(storage, eframe::APP_KEY)) - .unwrap_or_default(); + let state: AppState = if startup_options.persist_state { + storage + .and_then(|storage| eframe::get_value(storage, eframe::APP_KEY)) + .unwrap_or_default() + } else { + AppState::default() + }; let mut analytics = ViewerAnalytics::new(); analytics.on_viewer_started(&build_info, app_env); @@ -414,7 +419,9 @@ impl eframe::App for App { } fn save(&mut self, storage: &mut dyn eframe::Storage) { - eframe::set_value(storage, eframe::APP_KEY, &self.state); + if self.startup_options.persist_state { + eframe::set_value(storage, eframe::APP_KEY, &self.state); + } } fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame) { diff --git a/crates/re_viewer/src/misc/caches/mod.rs b/crates/re_viewer/src/misc/caches/mod.rs index db61a56109e25..e14d6968493ec 100644 --- a/crates/re_viewer/src/misc/caches/mod.rs +++ b/crates/re_viewer/src/misc/caches/mod.rs @@ -2,7 +2,7 @@ mod mesh_cache; mod tensor_decode_cache; mod tensor_image_cache; -use re_log_types::component_types::{self, TensorTrait}; +use re_log_types::component_types::{self}; pub use tensor_image_cache::ColoredTensorView; /// Does memoization of different things for the immediate mode UI. diff --git a/crates/re_viewer/src/misc/caches/tensor_decode_cache.rs b/crates/re_viewer/src/misc/caches/tensor_decode_cache.rs index f555ef2d8a53d..e942293e5f664 100644 --- a/crates/re_viewer/src/misc/caches/tensor_decode_cache.rs +++ b/crates/re_viewer/src/misc/caches/tensor_decode_cache.rs @@ -1,4 +1,4 @@ -use re_log_types::component_types::{Tensor, TensorDimension, TensorId, TensorTrait}; +use re_log_types::component_types::{Tensor, TensorDimension, TensorId}; #[derive(thiserror::Error, Clone, Debug)] pub enum TensorDecodeError { diff --git a/crates/re_viewer/src/misc/caches/tensor_image_cache.rs b/crates/re_viewer/src/misc/caches/tensor_image_cache.rs index c9856bee244bd..b96ba4ecd1491 100644 --- a/crates/re_viewer/src/misc/caches/tensor_image_cache.rs +++ b/crates/re_viewer/src/misc/caches/tensor_image_cache.rs @@ -4,7 +4,7 @@ use egui::{Color32, ColorImage}; use egui_extras::RetainedImage; use image::DynamicImage; use re_log_types::{ - component_types::{self, ClassId, Tensor, TensorData, TensorDataMeaning, TensorTrait}, + component_types::{self, ClassId, Tensor, TensorData, TensorDataMeaning}, RowId, }; use re_renderer::{ diff --git a/crates/re_viewer/src/ui/data_ui/image.rs b/crates/re_viewer/src/ui/data_ui/image.rs index 552b054d8ab26..dd55348e4c44f 100644 --- a/crates/re_viewer/src/ui/data_ui/image.rs +++ b/crates/re_viewer/src/ui/data_ui/image.rs @@ -2,7 +2,7 @@ use egui::{ColorImage, Vec2}; use itertools::Itertools as _; use re_log_types::{ - component_types::{ClassId, Tensor, TensorDataMeaning, TensorTrait}, + component_types::{ClassId, Tensor, TensorDataMeaning}, TensorElement, }; diff --git a/crates/re_viewer/src/ui/space_view_heuristics.rs b/crates/re_viewer/src/ui/space_view_heuristics.rs index f47117abb0bf7..df1da99569074 100644 --- a/crates/re_viewer/src/ui/space_view_heuristics.rs +++ b/crates/re_viewer/src/ui/space_view_heuristics.rs @@ -5,10 +5,7 @@ use itertools::Itertools; use nohash_hasher::IntSet; use re_arrow_store::{DataStore, LatestAtQuery, Timeline}; use re_data_store::{log_db::EntityDb, query_latest_single, ComponentName, EntityPath}; -use re_log_types::{ - component_types::{Tensor, TensorTrait}, - Component, -}; +use re_log_types::{component_types::Tensor, Component}; use crate::{ misc::{space_info::SpaceInfoCollection, ViewerContext}, diff --git a/crates/re_viewer/src/ui/view_bar_chart/scene.rs b/crates/re_viewer/src/ui/view_bar_chart/scene.rs index c22ca0f2ced9f..829d5138f1ac5 100644 --- a/crates/re_viewer/src/ui/view_bar_chart/scene.rs +++ b/crates/re_viewer/src/ui/view_bar_chart/scene.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use re_arrow_store::LatestAtQuery; use re_data_store::EntityPath; use re_log::warn_once; -use re_log_types::component_types::{self, InstanceKey, Tensor, TensorTrait as _}; +use re_log_types::component_types::{self, InstanceKey, Tensor}; use re_query::query_entity_with_primary; use crate::{misc::ViewerContext, ui::scene::SceneQuery}; diff --git a/crates/re_viewer/src/ui/view_category.rs b/crates/re_viewer/src/ui/view_category.rs index d37be10dd0894..9a23512e42af3 100644 --- a/crates/re_viewer/src/ui/view_category.rs +++ b/crates/re_viewer/src/ui/view_category.rs @@ -2,8 +2,7 @@ use re_arrow_store::{LatestAtQuery, TimeInt}; use re_data_store::{EntityPath, LogDb, Timeline}; use re_log_types::{ component_types::{ - Box3D, LineStrip2D, LineStrip3D, Point2D, Point3D, Rect2D, Scalar, Tensor, TensorTrait, - TextEntry, + Box3D, LineStrip2D, LineStrip3D, Point2D, Point3D, Rect2D, Scalar, Tensor, TextEntry, }, Arrow3D, Component, Mesh3D, Transform, }; diff --git a/crates/re_viewer/src/ui/view_spatial/eye.rs b/crates/re_viewer/src/ui/view_spatial/eye.rs index 101da1ca6d7c3..2874813111011 100644 --- a/crates/re_viewer/src/ui/view_spatial/eye.rs +++ b/crates/re_viewer/src/ui/view_spatial/eye.rs @@ -48,24 +48,24 @@ impl Eye { } } - pub fn ui_from_world(&self, rect: &Rect) -> Mat4 { - let aspect_ratio = rect.width() / rect.height(); + pub fn ui_from_world(&self, space2d_rect: Rect) -> Mat4 { + let aspect_ratio = space2d_rect.width() / space2d_rect.height(); let projection = if let Some(fov_y) = self.fov_y { Mat4::perspective_infinite_rh(fov_y, aspect_ratio, self.near()) } else { Mat4::orthographic_rh( - rect.left(), - rect.right(), - rect.bottom(), - rect.top(), + space2d_rect.left(), + space2d_rect.right(), + space2d_rect.bottom(), + space2d_rect.top(), self.near(), self.far(), ) }; - Mat4::from_translation(vec3(rect.center().x, rect.center().y, 0.0)) - * Mat4::from_scale(0.5 * vec3(rect.width(), -rect.height(), 1.0)) + Mat4::from_translation(vec3(space2d_rect.center().x, space2d_rect.center().y, 0.0)) + * Mat4::from_scale(0.5 * vec3(space2d_rect.width(), -space2d_rect.height(), 1.0)) * projection * self.world_from_view.inverse() } @@ -80,7 +80,7 @@ impl Eye { /// Picking ray for a given pointer in the parent space /// (i.e. prior to camera transform, "world" space) - pub fn picking_ray(&self, screen_rect: &Rect, pointer: glam::Vec2) -> macaw::Ray3 { + pub fn picking_ray(&self, screen_rect: Rect, pointer: glam::Vec2) -> macaw::Ray3 { if let Some(fov_y) = self.fov_y { let (w, h) = (screen_rect.width(), screen_rect.height()); let aspect_ratio = w / h; 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 7f7c9cb90a7c8..a3f9b3fc7e724 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/mod.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/mod.rs @@ -8,7 +8,7 @@ use re_log_types::{ }; use re_renderer::{Color32, OutlineMaskPreference, Size}; -use super::{eye::Eye, SpaceCamera3D, SpatialNavigationMode}; +use super::{SpaceCamera3D, SpatialNavigationMode}; use crate::{ misc::{mesh_loader::LoadedMesh, SpaceViewHighlights, TransformCache, ViewerContext}, ui::{ @@ -21,7 +21,7 @@ mod picking; mod primitives; mod scene_part; -pub use self::picking::{AdditionalPickingInfo, PickingRayHit, PickingResult}; +pub use self::picking::{AdditionalPickingInfo, PickingContext, PickingRayHit, PickingResult}; pub use self::primitives::SceneSpatialPrimitives; use scene_part::ScenePart; @@ -172,7 +172,7 @@ impl SceneSpatial { // -- // Note: Lines2DPart handles both Segments and LinesPaths since they are unified on the logging-side. &scene_part::Lines2DPart, - &scene_part::Points2DPart, + &scene_part::Points2DPart { max_labels: 10 }, // --- &scene_part::CamerasPart, ]; @@ -246,28 +246,4 @@ impl SceneSpatial { SpatialNavigationMode::ThreeD } - - #[allow(clippy::too_many_arguments)] - pub fn picking( - &self, - render_ctx: &re_renderer::RenderContext, - gpu_readback_identifier: re_renderer::GpuReadbackIdentifier, - previous_picking_result: &Option, - pointer_in_ui: glam::Vec2, - ui_rect: &egui::Rect, - eye: &Eye, - ui_interaction_radius: f32, - ) -> PickingResult { - picking::picking( - render_ctx, - gpu_readback_identifier, - previous_picking_result, - pointer_in_ui, - ui_rect, - eye, - &self.primitives, - &self.ui, - ui_interaction_radius, - ) - } } 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 ea813f460c919..d74d80ef1ee18 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/picking.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/picking.rs @@ -1,3 +1,5 @@ +//! Handles picking in 2D & 3D spaces. + use itertools::Itertools as _; use re_data_store::InstancePathHash; @@ -18,6 +20,9 @@ pub enum AdditionalPickingInfo { /// The hit was a textured rect at the given uv coordinates (ranging from 0 to 1) TexturedRect(glam::Vec2), + /// The result came from GPU based picking. + GpuPickingResult, + /// We hit a egui ui element, meaning that depth information is not usable. GuiOverlay, } @@ -36,9 +41,6 @@ pub struct PickingRayHit { /// Any additional information about the picking hit. pub info: AdditionalPickingInfo, - - /// True if this picking result came from a GPU picking pass. - pub used_gpu_picking: bool, } impl PickingRayHit { @@ -48,9 +50,12 @@ impl PickingRayHit { ray_t: t, info: AdditionalPickingInfo::None, depth_offset: 0, - used_gpu_picking: false, } } + + pub fn space_position(&self, ray_in_world: &macaw::Ray3) -> glam::Vec3 { + ray_in_world.origin + ray_in_world.dir * self.ray_t + } } #[derive(Clone)] @@ -61,33 +66,25 @@ pub struct PickingResult { /// 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, - - /// The picking ray used. Given in the coordinates of the space the picking is performed in. - picking_ray: macaw::Ray3, } impl PickingResult { - /// The space position of a given hit. - #[allow(dead_code)] - pub fn space_position(&self, hit: &PickingRayHit) -> glam::Vec3 { - self.picking_ray.origin + self.picking_ray.dir * hit.ray_t - } - /// 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; -struct PickingContext { - pointer_in_ui: glam::Vec2, - ray_in_world: macaw::Ray3, - ui_from_world: glam::Mat4, - max_side_ui_dist_sq: f32, -} - +/// State used to build up picking results. struct PickingState { closest_opaque_side_ui_dist_sq: f32, closest_opaque_pick: PickingRayHit, @@ -138,146 +135,208 @@ impl PickingState { } } -#[allow(clippy::too_many_arguments)] -pub fn picking( - render_ctx: &re_renderer::RenderContext, - gpu_readback_identifier: re_renderer::GpuReadbackIdentifier, - previous_picking_result: &Option, - pointer_in_ui: glam::Vec2, - ui_rect: &egui::Rect, - eye: &Eye, - primitives: &SceneSpatialPrimitives, - ui_data: &SceneSpatialUiData, - ui_interaction_radius: f32, -) -> PickingResult { - crate::profile_function!(); +/// Picking context in which picking is performed. +pub struct PickingContext { + /// Cursor position in the UI coordinate system. + pub pointer_in_ui: glam::Vec2, - let max_side_ui_dist_sq = ui_interaction_radius * ui_interaction_radius; - - let context = PickingContext { - pointer_in_ui, - ui_from_world: eye.ui_from_world(ui_rect), - ray_in_world: eye.picking_ray(ui_rect, pointer_in_ui), - max_side_ui_dist_sq, - }; - 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, - used_gpu_picking: false, - }, - // 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; - - picking_points(&context, &mut state, points); - picking_lines(&context, &mut state, line_strips); - picking_meshes(&context, &mut state, meshes); - picking_textured_rects( - &context, - &mut state, - textured_rectangles, - textured_rectangles_ids, - ); - picking_ui_rects(&context, &mut state, ui_data); - - // GPU based picking. - // Only look at newest available result, discard everything else. - let mut gpu_picking_result = None; - while let Some(picking_result) = - PickingLayerProcessor::next_readback_result::<()>(render_ctx, gpu_readback_identifier) - { - gpu_picking_result = Some(picking_result); - } - // TODO(andreas): Use gpu picking as fallback for now to fix meshes. Should combine instead! - if state.closest_opaque_pick.instance_path_hash == InstancePathHash::NONE { - if let Some(gpu_picking_result) = gpu_picking_result { - // TODO(andreas): Pick middle pixel for now. But we soon want to snap to the closest object using a bigger picking rect. - let pos_on_picking_rect = gpu_picking_result.rect.extent / 2; - let picked_id = gpu_picking_result.picked_id(pos_on_picking_rect); - let picked_object = instance_path_hash_from_picking_layer_id(picked_id); - - // It is old data, the object might be gone by now! - if picked_object.is_some() { - // TODO(andreas): Once this is the primary path we should not awkwardly reconstruct the ray_t here. It's entirely correct either! - state.closest_opaque_pick.ray_t = gpu_picking_result - .picked_world_position(pos_on_picking_rect) - .distance(context.ray_in_world.origin); - state.closest_opaque_pick.instance_path_hash = picked_object; - state.closest_opaque_pick.used_gpu_picking = true; - } - } 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 previous_opaque_hit.used_gpu_picking { - state.closest_opaque_pick = previous_opaque_hit.clone(); - } - } + /// Cursor position on the renderer canvas in pixels. + pub pointer_in_pixel: glam::Vec2, + + /// Cursor position in the 2D space coordinate system. + /// + /// For 3D spaces this is equal to the cursor position in pixel coordinate system. + pub pointer_in_space2d: glam::Vec2, + + /// 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 { + /// Radius in which cursor interactions may snap to the nearest object even if the cursor + /// does not hover it directly. + /// + /// Note that this needs to be scaled when zooming is applied by the virtual->visible ui rect transform. + pub const UI_INTERACTION_RADIUS: f32 = 5.0; + + pub fn new( + pointer_in_ui: egui::Pos2, + space2d_from_ui: eframe::emath::RectTransform, + ui_clip_rect: egui::Rect, + pixels_from_points: f32, + eye: &Eye, + ) -> PickingContext { + let pointer_in_space2d = space2d_from_ui.transform_pos(pointer_in_ui); + let pointer_in_space2d = glam::vec2(pointer_in_space2d.x, pointer_in_space2d.y); + let pointer_in_pixel = (pointer_in_ui - ui_clip_rect.left_top()) * pixels_from_points; + + 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, } } - state.sort_and_remove_hidden_transparent(); + /// Performs picking for a given scene. + pub fn pick( + &self, + render_ctx: &re_renderer::RenderContext, + gpu_readback_identifier: re_renderer::GpuReadbackIdentifier, + previous_picking_result: &Option, + primitives: &SceneSpatialPrimitives, + ui_data: &SceneSpatialUiData, + ) -> PickingResult { + crate::profile_function!(); + + let max_side_ui_dist_sq = Self::UI_INTERACTION_RADIUS * Self::UI_INTERACTION_RADIUS; + + 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( + render_ctx, + gpu_readback_identifier, + &mut state, + self, + previous_picking_result, + ); + + picking_lines(self, &mut state, line_strips); + picking_textured_rects( + self, + &mut state, + textured_rectangles, + textured_rectangles_ids, + ); + picking_ui_rects(self, &mut state, ui_data); + + state.sort_and_remove_hidden_transparent(); - PickingResult { - opaque_hit: state - .closest_opaque_pick - .instance_path_hash - .is_some() - .then_some(state.closest_opaque_pick), - transparent_hits: state.transparent_hits, - picking_ray: context.ray_in_world, + PickingResult { + opaque_hit: state + .closest_opaque_pick + .instance_path_hash + .is_some() + .then_some(state.closest_opaque_pick), + transparent_hits: state.transparent_hits, + } } } -fn picking_points( - context: &PickingContext, +fn picking_gpu( + render_ctx: &re_renderer::RenderContext, + gpu_readback_identifier: u64, state: &mut PickingState, - points: &re_renderer::PointCloudBuilder, + context: &PickingContext, + previous_picking_result: &Option, ) { crate::profile_function!(); - for (batch, vertex_iter) in points.iter_vertices_and_userdata_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; + // Only look at newest available result, discard everything else. + let mut gpu_picking_result = None; + while let Some(picking_result) = + PickingLayerProcessor::next_readback_result::<()>(render_ctx, gpu_readback_identifier) + { + gpu_picking_result = Some(picking_result); + } - for (point, instance_hash) in vertex_iter { - if instance_hash.is_none() { - continue; + if let Some(gpu_picking_result) = gpu_picking_result { + // First, figure out where on the rect the cursor is by now. + // (for simplicity, we assume the screen hasn't been resized) + let pointer_on_picking_rect = + context.pointer_in_pixel - gpu_picking_result.rect.left_top.as_vec2(); + // The cursor might have moved outside of the rect. Clamp it back in. + let pointer_on_picking_rect = pointer_on_picking_rect.clamp( + glam::Vec2::ZERO, + (gpu_picking_result.rect.extent - glam::UVec2::ONE).as_vec2(), + ); + + // Find closest non-zero pixel to the cursor. + let mut picked_id = re_renderer::PickingLayerId::default(); + let mut picked_on_picking_rect = glam::Vec2::ZERO; + let mut closest_rect_distance_sq = f32::INFINITY; + + for (i, id) in gpu_picking_result.picking_id_data.iter().enumerate() { + if id.object.0 != 0 { + let current_pos_on_picking_rect = glam::uvec2( + i as u32 % gpu_picking_result.rect.extent.x, + i as u32 / gpu_picking_result.rect.extent.x, + ) + .as_vec2() + + glam::vec2(0.5, 0.5); // Use pixel center for distances. + let distance_sq = + current_pos_on_picking_rect.distance_squared(pointer_on_picking_rect); + if distance_sq < closest_rect_distance_sq { + picked_on_picking_rect = current_pos_on_picking_rect; + closest_rect_distance_sq = distance_sq; + picked_id = *id; + } } + } + if picked_id == re_renderer::PickingLayerId::default() { + // Nothing found. + return; + } - // TODO(emilk): take point radius into account - let pos_in_ui = ui_from_batch.project_point3(point.position); - let dist_sq = pos_in_ui.truncate().distance_squared(context.pointer_in_ui); - if dist_sq <= state.closest_opaque_side_ui_dist_sq { - let t = context - .ray_in_world - .closest_t_to_point(batch.world_from_obj.transform_point3(point.position)); - state.check_hit( - dist_sq, - PickingRayHit::from_instance_and_t(*instance_hash, t), - false, - ); + 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, + ); + } 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(); } } } @@ -311,10 +370,10 @@ fn picking_lines( 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_ui, + context.pointer_in_space2d, ); - if side_ui_dist_sq < context.max_side_ui_dist_sq { + 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]); @@ -329,31 +388,6 @@ fn picking_lines( } } -fn picking_meshes( - context: &PickingContext, - state: &mut PickingState, - meshes: &[super::MeshSource], -) { - crate::profile_function!(); - - for mesh in meshes { - if !mesh.picking_instance_hash.is_some() { - continue; - } - let ray_in_mesh = (mesh.world_from_mesh.inverse() * context.ray_in_world).normalize(); - let t = crate::math::ray_bbox_intersect(&ray_in_mesh, mesh.mesh.bbox()); - - if t < 0.0 { - let side_ui_dist_sq = 0.0; - state.check_hit( - side_ui_dist_sq, - PickingRayHit::from_instance_and_t(mesh.picking_instance_hash, t), - false, - ); - } - } -} - fn picking_textured_rects( context: &PickingContext, state: &mut PickingState, @@ -392,7 +426,6 @@ fn picking_textured_rects( ray_t: t, info: AdditionalPickingInfo::TexturedRect(glam::vec2(u, v)), depth_offset: rect.depth_offset, - used_gpu_picking: false, }; state.check_hit(0.0, picking_hit, rect.multiplicative_tint.a() < 1.0); } @@ -406,7 +439,7 @@ fn picking_ui_rects( ) { crate::profile_function!(); - let egui_pos = egui::pos2(context.pointer_in_ui.x, context.pointer_in_ui.y); + 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( @@ -416,7 +449,6 @@ fn picking_ui_rects( ray_t: 0.0, info: AdditionalPickingInfo::GuiOverlay, depth_offset: 0, - used_gpu_picking: false, }, false, ); 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 e8a56e454b272..c495496b3c9ac 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs @@ -25,7 +25,7 @@ pub struct SceneSpatialPrimitives { pub textured_rectangles: Vec, pub line_strips: LineStripSeriesBuilder, - pub points: PointCloudBuilder, + pub points: PointCloudBuilder, pub meshes: Vec, pub depth_clouds: DepthClouds, diff --git a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs index e62501b5e0eed..62d7cde09458a 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use re_data_store::{query_latest_single, EntityPath, EntityProperties, InstancePathHash}; use re_log_types::{ - component_types::{ColorRGBA, InstanceKey, Tensor, TensorData, TensorDataMeaning, TensorTrait}, + component_types::{ColorRGBA, InstanceKey, Tensor, TensorData, TensorDataMeaning}, Component, Transform, }; use re_query::{query_primary_with_history, EntityView, QueryError}; diff --git a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/mod.rs b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/mod.rs index d4811ca9864d5..a76dbf050bf4d 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/mod.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/mod.rs @@ -11,6 +11,9 @@ mod meshes; mod points2d; mod points3d; +use std::sync::Arc; + +use ahash::HashMap; pub(crate) use arrows3d::Arrows3DPart; pub(crate) use boxes2d::Boxes2DPart; pub(crate) use boxes3d::Boxes3DPart; @@ -21,11 +24,15 @@ pub(crate) use lines3d::Lines3DPart; pub(crate) use meshes::MeshPart; pub(crate) use points2d::Points2DPart; pub(crate) use points3d::Points3DPart; +use re_log_types::component_types::{ClassId, ColorRGBA, KeypointId, Radius}; use super::SceneSpatial; use crate::{ misc::{SpaceViewHighlights, TransformCache, ViewerContext}, - ui::scene::SceneQuery, + ui::{ + annotations::ResolvedAnnotationInfo, scene::SceneQuery, view_spatial::scene::Keypoints, + Annotations, DefaultColor, + }, }; use re_data_store::{EntityPath, EntityProperties, InstancePathHash}; @@ -42,21 +49,155 @@ pub trait ScenePart { /// Computes the instance hash that should be used for picking (in turn for selecting/hover) /// -/// Takes into account the currently the object properties, currently highlighted objects, and number of instances. -pub fn instance_path_hash_for_picking( +/// TODO(andreas): Resolve the hash-for-picking when retrieving the picking result instead of doing it ahead of time here to speed up things. +/// (gpu picking would always get the "most fine grained hash" which we could then resolve to groups etc. depending on selection state) +/// Right now this is a bit hard to do since number of instances depends on the Primary. This is expected to change soon. +pub fn instance_path_hash_for_picking( ent_path: &EntityPath, instance_key: re_log_types::component_types::InstanceKey, - entity_view: &re_query::EntityView, + entity_view: &re_query::EntityView, props: &EntityProperties, any_part_selected: bool, ) -> InstancePathHash { if props.interactive { - if entity_view.num_instances() == 1 || !any_part_selected { - InstancePathHash::entity_splat(ent_path) - } else { - InstancePathHash::instance(ent_path, instance_key) - } + InstancePathHash::instance( + ent_path, + instance_key_for_picking(instance_key, entity_view, any_part_selected), + ) } else { InstancePathHash::NONE } } + +/// Computes the instance key that should be used for picking (in turn for selecting/hover) +/// +/// Assumes the entity is interactive. +/// +/// TODO(andreas): Resolve the hash-for-picking when retrieving the picking result instead of doing it ahead of time here to speed up things. +/// (gpu picking would always get the "most fine grained hash" which we could then resolve to groups etc. depending on selection state) +/// Right now this is a bit hard to do since number of instances depends on the Primary. This is expected to change soon. +pub fn instance_key_for_picking( + instance_key: re_log_types::component_types::InstanceKey, + entity_view: &re_query::EntityView, + any_part_selected: bool, +) -> re_log_types::component_types::InstanceKey { + // If no part of the entity is selected or if there is only one instance, selecting + // should select the entire entity, not the specific instance. + // (the splat key means that no particular instance is selected but all at once instead) + if entity_view.num_instances() == 1 || !any_part_selected { + re_log_types::component_types::InstanceKey::SPLAT + } else { + instance_key + } +} + +/// See [`instance_key_for_picking`] +pub fn instance_key_to_picking_id( + instance_key: re_log_types::component_types::InstanceKey, + entity_view: &re_query::EntityView, + any_part_selected: bool, +) -> re_renderer::PickingLayerInstanceId { + re_renderer::PickingLayerInstanceId( + instance_key_for_picking(instance_key, entity_view, any_part_selected).0, + ) +} + +/// Process [`ColorRGBA`] components using annotations and default colors. +pub fn process_colors<'a, Primary>( + entity_view: &'a re_query::EntityView, + ent_path: &'a EntityPath, + annotation_infos: &'a [ResolvedAnnotationInfo], +) -> Result + 'a, re_query::QueryError> +where + Primary: re_log_types::SerializableComponent + re_log_types::DeserializableComponent, + for<'b> &'b Primary::ArrayType: IntoIterator, +{ + crate::profile_function!(); + let default_color = DefaultColor::EntityPath(ent_path); + + Ok(itertools::izip!( + annotation_infos.iter(), + entity_view.iter_component::()?, + ) + .map(move |(annotation_info, color)| { + annotation_info.color(color.map(move |c| c.to_array()).as_ref(), default_color) + })) +} + +/// Process [`Radius`] components to [`re_renderer::Size`] using auto size where no radius is specified. +pub fn process_radii<'a, Primary>( + ent_path: &EntityPath, + entity_view: &'a re_query::EntityView, +) -> Result + 'a, re_query::QueryError> +where + Primary: re_log_types::SerializableComponent + re_log_types::DeserializableComponent, + for<'b> &'b Primary::ArrayType: IntoIterator, +{ + crate::profile_function!(); + let ent_path = ent_path.clone(); + Ok(entity_view.iter_component::()?.map(move |radius| { + radius.map_or(re_renderer::Size::AUTO, |r| { + if 0.0 <= r.0 && r.0.is_finite() { + re_renderer::Size::new_scene(r.0) + } else { + if r.0 < 0.0 { + re_log::warn_once!("Found negative radius in entity {ent_path}"); + } else if r.0.is_infinite() { + re_log::warn_once!("Found infinite radius in entity {ent_path}"); + } else { + re_log::warn_once!("Found NaN radius in entity {ent_path}"); + } + re_renderer::Size::AUTO + } + }) + })) +} + +/// Resolves all annotations and keypoints for the given entity view. +fn process_annotations_and_keypoints( + query: &SceneQuery<'_>, + entity_view: &re_query::EntityView, + annotations: &Arc, +) -> Result<(Vec, super::Keypoints), re_query::QueryError> +where + Primary: re_log_types::SerializableComponent + re_log_types::DeserializableComponent, + for<'b> &'b Primary::ArrayType: IntoIterator, + glam::Vec3: std::convert::From, +{ + crate::profile_function!(); + + let mut keypoints: Keypoints = HashMap::default(); + + // No need to process annotations if we don't have keypoints or class-ids + if !entity_view.has_component::() && !entity_view.has_component::() { + let resolved_annotation = annotations.class_description(None).annotation_info(); + return Ok(( + vec![resolved_annotation; entity_view.num_instances()], + keypoints, + )); + } + + let annotation_info = itertools::izip!( + entity_view.iter_primary()?, + entity_view.iter_component::()?, + entity_view.iter_component::()?, + ) + .map(|(position, keypoint_id, class_id)| { + let class_description = annotations.class_description(class_id); + + if let (Some(keypoint_id), Some(class_id), Some(position)) = + (keypoint_id, class_id, position) + { + keypoints + .entry((class_id, query.latest_at.as_i64())) + .or_insert_with(Default::default) + .insert(keypoint_id, position.into()); + class_description.annotation_info_with_keypoint(keypoint_id) + } else { + class_description.annotation_info() + } + }) + .collect(); + + Ok((annotation_info, keypoints)) +} diff --git a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/points2d.rs b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/points2d.rs index d2e86093e0567..984c1568caa13 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/points2d.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/points2d.rs @@ -1,125 +1,169 @@ use glam::Mat4; -use re_data_store::{EntityPath, EntityProperties}; +use re_data_store::{EntityPath, EntityProperties, InstancePathHash}; use re_log_types::{ component_types::{ClassId, ColorRGBA, InstanceKey, KeypointId, Label, Point2D, Radius}, Component, }; use re_query::{query_primary_with_history, EntityView, QueryError}; -use re_renderer::Size; use crate::{ misc::{SpaceViewHighlights, SpaceViewOutlineMasks, TransformCache, ViewerContext}, ui::{ + annotations::ResolvedAnnotationInfo, scene::SceneQuery, - view_spatial::{scene::Keypoints, SceneSpatial, UiLabel, UiLabelTarget}, - DefaultColor, + view_spatial::{SceneSpatial, UiLabel, UiLabelTarget}, }, }; -use super::{instance_path_hash_for_picking, ScenePart}; +use super::{ + instance_key_to_picking_id, instance_path_hash_for_picking, process_annotations_and_keypoints, + process_colors, process_radii, ScenePart, +}; -pub struct Points2DPart; +pub struct Points2DPart { + /// If the number of points in the batch is > max_labels, don't render point labels. + pub(crate) max_labels: usize, +} impl Points2DPart { + fn process_labels<'a>( + entity_view: &'a EntityView, + instance_path_hashes: &'a [InstancePathHash], + colors: &'a [egui::Color32], + annotation_infos: &'a [ResolvedAnnotationInfo], + ) -> Result + 'a, QueryError> { + let labels = itertools::izip!( + annotation_infos.iter(), + entity_view.iter_primary()?, + entity_view.iter_component::