From 6f7a2f73b8889d3210b6d67521061a939f10be83 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 11:04:54 +0200 Subject: [PATCH 1/9] introduce new Chunk zero-copy zero-deser iterators --- crates/store/re_chunk/Cargo.toml | 1 + crates/store/re_chunk/src/iter.rs | 125 +++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 4 deletions(-) diff --git a/crates/store/re_chunk/Cargo.toml b/crates/store/re_chunk/Cargo.toml index e6007cb1dbc6..8a4cd67eaa95 100644 --- a/crates/store/re_chunk/Cargo.toml +++ b/crates/store/re_chunk/Cargo.toml @@ -55,6 +55,7 @@ arrow2 = { workspace = true, features = [ "compute_filter", ] } backtrace.workspace = true +bytemuck.workspace = true document-features.workspace = true itertools.workspace = true nohash-hasher.workspace = true diff --git a/crates/store/re_chunk/src/iter.rs b/crates/store/re_chunk/src/iter.rs index 4dcc2406161e..7fe34094d970 100644 --- a/crates/store/re_chunk/src/iter.rs +++ b/crates/store/re_chunk/src/iter.rs @@ -1,13 +1,16 @@ use std::sync::Arc; use arrow2::{ - array::{Array as ArrowArray, PrimitiveArray}, + array::{ + Array as ArrowArray, FixedSizeListArray as ArrowFixedSizeListArray, + PrimitiveArray as ArrowPrimitiveArray, Utf8Array as ArrowUtf8Array, + }, Either, }; -use itertools::izip; +use itertools::{izip, Itertools}; use re_log_types::{TimeInt, Timeline}; -use re_types_core::{Component, ComponentName}; +use re_types_core::{ArrowString, Component, ComponentName}; use crate::{Chunk, ChunkTimeline, RowId}; @@ -125,6 +128,7 @@ impl Chunk { /// See also: /// * [`Self::iter_component`]. /// * [`Self::iter_primitive`]. + #[inline] pub fn iter_component_arrays( &self, component_name: &ComponentName, @@ -143,6 +147,9 @@ impl Chunk { /// Use this when working with simple arrow datatypes and performance matters (e.g. scalars, /// points, etc). /// + /// See also: + /// * [`Self::iter_primitive_array`] + /// * [`Self::iter_string`] /// * [`Self::iter_component_arrays`]. /// * [`Self::iter_component`]. #[inline] @@ -157,7 +164,7 @@ impl Chunk { let Some(values) = list_array .values() .as_any() - .downcast_ref::>() + .downcast_ref::>() else { if cfg!(debug_assertions) { panic!("downcast failed for {component_name}, data discarded"); @@ -174,6 +181,116 @@ impl Chunk { .map(move |(idx, len)| &values[idx..idx + len]), ) } + + /// Returns an iterator over the raw primitive arrays of a [`Chunk`], for a given component. + /// + /// This is a very fast path: the entire column will be downcasted at once, and then every + /// component batch will be a slice reference into that global slice. + /// Use this when working with simple arrow datatypes and performance matters (e.g. scalars, + /// points, etc). + /// + /// See also: + /// * [`Self::iter_primitive`] + /// * [`Self::iter_string`] + /// * [`Self::iter_component_arrays`]. + /// * [`Self::iter_component`]. + pub fn iter_primitive_array( + &self, + component_name: &ComponentName, + ) -> impl Iterator + '_ + where + [T; N]: bytemuck::Pod, + { + let Some(list_array) = self.components.get(component_name) else { + return Either::Left(std::iter::empty()); + }; + + let Some(fixed_size_list_array) = list_array + .values() + .as_any() + .downcast_ref::() + else { + if cfg!(debug_assertions) { + panic!("downcast failed for {component_name}, data discarded"); + } else { + re_log::error_once!("downcast failed for {component_name}, data discarded"); + } + return Either::Left(std::iter::empty()); + }; + + let Some(values) = fixed_size_list_array + .values() + .as_any() + .downcast_ref::>() + else { + if cfg!(debug_assertions) { + panic!("downcast failed for {component_name}, data discarded"); + } else { + re_log::error_once!("downcast failed for {component_name}, data discarded"); + } + return Either::Left(std::iter::empty()); + }; + + let size = fixed_size_list_array.size(); + let values = values.values().as_slice(); + + // NOTE: No need for validity checks here, `iter_offsets` already takes care of that. + Either::Right( + self.iter_component_offsets(component_name) + .map(move |(idx, len)| { + bytemuck::cast_slice(&values[idx * size..idx * size + len * size]) + }), + ) + } + + /// Returns an iterator over the raw primitive strings of a [`Chunk`], for a given component. + /// + /// This is a very fast path: the entire column will be downcasted at once, and then every + /// component batch will be a slice reference into that global slice. + /// Use this when working with simple arrow datatypes and performance matters (e.g. labels, etc). + /// + /// See also: + /// * [`Self::iter_primitive`] + /// * [`Self::iter_primitive_array`] + /// * [`Self::iter_component_arrays`]. + /// * [`Self::iter_component`]. + pub fn iter_string( + &self, + component_name: &ComponentName, + ) -> impl Iterator> + '_ { + let Some(list_array) = self.components.get(component_name) else { + return Either::Left(std::iter::empty()); + }; + + let Some(utf8_array) = list_array + .values() + .as_any() + .downcast_ref::>() + else { + if cfg!(debug_assertions) { + panic!("downcast failed for {component_name}, data discarded"); + } else { + re_log::error_once!("downcast failed for {component_name}, data discarded"); + } + return Either::Left(std::iter::empty()); + }; + + let values = utf8_array.values(); + let offsets = utf8_array.offsets(); + let lengths = utf8_array.offsets().lengths().collect_vec(); + + // NOTE: No need for validity checks here, `iter_offsets` already takes care of that. + Either::Right( + self.iter_component_offsets(component_name) + .map(move |(idx, len)| { + let offsets = &offsets.as_slice()[idx..idx + len]; + let lengths = &lengths.as_slice()[idx..idx + len]; + izip!(offsets, lengths) + .map(|(&idx, &len)| ArrowString(values.clone().sliced(idx as _, len))) + .collect_vec() + }), + ) + } } // --- From 85ca1c55ef053582c6843d5055844cf46cd6d69f Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 11:06:12 +0200 Subject: [PATCH 2/9] introduce new HybridResults zero-deser helpers --- crates/viewer/re_space_view/Cargo.toml | 2 + .../viewer/re_space_view/src/results_ext2.rs | 80 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/crates/viewer/re_space_view/Cargo.toml b/crates/viewer/re_space_view/Cargo.toml index 99453aa42b0d..488ca78f0f4d 100644 --- a/crates/viewer/re_space_view/Cargo.toml +++ b/crates/viewer/re_space_view/Cargo.toml @@ -35,5 +35,7 @@ re_viewer_context.workspace = true re_viewport_blueprint.workspace = true ahash.workspace = true +bytemuck.workspace = true egui.workspace = true +itertools.workspace = true nohash-hasher.workspace = true diff --git a/crates/viewer/re_space_view/src/results_ext2.rs b/crates/viewer/re_space_view/src/results_ext2.rs index 1c716d3b282a..cd6f842170d8 100644 --- a/crates/viewer/re_space_view/src/results_ext2.rs +++ b/crates/viewer/re_space_view/src/results_ext2.rs @@ -361,3 +361,83 @@ impl<'a> RangeResultsExt for HybridResults<'a> { } } } + +// --- + +use re_chunk::{RowId, TimeInt, Timeline}; +use re_chunk_store::external::{re_chunk, re_chunk::external::arrow2}; + +/// The iterator type backing [`HybridResults::iter_as`]. +pub struct HybridResultsChunkIter<'a> { + chunks: Cow<'a, [Chunk]>, + timeline: Timeline, + component_name: ComponentName, +} + +impl<'a> HybridResultsChunkIter<'a> { + /// Iterate as indexed primitives. + /// + /// See [`Chunk::iter_primitive`] for more information. + pub fn primitive( + &'a self, + ) -> impl Iterator + 'a { + self.chunks.iter().flat_map(move |chunk| { + itertools::izip!( + chunk.iter_component_indices(&self.timeline, &self.component_name), + chunk.iter_primitive::(&self.component_name) + ) + }) + } + + /// Iterate as indexed primitive arrays. + /// + /// See [`Chunk::iter_primitive_array`] for more information. + pub fn primitive_array( + &'a self, + ) -> impl Iterator + 'a + where + [T; N]: bytemuck::Pod, + { + self.chunks.iter().flat_map(move |chunk| { + itertools::izip!( + chunk.iter_component_indices(&self.timeline, &self.component_name), + chunk.iter_primitive_array::(&self.component_name) + ) + }) + } + + /// Iterate as indexed UTF-8 strings. + /// + /// See [`Chunk::iter_string`] for more information. + pub fn string( + &'a self, + ) -> impl Iterator)> + 'a { + self.chunks.iter().flat_map(|chunk| { + itertools::izip!( + chunk.iter_component_indices(&self.timeline, &self.component_name), + chunk.iter_string(&self.component_name) + ) + }) + } +} + +impl<'a> HybridResults<'a> { + /// Returns a zero-copy iterator over all the results for the given `(timeline, component)` pair. + /// + /// Call one of the following methods on the returned [`HybridResultsChunkIter`]: + /// * [`HybridResultsChunkIter::primitive`] + /// * [`HybridResultsChunkIter::primitive_array`] + /// * [`HybridResultsChunkIter::string`] + pub fn iter_as( + &'a self, + timeline: Timeline, + component_name: ComponentName, + ) -> HybridResultsChunkIter<'a> { + let chunks = self.get_optional_chunks(&component_name); + HybridResultsChunkIter { + chunks, + timeline, + component_name, + } + } +} From 5ec73bfceea9593b2c8b15411b95bb5e01a19961 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 11:06:54 +0200 Subject: [PATCH 3/9] introduce new chunk-based entity_iterator --- Cargo.lock | 4 + .../viewer/re_space_view_spatial/Cargo.toml | 1 + .../src/visualizers/mod.rs | 4 +- .../visualizers/utilities/entity_iterator.rs | 124 +++++++++++++++++- .../src/visualizers/utilities/labels.rs | 74 +++++++++++ .../src/visualizers/utilities/mod.rs | 3 +- 6 files changed, 206 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index faa5aed94c38..43c5eaf72990 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4302,6 +4302,7 @@ dependencies = [ "ahash", "anyhow", "backtrace", + "bytemuck", "criterion", "crossbeam", "document-features", @@ -4919,7 +4920,9 @@ name = "re_space_view" version = "0.18.0-alpha.1+dev" dependencies = [ "ahash", + "bytemuck", "egui", + "itertools 0.13.0", "nohash-hasher", "re_chunk_store", "re_entity_db", @@ -4999,6 +5002,7 @@ dependencies = [ "re_log_types", "re_math", "re_query", + "re_query2", "re_renderer", "re_space_view", "re_tracing", diff --git a/crates/viewer/re_space_view_spatial/Cargo.toml b/crates/viewer/re_space_view_spatial/Cargo.toml index 10d53674e49d..d538505fde6b 100644 --- a/crates/viewer/re_space_view_spatial/Cargo.toml +++ b/crates/viewer/re_space_view_spatial/Cargo.toml @@ -28,6 +28,7 @@ re_log_types.workspace = true re_log.workspace = true re_math = { workspace = true, features = ["serde"] } re_query.workspace = true +re_query2.workspace = true re_renderer = { workspace = true, features = [ "import-gltf", "import-obj", diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs index 5f4c19ce1021..f805c5f6aa65 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/mod.rs @@ -27,8 +27,8 @@ pub use segmentation_images::SegmentationImageVisualizer; pub use transform3d_arrows::{add_axis_arrows, AxisLengthDetector, Transform3DArrowsVisualizer}; pub use utilities::{ bounding_box_for_textured_rect, entity_iterator, process_labels_2d, process_labels_3d, - textured_rect_from_image, textured_rect_from_tensor, SpatialViewVisualizerData, UiLabel, - UiLabelTarget, MAX_NUM_LABELS_PER_ENTITY, + process_labels_3d_2, textured_rect_from_image, textured_rect_from_tensor, + SpatialViewVisualizerData, UiLabel, UiLabelTarget, MAX_NUM_LABELS_PER_ENTITY, }; // --- diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs index facb42d77ebf..fa599bbb5e46 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/entity_iterator.rs @@ -2,7 +2,9 @@ use itertools::Either; use re_chunk_store::{LatestAtQuery, RangeQuery}; use re_log_types::{TimeInt, Timeline}; use re_space_view::{ - latest_at_with_blueprint_resolved_data, range_with_blueprint_resolved_data, HybridResults, + latest_at_with_blueprint_resolved_data, latest_at_with_blueprint_resolved_data2, + range_with_blueprint_resolved_data, range_with_blueprint_resolved_data2, HybridResults, + HybridResults2, }; use re_types::Archetype; use re_viewer_context::{ @@ -209,3 +211,123 @@ where Ok(()) } + +// --- Chunk-based APIs --- + +pub fn query_archetype_with_history2<'a, A: Archetype>( + ctx: &'a ViewContext<'a>, + timeline: &Timeline, + timeline_cursor: TimeInt, + query_range: &QueryRange, + data_result: &'a re_viewer_context::DataResult, +) -> HybridResults2<'a> { + match query_range { + QueryRange::TimeRange(time_range) => { + let range_query = RangeQuery::new( + *timeline, + re_log_types::ResolvedTimeRange::from_relative_time_range( + time_range, + timeline_cursor, + ), + ); + let results = range_with_blueprint_resolved_data2( + ctx, + None, + &range_query, + data_result, + A::all_components().iter().copied(), + ); + (range_query, results).into() + } + QueryRange::LatestAt => { + let latest_query = LatestAtQuery::new(*timeline, timeline_cursor); + let query_shadowed_defaults = false; + let results = latest_at_with_blueprint_resolved_data2( + ctx, + None, + &latest_query, + data_result, + A::all_components().iter().copied(), + query_shadowed_defaults, + ); + (latest_query, results).into() + } + } +} + +/// Iterates through all entity views for a given archetype. +/// +/// The callback passed in gets passed along an [`SpatialSceneEntityContext`] which contains +/// various useful information about an entity in the context of the current scene. +pub fn process_archetype2( + ctx: &ViewContext<'_>, + query: &ViewQuery<'_>, + view_ctx: &ViewContextCollection, + mut fun: F, +) -> Result<(), SpaceViewSystemExecutionError> +where + A: Archetype, + F: FnMut( + &QueryContext<'_>, + &SpatialSceneEntityContext<'_>, + &HybridResults2<'_>, + ) -> Result<(), SpaceViewSystemExecutionError>, +{ + let transforms = view_ctx.get::()?; + let depth_offsets = view_ctx.get::()?; + let annotations = view_ctx.get::()?; + + let latest_at = query.latest_at_query(); + + let system_identifier = System::identifier(); + + for data_result in query.iter_visible_data_results(ctx, system_identifier) { + // The transform that considers pinholes only makes sense if this is a 3D space-view + let world_from_entity = + if view_ctx.space_view_class_identifier() == SpatialSpaceView3D::identifier() { + transforms.reference_from_entity(&data_result.entity_path) + } else { + transforms.reference_from_entity_ignoring_pinhole( + &data_result.entity_path, + ctx.recording(), + &latest_at, + ) + }; + + let Some(world_from_entity) = world_from_entity else { + continue; + }; + let depth_offset_key = (system_identifier, data_result.entity_path.hash()); + let entity_context = SpatialSceneEntityContext { + world_from_entity, + depth_offset: depth_offsets + .per_entity_and_visualizer + .get(&depth_offset_key) + .copied() + .unwrap_or_default(), + annotations: annotations.0.find(&data_result.entity_path), + highlight: query + .highlights + .entity_outline_mask(data_result.entity_path.hash()), + space_view_class_identifier: view_ctx.space_view_class_identifier(), + }; + + let results = query_archetype_with_history2::( + ctx, + &query.timeline, + query.latest_at, + data_result.query_range(), + data_result, + ); + + let mut query_ctx = ctx.query_context(data_result, &latest_at); + query_ctx.archetype_name = Some(A::name()); + + { + re_tracing::profile_scope!(format!("{}", data_result.entity_path)); + fun(&query_ctx, &entity_context, &results)?; + } + } + + Ok(()) +} diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/labels.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/labels.rs index 031c7ed15dfa..f0d221556d1d 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/labels.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/labels.rs @@ -39,6 +39,8 @@ pub const MAX_NUM_LABELS_PER_ENTITY: usize = 30; /// /// Does nothing if there's no positions or no labels passed. /// Otherwise, produces one label per position passed. +// +// TODO(cmc): remove pub fn process_labels_3d<'a>( entity_path: &'a EntityPath, positions: impl Iterator + 'a, @@ -67,10 +69,44 @@ pub fn process_labels_3d<'a>( }) } +/// Produces 3D ui labels from component data. +/// +/// Does nothing if there's no positions or no labels passed. +/// Otherwise, produces one label per position passed. +pub fn process_labels_3d_2<'a>( + entity_path: &'a EntityPath, + positions: impl Iterator + 'a, + labels: &'a [re_types::ArrowString], + colors: &'a [egui::Color32], + annotation_infos: &'a ResolvedAnnotationInfos, + world_from_obj: glam::Affine3A, +) -> impl Iterator + 'a { + let labels = izip!( + annotation_infos.iter(), + labels.iter().map(Some).chain(std::iter::repeat(None)) + ) + .map(|(annotation_info, label)| annotation_info.label(label.map(|l| l.as_str()))); + + let colors = clamped_or(colors, &egui::Color32::WHITE); + + itertools::izip!(positions, labels, colors) + .enumerate() + .filter_map(move |(i, (point, label, color))| { + label.map(|label| UiLabel { + text: label, + color: *color, + target: UiLabelTarget::Position3D(world_from_obj.transform_point3(point)), + labeled_instance: InstancePathHash::instance(entity_path, Instance::from(i as u64)), + }) + }) +} + /// Produces 2D ui labels from component data. /// /// Does nothing if there's no positions or no labels passed. /// Otherwise, produces one label per position passed. +// +// TODO(cmc): remove pub fn process_labels_2d<'a>( entity_path: &'a EntityPath, positions: impl Iterator + 'a, @@ -104,3 +140,41 @@ pub fn process_labels_2d<'a>( }) }) } + +/// Produces 2D ui labels from component data. +/// +/// Does nothing if there's no positions or no labels passed. +/// Otherwise, produces one label per position passed. +pub fn process_labels_2d_2<'a>( + entity_path: &'a EntityPath, + positions: impl Iterator + 'a, + labels: &'a [re_types::ArrowString], + colors: &'a [egui::Color32], + annotation_infos: &'a ResolvedAnnotationInfos, + world_from_obj: glam::Affine3A, +) -> impl Iterator + 'a { + let labels = izip!( + annotation_infos.iter(), + labels.iter().map(Some).chain(std::iter::repeat(None)) + ) + .map(|(annotation_info, label)| annotation_info.label(label.map(|l| l.as_str()))); + + let colors = clamped_or(colors, &egui::Color32::WHITE); + + itertools::izip!(positions, labels, colors) + .enumerate() + .filter_map(move |(i, (point, label, color))| { + label.map(|label| { + let point = world_from_obj.transform_point3(glam::Vec3::new(point.x, point.y, 0.0)); + UiLabel { + text: label, + color: *color, + target: UiLabelTarget::Point2D(egui::pos2(point.x, point.y)), + labeled_instance: InstancePathHash::instance( + entity_path, + Instance::from(i as u64), + ), + } + }) + }) +} diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs index 57bca95c769a..b450dc6eff64 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/utilities/mod.rs @@ -4,7 +4,8 @@ mod spatial_view_visualizer; mod textured_rect; pub use labels::{ - process_labels_2d, process_labels_3d, UiLabel, UiLabelTarget, MAX_NUM_LABELS_PER_ENTITY, + process_labels_2d, process_labels_2d_2, process_labels_3d, process_labels_3d_2, UiLabel, + UiLabelTarget, MAX_NUM_LABELS_PER_ENTITY, }; pub use spatial_view_visualizer::SpatialViewVisualizerData; pub use textured_rect::{ From 8b4025436f8c4fd2e59e7a2aa196ad0db7cedeaa Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 11:07:05 +0200 Subject: [PATCH 4/9] zero-deser point2d visualizer --- .../src/visualizers/points2d.rs | 77 +++++++++++-------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/points2d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/points2d.rs index 0734d69cdf05..61749ca70ccc 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/points2d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/points2d.rs @@ -1,10 +1,10 @@ use itertools::Itertools as _; -use re_query::range_zip_1x5; use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, PointCloudBuilder}; use re_types::{ archetypes::Points2D, components::{ClassId, Color, DrawOrder, KeypointId, Position2D, Radius, Text}, + ArrowString, Loggable as _, }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, @@ -23,7 +23,7 @@ use crate::{ }; use super::{ - filter_visualizable_2d_entities, process_labels_2d, SpatialViewVisualizerData, + filter_visualizable_2d_entities, utilities::process_labels_2d_2, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES, }; @@ -141,10 +141,10 @@ impl Points2DVisualizer { ) }; - self.data.ui_labels.extend(process_labels_2d( + self.data.ui_labels.extend(process_labels_2d_2( entity_path, label_positions, - data.labels, + &data.labels, &colors, &annotation_infos, ent_context.world_from_entity, @@ -166,7 +166,7 @@ pub struct Points2DComponentData<'a> { // Clamped to edge pub colors: &'a [Color], pub radii: &'a [Radius], - pub labels: &'a [Text], + pub labels: Vec, pub keypoint_ids: &'a [KeypointId], pub class_ids: &'a [ClassId], } @@ -211,53 +211,62 @@ impl VisualizerSystem for Points2DVisualizer { line_builder .radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES); - super::entity_iterator::process_archetype::( + super::entity_iterator::process_archetype2::( ctx, view_query, context_systems, |ctx, spatial_ctx, results| { - use re_space_view::RangeResultsExt as _; + use re_space_view::RangeResultsExt2 as _; - let resolver = ctx.recording().resolver(); - - let positions = match results.get_required_component_dense::(resolver) { - Some(positions) => positions?, - _ => return Ok(()), + let Some(all_position_chunks) = results.get_required_chunks(&Position2D::name()) + else { + return Ok(()); }; - let num_positions = positions - .range_indexed() - .map(|(_, positions)| positions.len()) - .sum::(); + let num_positions = all_position_chunks + .iter() + .flat_map(|chunk| chunk.iter_primitive_array::<3, f32>(&Position2D::name())) + .map(|points| points.len()) + .sum(); + if num_positions == 0 { return Ok(()); } point_builder.reserve(num_positions)?; - let colors = results.get_or_empty_dense(resolver)?; - let radii = results.get_or_empty_dense(resolver)?; - let labels = results.get_or_empty_dense(resolver)?; - let class_ids = results.get_or_empty_dense(resolver)?; - let keypoint_ids = results.get_or_empty_dense(resolver)?; - - let data = range_zip_1x5( - positions.range_indexed(), - colors.range_indexed(), - radii.range_indexed(), - labels.range_indexed(), - class_ids.range_indexed(), - keypoint_ids.range_indexed(), + let timeline = ctx.query.timeline(); + let all_positions_indexed = all_position_chunks.iter().flat_map(|chunk| { + itertools::izip!( + chunk.iter_component_indices(&timeline, &Position2D::name()), + chunk.iter_primitive_array::<2, f32>(&Position2D::name()) + ) + }); + let all_colors = results.iter_as(timeline, Color::name()); + let all_radii = results.iter_as(timeline, Radius::name()); + let all_labels = results.iter_as(timeline, Text::name()); + let all_class_ids = results.iter_as(timeline, ClassId::name()); + let all_keypoint_ids = results.iter_as(timeline, KeypointId::name()); + + let data = re_query2::range_zip_1x5( + all_positions_indexed, + all_colors.primitive::(), + all_radii.primitive::(), + all_labels.string(), + all_class_ids.primitive::(), + all_keypoint_ids.primitive::(), ) .map( |(_index, positions, colors, radii, labels, class_ids, keypoint_ids)| { Points2DComponentData { - positions, - colors: colors.unwrap_or_default(), - radii: radii.unwrap_or_default(), + positions: bytemuck::cast_slice(positions), + colors: colors.map_or(&[], |colors| bytemuck::cast_slice(colors)), + radii: radii.map_or(&[], |radii| bytemuck::cast_slice(radii)), labels: labels.unwrap_or_default(), - class_ids: class_ids.unwrap_or_default(), - keypoint_ids: keypoint_ids.unwrap_or_default(), + class_ids: class_ids + .map_or(&[], |class_ids| bytemuck::cast_slice(class_ids)), + keypoint_ids: keypoint_ids + .map_or(&[], |keypoint_ids| bytemuck::cast_slice(keypoint_ids)), } }, ); From 35b599006493487a273e2b6bd5b833850cf4b935 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 11:07:12 +0200 Subject: [PATCH 5/9] zero-deser point3d visualizer --- .../src/visualizers/points3d.rs | 80 +++++++++++-------- 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs index 50873b163273..4243b8c2414f 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/points3d.rs @@ -1,10 +1,10 @@ -use itertools::Itertools as _; +use itertools::Itertools; -use re_query::range_zip_1x5; use re_renderer::{LineDrawableBuilder, PickingLayerInstanceId, PointCloudBuilder}; use re_types::{ archetypes::Points3D, components::{ClassId, Color, KeypointId, Position3D, Radius, Text}, + ArrowString, Loggable, }; use re_viewer_context::{ auto_color_for_entity_path, ApplicableEntities, IdentifiedViewSystem, QueryContext, @@ -23,7 +23,7 @@ use crate::{ }; use super::{ - filter_visualizable_3d_entities, process_labels_3d, SpatialViewVisualizerData, + filter_visualizable_3d_entities, process_labels_3d_2, SpatialViewVisualizerData, SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES, }; @@ -48,7 +48,7 @@ struct Points3DComponentData<'a> { // Clamped to edge colors: &'a [Color], radii: &'a [Radius], - labels: &'a [Text], + labels: Vec, keypoint_ids: &'a [KeypointId], class_ids: &'a [ClassId], } @@ -144,10 +144,10 @@ impl Points3DVisualizer { positions }; - self.data.ui_labels.extend(process_labels_3d( + self.data.ui_labels.extend(process_labels_3d_2( entity_path, label_positions.iter().copied(), - data.labels, + &data.labels, &colors, &annotation_infos, ent_context.world_from_entity, @@ -199,53 +199,62 @@ impl VisualizerSystem for Points3DVisualizer { line_builder .radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_POINT_OUTLINES); - super::entity_iterator::process_archetype::( + super::entity_iterator::process_archetype2::( ctx, view_query, context_systems, |ctx, spatial_ctx, results| { - use re_space_view::RangeResultsExt as _; + use re_space_view::RangeResultsExt2 as _; - let resolver = ctx.recording().resolver(); - - let positions = match results.get_required_component_dense::(resolver) { - Some(positions) => positions?, - _ => return Ok(()), + let Some(all_position_chunks) = results.get_required_chunks(&Position3D::name()) + else { + return Ok(()); }; - let num_positions = positions - .range_indexed() - .map(|(_, positions)| positions.len()) - .sum::(); + let num_positions = all_position_chunks + .iter() + .flat_map(|chunk| chunk.iter_primitive_array::<3, f32>(&Position3D::name())) + .map(|points| points.len()) + .sum(); + if num_positions == 0 { return Ok(()); } point_builder.reserve(num_positions)?; - let colors = results.get_or_empty_dense(resolver)?; - let radii = results.get_or_empty_dense(resolver)?; - let labels = results.get_or_empty_dense(resolver)?; - let class_ids = results.get_or_empty_dense(resolver)?; - let keypoint_ids = results.get_or_empty_dense(resolver)?; - - let data = range_zip_1x5( - positions.range_indexed(), - colors.range_indexed(), - radii.range_indexed(), - labels.range_indexed(), - class_ids.range_indexed(), - keypoint_ids.range_indexed(), + let timeline = ctx.query.timeline(); + let all_positions_indexed = all_position_chunks.iter().flat_map(|chunk| { + itertools::izip!( + chunk.iter_component_indices(&timeline, &Position3D::name()), + chunk.iter_primitive_array::<3, f32>(&Position3D::name()) + ) + }); + let all_colors = results.iter_as(timeline, Color::name()); + let all_radii = results.iter_as(timeline, Radius::name()); + let all_labels = results.iter_as(timeline, Text::name()); + let all_class_ids = results.iter_as(timeline, ClassId::name()); + let all_keypoint_ids = results.iter_as(timeline, KeypointId::name()); + + let data = re_query2::range_zip_1x5( + all_positions_indexed, + all_colors.primitive::(), + all_radii.primitive::(), + all_labels.string(), + all_class_ids.primitive::(), + all_keypoint_ids.primitive::(), ) .map( |(_index, positions, colors, radii, labels, class_ids, keypoint_ids)| { Points3DComponentData { - positions, - colors: colors.unwrap_or_default(), - radii: radii.unwrap_or_default(), + positions: bytemuck::cast_slice(positions), + colors: colors.map_or(&[], |colors| bytemuck::cast_slice(colors)), + radii: radii.map_or(&[], |radii| bytemuck::cast_slice(radii)), labels: labels.unwrap_or_default(), - class_ids: class_ids.unwrap_or_default(), - keypoint_ids: keypoint_ids.unwrap_or_default(), + class_ids: class_ids + .map_or(&[], |class_ids| bytemuck::cast_slice(class_ids)), + keypoint_ids: keypoint_ids + .map_or(&[], |keypoint_ids| bytemuck::cast_slice(keypoint_ids)), } }, ); @@ -281,6 +290,7 @@ impl VisualizerSystem for Points3DVisualizer { } impl TypedComponentFallbackProvider for Points3DVisualizer { + #[inline] fn fallback_for(&self, ctx: &QueryContext<'_>) -> Color { auto_color_for_entity_path(ctx.target_entity_path) } From a52cea0bbb0a9971d4415860d5d58345619c6bb1 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 11:53:19 +0200 Subject: [PATCH 6/9] lints --- crates/viewer/re_space_view/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/re_space_view/src/lib.rs b/crates/viewer/re_space_view/src/lib.rs index ac3015ceeb0f..7b90363a1eb2 100644 --- a/crates/viewer/re_space_view/src/lib.rs +++ b/crates/viewer/re_space_view/src/lib.rs @@ -24,7 +24,7 @@ pub use query2::{ pub use results_ext::{HybridLatestAtResults, HybridResults, RangeResultsExt}; pub use results_ext2::{ HybridLatestAtResults as HybridLatestAtResults2, HybridResults as HybridResults2, - RangeResultsExt as RangeResultsExt2, + HybridResultsChunkIter, RangeResultsExt as RangeResultsExt2, }; pub use screenshot::ScreenshotMode; pub use view_property_ui::view_property_ui; From c32f9435058127e0cf828e63f229c96125d3c510 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 11:50:32 +0200 Subject: [PATCH 7/9] chunkified (almost)zero-deser mesh view --- .../src/visualizers/meshes.rs | 109 ++++++++++++------ 1 file changed, 72 insertions(+), 37 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs b/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs index 7c25c36147e8..39036ec21cd7 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/meshes.rs @@ -1,6 +1,7 @@ +use itertools::Itertools as _; + use re_chunk_store::RowId; use re_log_types::{hash::Hash64, Instance, TimeInt}; -use re_query::range_zip_1x7; use re_renderer::renderer::MeshInstance; use re_renderer::RenderContext; use re_types::{ @@ -8,6 +9,7 @@ use re_types::{ components::{ AlbedoFactor, ClassId, Color, Position3D, TensorData, Texcoord2D, TriangleIndices, Vector3D, }, + Loggable as _, }; use re_viewer_context::{ ApplicableEntities, IdentifiedViewSystem, QueryContext, SpaceViewSystemExecutionError, @@ -174,69 +176,102 @@ impl VisualizerSystem for Mesh3DVisualizer { let mut instances = Vec::new(); - super::entity_iterator::process_archetype::( + super::entity_iterator::process_archetype2::( ctx, view_query, context_systems, |ctx, spatial_ctx, results| { - use re_space_view::RangeResultsExt as _; - - let resolver = ctx.recording().resolver(); + use re_space_view::RangeResultsExt2 as _; - let vertex_positions = - match results.get_required_component_dense::(resolver) { - Some(positions) => positions?, - _ => return Ok(()), - }; + let Some(all_vertex_position_chunks) = + results.get_required_chunks(&Position3D::name()) + else { + return Ok(()); + }; - let vertex_normals = results.get_or_empty_dense(resolver)?; - let vertex_colors = results.get_or_empty_dense(resolver)?; - let vertex_texcoords = results.get_or_empty_dense(resolver)?; - let triangle_indices = results.get_or_empty_dense(resolver)?; - let albedo_factors = results.get_or_empty_dense(resolver)?; - let albedo_textures = results.get_or_empty_dense(resolver)?; - let class_ids = results.get_or_empty_dense(resolver)?; + let timeline = ctx.query.timeline(); + let all_vertex_positions_indexed = + all_vertex_position_chunks.iter().flat_map(|chunk| { + itertools::izip!( + chunk.iter_component_indices(&timeline, &Position3D::name()), + chunk.iter_primitive_array::<3, f32>(&Position3D::name()) + ) + }); + let all_vertex_normals = results.iter_as(timeline, Vector3D::name()); + let all_vertex_colors = results.iter_as(timeline, Color::name()); + let all_vertex_texcoords = results.iter_as(timeline, Texcoord2D::name()); + let all_triangle_indices = results.iter_as(timeline, TriangleIndices::name()); + let all_albedo_factors = results.iter_as(timeline, AlbedoFactor::name()); + let all_class_ids = results.iter_as(timeline, ClassId::name()); + + // TODO(#6386): we have to deserialize here because `TensorData` is still a complex + // type at this point. + let all_albedo_textures_chunks = results.get_optional_chunks(&TensorData::name()); + let mut all_albedo_textures_iters = all_albedo_textures_chunks + .iter() + .map(|chunk| chunk.iter_component::()) + .collect_vec(); + let all_albedo_textures_indexed = { + let all_albedo_textures = all_albedo_textures_iters + .iter_mut() + .flat_map(|it| it.into_iter()); + let all_albedo_textures_indices = + all_albedo_textures_chunks.iter().flat_map(|chunk| { + chunk.iter_component_indices(&timeline, &TensorData::name()) + }); + itertools::izip!(all_albedo_textures_indices, all_albedo_textures) + }; let query_result_hash = results.query_result_hash(); - let data = range_zip_1x7( - vertex_positions.range_indexed(), - vertex_normals.range_indexed(), - vertex_colors.range_indexed(), - vertex_texcoords.range_indexed(), - triangle_indices.range_indexed(), - albedo_factors.range_indexed(), - albedo_textures.range_indexed(), - class_ids.range_indexed(), + let data = re_query2::range_zip_1x7( + all_vertex_positions_indexed, + all_vertex_normals.primitive_array::<3, f32>(), + all_vertex_colors.primitive::(), + all_vertex_texcoords.primitive_array::<2, f32>(), + all_triangle_indices.primitive_array::<3, u32>(), + all_albedo_factors.primitive::(), + all_albedo_textures_indexed, + all_class_ids.primitive::(), ) .map( |( - &index, + index, vertex_positions, vertex_normals, vertex_colors, vertex_texcoords, triangle_indices, - albedo_factor, - albedo_texture, + albedo_factors, + albedo_textures, class_ids, )| { Mesh3DComponentData { index, query_result_hash, - vertex_positions, - vertex_normals: vertex_normals.unwrap_or_default(), - vertex_colors: vertex_colors.unwrap_or_default(), - vertex_texcoords: vertex_texcoords.unwrap_or_default(), - triangle_indices, - albedo_factor: albedo_factor.and_then(|v| v.first()), - albedo_texture: albedo_texture.and_then(|v| v.first()), - class_ids: class_ids.unwrap_or_default(), + vertex_positions: bytemuck::cast_slice(vertex_positions), + vertex_normals: vertex_normals + .map_or(&[], |vertex_normals| bytemuck::cast_slice(vertex_normals)), + vertex_colors: vertex_colors + .map_or(&[], |vertex_colors| bytemuck::cast_slice(vertex_colors)), + vertex_texcoords: vertex_texcoords.map_or(&[], |vertex_texcoords| { + bytemuck::cast_slice(vertex_texcoords) + }), + triangle_indices: triangle_indices.map(bytemuck::cast_slice), + albedo_factor: albedo_factors + .map_or(&[] as &[AlbedoFactor], |albedo_factors| { + bytemuck::cast_slice(albedo_factors) + }) + .first(), + albedo_texture: albedo_textures.and_then(|v| v.first()), + class_ids: class_ids + .map_or(&[], |class_ids| bytemuck::cast_slice(class_ids)), } }, ); self.process_data(ctx, render_ctx, &mut instances, spatial_ctx, data); + Ok(()) }, )?; From 0952e3b6534ce356155cbee09ba1d49a89767619 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 12:41:24 +0200 Subject: [PATCH 8/9] new iter_buffer tooling --- crates/store/re_chunk/src/iter.rs | 85 +++++++++++++++++-- .../store/re_types_core/src/arrow_buffer.rs | 2 +- .../viewer/re_space_view/src/results_ext2.rs | 14 +++ 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/crates/store/re_chunk/src/iter.rs b/crates/store/re_chunk/src/iter.rs index 7fe34094d970..a162b76b1d70 100644 --- a/crates/store/re_chunk/src/iter.rs +++ b/crates/store/re_chunk/src/iter.rs @@ -3,14 +3,15 @@ use std::sync::Arc; use arrow2::{ array::{ Array as ArrowArray, FixedSizeListArray as ArrowFixedSizeListArray, - PrimitiveArray as ArrowPrimitiveArray, Utf8Array as ArrowUtf8Array, + ListArray as ArrowListArray, PrimitiveArray as ArrowPrimitiveArray, + Utf8Array as ArrowUtf8Array, }, Either, }; use itertools::{izip, Itertools}; use re_log_types::{TimeInt, Timeline}; -use re_types_core::{ArrowString, Component, ComponentName}; +use re_types_core::{ArrowBuffer, ArrowString, Component, ComponentName}; use crate::{Chunk, ChunkTimeline, RowId}; @@ -126,8 +127,11 @@ impl Chunk { /// Returns an iterator over the raw arrays of a [`Chunk`], for a given component. /// /// See also: + /// * [`Self::iter_primitive`] + /// * [`Self::iter_primitive_array`] + /// * [`Self::iter_string`] + /// * [`Self::iter_buffer`]. /// * [`Self::iter_component`]. - /// * [`Self::iter_primitive`]. #[inline] pub fn iter_component_arrays( &self, @@ -150,6 +154,7 @@ impl Chunk { /// See also: /// * [`Self::iter_primitive_array`] /// * [`Self::iter_string`] + /// * [`Self::iter_buffer`]. /// * [`Self::iter_component_arrays`]. /// * [`Self::iter_component`]. #[inline] @@ -192,6 +197,7 @@ impl Chunk { /// See also: /// * [`Self::iter_primitive`] /// * [`Self::iter_string`] + /// * [`Self::iter_buffer`]. /// * [`Self::iter_component_arrays`]. /// * [`Self::iter_component`]. pub fn iter_primitive_array( @@ -243,7 +249,7 @@ impl Chunk { ) } - /// Returns an iterator over the raw primitive strings of a [`Chunk`], for a given component. + /// Returns an iterator over the raw strings of a [`Chunk`], for a given component. /// /// This is a very fast path: the entire column will be downcasted at once, and then every /// component batch will be a slice reference into that global slice. @@ -252,6 +258,7 @@ impl Chunk { /// See also: /// * [`Self::iter_primitive`] /// * [`Self::iter_primitive_array`] + /// * [`Self::iter_buffer`]. /// * [`Self::iter_component_arrays`]. /// * [`Self::iter_component`]. pub fn iter_string( @@ -291,6 +298,69 @@ impl Chunk { }), ) } + + /// Returns an iterator over the raw buffers of a [`Chunk`], for a given component. + /// + /// This is a very fast path: the entire column will be downcasted at once, and then every + /// component batch will be a slice reference into that global slice. + /// Use this when working with simple arrow datatypes and performance matters (e.g. blobs, etc). + /// + /// See also: + /// * [`Self::iter_primitive`] + /// * [`Self::iter_primitive_array`] + /// * [`Self::iter_string`]. + /// * [`Self::iter_component_arrays`]. + /// * [`Self::iter_component`]. + pub fn iter_buffer( + &self, + component_name: &ComponentName, + ) -> impl Iterator>> + '_ { + let Some(list_array) = self.components.get(component_name) else { + return Either::Left(std::iter::empty()); + }; + + let Some(inner_list_array) = list_array + .values() + .as_any() + .downcast_ref::>() + else { + if cfg!(debug_assertions) { + panic!("downcast failed for {component_name}, data discarded"); + } else { + re_log::error_once!("downcast failed for {component_name}, data discarded"); + } + return Either::Left(std::iter::empty()); + }; + + let Some(values) = inner_list_array + .values() + .as_any() + .downcast_ref::>() + else { + if cfg!(debug_assertions) { + panic!("downcast failed for {component_name}, data discarded"); + } else { + re_log::error_once!("downcast failed for {component_name}, data discarded"); + } + return Either::Left(std::iter::empty()); + }; + + let values = values.values(); + let offsets = inner_list_array.offsets(); + let lengths = inner_list_array.offsets().lengths().collect_vec(); + + // NOTE: No need for validity checks here, `iter_offsets` already takes care of that. + Either::Right( + self.iter_component_offsets(component_name) + .map(move |(idx, len)| { + let offsets = &offsets.as_slice()[idx..idx + len]; + let lengths = &lengths.as_slice()[idx..idx + len]; + izip!(offsets, lengths) + .map(|(&idx, &len)| values.clone().sliced(idx as _, len).into()) + .collect_vec() + }), + ) + } } // --- @@ -419,8 +489,11 @@ impl Chunk { /// through enum types across many timestamps). /// /// See also: - /// * [`Self::iter_component`]. - /// * [`Self::iter_primitive`]. + /// * [`Self::iter_primitive`] + /// * [`Self::iter_primitive_array`] + /// * [`Self::iter_string`] + /// * [`Self::iter_buffer`]. + /// * [`Self::iter_component_arrays`]. #[inline] pub fn iter_component( &self, diff --git a/crates/store/re_types_core/src/arrow_buffer.rs b/crates/store/re_types_core/src/arrow_buffer.rs index ddd7b89a33cf..325b99ca998d 100644 --- a/crates/store/re_types_core/src/arrow_buffer.rs +++ b/crates/store/re_types_core/src/arrow_buffer.rs @@ -9,7 +9,7 @@ use arrow2::buffer::Buffer; /// arise from returning a `&[T]` directly, but is significantly more /// performant than doing the full allocation necessary to return a `Vec`. #[derive(Clone, Debug, Default, PartialEq)] -pub struct ArrowBuffer(Buffer); +pub struct ArrowBuffer(pub Buffer); impl crate::SizeBytes for ArrowBuffer { #[inline] diff --git a/crates/viewer/re_space_view/src/results_ext2.rs b/crates/viewer/re_space_view/src/results_ext2.rs index cd6f842170d8..c1ea2eb97c0a 100644 --- a/crates/viewer/re_space_view/src/results_ext2.rs +++ b/crates/viewer/re_space_view/src/results_ext2.rs @@ -419,6 +419,20 @@ impl<'a> HybridResultsChunkIter<'a> { ) }) } + + /// Iterate as indexed buffers. + /// + /// See [`Chunk::iter_buffer`] for more information. + pub fn buffer( + &'a self, + ) -> impl Iterator>)> + 'a { + self.chunks.iter().flat_map(|chunk| { + itertools::izip!( + chunk.iter_component_indices(&self.timeline, &self.component_name), + chunk.iter_buffer(&self.component_name) + ) + }) + } } impl<'a> HybridResults<'a> { From c070efbee3713bf6459190aa5ace203a5a8b3678 Mon Sep 17 00:00:00 2001 From: Clement Rey Date: Tue, 30 Jul 2024 12:41:46 +0200 Subject: [PATCH 9/9] chunkified (almost)deser-free asset visualizer --- .../src/visualizers/assets3d.rs | 69 +++++++++++++------ 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs b/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs index 2c7904873d99..028f9a6e036d 100644 --- a/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs +++ b/crates/viewer/re_space_view_spatial/src/visualizers/assets3d.rs @@ -1,11 +1,13 @@ +use itertools::Itertools as _; + use re_chunk_store::RowId; use re_log_types::{hash::Hash64, Instance, TimeInt}; -use re_query::range_zip_1x2; use re_renderer::renderer::MeshInstance; use re_renderer::RenderContext; use re_types::{ archetypes::Asset3D, components::{Blob, MediaType, OutOfTreeTransform3D}, + ArrowBuffer, ArrowString, Loggable as _, }; use re_viewer_context::{ ApplicableEntities, IdentifiedViewSystem, QueryContext, SpaceViewSystemExecutionError, @@ -35,8 +37,8 @@ impl Default for Asset3DVisualizer { struct Asset3DComponentData<'a> { index: (TimeInt, RowId), - blob: &'a Blob, - media_type: Option<&'a MediaType>, + blob: ArrowBuffer, + media_type: Option, transform: Option<&'a OutOfTreeTransform3D>, } @@ -55,8 +57,8 @@ impl Asset3DVisualizer { for data in data { let mesh = Asset3D { - blob: data.blob.clone(), - media_type: data.media_type.cloned(), + blob: data.blob.clone().into(), + media_type: data.media_type.clone().map(Into::into), // NOTE: Don't even try to cache the transform! transform: None, @@ -75,7 +77,7 @@ impl Asset3DVisualizer { versioned_instance_path_hash: picking_instance_hash .versioned(primary_row_id), query_result_hash: Hash64::ZERO, - media_type: data.media_type.cloned(), + media_type: data.media_type.clone().map(Into::into), }, AnyMesh::Asset(&mesh), render_ctx, @@ -144,38 +146,61 @@ impl VisualizerSystem for Asset3DVisualizer { let mut instances = Vec::new(); - super::entity_iterator::process_archetype::( + super::entity_iterator::process_archetype2::( ctx, view_query, context_systems, |ctx, spatial_ctx, results| { - use re_space_view::RangeResultsExt as _; - - let resolver = ctx.recording().resolver(); + use re_space_view::RangeResultsExt2 as _; - let blobs = match results.get_required_component_dense::(resolver) { - Some(blobs) => blobs?, - _ => return Ok(()), + let Some(all_blob_chunks) = results.get_required_chunks(&Blob::name()) else { + return Ok(()); }; - let media_types = results.get_or_empty_dense(resolver)?; - let transforms = results.get_or_empty_dense(resolver)?; + let timeline = ctx.query.timeline(); + let all_blobs_indexed = all_blob_chunks.iter().flat_map(|chunk| { + itertools::izip!( + chunk.iter_component_indices(&timeline, &Blob::name()), + chunk.iter_buffer::(&Blob::name()) + ) + }); + let all_media_types = results.iter_as(timeline, MediaType::name()); + + // TODO(#6831): we have to deserialize here because `OutOfTreeTransform3D` is + // still a complex type at this point. + let all_transform_chunks = + results.get_optional_chunks(&OutOfTreeTransform3D::name()); + let mut all_transform_iters = all_transform_chunks + .iter() + .map(|chunk| chunk.iter_component::()) + .collect_vec(); + let all_transforms_indexed = { + let all_albedo_textures = + all_transform_iters.iter_mut().flat_map(|it| it.into_iter()); + let all_albedo_textures_indices = + all_transform_chunks.iter().flat_map(|chunk| { + chunk.iter_component_indices(&timeline, &OutOfTreeTransform3D::name()) + }); + itertools::izip!(all_albedo_textures_indices, all_albedo_textures) + }; - let data = range_zip_1x2( - blobs.range_indexed(), - media_types.range_indexed(), - transforms.range_indexed(), + let data = re_query2::range_zip_1x2( + all_blobs_indexed, + all_media_types.string(), + all_transforms_indexed, ) - .filter_map(|(&index, blobs, media_types, transforms)| { + .filter_map(|(index, blobs, media_types, transforms)| { blobs.first().map(|blob| Asset3DComponentData { index, - blob, - media_type: media_types.and_then(|media_types| media_types.first()), + blob: blob.clone(), + media_type: media_types + .and_then(|media_types| media_types.first().cloned()), transform: transforms.and_then(|transforms| transforms.first()), }) }); self.process_data(ctx, render_ctx, &mut instances, spatial_ctx, data); + Ok(()) }, )?;