diff --git a/crates/store/re_types/definitions/rerun/archetypes/image.fbs b/crates/store/re_types/definitions/rerun/archetypes/image.fbs index bd73ee273b01..4ce305f96ab4 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/image.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/image.fbs @@ -24,7 +24,7 @@ namespace rerun.archetypes; /// \example archetypes/image_formats title="Logging images with various formats" image="https://static.rerun.io/image_formats/7b8a162fcfd266f303980439beea997dc8544c24/full.png" /// \example archetypes/image_send_columns !api title="Image from file, PIL & OpenCV" image="https://static.rerun.io/image_advanced/81fc8a255488615510790ee41be314e054978d51/full.png" table Image ( - // TODO(#7245): "attr.rust.archetype_eager", + "attr.rust.archetype_eager", "attr.rust.derive": "PartialEq", "attr.cpp.no_field_ctors", "attr.docs.category": "Image & tensor", diff --git a/crates/store/re_types/definitions/rerun/archetypes/mesh3d.fbs b/crates/store/re_types/definitions/rerun/archetypes/mesh3d.fbs index a3472cefb4b9..9451497bb49e 100644 --- a/crates/store/re_types/definitions/rerun/archetypes/mesh3d.fbs +++ b/crates/store/re_types/definitions/rerun/archetypes/mesh3d.fbs @@ -13,7 +13,7 @@ namespace rerun.archetypes; /// \example archetypes/mesh3d_partial_updates !api title="3D mesh with partial updates" image="https://static.rerun.io/mesh3d_partial_updates/7de33d26220585691a403098c953cd46f94c3262/1200w.png" /// \example archetypes/mesh3d_instancing title="3D mesh with instancing" image="https://static.rerun.io/mesh3d_leaf_transforms3d/c2d0ee033129da53168f5705625a9b033f3a3d61/1200w.png" table Mesh3D ( - // TODO(#7245): "attr.rust.archetype_eager", + "attr.rust.archetype_eager", "attr.rust.derive": "PartialEq", "attr.docs.category": "Spatial 3D", "attr.docs.view_types": "Spatial3DView, Spatial2DView: if logged above active projection" diff --git a/crates/store/re_types/src/archetypes/image.rs b/crates/store/re_types/src/archetypes/image.rs index e01ce1dc934a..d79223345513 100644 --- a/crates/store/re_types/src/archetypes/image.rs +++ b/crates/store/re_types/src/archetypes/image.rs @@ -126,23 +126,23 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// /// -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Default)] pub struct Image { /// The raw image data. - pub buffer: crate::components::ImageBuffer, + pub buffer: Option, /// The format of the image. - pub format: crate::components::ImageFormat, + pub format: Option, /// Opacity of the image, useful for layering several images. /// /// Defaults to 1.0 (fully opaque). - pub opacity: Option, + pub opacity: Option, /// An optional floating point value that specifies the 2D drawing order. /// /// Objects with higher values are drawn on top of those with lower values. - pub draw_order: Option, + pub draw_order: Option, } impl Image { @@ -271,50 +271,20 @@ impl ::re_types_core::Archetype for Image { re_tracing::profile_function!(); use ::re_types_core::{Loggable as _, ResultExt as _}; let arrays_by_descr: ::nohash_hasher::IntMap<_, _> = arrow_data.into_iter().collect(); - let buffer = { - let array = arrays_by_descr - .get(&Self::descriptor_buffer()) - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.archetypes.Image#buffer")?; - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Image#buffer")? - .into_iter() - .next() - .flatten() - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.archetypes.Image#buffer")? - }; - let format = { - let array = arrays_by_descr - .get(&Self::descriptor_format()) - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.archetypes.Image#format")?; - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Image#format")? - .into_iter() - .next() - .flatten() - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.archetypes.Image#format")? - }; - let opacity = if let Some(array) = arrays_by_descr.get(&Self::descriptor_opacity()) { - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Image#opacity")? - .into_iter() - .next() - .flatten() - } else { - None - }; - let draw_order = if let Some(array) = arrays_by_descr.get(&Self::descriptor_draw_order()) { - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Image#draw_order")? - .into_iter() - .next() - .flatten() - } else { - None - }; + let buffer = arrays_by_descr + .get(&Self::descriptor_buffer()) + .map(|array| SerializedComponentBatch::new(array.clone(), Self::descriptor_buffer())); + let format = arrays_by_descr + .get(&Self::descriptor_format()) + .map(|array| SerializedComponentBatch::new(array.clone(), Self::descriptor_format())); + let opacity = arrays_by_descr + .get(&Self::descriptor_opacity()) + .map(|array| SerializedComponentBatch::new(array.clone(), Self::descriptor_opacity())); + let draw_order = arrays_by_descr + .get(&Self::descriptor_draw_order()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_draw_order()) + }); Ok(Self { buffer, format, @@ -325,39 +295,15 @@ impl ::re_types_core::Archetype for Image { } impl ::re_types_core::AsComponents for Image { - fn as_component_batches(&self) -> Vec> { - re_tracing::profile_function!(); + #[inline] + fn as_serialized_batches(&self) -> Vec { use ::re_types_core::Archetype as _; [ - Some(Self::indicator()), - (Some(&self.buffer as &dyn ComponentBatch)).map(|batch| { - ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_buffer()), - } - }), - (Some(&self.format as &dyn ComponentBatch)).map(|batch| { - ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_format()), - } - }), - (self - .opacity - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_opacity()), - }), - (self - .draw_order - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_draw_order()), - }), + Self::indicator().serialized(), + self.buffer.clone(), + self.format.clone(), + self.opacity.clone(), + self.draw_order.clone(), ] .into_iter() .flatten() @@ -375,19 +321,160 @@ impl Image { format: impl Into, ) -> Self { Self { - buffer: buffer.into(), - format: format.into(), + buffer: try_serialize_field(Self::descriptor_buffer(), [buffer]), + format: try_serialize_field(Self::descriptor_format(), [format]), opacity: None, draw_order: None, } } + /// Update only some specific fields of a `Image`. + #[inline] + pub fn update_fields() -> Self { + Self::default() + } + + /// Clear all the fields of a `Image`. + #[inline] + pub fn clear_fields() -> Self { + use ::re_types_core::Loggable as _; + Self { + buffer: Some(SerializedComponentBatch::new( + crate::components::ImageBuffer::arrow_empty(), + Self::descriptor_buffer(), + )), + format: Some(SerializedComponentBatch::new( + crate::components::ImageFormat::arrow_empty(), + Self::descriptor_format(), + )), + opacity: Some(SerializedComponentBatch::new( + crate::components::Opacity::arrow_empty(), + Self::descriptor_opacity(), + )), + draw_order: Some(SerializedComponentBatch::new( + crate::components::DrawOrder::arrow_empty(), + Self::descriptor_draw_order(), + )), + } + } + + /// Partitions the component data into multiple sub-batches. + /// + /// Specifically, this transforms the existing [`SerializedComponentBatch`]es data into [`SerializedComponentColumn`]s + /// instead, via [`SerializedComponentBatch::partitioned`]. + /// + /// This makes it possible to use `RecordingStream::send_columns` to send columnar data directly into Rerun. + /// + /// The specified `lengths` must sum to the total length of the component batch. + /// + /// [`SerializedComponentColumn`]: [::re_types_core::SerializedComponentColumn] + #[inline] + pub fn columns( + self, + _lengths: I, + ) -> SerializationResult> + where + I: IntoIterator + Clone, + { + let columns = [ + self.buffer + .map(|buffer| buffer.partitioned(_lengths.clone())) + .transpose()?, + self.format + .map(|format| format.partitioned(_lengths.clone())) + .transpose()?, + self.opacity + .map(|opacity| opacity.partitioned(_lengths.clone())) + .transpose()?, + self.draw_order + .map(|draw_order| draw_order.partitioned(_lengths.clone())) + .transpose()?, + ]; + let indicator_column = + ::re_types_core::indicator_column::(_lengths.into_iter().count())?; + Ok(columns.into_iter().chain([indicator_column]).flatten()) + } + + /// Helper to partition the component data into unit-length sub-batches. + /// + /// This is semantically similar to calling [`Self::columns`] with `std::iter::take(1).repeat(n)`, + /// where `n` is automatically guessed. + #[inline] + pub fn columns_of_unit_batches( + self, + ) -> SerializationResult> { + let len_buffer = self.buffer.as_ref().map(|b| b.array.len()); + let len_format = self.format.as_ref().map(|b| b.array.len()); + let len_opacity = self.opacity.as_ref().map(|b| b.array.len()); + let len_draw_order = self.draw_order.as_ref().map(|b| b.array.len()); + let len = None + .or(len_buffer) + .or(len_format) + .or(len_opacity) + .or(len_draw_order) + .unwrap_or(0); + self.columns(std::iter::repeat(1).take(len)) + } + + /// The raw image data. + #[inline] + pub fn with_buffer(mut self, buffer: impl Into) -> Self { + self.buffer = try_serialize_field(Self::descriptor_buffer(), [buffer]); + self + } + + /// This method makes it possible to pack multiple [`crate::components::ImageBuffer`] in a single component batch. + /// + /// This only makes sense when used in conjunction with [`Self::columns`]. [`Self::with_buffer`] should + /// be used when logging a single row's worth of data. + #[inline] + pub fn with_many_buffer( + mut self, + buffer: impl IntoIterator>, + ) -> Self { + self.buffer = try_serialize_field(Self::descriptor_buffer(), buffer); + self + } + + /// The format of the image. + #[inline] + pub fn with_format(mut self, format: impl Into) -> Self { + self.format = try_serialize_field(Self::descriptor_format(), [format]); + self + } + + /// This method makes it possible to pack multiple [`crate::components::ImageFormat`] in a single component batch. + /// + /// This only makes sense when used in conjunction with [`Self::columns`]. [`Self::with_format`] should + /// be used when logging a single row's worth of data. + #[inline] + pub fn with_many_format( + mut self, + format: impl IntoIterator>, + ) -> Self { + self.format = try_serialize_field(Self::descriptor_format(), format); + self + } + /// Opacity of the image, useful for layering several images. /// /// Defaults to 1.0 (fully opaque). #[inline] pub fn with_opacity(mut self, opacity: impl Into) -> Self { - self.opacity = Some(opacity.into()); + self.opacity = try_serialize_field(Self::descriptor_opacity(), [opacity]); + self + } + + /// This method makes it possible to pack multiple [`crate::components::Opacity`] in a single component batch. + /// + /// This only makes sense when used in conjunction with [`Self::columns`]. [`Self::with_opacity`] should + /// be used when logging a single row's worth of data. + #[inline] + pub fn with_many_opacity( + mut self, + opacity: impl IntoIterator>, + ) -> Self { + self.opacity = try_serialize_field(Self::descriptor_opacity(), opacity); self } @@ -396,7 +483,20 @@ impl Image { /// Objects with higher values are drawn on top of those with lower values. #[inline] pub fn with_draw_order(mut self, draw_order: impl Into) -> Self { - self.draw_order = Some(draw_order.into()); + self.draw_order = try_serialize_field(Self::descriptor_draw_order(), [draw_order]); + self + } + + /// This method makes it possible to pack multiple [`crate::components::DrawOrder`] in a single component batch. + /// + /// This only makes sense when used in conjunction with [`Self::columns`]. [`Self::with_draw_order`] should + /// be used when logging a single row's worth of data. + #[inline] + pub fn with_many_draw_order( + mut self, + draw_order: impl IntoIterator>, + ) -> Self { + self.draw_order = try_serialize_field(Self::descriptor_draw_order(), draw_order); self } } @@ -409,12 +509,4 @@ impl ::re_byte_size::SizeBytes for Image { + self.opacity.heap_size_bytes() + self.draw_order.heap_size_bytes() } - - #[inline] - fn is_pod() -> bool { - ::is_pod() - && ::is_pod() - && >::is_pod() - && >::is_pod() - } } diff --git a/crates/store/re_types/src/archetypes/image_ext.rs b/crates/store/re_types/src/archetypes/image_ext.rs index 004877acd2bd..ca6567a76ef5 100644 --- a/crates/store/re_types/src/archetypes/image_ext.rs +++ b/crates/store/re_types/src/archetypes/image_ext.rs @@ -55,13 +55,8 @@ impl Image { let (height, width) = (shape[non_empty_dim_inds[0]], shape[non_empty_dim_inds[1]]); - let image_format = ImageFormat { - width: width as _, - height: height as _, - pixel_format: None, - channel_datatype: Some(datatype), - color_model: Some(color_model), - }; + let image_format = + ImageFormat::from_color_model([width as _, height as _], color_model, datatype); Ok(Self::new(blob, image_format)) } @@ -98,14 +93,7 @@ impl Image { datatype: ChannelDatatype, ) -> Self { let buffer = bytes.into(); - - let image_format = ImageFormat { - width, - height, - pixel_format: None, - channel_datatype: Some(datatype), - color_model: Some(color_model), - }; + let image_format = ImageFormat::from_color_model([width, height], color_model, datatype); let num_expected_bytes = image_format.num_bytes(); if buffer.len() != num_expected_bytes { @@ -175,7 +163,7 @@ impl Image { #[cfg(feature = "image")] impl Image { - /// Construct a tensor from the contents of an image file. + /// Construct an image from the contents of an image file. /// /// This will spend CPU cycles decoding the image. /// To save CPU time and storage, we recommend you instead use @@ -192,68 +180,18 @@ impl Image { Ok(Self::from_image(image)?) } - /// Construct a tensor from something that can be turned into a [`image::DynamicImage`]. + /// Construct an image from something that can be turned into a [`image::DynamicImage`]. /// /// Requires the `image` feature. pub fn from_image(image: impl Into) -> Result { Self::from_dynamic_image(image.into()) } - /// Construct a tensor from [`image::DynamicImage`]. + /// Construct an image from [`image::DynamicImage`]. /// /// Requires the `image` feature. pub fn from_dynamic_image(image: image::DynamicImage) -> Result { - re_tracing::profile_function!(); - - let res = [image.width(), image.height()]; - - match image { - image::DynamicImage::ImageLuma8(image) => { - Ok(Self::from_elements(image.as_raw(), res, ColorModel::L)) - } - image::DynamicImage::ImageLuma16(image) => { - Ok(Self::from_elements(image.as_raw(), res, ColorModel::L)) - } - - image::DynamicImage::ImageLumaA8(image) => { - re_log::warn!( - "Rerun doesn't have native support for 8-bit Luma + Alpha. The image will be convert to RGBA." - ); - Self::from_image(image::DynamicImage::ImageLumaA8(image).to_rgba8()) - } - image::DynamicImage::ImageLumaA16(image) => { - re_log::warn!( - "Rerun doesn't have native support for 16-bit Luma + Alpha. The image will be convert to RGBA." - ); - Self::from_image(image::DynamicImage::ImageLumaA16(image).to_rgba16()) - } - - image::DynamicImage::ImageRgb8(image) => { - Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGB)) - } - image::DynamicImage::ImageRgb16(image) => { - Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGB)) - } - image::DynamicImage::ImageRgb32F(image) => { - Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGB)) - } - - image::DynamicImage::ImageRgba8(image) => { - Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGBA)) - } - image::DynamicImage::ImageRgba16(image) => { - Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGBA)) - } - image::DynamicImage::ImageRgba32F(image) => { - Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGBA)) - } - - _ => { - // It is very annoying that DynamicImage is #[non_exhaustive] - Err(ImageConversionError::UnsupportedImageColorType( - image.color(), - )) - } - } + let (image_buffer, image_format) = ImageBuffer::from_image(image)?; + Ok(Self::new(image_buffer, image_format)) } } diff --git a/crates/store/re_types/src/archetypes/mesh3d.rs b/crates/store/re_types/src/archetypes/mesh3d.rs index 9dfd9a44fa53..2d29473ea15b 100644 --- a/crates/store/re_types/src/archetypes/mesh3d.rs +++ b/crates/store/re_types/src/archetypes/mesh3d.rs @@ -106,27 +106,27 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// /// /// -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Default)] pub struct Mesh3D { /// The positions of each vertex. /// /// If no `triangle_indices` are specified, then each triplet of positions is interpreted as a triangle. - pub vertex_positions: Vec, + pub vertex_positions: Option, /// Optional indices for the triangles that make up the mesh. - pub triangle_indices: Option>, + pub triangle_indices: Option, /// An optional normal for each vertex. - pub vertex_normals: Option>, + pub vertex_normals: Option, /// An optional color for each vertex. - pub vertex_colors: Option>, + pub vertex_colors: Option, /// An optional uv texture coordinate for each vertex. - pub vertex_texcoords: Option>, + pub vertex_texcoords: Option, /// A color multiplier applied to the whole mesh. - pub albedo_factor: Option, + pub albedo_factor: Option, /// Optional albedo texture. /// @@ -134,15 +134,15 @@ pub struct Mesh3D { /// /// Currently supports only sRGB(A) textures, ignoring alpha. /// (meaning that the tensor must have 3 or 4 channels and use the `u8` format) - pub albedo_texture_buffer: Option, + pub albedo_texture_buffer: Option, /// The format of the `albedo_texture_buffer`, if any. - pub albedo_texture_format: Option, + pub albedo_texture_format: Option, /// Optional class Ids for the vertices. /// /// The [`components::ClassId`][crate::components::ClassId] provides colors and labels if not specified explicitly. - pub class_ids: Option>, + pub class_ids: Option, } impl Mesh3D { @@ -341,112 +341,57 @@ impl ::re_types_core::Archetype for Mesh3D { re_tracing::profile_function!(); use ::re_types_core::{Loggable as _, ResultExt as _}; let arrays_by_descr: ::nohash_hasher::IntMap<_, _> = arrow_data.into_iter().collect(); - let vertex_positions = { - let array = arrays_by_descr - .get(&Self::descriptor_vertex_positions()) - .ok_or_else(DeserializationError::missing_data) - .with_context("rerun.archetypes.Mesh3D#vertex_positions")?; - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#vertex_positions")? - .into_iter() - .map(|v| v.ok_or_else(DeserializationError::missing_data)) - .collect::>>() - .with_context("rerun.archetypes.Mesh3D#vertex_positions")? - }; - let triangle_indices = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_triangle_indices()) { - Some({ - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#triangle_indices")? - .into_iter() - .map(|v| v.ok_or_else(DeserializationError::missing_data)) - .collect::>>() - .with_context("rerun.archetypes.Mesh3D#triangle_indices")? - }) - } else { - None - }; - let vertex_normals = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_vertex_normals()) { - Some({ - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#vertex_normals")? - .into_iter() - .map(|v| v.ok_or_else(DeserializationError::missing_data)) - .collect::>>() - .with_context("rerun.archetypes.Mesh3D#vertex_normals")? - }) - } else { - None - }; - let vertex_colors = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_vertex_colors()) { - Some({ - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#vertex_colors")? - .into_iter() - .map(|v| v.ok_or_else(DeserializationError::missing_data)) - .collect::>>() - .with_context("rerun.archetypes.Mesh3D#vertex_colors")? - }) - } else { - None - }; - let vertex_texcoords = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_vertex_texcoords()) { - Some({ - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#vertex_texcoords")? - .into_iter() - .map(|v| v.ok_or_else(DeserializationError::missing_data)) - .collect::>>() - .with_context("rerun.archetypes.Mesh3D#vertex_texcoords")? - }) - } else { - None - }; - let albedo_factor = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_albedo_factor()) { - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#albedo_factor")? - .into_iter() - .next() - .flatten() - } else { - None - }; - let albedo_texture_buffer = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_albedo_texture_buffer()) { - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#albedo_texture_buffer")? - .into_iter() - .next() - .flatten() - } else { - None - }; - let albedo_texture_format = - if let Some(array) = arrays_by_descr.get(&Self::descriptor_albedo_texture_format()) { - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#albedo_texture_format")? - .into_iter() - .next() - .flatten() - } else { - None - }; - let class_ids = if let Some(array) = arrays_by_descr.get(&Self::descriptor_class_ids()) { - Some({ - ::from_arrow_opt(&**array) - .with_context("rerun.archetypes.Mesh3D#class_ids")? - .into_iter() - .map(|v| v.ok_or_else(DeserializationError::missing_data)) - .collect::>>() - .with_context("rerun.archetypes.Mesh3D#class_ids")? - }) - } else { - None - }; + let vertex_positions = arrays_by_descr + .get(&Self::descriptor_vertex_positions()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_vertex_positions()) + }); + let triangle_indices = arrays_by_descr + .get(&Self::descriptor_triangle_indices()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_triangle_indices()) + }); + let vertex_normals = arrays_by_descr + .get(&Self::descriptor_vertex_normals()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_vertex_normals()) + }); + let vertex_colors = arrays_by_descr + .get(&Self::descriptor_vertex_colors()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_vertex_colors()) + }); + let vertex_texcoords = arrays_by_descr + .get(&Self::descriptor_vertex_texcoords()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_vertex_texcoords()) + }); + let albedo_factor = arrays_by_descr + .get(&Self::descriptor_albedo_factor()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_albedo_factor()) + }); + let albedo_texture_buffer = arrays_by_descr + .get(&Self::descriptor_albedo_texture_buffer()) + .map(|array| { + SerializedComponentBatch::new( + array.clone(), + Self::descriptor_albedo_texture_buffer(), + ) + }); + let albedo_texture_format = arrays_by_descr + .get(&Self::descriptor_albedo_texture_format()) + .map(|array| { + SerializedComponentBatch::new( + array.clone(), + Self::descriptor_albedo_texture_format(), + ) + }); + let class_ids = arrays_by_descr + .get(&Self::descriptor_class_ids()) + .map(|array| { + SerializedComponentBatch::new(array.clone(), Self::descriptor_class_ids()) + }); Ok(Self { vertex_positions, triangle_indices, @@ -462,81 +407,20 @@ impl ::re_types_core::Archetype for Mesh3D { } impl ::re_types_core::AsComponents for Mesh3D { - fn as_component_batches(&self) -> Vec> { - re_tracing::profile_function!(); + #[inline] + fn as_serialized_batches(&self) -> Vec { use ::re_types_core::Archetype as _; [ - Some(Self::indicator()), - (Some(&self.vertex_positions as &dyn ComponentBatch)).map(|batch| { - ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_vertex_positions()), - } - }), - (self - .triangle_indices - .as_ref() - .map(|comp_batch| (comp_batch as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_triangle_indices()), - }), - (self - .vertex_normals - .as_ref() - .map(|comp_batch| (comp_batch as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_vertex_normals()), - }), - (self - .vertex_colors - .as_ref() - .map(|comp_batch| (comp_batch as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_vertex_colors()), - }), - (self - .vertex_texcoords - .as_ref() - .map(|comp_batch| (comp_batch as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_vertex_texcoords()), - }), - (self - .albedo_factor - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_albedo_factor()), - }), - (self - .albedo_texture_buffer - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_albedo_texture_buffer()), - }), - (self - .albedo_texture_format - .as_ref() - .map(|comp| (comp as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_albedo_texture_format()), - }), - (self - .class_ids - .as_ref() - .map(|comp_batch| (comp_batch as &dyn ComponentBatch))) - .map(|batch| ::re_types_core::ComponentBatchCowWithDescriptor { - batch: batch.into(), - descriptor_override: Some(Self::descriptor_class_ids()), - }), + Self::indicator().serialized(), + self.vertex_positions.clone(), + self.triangle_indices.clone(), + self.vertex_normals.clone(), + self.vertex_colors.clone(), + self.vertex_texcoords.clone(), + self.albedo_factor.clone(), + self.albedo_texture_buffer.clone(), + self.albedo_texture_format.clone(), + self.class_ids.clone(), ] .into_iter() .flatten() @@ -553,7 +437,10 @@ impl Mesh3D { vertex_positions: impl IntoIterator>, ) -> Self { Self { - vertex_positions: vertex_positions.into_iter().map(Into::into).collect(), + vertex_positions: try_serialize_field( + Self::descriptor_vertex_positions(), + vertex_positions, + ), triangle_indices: None, vertex_normals: None, vertex_colors: None, @@ -565,13 +452,160 @@ impl Mesh3D { } } + /// Update only some specific fields of a `Mesh3D`. + #[inline] + pub fn update_fields() -> Self { + Self::default() + } + + /// Clear all the fields of a `Mesh3D`. + #[inline] + pub fn clear_fields() -> Self { + use ::re_types_core::Loggable as _; + Self { + vertex_positions: Some(SerializedComponentBatch::new( + crate::components::Position3D::arrow_empty(), + Self::descriptor_vertex_positions(), + )), + triangle_indices: Some(SerializedComponentBatch::new( + crate::components::TriangleIndices::arrow_empty(), + Self::descriptor_triangle_indices(), + )), + vertex_normals: Some(SerializedComponentBatch::new( + crate::components::Vector3D::arrow_empty(), + Self::descriptor_vertex_normals(), + )), + vertex_colors: Some(SerializedComponentBatch::new( + crate::components::Color::arrow_empty(), + Self::descriptor_vertex_colors(), + )), + vertex_texcoords: Some(SerializedComponentBatch::new( + crate::components::Texcoord2D::arrow_empty(), + Self::descriptor_vertex_texcoords(), + )), + albedo_factor: Some(SerializedComponentBatch::new( + crate::components::AlbedoFactor::arrow_empty(), + Self::descriptor_albedo_factor(), + )), + albedo_texture_buffer: Some(SerializedComponentBatch::new( + crate::components::ImageBuffer::arrow_empty(), + Self::descriptor_albedo_texture_buffer(), + )), + albedo_texture_format: Some(SerializedComponentBatch::new( + crate::components::ImageFormat::arrow_empty(), + Self::descriptor_albedo_texture_format(), + )), + class_ids: Some(SerializedComponentBatch::new( + crate::components::ClassId::arrow_empty(), + Self::descriptor_class_ids(), + )), + } + } + + /// Partitions the component data into multiple sub-batches. + /// + /// Specifically, this transforms the existing [`SerializedComponentBatch`]es data into [`SerializedComponentColumn`]s + /// instead, via [`SerializedComponentBatch::partitioned`]. + /// + /// This makes it possible to use `RecordingStream::send_columns` to send columnar data directly into Rerun. + /// + /// The specified `lengths` must sum to the total length of the component batch. + /// + /// [`SerializedComponentColumn`]: [::re_types_core::SerializedComponentColumn] + #[inline] + pub fn columns( + self, + _lengths: I, + ) -> SerializationResult> + where + I: IntoIterator + Clone, + { + let columns = [ + self.vertex_positions + .map(|vertex_positions| vertex_positions.partitioned(_lengths.clone())) + .transpose()?, + self.triangle_indices + .map(|triangle_indices| triangle_indices.partitioned(_lengths.clone())) + .transpose()?, + self.vertex_normals + .map(|vertex_normals| vertex_normals.partitioned(_lengths.clone())) + .transpose()?, + self.vertex_colors + .map(|vertex_colors| vertex_colors.partitioned(_lengths.clone())) + .transpose()?, + self.vertex_texcoords + .map(|vertex_texcoords| vertex_texcoords.partitioned(_lengths.clone())) + .transpose()?, + self.albedo_factor + .map(|albedo_factor| albedo_factor.partitioned(_lengths.clone())) + .transpose()?, + self.albedo_texture_buffer + .map(|albedo_texture_buffer| albedo_texture_buffer.partitioned(_lengths.clone())) + .transpose()?, + self.albedo_texture_format + .map(|albedo_texture_format| albedo_texture_format.partitioned(_lengths.clone())) + .transpose()?, + self.class_ids + .map(|class_ids| class_ids.partitioned(_lengths.clone())) + .transpose()?, + ]; + let indicator_column = + ::re_types_core::indicator_column::(_lengths.into_iter().count())?; + Ok(columns.into_iter().chain([indicator_column]).flatten()) + } + + /// Helper to partition the component data into unit-length sub-batches. + /// + /// This is semantically similar to calling [`Self::columns`] with `std::iter::take(1).repeat(n)`, + /// where `n` is automatically guessed. + #[inline] + pub fn columns_of_unit_batches( + self, + ) -> SerializationResult> { + let len_vertex_positions = self.vertex_positions.as_ref().map(|b| b.array.len()); + let len_triangle_indices = self.triangle_indices.as_ref().map(|b| b.array.len()); + let len_vertex_normals = self.vertex_normals.as_ref().map(|b| b.array.len()); + let len_vertex_colors = self.vertex_colors.as_ref().map(|b| b.array.len()); + let len_vertex_texcoords = self.vertex_texcoords.as_ref().map(|b| b.array.len()); + let len_albedo_factor = self.albedo_factor.as_ref().map(|b| b.array.len()); + let len_albedo_texture_buffer = self.albedo_texture_buffer.as_ref().map(|b| b.array.len()); + let len_albedo_texture_format = self.albedo_texture_format.as_ref().map(|b| b.array.len()); + let len_class_ids = self.class_ids.as_ref().map(|b| b.array.len()); + let len = None + .or(len_vertex_positions) + .or(len_triangle_indices) + .or(len_vertex_normals) + .or(len_vertex_colors) + .or(len_vertex_texcoords) + .or(len_albedo_factor) + .or(len_albedo_texture_buffer) + .or(len_albedo_texture_format) + .or(len_class_ids) + .unwrap_or(0); + self.columns(std::iter::repeat(1).take(len)) + } + + /// The positions of each vertex. + /// + /// If no `triangle_indices` are specified, then each triplet of positions is interpreted as a triangle. + #[inline] + pub fn with_vertex_positions( + mut self, + vertex_positions: impl IntoIterator>, + ) -> Self { + self.vertex_positions = + try_serialize_field(Self::descriptor_vertex_positions(), vertex_positions); + self + } + /// Optional indices for the triangles that make up the mesh. #[inline] pub fn with_triangle_indices( mut self, triangle_indices: impl IntoIterator>, ) -> Self { - self.triangle_indices = Some(triangle_indices.into_iter().map(Into::into).collect()); + self.triangle_indices = + try_serialize_field(Self::descriptor_triangle_indices(), triangle_indices); self } @@ -581,7 +615,8 @@ impl Mesh3D { mut self, vertex_normals: impl IntoIterator>, ) -> Self { - self.vertex_normals = Some(vertex_normals.into_iter().map(Into::into).collect()); + self.vertex_normals = + try_serialize_field(Self::descriptor_vertex_normals(), vertex_normals); self } @@ -591,7 +626,7 @@ impl Mesh3D { mut self, vertex_colors: impl IntoIterator>, ) -> Self { - self.vertex_colors = Some(vertex_colors.into_iter().map(Into::into).collect()); + self.vertex_colors = try_serialize_field(Self::descriptor_vertex_colors(), vertex_colors); self } @@ -601,7 +636,8 @@ impl Mesh3D { mut self, vertex_texcoords: impl IntoIterator>, ) -> Self { - self.vertex_texcoords = Some(vertex_texcoords.into_iter().map(Into::into).collect()); + self.vertex_texcoords = + try_serialize_field(Self::descriptor_vertex_texcoords(), vertex_texcoords); self } @@ -611,7 +647,20 @@ impl Mesh3D { mut self, albedo_factor: impl Into, ) -> Self { - self.albedo_factor = Some(albedo_factor.into()); + self.albedo_factor = try_serialize_field(Self::descriptor_albedo_factor(), [albedo_factor]); + self + } + + /// This method makes it possible to pack multiple [`crate::components::AlbedoFactor`] in a single component batch. + /// + /// This only makes sense when used in conjunction with [`Self::columns`]. [`Self::with_albedo_factor`] should + /// be used when logging a single row's worth of data. + #[inline] + pub fn with_many_albedo_factor( + mut self, + albedo_factor: impl IntoIterator>, + ) -> Self { + self.albedo_factor = try_serialize_field(Self::descriptor_albedo_factor(), albedo_factor); self } @@ -626,7 +675,26 @@ impl Mesh3D { mut self, albedo_texture_buffer: impl Into, ) -> Self { - self.albedo_texture_buffer = Some(albedo_texture_buffer.into()); + self.albedo_texture_buffer = try_serialize_field( + Self::descriptor_albedo_texture_buffer(), + [albedo_texture_buffer], + ); + self + } + + /// This method makes it possible to pack multiple [`crate::components::ImageBuffer`] in a single component batch. + /// + /// This only makes sense when used in conjunction with [`Self::columns`]. [`Self::with_albedo_texture_buffer`] should + /// be used when logging a single row's worth of data. + #[inline] + pub fn with_many_albedo_texture_buffer( + mut self, + albedo_texture_buffer: impl IntoIterator>, + ) -> Self { + self.albedo_texture_buffer = try_serialize_field( + Self::descriptor_albedo_texture_buffer(), + albedo_texture_buffer, + ); self } @@ -636,7 +704,26 @@ impl Mesh3D { mut self, albedo_texture_format: impl Into, ) -> Self { - self.albedo_texture_format = Some(albedo_texture_format.into()); + self.albedo_texture_format = try_serialize_field( + Self::descriptor_albedo_texture_format(), + [albedo_texture_format], + ); + self + } + + /// This method makes it possible to pack multiple [`crate::components::ImageFormat`] in a single component batch. + /// + /// This only makes sense when used in conjunction with [`Self::columns`]. [`Self::with_albedo_texture_format`] should + /// be used when logging a single row's worth of data. + #[inline] + pub fn with_many_albedo_texture_format( + mut self, + albedo_texture_format: impl IntoIterator>, + ) -> Self { + self.albedo_texture_format = try_serialize_field( + Self::descriptor_albedo_texture_format(), + albedo_texture_format, + ); self } @@ -648,7 +735,7 @@ impl Mesh3D { mut self, class_ids: impl IntoIterator>, ) -> Self { - self.class_ids = Some(class_ids.into_iter().map(Into::into).collect()); + self.class_ids = try_serialize_field(Self::descriptor_class_ids(), class_ids); self } } @@ -666,17 +753,4 @@ impl ::re_byte_size::SizeBytes for Mesh3D { + self.albedo_texture_format.heap_size_bytes() + self.class_ids.heap_size_bytes() } - - #[inline] - fn is_pod() -> bool { - >::is_pod() - && >>::is_pod() - && >>::is_pod() - && >>::is_pod() - && >>::is_pod() - && >::is_pod() - && >::is_pod() - && >::is_pod() - && >>::is_pod() - } } diff --git a/crates/store/re_types/src/archetypes/mesh3d_ext.rs b/crates/store/re_types/src/archetypes/mesh3d_ext.rs index 25c0c771881c..853a8e3676d7 100644 --- a/crates/store/re_types/src/archetypes/mesh3d_ext.rs +++ b/crates/store/re_types/src/archetypes/mesh3d_ext.rs @@ -1,7 +1,8 @@ +use arrow::array::{self, Array}; + use crate::{ archetypes, - components::{self, TriangleIndices}, - datatypes::UVec3D, + components::{self}, }; use super::Mesh3D; @@ -23,13 +24,21 @@ pub enum Mesh3DError { impl Mesh3D { /// Use this image as the albedo texture. - pub fn with_albedo_texture_image(self, image: impl Into) -> Self { + #[inline] + pub fn with_albedo_texture_image(mut self, image: impl Into) -> Self { let image = image.into(); - self.with_albedo_texture_format(image.format) - .with_albedo_texture_buffer(image.buffer) + + self.albedo_texture_format = image + .format + .map(|batch| batch.with_descriptor_override(Self::descriptor_albedo_texture_format())); + self.albedo_texture_buffer = image + .buffer + .map(|batch| batch.with_descriptor_override(Self::descriptor_albedo_texture_buffer())); + self } /// Use this image as the albedo texture. + #[inline] pub fn with_albedo_texture( self, image_format: impl Into, @@ -41,41 +50,35 @@ impl Mesh3D { /// Check that this is a valid mesh, e.g. that the vertex indices are within bounds /// and that we have the same number of positions and normals (if any). + /// + /// Only use this when logging a whole new mesh. Not meaningful for field updates! pub fn sanity_check(&self) -> Result<(), Mesh3DError> { let num_vertices = self.num_vertices(); - if let Some(indices) = self.triangle_indices.as_ref() { - for &TriangleIndices(UVec3D([x, y, z])) in indices { - if num_vertices <= x as usize { - return Err(Mesh3DError::IndexOutOfBounds { - index: x, - num_vertices, - }); - } - if num_vertices <= y as usize { - return Err(Mesh3DError::IndexOutOfBounds { - index: y, - num_vertices, - }); - } - if num_vertices <= z as usize { + let index_data = self.triangle_indices.as_ref().map(|indices| { + array::as_fixed_size_list_array(&indices.array) + .values() + .to_data() + }); + + if let Some(index_data) = index_data { + for index in index_data.buffer::(0) { + if num_vertices <= *index as usize { return Err(Mesh3DError::IndexOutOfBounds { - index: z, + index: *index, num_vertices, }); } } - } else if self.vertex_positions.len() % 9 != 0 { - return Err(Mesh3DError::PositionsAreNotTriangles( - self.vertex_positions.len(), - )); + } else if num_vertices % 9 != 0 { + return Err(Mesh3DError::PositionsAreNotTriangles(num_vertices)); } if let Some(normals) = &self.vertex_normals { - if normals.len() != self.vertex_positions.len() { + if normals.array.len() != num_vertices { return Err(Mesh3DError::MismatchedPositionsNormals( - self.vertex_positions.len(), - normals.len(), + num_vertices, + normals.array.len(), )); } } @@ -86,14 +89,16 @@ impl Mesh3D { /// The total number of vertices. #[inline] pub fn num_vertices(&self) -> usize { - self.vertex_positions.len() + self.vertex_positions + .as_ref() + .map_or(0, |positions| positions.array.len()) } /// The total number of triangles. #[inline] pub fn num_triangles(&self) -> usize { - if let Some(indices) = self.triangle_indices.as_ref() { - indices.len() + if let Some(triangle_indices) = self.triangle_indices.as_ref() { + triangle_indices.array.len() } else { self.num_vertices() / 3 } diff --git a/crates/store/re_types/src/components/image_buffer_ext.rs b/crates/store/re_types/src/components/image_buffer_ext.rs new file mode 100644 index 000000000000..145fbd8d2f13 --- /dev/null +++ b/crates/store/re_types/src/components/image_buffer_ext.rs @@ -0,0 +1,96 @@ +#[cfg(feature = "image")] +use crate::{datatypes::ColorModel, image::ImageChannelType}; + +#[cfg(feature = "image")] +use super::{ImageBuffer, ImageFormat}; + +#[cfg(feature = "image")] +impl ImageBuffer { + /// Utility method for constructing an image & format + /// from a byte buffer given its resolution and using the data type of the given vector. + fn from_elements( + elements: &[T], + [width, height]: [u32; 2], + color_model: ColorModel, + ) -> (Self, ImageFormat) { + let datatype = T::CHANNEL_TYPE; + let bytes: &[u8] = bytemuck::cast_slice(elements); + let image_format = ImageFormat::from_color_model([width, height], color_model, datatype); + + let num_expected_bytes = image_format.num_bytes(); + if bytes.len() != num_expected_bytes { + re_log::warn_once!( + "Expected {width}x{height} {color_model:?} {datatype:?} image to be {num_expected_bytes} B, but got {} B", bytes.len() + ); + } + + (Self(bytes.into()), image_format) + } + + /// Construct an image buffer & image format from something that can be turned into a [`image::DynamicImage`]. + /// + /// Requires the `image` feature. + pub fn from_image( + image: impl Into, + ) -> Result<(Self, ImageFormat), crate::image::ImageConversionError> { + Self::from_dynamic_image(image.into()) + } + + /// Construct an image buffer & image format from [`image::DynamicImage`]. + /// + /// Requires the `image` feature. + pub fn from_dynamic_image( + image: image::DynamicImage, + ) -> Result<(Self, ImageFormat), crate::image::ImageConversionError> { + re_tracing::profile_function!(); + + let res = [image.width(), image.height()]; + + match image { + image::DynamicImage::ImageLuma8(image) => { + Ok(Self::from_elements(image.as_raw(), res, ColorModel::L)) + } + image::DynamicImage::ImageLuma16(image) => { + Ok(Self::from_elements(image.as_raw(), res, ColorModel::L)) + } + + image::DynamicImage::ImageLumaA8(image) => { + re_log::warn!( + "Rerun doesn't have native support for 8-bit Luma + Alpha. The image will be convert to RGBA." + ); + Self::from_image(image::DynamicImage::ImageLumaA8(image).to_rgba8()) + } + image::DynamicImage::ImageLumaA16(image) => { + re_log::warn!( + "Rerun doesn't have native support for 16-bit Luma + Alpha. The image will be convert to RGBA." + ); + Self::from_image(image::DynamicImage::ImageLumaA16(image).to_rgba16()) + } + + image::DynamicImage::ImageRgb8(image) => { + Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGB)) + } + image::DynamicImage::ImageRgb16(image) => { + Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGB)) + } + image::DynamicImage::ImageRgb32F(image) => { + Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGB)) + } + + image::DynamicImage::ImageRgba8(image) => { + Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGBA)) + } + image::DynamicImage::ImageRgba16(image) => { + Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGBA)) + } + image::DynamicImage::ImageRgba32F(image) => { + Ok(Self::from_elements(image.as_raw(), res, ColorModel::RGBA)) + } + + _ => { + // It is very annoying that DynamicImage is #[non_exhaustive] + Err(crate::image::ImageConversionError::UnsupportedImageColorType(image.color())) + } + } + } +} diff --git a/crates/store/re_types/src/components/image_format_ext.rs b/crates/store/re_types/src/components/image_format_ext.rs index c1dc10709633..988c4040229c 100644 --- a/crates/store/re_types/src/components/image_format_ext.rs +++ b/crates/store/re_types/src/components/image_format_ext.rs @@ -32,6 +32,15 @@ impl ImageFormat { datatypes::ImageFormat::from_pixel_format([width, height], pixel_format).into() } + /// Create a new image format from a color model and datatype. + pub fn from_color_model( + [width, height]: [u32; 2], + color_model: ColorModel, + datatype: ChannelDatatype, + ) -> Self { + datatypes::ImageFormat::from_color_model([width, height], color_model, datatype).into() + } + /// Determine if the image format has an alpha channel. #[inline] pub fn has_alpha(&self) -> bool { diff --git a/crates/store/re_types/src/components/mod.rs b/crates/store/re_types/src/components/mod.rs index d1a4df006de3..4cf2ea54302b 100644 --- a/crates/store/re_types/src/components/mod.rs +++ b/crates/store/re_types/src/components/mod.rs @@ -35,6 +35,7 @@ mod half_size2d_ext; mod half_size3d; mod half_size3d_ext; mod image_buffer; +mod image_buffer_ext; mod image_format; mod image_format_ext; mod image_plane_distance; diff --git a/crates/store/re_types/src/datatypes/blob_ext.rs b/crates/store/re_types/src/datatypes/blob_ext.rs index 8d5c897215fe..820521b6c7f2 100644 --- a/crates/store/re_types/src/datatypes/blob_ext.rs +++ b/crates/store/re_types/src/datatypes/blob_ext.rs @@ -20,6 +20,12 @@ impl From> for Blob { } } +impl From<&[u8]> for Blob { + fn from(bytes: &[u8]) -> Self { + Self(bytes.into()) + } +} + impl std::ops::Deref for Blob { type Target = re_types_core::ArrowBuffer; diff --git a/crates/store/re_types/src/datatypes/image_format_ext.rs b/crates/store/re_types/src/datatypes/image_format_ext.rs index 6f711b085d62..b637d00d1075 100644 --- a/crates/store/re_types/src/datatypes/image_format_ext.rs +++ b/crates/store/re_types/src/datatypes/image_format_ext.rs @@ -61,6 +61,22 @@ impl ImageFormat { } } + /// Create a new image format from a color model and datatype. + #[inline] + pub fn from_color_model( + [width, height]: [u32; 2], + color_model: ColorModel, + datatype: ChannelDatatype, + ) -> Self { + Self { + width, + height, + pixel_format: None, + channel_datatype: Some(datatype), + color_model: Some(color_model), + } + } + /// Determine if the image format has an alpha channel. #[inline] pub fn has_alpha(&self) -> bool { diff --git a/crates/store/re_types/tests/types/mesh3d.rs b/crates/store/re_types/tests/types/mesh3d.rs index 411a7dc4dc8c..5c85a8972747 100644 --- a/crates/store/re_types/tests/types/mesh3d.rs +++ b/crates/store/re_types/tests/types/mesh3d.rs @@ -1,8 +1,8 @@ use re_types::{ archetypes::Mesh3D, - components::{ClassId, Position3D, Texcoord2D, TriangleIndices, Vector3D}, + components::{AlbedoFactor, ClassId, Color, Position3D, Texcoord2D, TriangleIndices, Vector3D}, datatypes::{Rgba32, UVec3D, Vec2D, Vec3D}, - Archetype as _, AsComponents as _, + Archetype as _, AsComponents as _, ComponentBatch, }; #[test] @@ -14,30 +14,48 @@ fn roundtrip() { vertex_positions: vec![ Position3D(Vec3D([1.0, 2.0, 3.0])), Position3D(Vec3D([10.0, 20.0, 30.0])), - ], - triangle_indices: Some(vec![ + ] + .serialized() + .map(|batch| batch.with_descriptor_override(Mesh3D::descriptor_vertex_positions())), + triangle_indices: vec![ TriangleIndices(UVec3D([1, 2, 3])), // TriangleIndices(UVec3D([4, 5, 6])), // - ]), - vertex_normals: Some(vec![ + ] + .serialized() + .map(|batch| batch.with_descriptor_override(Mesh3D::descriptor_triangle_indices())), + vertex_normals: vec![ Vector3D(Vec3D([4.0, 5.0, 6.0])), // Vector3D(Vec3D([40.0, 50.0, 60.0])), // - ]), - vertex_colors: Some(vec![ - Rgba32::from_unmultiplied_rgba(0xAA, 0x00, 0x00, 0xCC).into(), // - Rgba32::from_unmultiplied_rgba(0x00, 0xBB, 0x00, 0xDD).into(), - ]), - vertex_texcoords: Some(vec![ + ] + .serialized() + .map(|batch| batch.with_descriptor_override(Mesh3D::descriptor_vertex_normals())), + vertex_colors: vec![ + Color::from_unmultiplied_rgba(0xAA, 0x00, 0x00, 0xCC), + Color::from_unmultiplied_rgba(0x00, 0xBB, 0x00, 0xDD), + ] + .serialized() + .map(|batch| batch.with_descriptor_override(Mesh3D::descriptor_vertex_colors())), + vertex_texcoords: vec![ Texcoord2D(Vec2D([0.0, 1.0])), // Texcoord2D(Vec2D([2.0, 3.0])), // - ]), - albedo_factor: Some(Rgba32::from_unmultiplied_rgba(0xEE, 0x11, 0x22, 0x33).into()), - albedo_texture_format: Some(texture_format), - albedo_texture_buffer: Some(texture_buffer.clone()), - class_ids: Some(vec![ + ] + .serialized() + .map(|batch| batch.with_descriptor_override(Mesh3D::descriptor_vertex_texcoords())), + albedo_factor: AlbedoFactor(Rgba32::from_unmultiplied_rgba(0xEE, 0x11, 0x22, 0x33)) + .serialized() + .map(|batch| batch.with_descriptor_override(Mesh3D::descriptor_albedo_factor())), + albedo_texture_format: texture_format.serialized().map(|batch| { + batch.with_descriptor_override(Mesh3D::descriptor_albedo_texture_format()) + }), + albedo_texture_buffer: texture_buffer.serialized().map(|batch| { + batch.with_descriptor_override(Mesh3D::descriptor_albedo_texture_buffer()) + }), + class_ids: vec![ ClassId::from(126), // ClassId::from(127), // - ]), + ] + .serialized() + .map(|batch| batch.with_descriptor_override(Mesh3D::descriptor_class_ids())), }; let arch = Mesh3D::new([[1.0, 2.0, 3.0], [10.0, 20.0, 30.0]]) diff --git a/crates/viewer/re_data_ui/src/instance_path.rs b/crates/viewer/re_data_ui/src/instance_path.rs index dc52f05b96c0..dd3f07dae494 100644 --- a/crates/viewer/re_data_ui/src/instance_path.rs +++ b/crates/viewer/re_data_ui/src/instance_path.rs @@ -8,7 +8,7 @@ use re_types::{ archetypes, components, datatypes::{ChannelDatatype, ColorModel}, image::ImageKind, - static_assert_struct_has_fields, Archetype, Component, ComponentName, + Archetype, Component, ComponentName, }; use re_ui::UiExt as _; use re_viewer_context::{ @@ -273,7 +273,7 @@ fn preview_if_image_ui( component_map: &IntMap, ) -> Option<()> { // First check assumptions: - static_assert_struct_has_fields!( + debug_assert_archetype_has_components!( archetypes::Image, buffer: components::ImageBuffer, format: components::ImageFormat diff --git a/crates/viewer/re_view_spatial/src/lib.rs b/crates/viewer/re_view_spatial/src/lib.rs index b2b8604a1045..f703f61d99a1 100644 --- a/crates/viewer/re_view_spatial/src/lib.rs +++ b/crates/viewer/re_view_spatial/src/lib.rs @@ -44,10 +44,8 @@ use re_viewer_context::{ImageDecodeCache, ViewerContext}; use re_log_types::debug_assert_archetype_has_components; use re_renderer::RenderContext; use re_types::{ - archetypes, blueprint::components::BackgroundKind, - components::{self, Color, ImageFormat, MediaType, Resolution}, - static_assert_struct_has_fields, + components::{Color, ImageFormat, MediaType, Resolution}, }; use re_viewport_blueprint::{ViewProperty, ViewPropertyQueryError}; @@ -65,8 +63,8 @@ fn resolution_of_image_at( entity_path: &re_log_types::EntityPath, ) -> Option { // First check assumptions: - static_assert_struct_has_fields!(archetypes::Image, format: components::ImageFormat); - debug_assert_archetype_has_components!(archetypes::EncodedImage, blob: components::Blob); + debug_assert_archetype_has_components!(re_types::archetypes::Image, format: re_types::components::ImageFormat); + debug_assert_archetype_has_components!(re_types::archetypes::EncodedImage, blob: re_types::components::Blob); let db = ctx.recording(); diff --git a/crates/viewer/re_view_spatial/src/mesh_cache.rs b/crates/viewer/re_view_spatial/src/mesh_cache.rs index b3fc594f2ad1..2eddbb38a5d1 100644 --- a/crates/viewer/re_view_spatial/src/mesh_cache.rs +++ b/crates/viewer/re_view_spatial/src/mesh_cache.rs @@ -10,7 +10,7 @@ use re_renderer::RenderContext; use re_types::{components::MediaType, Component as _}; use re_viewer_context::Cache; -use crate::mesh_loader::{LoadedMesh, NativeAsset3D}; +use crate::mesh_loader::{LoadedMesh, NativeAsset3D, NativeMesh3D}; // ---------------------------------------------------------------------------- @@ -40,7 +40,7 @@ pub enum AnyMesh<'a> { asset: NativeAsset3D<'a>, }, Mesh { - mesh: &'a re_types::archetypes::Mesh3D, + mesh: NativeMesh3D<'a>, /// If there are any textures associated with that mesh (albedo etc), they use this /// hash for texture manager lookup. diff --git a/crates/viewer/re_view_spatial/src/mesh_loader.rs b/crates/viewer/re_view_spatial/src/mesh_loader.rs index 45e8a3860272..3e6dd956ec47 100644 --- a/crates/viewer/re_view_spatial/src/mesh_loader.rs +++ b/crates/viewer/re_view_spatial/src/mesh_loader.rs @@ -1,10 +1,10 @@ use itertools::Itertools; use re_chunk_store::RowId; use re_renderer::{mesh::GpuMesh, RenderContext, Rgba32Unmul}; -use re_types::{archetypes::Mesh3D, components::MediaType}; +use re_types::components::MediaType; use re_viewer_context::{gpu_bridge::texture_creation_desc_from_color_image, ImageInfo}; -use crate::mesh_cache::AnyMesh; +use crate::{mesh_cache::AnyMesh, visualizers::entity_iterator::clamped_vec_or}; #[derive(Debug, Clone)] pub struct NativeAsset3D<'a> { @@ -13,6 +13,21 @@ pub struct NativeAsset3D<'a> { pub albedo_factor: Option, } +#[derive(Debug, Clone)] +pub struct NativeMesh3D<'a> { + pub vertex_positions: &'a [glam::Vec3], + pub vertex_normals: Option<&'a [glam::Vec3]>, + pub vertex_colors: Option<&'a [re_renderer::Rgba32Unmul]>, + pub vertex_texcoords: Option<&'a [glam::Vec2]>, + + pub triangle_indices: Option<&'a [glam::UVec3]>, + + pub albedo_factor: Option, + + pub albedo_texture_buffer: Option, + pub albedo_texture_format: Option, +} + pub struct LoadedMesh { name: String, @@ -84,31 +99,28 @@ impl LoadedMesh { fn load_mesh3d( name: String, - mesh3d: &Mesh3D, + mesh3d: NativeMesh3D<'_>, texture_key: u64, render_ctx: &RenderContext, ) -> anyhow::Result { re_tracing::profile_function!(); - let Mesh3D { + let NativeMesh3D { vertex_positions, vertex_normals, vertex_colors, vertex_texcoords, triangle_indices, albedo_factor, - class_ids: _, albedo_texture_buffer, albedo_texture_format, } = mesh3d; - let vertex_positions: &[glam::Vec3] = bytemuck::cast_slice(vertex_positions.as_slice()); let num_positions = vertex_positions.len(); - let triangle_indices = if let Some(indices) = triangle_indices { + let triangle_indices = if let Some(triangle_indices) = triangle_indices { re_tracing::profile_scope!("copy_indices"); - let indices: &[glam::UVec3] = bytemuck::cast_slice(indices); - indices.to_vec() + triangle_indices.to_vec() } else { re_tracing::profile_scope!("generate_indices"); anyhow::ensure!(num_positions % 3 == 0); @@ -121,17 +133,14 @@ impl LoadedMesh { let vertex_colors = if let Some(vertex_colors) = vertex_colors { re_tracing::profile_scope!("copy_colors"); - vertex_colors - .iter() - .map(|c| Rgba32Unmul::from_rgba_unmul_array(c.to_array())) - .collect() + clamped_vec_or(vertex_colors, num_positions, &Rgba32Unmul::WHITE) } else { vec![Rgba32Unmul::WHITE; num_positions] }; let vertex_normals = if let Some(normals) = vertex_normals { re_tracing::profile_scope!("collect_normals"); - normals.iter().map(|v| v.0.into()).collect::>() + clamped_vec_or(normals, num_positions, &glam::Vec3::ZERO) } else { // TODO(andreas): Calculate normals vec![glam::Vec3::ZERO; num_positions] @@ -139,7 +148,7 @@ impl LoadedMesh { let vertex_texcoords = if let Some(texcoords) = vertex_texcoords { re_tracing::profile_scope!("collect_texcoords"); - texcoords.iter().map(|v| v.0.into()).collect::>() + clamped_vec_or(texcoords, num_positions, &glam::Vec2::ZERO) } else { vec![glam::Vec2::ZERO; num_positions] }; @@ -150,8 +159,8 @@ impl LoadedMesh { }; let albedo = try_get_or_create_albedo_texture( - albedo_texture_buffer, - albedo_texture_format, + &albedo_texture_buffer, + &albedo_texture_format, render_ctx, texture_key, &name, @@ -174,7 +183,7 @@ impl LoadedMesh { label: name.clone().into(), index_range: 0..num_indices as _, albedo, - albedo_factor: albedo_factor.map_or(re_renderer::Rgba::WHITE, |c| c.0.into()), + albedo_factor: albedo_factor.unwrap_or(re_renderer::Color32::WHITE).into(), }], }; @@ -200,22 +209,24 @@ impl LoadedMesh { } fn try_get_or_create_albedo_texture( - albedo_texture_buffer: &Option, - albedo_texture_format: &Option, + albedo_texture_buffer: &Option, + albedo_texture_format: &Option, render_ctx: &RenderContext, texture_key: u64, name: &str, ) -> Option { let (Some(albedo_texture_buffer), Some(albedo_texture_format)) = - (&albedo_texture_buffer, albedo_texture_format) + (albedo_texture_buffer, albedo_texture_format) else { return None; }; + re_tracing::profile_function!(); + let image_info = ImageInfo { - buffer_row_id: RowId::ZERO, // unused - buffer: albedo_texture_buffer.0.clone(), - format: albedo_texture_format.0, + buffer_row_id: RowId::ZERO, // unused + buffer: albedo_texture_buffer.clone(), // shallow clone + format: *albedo_texture_format, kind: re_types::image::ImageKind::Color, }; diff --git a/crates/viewer/re_view_spatial/src/visualizers/meshes.rs b/crates/viewer/re_view_spatial/src/visualizers/meshes.rs index c025ccc57bad..ed17d90ec529 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/meshes.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/meshes.rs @@ -4,8 +4,8 @@ use re_renderer::{renderer::GpuMeshInstance, RenderContext}; use re_types::{ archetypes::Mesh3D, components::{ - AlbedoFactor, ClassId, Color, ImageBuffer, ImageFormat, Position3D, Texcoord2D, - TriangleIndices, Vector3D, + AlbedoFactor, Color, ImageBuffer, ImageFormat, Position3D, Texcoord2D, TriangleIndices, + Vector3D, }, Component as _, }; @@ -15,14 +15,12 @@ use re_viewer_context::{ VisualizableFilterContext, VisualizerQueryInfo, VisualizerSystem, }; -use super::{ - entity_iterator::clamped_vec_or_empty, filter_visualizable_3d_entities, - SpatialViewVisualizerData, -}; +use super::{filter_visualizable_3d_entities, SpatialViewVisualizerData}; use crate::{ contexts::SpatialSceneEntityContext, mesh_cache::{AnyMesh, MeshCache, MeshCacheKey}, + mesh_loader::NativeMesh3D, view_kind::SpatialViewKind, }; @@ -41,18 +39,7 @@ impl Default for Mesh3DVisualizer { struct Mesh3DComponentData<'a> { index: (TimeInt, RowId), query_result_hash: Hash64, - - vertex_positions: &'a [Position3D], - vertex_normals: &'a [Vector3D], - vertex_colors: &'a [Color], - vertex_texcoords: &'a [Texcoord2D], - - triangle_indices: Option<&'a [TriangleIndices]>, - albedo_factor: Option<&'a AlbedoFactor>, - albedo_buffer: Option, - albedo_format: Option, - - class_ids: &'a [ClassId], + native_mesh: NativeMesh3D<'a>, } // NOTE: Do not put profile scopes in these methods. They are called for all entities and all @@ -76,7 +63,7 @@ impl Mesh3DVisualizer { // Skip over empty meshes. // Note that we can deal with zero normals/colors/texcoords/indices just fine (we generate them), // but re_renderer insists on having at a non-zero vertex list. - if data.vertex_positions.is_empty() { + if data.native_mesh.vertex_positions.is_empty() { continue; } @@ -87,30 +74,11 @@ impl Mesh3DVisualizer { media_type: None, }; - let vertex_normals = - clamped_vec_or_empty(data.vertex_normals, data.vertex_positions.len()); - let vertex_colors = - clamped_vec_or_empty(data.vertex_colors, data.vertex_positions.len()); - let vertex_texcoords = - clamped_vec_or_empty(data.vertex_texcoords, data.vertex_positions.len()); - c.entry( &entity_path.to_string(), key.clone(), AnyMesh::Mesh { - mesh: &Mesh3D { - vertex_positions: data.vertex_positions.to_owned(), - triangle_indices: data.triangle_indices.map(ToOwned::to_owned), - vertex_normals: (!vertex_normals.is_empty()).then_some(vertex_normals), - vertex_colors: (!vertex_colors.is_empty()).then_some(vertex_colors), - vertex_texcoords: (!vertex_texcoords.is_empty()) - .then_some(vertex_texcoords), - albedo_factor: data.albedo_factor.copied(), - albedo_texture_buffer: data.albedo_buffer.clone(), // shallow clone - albedo_texture_format: data.albedo_format, - class_ids: (!data.class_ids.is_empty()) - .then(|| data.class_ids.to_owned()), - }, + mesh: data.native_mesh, texture_key: re_log_types::hash::Hash64::hash(&key).hash64(), }, render_ctx, @@ -199,11 +167,10 @@ impl VisualizerSystem for Mesh3DVisualizer { let all_albedo_factors = results.iter_as(timeline, AlbedoFactor::name()); let all_albedo_buffers = results.iter_as(timeline, ImageBuffer::name()); let all_albedo_formats = results.iter_as(timeline, ImageFormat::name()); - let all_class_ids = results.iter_as(timeline, ClassId::name()); let query_result_hash = results.query_result_hash(); - let data = re_query::range_zip_1x8( + let data = re_query::range_zip_1x7( all_vertex_positions_indexed, all_vertex_normals.slice::<[f32; 3]>(), all_vertex_colors.slice::(), @@ -213,7 +180,6 @@ impl VisualizerSystem for Mesh3DVisualizer { all_albedo_buffers.slice::<&[u8]>(), // Legit call to `component_slow`, `ImageFormat` is real complicated. all_albedo_formats.component_slow::(), - all_class_ids.slice::(), ) .map( |( @@ -226,33 +192,32 @@ impl VisualizerSystem for Mesh3DVisualizer { albedo_factors, albedo_buffers, albedo_formats, - class_ids, )| { Mesh3DComponentData { index, query_result_hash, - 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_buffer: albedo_buffers - .unwrap_or_default() - .first() - .cloned() - .map(Into::into), // shallow clone - albedo_format: albedo_formats.unwrap_or_default().first().copied(), - class_ids: class_ids - .map_or(&[], |class_ids| bytemuck::cast_slice(class_ids)), + + // Note that we rely in clamping in the mesh loader for some of these. + // (which means that if we have a mesh cache hit, we don't have to bother with clamping logic here!) + native_mesh: NativeMesh3D { + vertex_positions: bytemuck::cast_slice(vertex_positions), + vertex_normals: vertex_normals.map(bytemuck::cast_slice), + vertex_colors: vertex_colors.map(bytemuck::cast_slice), + vertex_texcoords: vertex_texcoords.map(bytemuck::cast_slice), + triangle_indices: triangle_indices.map(bytemuck::cast_slice), + albedo_factor: albedo_factors + .map(bytemuck::cast_slice) + .and_then(|albedo_factors| albedo_factors.first().copied()), + albedo_texture_buffer: albedo_buffers + .unwrap_or_default() + .first() + .cloned() + .map(Into::into), // shallow clone + albedo_texture_format: albedo_formats + .unwrap_or_default() + .first() + .map(|format| format.0), + }, } }, ); diff --git a/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs b/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs index 2ca627bf1430..8a5ef6ee1629 100644 --- a/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs +++ b/crates/viewer/re_view_spatial/src/visualizers/utilities/entity_iterator.rs @@ -21,7 +21,7 @@ pub fn clamped_or<'a, T>(values: &'a [T], if_empty: &'a T) -> impl Iterator(values: &[T], clamped_len: usize) -> Vec { if values.len() == clamped_len { @@ -47,6 +47,19 @@ pub fn clamped_vec_or_empty(values: &[T], clamped_len: usize) -> Vec(values: &[T], clamped_len: usize, if_empty: &T) -> Vec { + let clamped = clamped_vec_or_empty(values, clamped_len); + if clamped.is_empty() { + vec![if_empty.clone(); clamped_len] + } else { + clamped + } +} + #[test] fn test_clamped_vec() { assert_eq!(clamped_vec_or_empty::(&[], 0), Vec::::default()); diff --git a/crates/viewer/re_viewer_context/src/cache/image_decode_cache.rs b/crates/viewer/re_viewer_context/src/cache/image_decode_cache.rs index 2a7324308126..6c8d37a9ba19 100644 --- a/crates/viewer/re_viewer_context/src/cache/image_decode_cache.rs +++ b/crates/viewer/re_viewer_context/src/cache/image_decode_cache.rs @@ -5,8 +5,7 @@ use re_chunk::RowId; use re_chunk_store::ChunkStoreEvent; use re_log_types::hash::Hash64; use re_types::{ - archetypes::Image, - components::MediaType, + components::{ImageBuffer, MediaType}, image::{ImageKind, ImageLoadError}, Component as _, }; @@ -96,9 +95,7 @@ fn decode_image( let dynamic_image = reader.decode()?; - let image_arch = Image::from_dynamic_image(dynamic_image)?; - - let Image { buffer, format, .. } = image_arch; + let (buffer, format) = ImageBuffer::from_dynamic_image(dynamic_image)?; Ok(ImageInfo { buffer_row_id: blob_row_id, diff --git a/crates/viewer/re_viewer_context/src/image_info.rs b/crates/viewer/re_viewer_context/src/image_info.rs index e8c9a8608518..585778f0cb61 100644 --- a/crates/viewer/re_viewer_context/src/image_info.rs +++ b/crates/viewer/re_viewer_context/src/image_info.rs @@ -399,11 +399,14 @@ mod tests { elements: &[T], ) -> ImageInfo { assert_eq!(elements.len(), 2 * 2); - let image = re_types::archetypes::Image::from_elements(elements, [2, 2], color_model); ImageInfo { buffer_row_id: RowId::ZERO, // unused - buffer: image.buffer.0, - format: image.format.0, + buffer: re_types::datatypes::Blob(bytemuck::cast_slice::<_, u8>(elements).into()), + format: re_types::datatypes::ImageFormat::from_color_model( + [2, 2], + color_model, + T::CHANNEL_TYPE, + ), kind: re_types::image::ImageKind::Color, } }