From ad7eca59dba9ea4f4e45627dbcfb1c9ac7a3ced3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 16:30:31 +0200 Subject: [PATCH 01/27] Refactor: introduce struct SliceSelection --- crates/re_tensor_ops/src/dimension_mapping.rs | 4 +- crates/re_viewer/src/ui/view_tensor/ui.rs | 67 +++++++++++-------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/crates/re_tensor_ops/src/dimension_mapping.rs b/crates/re_tensor_ops/src/dimension_mapping.rs index d2f9dbab1595..37b7517c8281 100644 --- a/crates/re_tensor_ops/src/dimension_mapping.rs +++ b/crates/re_tensor_ops/src/dimension_mapping.rs @@ -1,6 +1,6 @@ use re_log_types::component_types; -#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] pub struct DimensionSelector { pub visible: bool, pub dim_idx: usize, @@ -15,7 +15,7 @@ impl DimensionSelector { } } -#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)] pub struct DimensionMapping { /// Which dimensions have selectors, and are they visible? pub selectors: Vec, diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index 12d57d6b4374..8aa418718d47 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -16,6 +16,16 @@ use super::dimension_mapping_ui; // --- +/// How we slice a given tensor +#[derive(Hash)] +struct SliceSelection { + /// How we select which dimensions to project the tensor onto. + dimension_mapping: DimensionMapping, + + /// Selected value of every dimension (iff they are in [`DimensionMapping::selectors`]). + selector_values: BTreeMap, +} + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct ViewTensorState { /// How we select which dimensions to project the tensor onto. @@ -131,6 +141,10 @@ fn tensor_ui( let tensor_stats = ctx.cache.tensor_stats(tensor); let range = tensor_stats.range; let color_mapping = &state.color_mapping; + let slice_selection = SliceSelection { + dimension_mapping: state.dimension_mapping.clone(), + selector_values: state.selector_values.clone(), + }; match tensor.dtype() { TensorDataType::U8 => match ndarray::ArrayViewD::::try_from(tensor) { @@ -140,7 +154,7 @@ fn tensor_ui( color_mapping.color_from_normalized(value as f32 / 255.0) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -159,7 +173,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -179,7 +193,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -198,7 +212,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -218,7 +232,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -238,7 +252,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -258,7 +272,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -278,7 +292,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -297,7 +311,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -316,7 +330,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -335,7 +349,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(state, &tensor); + let slice = selected_tensor_slice(&slice_selection, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -571,35 +585,34 @@ impl TextureSettings { // ---------------------------------------------------------------------------- fn selected_tensor_slice<'a, T: Copy>( - state: &ViewTensorState, + slice_selection: &SliceSelection, tensor: &'a ndarray::ArrayViewD<'_, T>, ) -> ndarray::ArrayViewD<'a, T> { - let dim_mapping = &state.dimension_mapping; + let SliceSelection { + dimension_mapping, + selector_values, + } = slice_selection; - assert!(dim_mapping.is_valid(tensor.ndim())); + assert!(dimension_mapping.is_valid(tensor.ndim())); // TODO(andreas) - shouldn't just give up here - if dim_mapping.width.is_none() || dim_mapping.height.is_none() { + if dimension_mapping.width.is_none() || dimension_mapping.height.is_none() { return tensor.view(); } - let axis = dim_mapping + let axis = dimension_mapping .height .into_iter() - .chain(dim_mapping.width.into_iter()) - .chain(dim_mapping.selectors.iter().map(|s| s.dim_idx)) + .chain(dimension_mapping.width.into_iter()) + .chain(dimension_mapping.selectors.iter().map(|s| s.dim_idx)) .collect::>(); let mut slice = tensor.view().permuted_axes(axis); - for DimensionSelector { dim_idx, .. } in &dim_mapping.selectors { - let selector_value = state - .selector_values - .get(dim_idx) - .copied() - .unwrap_or_default() as usize; + for DimensionSelector { dim_idx, .. } in &dimension_mapping.selectors { + let selector_value = selector_values.get(dim_idx).copied().unwrap_or_default() as usize; assert!( selector_value < slice.shape()[2], - "Bad tensor slicing. Trying to select slice index {selector_value} of dim=2. tensor shape: {:?}, dim_mapping: {dim_mapping:#?}", + "Bad tensor slicing. Trying to select slice index {selector_value} of dim=2. tensor shape: {:?}, dim_mapping: {dimension_mapping:#?}", tensor.shape() ); @@ -607,10 +620,10 @@ fn selected_tensor_slice<'a, T: Copy>( // This call removes Axis(2), so the next iteration of the loop does the right thing again. slice.index_axis_inplace(Axis(2), selector_value); } - if dim_mapping.invert_height { + if dimension_mapping.invert_height { slice.invert_axis(Axis(0)); } - if dim_mapping.invert_width { + if dimension_mapping.invert_width { slice.invert_axis(Axis(1)); } From b7edd7f78f86454869f8b60ee5e62ca9cda151a5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 16:33:22 +0200 Subject: [PATCH 02/27] Refactor: use SliceSelection inside of ViewTensorState --- crates/re_viewer/src/ui/view_tensor/ui.rs | 63 +++++++++++------------ 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index 8aa418718d47..8a0f349387be 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -17,10 +17,10 @@ use super::dimension_mapping_ui; // --- /// How we slice a given tensor -#[derive(Hash)] +#[derive(Clone, Debug, Hash, serde::Deserialize, serde::Serialize)] struct SliceSelection { /// How we select which dimensions to project the tensor onto. - dimension_mapping: DimensionMapping, + dim_mapping: DimensionMapping, /// Selected value of every dimension (iff they are in [`DimensionMapping::selectors`]). selector_values: BTreeMap, @@ -28,11 +28,8 @@ struct SliceSelection { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct ViewTensorState { - /// How we select which dimensions to project the tensor onto. - dimension_mapping: DimensionMapping, - - /// Selected value of every dimension (iff they are in [`DimensionMapping::selectors`]). - selector_values: BTreeMap, + /// What slice are we vieiwing? + slice: SliceSelection, /// How we map values to colors. color_mapping: ColorMapping, @@ -49,8 +46,10 @@ pub struct ViewTensorState { impl ViewTensorState { pub fn create(tensor: &Tensor) -> ViewTensorState { Self { - selector_values: Default::default(), - dimension_mapping: DimensionMapping::create(tensor.shape()), + slice: SliceSelection { + dim_mapping: DimensionMapping::create(tensor.shape()), + selector_values: Default::default(), + }, color_mapping: ColorMapping::default(), texture_settings: TextureSettings::default(), tensor: Some(tensor.clone()), @@ -78,18 +77,18 @@ impl ViewTensorState { ui.separator(); ui.strong("Dimension Mapping"); - dimension_mapping_ui(ctx.re_ui, ui, &mut self.dimension_mapping, tensor.shape()); + dimension_mapping_ui(ctx.re_ui, ui, &mut self.slice.dim_mapping, tensor.shape()); let default_mapping = DimensionMapping::create(tensor.shape()); if ui .add_enabled( - self.dimension_mapping != default_mapping, + self.slice.dim_mapping != default_mapping, egui::Button::new("Reset mapping"), ) .on_disabled_hover_text("The default is already set up.") .on_hover_text("Reset dimension mapping to the default.") .clicked() { - self.dimension_mapping = DimensionMapping::create(tensor.shape()); + self.slice.dim_mapping = DimensionMapping::create(tensor.shape()); } } } @@ -104,15 +103,16 @@ pub(crate) fn view_tensor( state.tensor = Some(tensor.clone()); - if !state.dimension_mapping.is_valid(tensor.num_dim()) { - state.dimension_mapping = DimensionMapping::create(tensor.shape()); + if !state.slice.dim_mapping.is_valid(tensor.num_dim()) { + state.slice.dim_mapping = DimensionMapping::create(tensor.shape()); } let default_item_spacing = ui.spacing_mut().item_spacing; ui.spacing_mut().item_spacing.y = 0.0; // No extra spacing between sliders and tensor if state - .dimension_mapping + .slice + .dim_mapping .selectors .iter() .any(|selector| selector.visible) @@ -141,10 +141,6 @@ fn tensor_ui( let tensor_stats = ctx.cache.tensor_stats(tensor); let range = tensor_stats.range; let color_mapping = &state.color_mapping; - let slice_selection = SliceSelection { - dimension_mapping: state.dimension_mapping.clone(), - selector_values: state.selector_values.clone(), - }; match tensor.dtype() { TensorDataType::U8 => match ndarray::ArrayViewD::::try_from(tensor) { @@ -154,7 +150,7 @@ fn tensor_ui( color_mapping.color_from_normalized(value as f32 / 255.0) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -173,7 +169,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -193,7 +189,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -212,7 +208,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -232,7 +228,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -252,7 +248,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -272,7 +268,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -292,7 +288,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -311,7 +307,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -330,7 +326,7 @@ fn tensor_ui( )) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -349,7 +345,7 @@ fn tensor_ui( ) as f32) }; - let slice = selected_tensor_slice(&slice_selection, &tensor); + let slice = selected_tensor_slice(&state.slice, &tensor); slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); } Err(err) => { @@ -589,7 +585,7 @@ fn selected_tensor_slice<'a, T: Copy>( tensor: &'a ndarray::ArrayViewD<'_, T>, ) -> ndarray::ArrayViewD<'a, T> { let SliceSelection { - dimension_mapping, + dim_mapping: dimension_mapping, selector_values, } = slice_selection; @@ -643,7 +639,7 @@ fn slice_ui( let ndims = slice.ndim(); if let Ok(slice) = slice.into_dimensionality::() { let dimension_labels = { - let dm = &view_state.dimension_mapping; + let dm = &view_state.slice.dim_mapping; [ ( dimension_name(tensor_shape, dm.width.unwrap()), @@ -847,7 +843,7 @@ fn paint_axis_names( } fn selectors_ui(ui: &mut egui::Ui, state: &mut ViewTensorState, tensor: &Tensor) { - for selector in &state.dimension_mapping.selectors { + for selector in &state.slice.dim_mapping.selectors { if !selector.visible { continue; } @@ -856,6 +852,7 @@ fn selectors_ui(ui: &mut egui::Ui, state: &mut ViewTensorState, tensor: &Tensor) let size = dim.size; let selector_value = state + .slice .selector_values .entry(selector.dim_idx) .or_insert_with(|| size / 2); // start in the middle From f7635e11d1b8f05456af44889dcc5db4becaa26a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 18:46:09 +0200 Subject: [PATCH 03/27] MVP of tensor colormapping on GPU --- crates/re_log_types/src/data.rs | 19 ++ crates/re_viewer/src/ui/view_spatial/mod.rs | 2 +- crates/re_viewer/src/ui/view_tensor/gpu.rs | 259 ++++++++++++++++++++ crates/re_viewer/src/ui/view_tensor/mod.rs | 2 + crates/re_viewer/src/ui/view_tensor/ui.rs | 106 +++++++- 5 files changed, 380 insertions(+), 8 deletions(-) create mode 100644 crates/re_viewer/src/ui/view_tensor/gpu.rs diff --git a/crates/re_log_types/src/data.rs b/crates/re_log_types/src/data.rs index 248c67f3acb7..7317be465c3e 100644 --- a/crates/re_log_types/src/data.rs +++ b/crates/re_log_types/src/data.rs @@ -114,6 +114,25 @@ impl TensorDataType { } } + #[inline] + pub fn min_value(&self) -> f64 { + match self { + Self::U8 => u8::MIN as _, + Self::U16 => u16::MIN as _, + Self::U32 => u32::MIN as _, + Self::U64 => u64::MIN as _, + + Self::I8 => i8::MIN as _, + Self::I16 => i16::MIN as _, + Self::I32 => i32::MIN as _, + Self::I64 => i64::MIN as _, + + Self::F16 => f16::MIN.into(), + Self::F32 => f32::MIN as _, + Self::F64 => f64::MIN, + } + } + #[inline] pub fn max_value(&self) -> f64 { match self { diff --git a/crates/re_viewer/src/ui/view_spatial/mod.rs b/crates/re_viewer/src/ui/view_spatial/mod.rs index 141f6c3f9dd6..1d805ce3ab41 100644 --- a/crates/re_viewer/src/ui/view_spatial/mod.rs +++ b/crates/re_viewer/src/ui/view_spatial/mod.rs @@ -5,7 +5,7 @@ mod space_camera_3d; mod ui; mod ui_2d; mod ui_3d; -mod ui_renderer_bridge; +pub mod ui_renderer_bridge; // TODO: move reusable parts into its own module pub use self::scene::{Image, MeshSource, MeshSourceData, SceneSpatial, UiLabel, UiLabelTarget}; pub use self::space_camera_3d::SpaceCamera3D; diff --git a/crates/re_viewer/src/ui/view_tensor/gpu.rs b/crates/re_viewer/src/ui/view_tensor/gpu.rs new file mode 100644 index 000000000000..8bbee2be8ca8 --- /dev/null +++ b/crates/re_viewer/src/ui/view_tensor/gpu.rs @@ -0,0 +1,259 @@ +use re_log_types::{ + component_types::{Tensor, TensorCastError}, + TensorDataType, +}; +use re_renderer::{ + renderer::{ColormappedTexture, RectangleDrawData, TextureFilterMag, TextureFilterMin}, + resource_managers::Texture2DCreationDesc, + view_builder::{TargetConfiguration, ViewBuilder}, +}; + +use crate::misc::caches::TensorStats; + +use super::{ + ui::{selected_tensor_slice, SliceSelection}, + ViewTensorState, +}; + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum TensorUploadError { + #[error(transparent)] + TensorCastError(#[from] TensorCastError), + + #[error("Expected a 2D slice")] + Not2D, + + #[error("Unsupported datatype: {0:?}")] // TODO: emilk narrow to f32 instead + UnsupportedDataType(TensorDataType), +} + +pub fn colormapped_texture( + render_ctx: &mut re_renderer::RenderContext, + tensor: &Tensor, + tensor_stats: &TensorStats, + state: &ViewTensorState, +) -> Result { + let texture = upload_texture_slice_to_gpu(render_ctx, tensor, state.slice())?; + let (min, max) = range(tensor.dtype(), tensor_stats); + + let color_mapping = state.color_mapping(); + + Ok(ColormappedTexture { + texture, + range: [min as f32, max as f32], + gamma: color_mapping.gamma, + color_mapper: Some(re_renderer::renderer::ColorMapper::Function( + color_mapping.renderer_colormap(), + )), + }) +} + +fn range(dtype: TensorDataType, tensor_stats: &TensorStats) -> (f64, f64) { + // TODO: return errors instead of logging them + let default_range = if dtype.is_integer() { + (dtype.min_value(), dtype.max_value()) + } else { + (0.0, 1.0) + }; + + let (min, max) = tensor_stats.range.unwrap_or_else(|| { + re_log::error_once!("Tensor is missing range!?"); // should only happen for compressed tensors, i.e. jpegs + default_range + }); + + if !min.is_finite() || !max.is_finite() { + re_log::error_once!("Non-finite tensor range"); + default_range + } else if min == max { + // uniform range. This can explode the colormapping, so let's map all colors to the middle: + (min - 1.0, max + 1.0) + } else { + (min, max) + } +} + +fn upload_texture_slice_to_gpu( + render_ctx: &mut re_renderer::RenderContext, + tensor: &Tensor, + slice_selection: &SliceSelection, +) -> Result { + let id = egui::util::hash((tensor.id(), slice_selection)); + + crate::misc::tensor_to_gpu::get_or_create_texture(render_ctx, id, || { + texture_desc_from_tensor(tensor, slice_selection) + }) +} + +fn texture_desc_from_tensor( + tensor: &Tensor, + slice_selection: &SliceSelection, +) -> Result, TensorUploadError> { + use wgpu::TextureFormat; + match tensor.dtype() { + TensorDataType::U8 => { + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R8Uint, |x| x) + } + TensorDataType::U16 => { + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R16Uint, |x| x) + } + TensorDataType::U32 => { + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Uint, |x| x) + } + TensorDataType::U64 => Err(TensorUploadError::UnsupportedDataType(tensor.dtype())), + TensorDataType::I8 => { + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R8Sint, |x| x) + } + TensorDataType::I16 => { + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R16Sint, |x| x) + } + TensorDataType::I32 => { + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Sint, |x| x) + } + TensorDataType::I64 => Err(TensorUploadError::UnsupportedDataType(tensor.dtype())), + TensorDataType::F16 => { + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R16Float, |x| x) + } + TensorDataType::F32 => { + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Float, |x| x) + } + TensorDataType::F64 => { + // narrow f64 -> f32: + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice( + tensor, + slice_selection, + TextureFormat::R32Float, + |x: f64| x as f32, + ) + } + } +} + +fn texture_desc_from_tensor_slice( + tensor: ndarray::ArrayViewD<'_, From>, + slice_selection: &SliceSelection, + format: wgpu::TextureFormat, + caster: impl Fn(From) -> To, +) -> Result, TensorUploadError> { + use ndarray::Dimension as _; + + let slice = selected_tensor_slice(slice_selection, &tensor); + let slice = slice + .into_dimensionality::() + .map_err(|_| TensorUploadError::Not2D)?; + + let (height, width) = slice.raw_dim().into_pattern(); + let mut pixels: Vec = vec![To::zeroed(); height * width]; + let pixels_view = ndarray::ArrayViewMut2::from_shape(slice.raw_dim(), pixels.as_mut_slice()) + .expect("Mismatched length."); + ndarray::Zip::from(pixels_view) + .and(slice) + .for_each(|pixel: &mut To, value: &From| { + *pixel = caster(*value); + }); + + Ok(Texture2DCreationDesc { + label: "tensor_slice".into(), + data: bytemuck::pod_collect_to_vec(&pixels).into(), + format, + width: width as u32, + height: height as u32, + }) +} + +// ---------------------------------------------------------------------------- + +pub fn paint( + render_ctx: &mut re_renderer::RenderContext, + painter: &egui::Painter, + slice_size: egui::Vec2, + image_position_on_screen: egui::Rect, + colormapped_texture: ColormappedTexture, + texture_options: egui::TextureOptions, +) -> anyhow::Result<()> { + crate::profile_function!(); + + let space_rect = egui::Rect::from_min_size(egui::Pos2::ZERO, slice_size); + + let textured_rectangle = re_renderer::renderer::TexturedRect { + top_left_corner_position: glam::Vec3::ZERO, + extent_u: glam::Vec3::X * slice_size.x, + extent_v: glam::Vec3::Y * slice_size.y, + colormapped_texture, + texture_filter_magnification: match texture_options.magnification { + egui::TextureFilter::Nearest => TextureFilterMag::Nearest, + egui::TextureFilter::Linear => TextureFilterMag::Linear, + }, + texture_filter_minification: match texture_options.minification { + egui::TextureFilter::Nearest => TextureFilterMin::Nearest, + egui::TextureFilter::Linear => TextureFilterMin::Linear, + }, + multiplicative_tint: egui::Rgba::WHITE, + depth_offset: 0, + outline_mask: Default::default(), + }; + + // ------------------------------------------------------------------------ + + let pixels_from_points = painter.ctx().pixels_per_point(); + let ui_from_space = egui::emath::RectTransform::from_to(space_rect, image_position_on_screen); + let space_from_ui = ui_from_space.inverse(); + let space_from_points = space_from_ui.scale().y; + let points_from_pixels = 1.0 / painter.ctx().pixels_per_point(); + let space_from_pixel = space_from_points * points_from_pixels; + + let resolution_in_pixel = get_viewport(painter.clip_rect(), pixels_from_points); + anyhow::ensure!(resolution_in_pixel[0] > 0 && resolution_in_pixel[1] > 0); + + let camera_position_space = space_from_ui.transform_pos(painter.clip_rect().min); + + let top_left_position = glam::vec2(camera_position_space.x, camera_position_space.y); + let target_config = TargetConfiguration { + name: "tensor_view".into(), + resolution_in_pixel, + view_from_world: macaw::IsoTransform::from_translation(-top_left_position.extend(0.0)), + projection_from_view: re_renderer::view_builder::Projection::Orthographic { + camera_mode: re_renderer::view_builder::OrthographicCameraMode::TopLeftCornerAndExtendZ, + vertical_world_size: space_from_pixel * resolution_in_pixel[1] as f32, + far_plane_distance: 1000.0, + }, + pixels_from_point: pixels_from_points, + auto_size_config: Default::default(), + outline_config: None, + }; + + // TODO(andreas): separate setup for viewbuilder doesn't make sense. + let mut view_builder = ViewBuilder::default(); + view_builder.setup_view(render_ctx, target_config)?; + + view_builder.queue_draw(&RectangleDrawData::new(render_ctx, &[textured_rectangle])?); + + let command_buffer = view_builder.draw(render_ctx, re_renderer::Rgba::TRANSPARENT)?; + + painter.add( + crate::ui::view_spatial::ui_renderer_bridge::renderer_paint_callback( + render_ctx, + command_buffer, + view_builder, + painter.clip_rect(), + painter.ctx().pixels_per_point(), + ), + ); + + Ok(()) +} + +fn get_viewport(clip_rect: egui::Rect, pixels_from_point: f32) -> [u32; 2] { + let min = (clip_rect.min.to_vec2() * pixels_from_point).round(); + let max = (clip_rect.max.to_vec2() * pixels_from_point).round(); + let resolution = max - min; + [resolution.x as u32, resolution.y as u32] +} diff --git a/crates/re_viewer/src/ui/view_tensor/mod.rs b/crates/re_viewer/src/ui/view_tensor/mod.rs index 349f77cc6aa4..87df6d5f397e 100644 --- a/crates/re_viewer/src/ui/view_tensor/mod.rs +++ b/crates/re_viewer/src/ui/view_tensor/mod.rs @@ -1,3 +1,5 @@ +mod gpu; + mod scene; pub(crate) use self::scene::SceneTensor; diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index 8a0f349387be..c185fb3cc20a 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -18,12 +18,12 @@ use super::dimension_mapping_ui; /// How we slice a given tensor #[derive(Clone, Debug, Hash, serde::Deserialize, serde::Serialize)] -struct SliceSelection { +pub struct SliceSelection { /// How we select which dimensions to project the tensor onto. - dim_mapping: DimensionMapping, + pub dim_mapping: DimensionMapping, /// Selected value of every dimension (iff they are in [`DimensionMapping::selectors`]). - selector_values: BTreeMap, + pub selector_values: BTreeMap, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] @@ -56,6 +56,14 @@ impl ViewTensorState { } } + pub fn slice(&self) -> &SliceSelection { + &self.slice + } + + pub fn color_mapping(&self) -> &ColorMapping { + &self.color_mapping + } + pub(crate) fn ui(&mut self, ctx: &mut crate::misc::ViewerContext<'_>, ui: &mut egui::Ui) { let Some(tensor) = &self.tensor else { ui.label("No Tensor shown in this Space View."); @@ -127,7 +135,82 @@ pub(crate) fn view_tensor( }); } - tensor_ui(ctx, ui, state, tensor); + // tensor_ui(ctx, ui, state, tensor); + + let dimension_labels = { + let dm = &state.slice.dim_mapping; + [ + ( + dimension_name(&tensor.shape, dm.width.unwrap()), + dm.invert_width, + ), + ( + dimension_name(&tensor.shape, dm.height.unwrap()), + dm.invert_height, + ), + ] + }; + + egui::ScrollArea::both().show(ui, |ui| { + let font_id = egui::TextStyle::Body.resolve(ui.style()); + + let (response, painter, image_rect) = paint_tensor_slice(ctx, ui, state, tensor).unwrap(); // TODO: don't unwrap + + if !response.hovered() { + paint_axis_names(ui, &painter, image_rect, font_id, dimension_labels); + } + }); +} + +fn paint_tensor_slice( + ctx: &mut crate::misc::ViewerContext<'_>, + ui: &mut egui::Ui, + state: &mut ViewTensorState, + tensor: &Tensor, +) -> anyhow::Result<(egui::Response, egui::Painter, egui::Rect)> { + crate::profile_function!(); + + let tensor_stats = ctx.cache.tensor_stats(tensor); + let colormapped_texture = + super::gpu::colormapped_texture(ctx.render_ctx, tensor, tensor_stats, state)?; + let texture = ctx + .render_ctx + .texture_manager_2d + .get(&colormapped_texture.texture)?; + let size = texture.creation_desc.size; + let width = size.width; + let height = size.height; + + let margin = egui::Vec2::ZERO; + let img_size = egui::vec2(width as _, height as _); + let img_size = Vec2::max(Vec2::splat(1.0), img_size); // better safe than sorry + let desired_size = match state.texture_settings.scaling { + TextureScaling::Original => img_size + margin, + TextureScaling::Fill => { + let desired_size = ui.available_size() - margin; + if state.texture_settings.keep_aspect_ratio { + let scale = (desired_size / img_size).min_elem(); + img_size * scale + } else { + desired_size + } + } + }; + + let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::hover()); + let rect = response.rect; + let image_rect = egui::Rect::from_min_max(rect.min + margin, rect.max); + + super::gpu::paint( + ctx.render_ctx, + &painter, + img_size, + image_rect, + colormapped_texture, + state.texture_settings.options, + )?; + + Ok((response, painter, image_rect)) } fn tensor_ui( @@ -357,6 +440,7 @@ fn tensor_ui( // ---------------------------------------------------------------------------- +// TODO(emilk): replace with the one from re_renderer #[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] enum Colormap { Greyscale, @@ -376,9 +460,9 @@ impl std::fmt::Display for Colormap { /// How we map values to colors. #[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)] -struct ColorMapping { +pub struct ColorMapping { map: Colormap, - gamma: f32, + pub gamma: f32, } impl Default for ColorMapping { @@ -391,6 +475,14 @@ impl Default for ColorMapping { } impl ColorMapping { + pub fn renderer_colormap(&self) -> re_renderer::Colormap { + match self.map { + Colormap::Greyscale => re_renderer::Colormap::Grayscale, + Colormap::Turbo => re_renderer::Colormap::Turbo, + Colormap::Virdis => re_renderer::Colormap::Viridis, + } + } + pub fn color_from_normalized(&self, f: f32) -> Color32 { let f = f.powf(self.gamma); @@ -580,7 +672,7 @@ impl TextureSettings { // ---------------------------------------------------------------------------- -fn selected_tensor_slice<'a, T: Copy>( +pub fn selected_tensor_slice<'a, T: Copy>( slice_selection: &SliceSelection, tensor: &'a ndarray::ArrayViewD<'_, T>, ) -> ndarray::ArrayViewD<'a, T> { From d301af92c03507978df25859595c597e448d0e5a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 19:49:22 +0200 Subject: [PATCH 04/27] Remove old ui code --- crates/re_viewer/src/ui/view_tensor/ui.rs | 399 ++-------------------- 1 file changed, 22 insertions(+), 377 deletions(-) diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index c185fb3cc20a..d4d3fb5473ac 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -1,13 +1,10 @@ use std::{collections::BTreeMap, fmt::Display}; use eframe::emath::Align2; -use egui::{epaint::TextShape, Color32, ColorImage, NumExt as _, Vec2}; -use ndarray::{Axis, Ix2}; +use egui::{epaint::TextShape, NumExt as _, Vec2}; +use ndarray::Axis; -use re_log_types::{ - component_types::{self, Tensor}, - TensorDataType, -}; +use re_log_types::component_types::{self, Tensor}; use re_tensor_ops::dimension_mapping::{DimensionMapping, DimensionSelector}; use crate::ui::data_ui::image::tensor_summary_ui_grid_contents; @@ -152,16 +149,29 @@ pub(crate) fn view_tensor( }; egui::ScrollArea::both().show(ui, |ui| { - let font_id = egui::TextStyle::Body.resolve(ui.style()); - - let (response, painter, image_rect) = paint_tensor_slice(ctx, ui, state, tensor).unwrap(); // TODO: don't unwrap - - if !response.hovered() { - paint_axis_names(ui, &painter, image_rect, font_id, dimension_labels); + if let Err(err) = fun_name(ctx, ui, state, tensor, dimension_labels) { + ui.label(ctx.re_ui.error_text(err.to_string())); } }); } +fn fun_name( + ctx: &mut crate::misc::ViewerContext<'_>, + ui: &mut egui::Ui, + state: &mut ViewTensorState, + tensor: &Tensor, + dimension_labels: [(String, bool); 2], +) -> anyhow::Result<()> { + let (response, painter, image_rect) = paint_tensor_slice(ctx, ui, state, tensor)?; + + if !response.hovered() { + let font_id = egui::TextStyle::Body.resolve(ui.style()); + paint_axis_names(ui, &painter, image_rect, font_id, dimension_labels); + } + + Ok(()) +} + fn paint_tensor_slice( ctx: &mut crate::misc::ViewerContext<'_>, ui: &mut egui::Ui, @@ -213,231 +223,6 @@ fn paint_tensor_slice( Ok((response, painter, image_rect)) } -fn tensor_ui( - ctx: &mut crate::misc::ViewerContext<'_>, - ui: &mut egui::Ui, - state: &mut ViewTensorState, - tensor: &Tensor, -) { - let tensor_shape = tensor.shape(); - - let tensor_stats = ctx.cache.tensor_stats(tensor); - let range = tensor_stats.range; - let color_mapping = &state.color_mapping; - - match tensor.dtype() { - TensorDataType::U8 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: u8| { - // We always use the full range for u8 - color_mapping.color_from_normalized(value as f32 / 255.0) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::U16 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: u16| { - let (tensor_min, tensor_max) = range.unwrap_or((0.0, u16::MAX as f64)); // the cache should provide the range - color_mapping.color_from_normalized(egui::remap( - value as f32, - tensor_min as f32..=tensor_max as f32, - 0.0..=1.0, - )) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::U32 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let (tensor_min, tensor_max) = range.unwrap_or((0.0, u32::MAX as f64)); // the cache should provide the range - - let color_from_value = |value: u32| { - color_mapping.color_from_normalized(egui::remap( - value as f64, - tensor_min..=tensor_max, - 0.0..=1.0, - ) as f32) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::U64 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: u64| { - let (tensor_min, tensor_max) = range.unwrap_or((0.0, u64::MAX as f64)); // the cache should provide the range - color_mapping.color_from_normalized(egui::remap( - value as f64, - tensor_min..=tensor_max, - 0.0..=1.0, - ) as f32) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::I8 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: i8| { - // We always use the full range for i8: - let (tensor_min, tensor_max) = (i8::MIN as f32, i8::MAX as f32); - color_mapping.color_from_normalized(egui::remap( - value as f32, - tensor_min..=tensor_max, - 0.0..=1.0, - )) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::I16 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: i16| { - let (tensor_min, tensor_max) = - range.unwrap_or((i16::MIN as f64, i16::MAX as f64)); // the cache should provide the range - color_mapping.color_from_normalized(egui::remap( - value as f32, - tensor_min as f32..=tensor_max as f32, - 0.0..=1.0, - )) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::I32 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: i32| { - let (tensor_min, tensor_max) = - range.unwrap_or((i32::MIN as f64, i32::MAX as f64)); // the cache should provide the range - color_mapping.color_from_normalized(egui::remap( - value as f64, - tensor_min..=tensor_max, - 0.0..=1.0, - ) as f32) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::I64 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: i64| { - let (tensor_min, tensor_max) = - range.unwrap_or((i64::MIN as f64, i64::MAX as f64)); // the cache should provide the range - color_mapping.color_from_normalized(egui::remap( - value as f64, - tensor_min..=tensor_max, - 0.0..=1.0, - ) as f32) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::F16 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: half::f16| { - let (tensor_min, tensor_max) = range.unwrap_or((0.0, 1.0)); // the cache should provide the range - color_mapping.color_from_normalized(egui::remap( - value.to_f32(), - tensor_min as f32..=tensor_max as f32, - 0.0..=1.0, - )) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::F32 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: f32| { - let (tensor_min, tensor_max) = range.unwrap_or((0.0, 1.0)); // the cache should provide the range - color_mapping.color_from_normalized(egui::remap( - value, - tensor_min as f32..=tensor_max as f32, - 0.0..=1.0, - )) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - - TensorDataType::F64 => match ndarray::ArrayViewD::::try_from(tensor) { - Ok(tensor) => { - let color_from_value = |value: f64| { - let (tensor_min, tensor_max) = range.unwrap_or((0.0, 1.0)); // the cache should provide the range - color_mapping.color_from_normalized(egui::remap( - value, - tensor_min..=tensor_max, - 0.0..=1.0, - ) as f32) - }; - - let slice = selected_tensor_slice(&state.slice, &tensor); - slice_ui(ctx, ui, state, tensor_shape, slice, color_from_value); - } - Err(err) => { - ui.label(ctx.re_ui.error_text(err.to_string())); - } - }, - } -} - // ---------------------------------------------------------------------------- // TODO(emilk): replace with the one from re_renderer @@ -483,25 +268,6 @@ impl ColorMapping { } } - pub fn color_from_normalized(&self, f: f32) -> Color32 { - let f = f.powf(self.gamma); - - match self.map { - Colormap::Greyscale => { - let lum = (f * 255.0 + 0.5) as u8; - Color32::from_gray(lum) - } - Colormap::Turbo => { - let [r, g, b, _] = re_renderer::colormap_turbo_srgb(f); - Color32::from_rgb(r, g, b) - } - Colormap::Virdis => { - let [r, g, b, _] = re_renderer::colormap_viridis_srgb(f); - Color32::from_rgb(r, g, b) - } - } - } - fn ui(&mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui) { let ColorMapping { map, gamma } = self; @@ -578,46 +344,6 @@ impl Default for TextureSettings { } } -// helpers -impl TextureSettings { - fn paint_image( - &self, - ui: &mut egui::Ui, - margin: Vec2, - image: ColorImage, - ) -> (egui::Response, egui::Painter, egui::Rect) { - let img_size = egui::vec2(image.size[0] as _, image.size[1] as _); - let img_size = Vec2::max(Vec2::splat(1.0), img_size); // better safe than sorry - let desired_size = match self.scaling { - TextureScaling::Original => img_size + margin, - TextureScaling::Fill => { - let desired_size = ui.available_size() - margin; - if self.keep_aspect_ratio { - let scale = (desired_size / img_size).min_elem(); - img_size * scale - } else { - desired_size - } - } - }; - - // TODO(cmc): don't recreate texture unless necessary - let texture = ui.ctx().load_texture("tensor_slice", image, self.options); - - let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::hover()); - let rect = response.rect; - let image_rect = egui::Rect::from_min_max(rect.min + margin, rect.max); - - let mut mesh = egui::Mesh::with_texture(texture.id()); - let uv = egui::Rect::from_min_max(egui::Pos2::ZERO, egui::pos2(1.0, 1.0)); - mesh.add_rect_with_uv(image_rect, uv, Color32::WHITE); - - painter.add(mesh); - - (response, painter, image_rect) - } -} - // ui impl TextureSettings { fn ui(&mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui) { @@ -718,43 +444,6 @@ pub fn selected_tensor_slice<'a, T: Copy>( slice } -fn slice_ui( - ctx: &mut crate::misc::ViewerContext<'_>, - ui: &mut egui::Ui, - view_state: &ViewTensorState, - tensor_shape: &[component_types::TensorDimension], - slice: ndarray::ArrayViewD<'_, T>, - color_from_value: impl Fn(T) -> Color32, -) { - crate::profile_function!(); - - let ndims = slice.ndim(); - if let Ok(slice) = slice.into_dimensionality::() { - let dimension_labels = { - let dm = &view_state.slice.dim_mapping; - [ - ( - dimension_name(tensor_shape, dm.width.unwrap()), - dm.invert_width, - ), - ( - dimension_name(tensor_shape, dm.height.unwrap()), - dm.invert_height, - ), - ] - }; - - let image = into_image(&slice, color_from_value); - egui::ScrollArea::both().show(ui, |ui| { - image_ui(ui, view_state, image, dimension_labels); - }); - } else { - ui.label(ctx.re_ui.error_text(format!( - "Only 2D slices supported at the moment, but slice ndim {ndims}" - ))); - } -} - fn dimension_name(shape: &[component_types::TensorDimension], dim_idx: usize) -> String { let dim = &shape[dim_idx]; dim.name.as_ref().map_or_else( @@ -763,50 +452,6 @@ fn dimension_name(shape: &[component_types::TensorDimension], dim_idx: usize) -> ) } -fn into_image( - slice: &ndarray::ArrayView2<'_, T>, - color_from_value: impl Fn(T) -> Color32, -) -> ColorImage { - crate::profile_function!(); - - use ndarray::Dimension as _; - let (height, width) = slice.raw_dim().into_pattern(); - let mut image = egui::ColorImage::new([width, height], Color32::DEBUG_COLOR); - - let image_view = - ndarray::ArrayViewMut2::from_shape(slice.raw_dim(), image.pixels.as_mut_slice()) - .expect("Mismatched length."); - - crate::profile_scope!("color_mapper"); - ndarray::Zip::from(image_view) - .and(slice) - .for_each(|pixel, value| { - *pixel = color_from_value(*value); - }); - - image -} - -fn image_ui( - ui: &mut egui::Ui, - view_state: &ViewTensorState, - image: ColorImage, - dimension_labels: [(String, bool); 2], -) { - crate::profile_function!(); - - let font_id = egui::TextStyle::Body.resolve(ui.style()); - - let margin = egui::vec2(0.0, 0.0); - - let (response, painter, image_rect) = - view_state.texture_settings.paint_image(ui, margin, image); - - if !response.hovered() { - paint_axis_names(ui, &painter, image_rect, font_id, dimension_labels); - } -} - fn paint_axis_names( ui: &mut egui::Ui, painter: &egui::Painter, From 3a7caa7af97d2747984afcfde1e61a21621b41ec Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 19:49:33 +0200 Subject: [PATCH 05/27] Support 64-bit tensors by narrowing to f32 --- crates/re_viewer/src/ui/view_tensor/gpu.rs | 29 ++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/crates/re_viewer/src/ui/view_tensor/gpu.rs b/crates/re_viewer/src/ui/view_tensor/gpu.rs index 8bbee2be8ca8..f875f72ad50b 100644 --- a/crates/re_viewer/src/ui/view_tensor/gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/gpu.rs @@ -22,9 +22,6 @@ pub enum TensorUploadError { #[error("Expected a 2D slice")] Not2D, - - #[error("Unsupported datatype: {0:?}")] // TODO: emilk narrow to f32 instead - UnsupportedDataType(TensorDataType), } pub fn colormapped_texture( @@ -102,7 +99,16 @@ fn texture_desc_from_tensor( let tensor = ndarray::ArrayViewD::::try_from(tensor)?; texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Uint, |x| x) } - TensorDataType::U64 => Err(TensorUploadError::UnsupportedDataType(tensor.dtype())), + TensorDataType::U64 => { + // narrow to f32: + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice( + tensor, + slice_selection, + TextureFormat::R32Float, + |x: u64| x as f32, + ) + } TensorDataType::I8 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R8Sint, |x| x) @@ -115,7 +121,16 @@ fn texture_desc_from_tensor( let tensor = ndarray::ArrayViewD::::try_from(tensor)?; texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Sint, |x| x) } - TensorDataType::I64 => Err(TensorUploadError::UnsupportedDataType(tensor.dtype())), + TensorDataType::I64 => { + // narrow to f32: + let tensor = ndarray::ArrayViewD::::try_from(tensor)?; + texture_desc_from_tensor_slice( + tensor, + slice_selection, + TextureFormat::R32Float, + |x: i64| x as f32, + ) + } TensorDataType::F16 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R16Float, |x| x) @@ -125,7 +140,7 @@ fn texture_desc_from_tensor( texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Float, |x| x) } TensorDataType::F64 => { - // narrow f64 -> f32: + // narrow to f32: let tensor = ndarray::ArrayViewD::::try_from(tensor)?; texture_desc_from_tensor_slice( tensor, @@ -148,7 +163,7 @@ fn texture_desc_from_tensor_slice( let slice = selected_tensor_slice(slice_selection, &tensor); let slice = slice .into_dimensionality::() - .map_err(|_| TensorUploadError::Not2D)?; + .map_err(|_err| TensorUploadError::Not2D)?; let (height, width) = slice.raw_dim().into_pattern(); let mut pixels: Vec = vec![To::zeroed(); height * width]; From cd9d62eb032452ce9496ddc8ea4c54582519c0f2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 19:55:00 +0200 Subject: [PATCH 06/27] Allow more colormap options --- crates/re_renderer/src/colormap.rs | 25 +++++++++++++++ crates/re_viewer/src/ui/view_tensor/gpu.rs | 2 +- crates/re_viewer/src/ui/view_tensor/ui.rs | 37 ++++------------------ 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/crates/re_renderer/src/colormap.rs b/crates/re_renderer/src/colormap.rs index c64d85dbb24b..a01c50a10ea0 100644 --- a/crates/re_renderer/src/colormap.rs +++ b/crates/re_renderer/src/colormap.rs @@ -6,6 +6,7 @@ use glam::{Vec2, Vec3A, Vec4, Vec4Swizzles}; // NOTE: Keep in sync with `colormap.wgsl`! #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[repr(u32)] pub enum Colormap { // Reserve 0 for "disabled" @@ -19,6 +20,30 @@ pub enum Colormap { Inferno = 6, } +impl Colormap { + pub const ALL: [Self; 6] = [ + Self::Grayscale, + Self::Turbo, + Self::Viridis, + Self::Plasma, + Self::Magma, + Self::Inferno, + ]; +} + +impl std::fmt::Display for Colormap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Colormap::Grayscale => write!(f, "Grayscale"), + Colormap::Turbo => write!(f, "Turbo"), + Colormap::Viridis => write!(f, "Viridis"), + Colormap::Plasma => write!(f, "Plasma"), + Colormap::Magma => write!(f, "Magma"), + Colormap::Inferno => write!(f, "Inferno"), + } + } +} + pub fn colormap_srgb(which: Colormap, t: f32) -> [u8; 4] { match which { Colormap::Grayscale => grayscale_srgb(t), diff --git a/crates/re_viewer/src/ui/view_tensor/gpu.rs b/crates/re_viewer/src/ui/view_tensor/gpu.rs index f875f72ad50b..e51d479f3928 100644 --- a/crates/re_viewer/src/ui/view_tensor/gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/gpu.rs @@ -40,7 +40,7 @@ pub fn colormapped_texture( range: [min as f32, max as f32], gamma: color_mapping.gamma, color_mapper: Some(re_renderer::renderer::ColorMapper::Function( - color_mapping.renderer_colormap(), + color_mapping.map, )), }) } diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index d4d3fb5473ac..7ab85bfe5cab 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -5,6 +5,7 @@ use egui::{epaint::TextShape, NumExt as _, Vec2}; use ndarray::Axis; use re_log_types::component_types::{self, Tensor}; +use re_renderer::Colormap; use re_tensor_ops::dimension_mapping::{DimensionMapping, DimensionSelector}; use crate::ui::data_ui::image::tensor_summary_ui_grid_contents; @@ -225,49 +226,23 @@ fn paint_tensor_slice( // ---------------------------------------------------------------------------- -// TODO(emilk): replace with the one from re_renderer -#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] -enum Colormap { - Greyscale, - Turbo, - Virdis, -} - -impl std::fmt::Display for Colormap { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Colormap::Greyscale => "Greyscale", - Colormap::Turbo => "Turbo", - Colormap::Virdis => "Viridis", - }) - } -} - /// How we map values to colors. #[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct ColorMapping { - map: Colormap, + pub map: Colormap, pub gamma: f32, } impl Default for ColorMapping { fn default() -> Self { Self { - map: Colormap::Virdis, + map: Colormap::Viridis, gamma: 1.0, } } } impl ColorMapping { - pub fn renderer_colormap(&self) -> re_renderer::Colormap { - match self.map { - Colormap::Greyscale => re_renderer::Colormap::Grayscale, - Colormap::Turbo => re_renderer::Colormap::Turbo, - Colormap::Virdis => re_renderer::Colormap::Viridis, - } - } - fn ui(&mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui) { let ColorMapping { map, gamma } = self; @@ -276,9 +251,9 @@ impl ColorMapping { .selected_text(map.to_string()) .show_ui(ui, |ui| { ui.style_mut().wrap = Some(false); - ui.selectable_value(map, Colormap::Greyscale, Colormap::Greyscale.to_string()); - ui.selectable_value(map, Colormap::Virdis, Colormap::Virdis.to_string()); - ui.selectable_value(map, Colormap::Turbo, Colormap::Turbo.to_string()); + for option in Colormap::ALL { + ui.selectable_value(map, option, option.to_string()); + } }); ui.end_row(); From 8f81611751cf178211d9e5205427ef72a7a41209 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 19:56:15 +0200 Subject: [PATCH 07/27] Clippy --- crates/re_viewer/src/ui/view_tensor/gpu.rs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/re_viewer/src/ui/view_tensor/gpu.rs b/crates/re_viewer/src/ui/view_tensor/gpu.rs index e51d479f3928..b2ee579f5549 100644 --- a/crates/re_viewer/src/ui/view_tensor/gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/gpu.rs @@ -89,21 +89,21 @@ fn texture_desc_from_tensor( match tensor.dtype() { TensorDataType::U8 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R8Uint, |x| x) + texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R8Uint, |x| x) } TensorDataType::U16 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R16Uint, |x| x) + texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R16Uint, |x| x) } TensorDataType::U32 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Uint, |x| x) + texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R32Uint, |x| x) } TensorDataType::U64 => { // narrow to f32: let tensor = ndarray::ArrayViewD::::try_from(tensor)?; texture_desc_from_tensor_slice( - tensor, + &tensor, slice_selection, TextureFormat::R32Float, |x: u64| x as f32, @@ -111,21 +111,21 @@ fn texture_desc_from_tensor( } TensorDataType::I8 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R8Sint, |x| x) + texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R8Sint, |x| x) } TensorDataType::I16 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R16Sint, |x| x) + texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R16Sint, |x| x) } TensorDataType::I32 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Sint, |x| x) + texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R32Sint, |x| x) } TensorDataType::I64 => { // narrow to f32: let tensor = ndarray::ArrayViewD::::try_from(tensor)?; texture_desc_from_tensor_slice( - tensor, + &tensor, slice_selection, TextureFormat::R32Float, |x: i64| x as f32, @@ -133,17 +133,17 @@ fn texture_desc_from_tensor( } TensorDataType::F16 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R16Float, |x| x) + texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R16Float, |x| x) } TensorDataType::F32 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(tensor, slice_selection, TextureFormat::R32Float, |x| x) + texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R32Float, |x| x) } TensorDataType::F64 => { // narrow to f32: let tensor = ndarray::ArrayViewD::::try_from(tensor)?; texture_desc_from_tensor_slice( - tensor, + &tensor, slice_selection, TextureFormat::R32Float, |x: f64| x as f32, @@ -153,14 +153,14 @@ fn texture_desc_from_tensor( } fn texture_desc_from_tensor_slice( - tensor: ndarray::ArrayViewD<'_, From>, + tensor: &ndarray::ArrayViewD<'_, From>, slice_selection: &SliceSelection, format: wgpu::TextureFormat, caster: impl Fn(From) -> To, ) -> Result, TensorUploadError> { use ndarray::Dimension as _; - let slice = selected_tensor_slice(slice_selection, &tensor); + let slice = selected_tensor_slice(slice_selection, tensor); let slice = slice .into_dimensionality::() .map_err(|_err| TensorUploadError::Not2D)?; From 4bf9cbb950bc293e55017eeb47cf10da313f7f8d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 21:51:27 +0200 Subject: [PATCH 08/27] Report range errors instead of ignoring them --- crates/re_viewer/src/ui/view_tensor/gpu.rs | 30 ++++++++++------------ 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/crates/re_viewer/src/ui/view_tensor/gpu.rs b/crates/re_viewer/src/ui/view_tensor/gpu.rs index b2ee579f5549..dd5c1cafeb1a 100644 --- a/crates/re_viewer/src/ui/view_tensor/gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/gpu.rs @@ -22,6 +22,13 @@ pub enum TensorUploadError { #[error("Expected a 2D slice")] Not2D, + + /// This is weird. Should only happen with JPEGs, and those should have been decoded already + #[error("Missing a range.")] + MissingRange, + + #[error("Non-finite range of vlaues")] + NonfiniteRange, } pub fn colormapped_texture( @@ -30,8 +37,8 @@ pub fn colormapped_texture( tensor_stats: &TensorStats, state: &ViewTensorState, ) -> Result { + let (min, max) = range(tensor_stats)?; let texture = upload_texture_slice_to_gpu(render_ctx, tensor, state.slice())?; - let (min, max) = range(tensor.dtype(), tensor_stats); let color_mapping = state.color_mapping(); @@ -45,27 +52,16 @@ pub fn colormapped_texture( }) } -fn range(dtype: TensorDataType, tensor_stats: &TensorStats) -> (f64, f64) { - // TODO: return errors instead of logging them - let default_range = if dtype.is_integer() { - (dtype.min_value(), dtype.max_value()) - } else { - (0.0, 1.0) - }; - - let (min, max) = tensor_stats.range.unwrap_or_else(|| { - re_log::error_once!("Tensor is missing range!?"); // should only happen for compressed tensors, i.e. jpegs - default_range - }); +fn range(tensor_stats: &TensorStats) -> Result<(f64, f64), TensorUploadError> { + let (min, max) = tensor_stats.range.ok_or(TensorUploadError::MissingRange)?; if !min.is_finite() || !max.is_finite() { - re_log::error_once!("Non-finite tensor range"); - default_range + Err(TensorUploadError::NonfiniteRange) } else if min == max { // uniform range. This can explode the colormapping, so let's map all colors to the middle: - (min - 1.0, max + 1.0) + Ok((min - 1.0, max + 1.0)) } else { - (min, max) + Ok((min, max)) } } From 9ea142ea006214c04dd2a0c4949041b1bd2713cf Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 22:31:06 +0200 Subject: [PATCH 09/27] Sort colormaps --- crates/re_data_store/src/entity_properties.rs | 13 ++++++------ crates/re_renderer/shader/colormap.wgsl | 20 +++++++++---------- crates/re_renderer/src/colormap.rs | 18 ++++++++--------- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/crates/re_data_store/src/entity_properties.rs b/crates/re_data_store/src/entity_properties.rs index ca7915c9bd70..0c95b3b593ca 100644 --- a/crates/re_data_store/src/entity_properties.rs +++ b/crates/re_data_store/src/entity_properties.rs @@ -143,23 +143,24 @@ impl ExtraQueryHistory { pub enum Colormap { /// Perceptually even Grayscale, + + Inferno, + Magma, + Plasma, #[default] Turbo, Viridis, - Plasma, - Magma, - Inferno, } impl std::fmt::Display for Colormap { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { Colormap::Grayscale => "Grayscale", + Colormap::Inferno => "Inferno", + Colormap::Magma => "Magma", + Colormap::Plasma => "Plasma", Colormap::Turbo => "Turbo", Colormap::Viridis => "Viridis", - Colormap::Plasma => "Plasma", - Colormap::Magma => "Magma", - Colormap::Inferno => "Inferno", }) } } diff --git a/crates/re_renderer/shader/colormap.wgsl b/crates/re_renderer/shader/colormap.wgsl index d12b43246dca..59be61afdfe4 100644 --- a/crates/re_renderer/shader/colormap.wgsl +++ b/crates/re_renderer/shader/colormap.wgsl @@ -3,11 +3,11 @@ // NOTE: Keep in sync with `colormap.rs`! const COLORMAP_GRAYSCALE: u32 = 1u; -const COLORMAP_TURBO: u32 = 2u; -const COLORMAP_VIRIDIS: u32 = 3u; +const COLORMAP_INFERNO: u32 = 2u; +const COLORMAP_MAGMA: u32 = 3u; const COLORMAP_PLASMA: u32 = 4u; -const COLORMAP_MAGMA: u32 = 5u; -const COLORMAP_INFERNO: u32 = 6u; +const COLORMAP_TURBO: u32 = 5u; +const COLORMAP_VIRIDIS: u32 = 6u; /// Returns a gamma-space sRGB in 0-1 range. /// @@ -15,16 +15,16 @@ const COLORMAP_INFERNO: u32 = 6u; fn colormap_srgb(which: u32, t: f32) -> Vec3 { if which == COLORMAP_GRAYSCALE { return linear_from_srgb(Vec3(t)); + } else if which == COLORMAP_INFERNO { + return colormap_inferno_srgb(t); + } else if which == COLORMAP_MAGMA { + return colormap_magma_srgb(t); + } else if which == COLORMAP_PLASMA { + return colormap_plasma_srgb(t); } else if which == COLORMAP_TURBO { return colormap_turbo_srgb(t); } else if which == COLORMAP_VIRIDIS { return colormap_viridis_srgb(t); - } else if which == COLORMAP_PLASMA { - return colormap_plasma_srgb(t); - } else if which == COLORMAP_MAGMA { - return colormap_magma_srgb(t); - } else if which == COLORMAP_INFERNO { - return colormap_inferno_srgb(t); } else { return ERROR_RGBA.rgb; } diff --git a/crates/re_renderer/src/colormap.rs b/crates/re_renderer/src/colormap.rs index a01c50a10ea0..d57609bd43e8 100644 --- a/crates/re_renderer/src/colormap.rs +++ b/crates/re_renderer/src/colormap.rs @@ -13,21 +13,21 @@ pub enum Colormap { /// Perceptually even #[default] Grayscale = 1, + Inferno = 6, + Magma = 5, + Plasma = 4, Turbo = 2, Viridis = 3, - Plasma = 4, - Magma = 5, - Inferno = 6, } impl Colormap { pub const ALL: [Self; 6] = [ Self::Grayscale, + Self::Inferno, + Self::Magma, + Self::Plasma, Self::Turbo, Self::Viridis, - Self::Plasma, - Self::Magma, - Self::Inferno, ]; } @@ -35,11 +35,11 @@ impl std::fmt::Display for Colormap { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Colormap::Grayscale => write!(f, "Grayscale"), + Colormap::Inferno => write!(f, "Inferno"), + Colormap::Magma => write!(f, "Magma"), + Colormap::Plasma => write!(f, "Plasma"), Colormap::Turbo => write!(f, "Turbo"), Colormap::Viridis => write!(f, "Viridis"), - Colormap::Plasma => write!(f, "Plasma"), - Colormap::Magma => write!(f, "Magma"), - Colormap::Inferno => write!(f, "Inferno"), } } } From 3b43653cd6e5c7b379815dd980e797d4bd81897a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Thu, 13 Apr 2023 22:33:12 +0200 Subject: [PATCH 10/27] Shorten function name --- crates/re_viewer/src/ui/view_tensor/gpu.rs | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/re_viewer/src/ui/view_tensor/gpu.rs b/crates/re_viewer/src/ui/view_tensor/gpu.rs index dd5c1cafeb1a..761b07517587 100644 --- a/crates/re_viewer/src/ui/view_tensor/gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/gpu.rs @@ -85,20 +85,20 @@ fn texture_desc_from_tensor( match tensor.dtype() { TensorDataType::U8 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R8Uint, |x| x) + to_texture_desc(&tensor, slice_selection, TextureFormat::R8Uint, |x| x) } TensorDataType::U16 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R16Uint, |x| x) + to_texture_desc(&tensor, slice_selection, TextureFormat::R16Uint, |x| x) } TensorDataType::U32 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R32Uint, |x| x) + to_texture_desc(&tensor, slice_selection, TextureFormat::R32Uint, |x| x) } TensorDataType::U64 => { // narrow to f32: let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice( + to_texture_desc( &tensor, slice_selection, TextureFormat::R32Float, @@ -107,20 +107,20 @@ fn texture_desc_from_tensor( } TensorDataType::I8 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R8Sint, |x| x) + to_texture_desc(&tensor, slice_selection, TextureFormat::R8Sint, |x| x) } TensorDataType::I16 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R16Sint, |x| x) + to_texture_desc(&tensor, slice_selection, TextureFormat::R16Sint, |x| x) } TensorDataType::I32 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R32Sint, |x| x) + to_texture_desc(&tensor, slice_selection, TextureFormat::R32Sint, |x| x) } TensorDataType::I64 => { // narrow to f32: let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice( + to_texture_desc( &tensor, slice_selection, TextureFormat::R32Float, @@ -129,16 +129,16 @@ fn texture_desc_from_tensor( } TensorDataType::F16 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R16Float, |x| x) + to_texture_desc(&tensor, slice_selection, TextureFormat::R16Float, |x| x) } TensorDataType::F32 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice(&tensor, slice_selection, TextureFormat::R32Float, |x| x) + to_texture_desc(&tensor, slice_selection, TextureFormat::R32Float, |x| x) } TensorDataType::F64 => { // narrow to f32: let tensor = ndarray::ArrayViewD::::try_from(tensor)?; - texture_desc_from_tensor_slice( + to_texture_desc( &tensor, slice_selection, TextureFormat::R32Float, @@ -148,7 +148,7 @@ fn texture_desc_from_tensor( } } -fn texture_desc_from_tensor_slice( +fn to_texture_desc( tensor: &ndarray::ArrayViewD<'_, From>, slice_selection: &SliceSelection, format: wgpu::TextureFormat, From f6a42aa140a5f3678c921b5db9c5f2f7a883d00a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 09:04:02 +0200 Subject: [PATCH 11/27] Create module gpu_bridge --- crates/re_viewer/src/gpu_bridge/mod.rs | 23 +++++++++++++++++++ .../src/{misc => gpu_bridge}/tensor_to_gpu.rs | 20 +++++----------- crates/re_viewer/src/lib.rs | 1 + crates/re_viewer/src/misc/mod.rs | 1 - .../view_spatial/scene/scene_part/images.rs | 2 +- crates/re_viewer/src/ui/view_tensor/gpu.rs | 2 +- 6 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 crates/re_viewer/src/gpu_bridge/mod.rs rename crates/re_viewer/src/{misc => gpu_bridge}/tensor_to_gpu.rs (97%) diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs new file mode 100644 index 000000000000..139f83513f3b --- /dev/null +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -0,0 +1,23 @@ +//! Bridge to `re_renderer` + +mod tensor_to_gpu; +pub use tensor_to_gpu::tensor_to_gpu; + +// ---------------------------------------------------------------------------- + +use re_renderer::{ + resource_managers::{GpuTexture2DHandle, Texture2DCreationDesc}, + RenderContext, +}; + +pub fn get_or_create_texture<'a, Err>( + render_ctx: &mut RenderContext, + texture_key: u64, + try_create_texture_desc: impl FnOnce() -> Result, Err>, +) -> Result { + render_ctx.texture_manager_2d.get_or_create_with( + texture_key, + &mut render_ctx.gpu_resources.textures, + try_create_texture_desc, + ) +} diff --git a/crates/re_viewer/src/misc/tensor_to_gpu.rs b/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs similarity index 97% rename from crates/re_viewer/src/misc/tensor_to_gpu.rs rename to crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs index f6882cd2c203..ad8713a829f7 100644 --- a/crates/re_viewer/src/misc/tensor_to_gpu.rs +++ b/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs @@ -1,3 +1,5 @@ +//! Upload [`Tensor`] to [`re_renderer`]. + use std::borrow::Cow; use bytemuck::{allocation::pod_collect_to_vec, cast_slice, Pod}; @@ -7,11 +9,13 @@ use wgpu::TextureFormat; use re_log_types::component_types::{Tensor, TensorData}; use re_renderer::{ renderer::{ColorMapper, ColormappedTexture}, - resource_managers::{GpuTexture2DHandle, Texture2DCreationDesc}, + resource_managers::Texture2DCreationDesc, RenderContext, }; -use super::caches::TensorStats; +use crate::misc::caches::TensorStats; + +use super::get_or_create_texture; // ---------------------------------------------------------------------------- @@ -382,18 +386,6 @@ fn general_texture_creation_desc_from_tensor<'a>( }) } -pub fn get_or_create_texture<'a, Err>( - render_ctx: &mut RenderContext, - texture_key: u64, - try_create_texture_desc: impl FnOnce() -> Result, Err>, -) -> Result { - render_ctx.texture_manager_2d.get_or_create_with( - texture_key, - &mut render_ctx.gpu_resources.textures, - try_create_texture_desc, - ) -} - fn cast_slice_to_cow(slice: &[From]) -> Cow<'_, [u8]> { cast_slice(slice).into() } diff --git a/crates/re_viewer/src/lib.rs b/crates/re_viewer/src/lib.rs index 10ceb56ce0c6..85a2939b3160 100644 --- a/crates/re_viewer/src/lib.rs +++ b/crates/re_viewer/src/lib.rs @@ -5,6 +5,7 @@ mod app; pub mod env_vars; +pub(crate) mod gpu_bridge; pub mod math; mod misc; mod remote_viewer_app; diff --git a/crates/re_viewer/src/misc/mod.rs b/crates/re_viewer/src/misc/mod.rs index 023aafc3d6e9..7ba4a8e82f05 100644 --- a/crates/re_viewer/src/misc/mod.rs +++ b/crates/re_viewer/src/misc/mod.rs @@ -6,7 +6,6 @@ pub(crate) mod mesh_loader; pub mod queries; mod selection_state; pub(crate) mod space_info; -pub mod tensor_to_gpu; pub(crate) mod time_control; pub(crate) mod time_control_ui; mod transform_cache; diff --git a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs index 16dc90f6ad64..0c2c29156e50 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs @@ -45,7 +45,7 @@ fn push_tensor_texture( let debug_name = entity_path.to_string(); let tensor_stats = ctx.cache.tensor_stats(tensor); - match crate::misc::tensor_to_gpu::tensor_to_gpu( + match crate::gpu_bridge::tensor_to_gpu( ctx.render_ctx, &debug_name, tensor, diff --git a/crates/re_viewer/src/ui/view_tensor/gpu.rs b/crates/re_viewer/src/ui/view_tensor/gpu.rs index 761b07517587..3eec620720e4 100644 --- a/crates/re_viewer/src/ui/view_tensor/gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/gpu.rs @@ -72,7 +72,7 @@ fn upload_texture_slice_to_gpu( ) -> Result { let id = egui::util::hash((tensor.id(), slice_selection)); - crate::misc::tensor_to_gpu::get_or_create_texture(render_ctx, id, || { + crate::gpu_bridge::get_or_create_texture(render_ctx, id, || { texture_desc_from_tensor(tensor, slice_selection) }) } From df23e3be1a96116a7af5042571efa2b00acda13b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 09:15:00 +0200 Subject: [PATCH 12/27] Move some code around --- crates/re_renderer/src/lib.rs | 2 +- crates/re_viewer/src/gpu_bridge/mod.rs | 151 +++++++++++++++++- crates/re_viewer/src/ui/view_spatial/ui_2d.rs | 10 +- crates/re_viewer/src/ui/view_spatial/ui_3d.rs | 10 +- .../src/ui/view_spatial/ui_renderer_bridge.rs | 63 -------- crates/re_viewer/src/ui/view_tensor/mod.rs | 2 +- .../{gpu.rs => tensor_slice_to_gpu.rs} | 95 +---------- crates/re_viewer/src/ui/view_tensor/ui.rs | 10 +- 8 files changed, 170 insertions(+), 173 deletions(-) rename crates/re_viewer/src/ui/view_tensor/{gpu.rs => tensor_slice_to_gpu.rs} (60%) diff --git a/crates/re_renderer/src/lib.rs b/crates/re_renderer/src/lib.rs index 3b9d660be33e..770c0589f7fe 100644 --- a/crates/re_renderer/src/lib.rs +++ b/crates/re_renderer/src/lib.rs @@ -41,7 +41,7 @@ pub use depth_offset::DepthOffset; pub use line_strip_builder::{LineStripBuilder, LineStripSeriesBuilder}; pub use point_cloud_builder::{PointCloudBatchBuilder, PointCloudBuilder}; pub use size::Size; -pub use view_builder::AutoSizeConfig; +pub use view_builder::{AutoSizeConfig, ViewBuilder}; pub use wgpu_resources::WgpuResourcePoolStatistics; mod draw_phases; diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index 139f83513f3b..26523766023f 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -5,11 +5,21 @@ pub use tensor_to_gpu::tensor_to_gpu; // ---------------------------------------------------------------------------- +use egui::mutex::Mutex; + use re_renderer::{ + renderer::ColormappedTexture, resource_managers::{GpuTexture2DHandle, Texture2DCreationDesc}, - RenderContext, + RenderContext, ViewBuilder, }; +pub fn viewport_resolution_in_pixels(clip_rect: egui::Rect, pixels_from_point: f32) -> [u32; 2] { + let min = (clip_rect.min.to_vec2() * pixels_from_point).round(); + let max = (clip_rect.max.to_vec2() * pixels_from_point).round(); + let resolution = max - min; + [resolution.x as u32, resolution.y as u32] +} + pub fn get_or_create_texture<'a, Err>( render_ctx: &mut RenderContext, texture_key: u64, @@ -21,3 +31,142 @@ pub fn get_or_create_texture<'a, Err>( try_create_texture_desc, ) } + +/// Render a `re_render` view using the given clip rectangle. +pub fn renderer_paint_callback( + render_ctx: &mut re_renderer::RenderContext, + command_buffer: wgpu::CommandBuffer, + view_builder: re_renderer::ViewBuilder, + clip_rect: egui::Rect, + pixels_from_point: f32, +) -> egui::PaintCallback { + crate::profile_function!(); + + slotmap::new_key_type! { pub struct ViewBuilderHandle; } + type ViewBuilderMap = slotmap::SlotMap; + + // egui paint callback are copyable / not a FnOnce (this in turn is because egui primitives can be callbacks and are copyable) + let command_buffer = std::sync::Arc::new(Mutex::new(Some(command_buffer))); + + let composition_view_builder_map = render_ctx + .active_frame + .per_frame_data_helper + .entry::() + .or_insert_with(Default::default); + let view_builder_handle = composition_view_builder_map.insert(view_builder); + + let screen_position = (clip_rect.min.to_vec2() * pixels_from_point).round(); + let screen_position = glam::vec2(screen_position.x, screen_position.y); + + egui::PaintCallback { + rect: clip_rect, + callback: std::sync::Arc::new( + egui_wgpu::CallbackFn::new() + .prepare( + move |_device, _queue, _encoder, _paint_callback_resources| { + let mut command_buffer = command_buffer.lock(); + vec![std::mem::replace(&mut *command_buffer, None) + .expect("egui_wgpu prepare callback called more than once")] + }, + ) + .paint(move |_info, render_pass, paint_callback_resources| { + crate::profile_scope!("paint"); + // TODO(andreas): This should work as well but doesn't work in the 3d view. + // Looks like a bug in egui, but unclear what's going on. + //let clip_rect = info.clip_rect_in_pixels(); + + let ctx = paint_callback_resources.get::().unwrap(); + ctx.active_frame + .per_frame_data_helper + .get::() + .unwrap()[view_builder_handle] + .composite(ctx, render_pass, screen_position) + .expect("Failed compositing view builder with main target."); + }), + ), + } +} + +pub fn render_image( + render_ctx: &mut re_renderer::RenderContext, + painter: &egui::Painter, + image_size_in_space: egui::Vec2, + image_position_on_screen: egui::Rect, + colormapped_texture: ColormappedTexture, + texture_options: egui::TextureOptions, +) -> anyhow::Result<()> { + crate::profile_function!(); + + use re_renderer::renderer::{TextureFilterMag, TextureFilterMin}; + + let space_rect = egui::Rect::from_min_size(egui::Pos2::ZERO, image_size_in_space); + + let textured_rectangle = re_renderer::renderer::TexturedRect { + top_left_corner_position: glam::Vec3::ZERO, + extent_u: glam::Vec3::X * image_size_in_space.x, + extent_v: glam::Vec3::Y * image_size_in_space.y, + colormapped_texture, + texture_filter_magnification: match texture_options.magnification { + egui::TextureFilter::Nearest => TextureFilterMag::Nearest, + egui::TextureFilter::Linear => TextureFilterMag::Linear, + }, + texture_filter_minification: match texture_options.minification { + egui::TextureFilter::Nearest => TextureFilterMin::Nearest, + egui::TextureFilter::Linear => TextureFilterMin::Linear, + }, + multiplicative_tint: egui::Rgba::WHITE, + depth_offset: 0, + outline_mask: Default::default(), + }; + + // ------------------------------------------------------------------------ + + let pixels_from_points = painter.ctx().pixels_per_point(); + let ui_from_space = egui::emath::RectTransform::from_to(space_rect, image_position_on_screen); + let space_from_ui = ui_from_space.inverse(); + let space_from_points = space_from_ui.scale().y; + let points_from_pixels = 1.0 / painter.ctx().pixels_per_point(); + let space_from_pixel = space_from_points * points_from_pixels; + + let resolution_in_pixel = + crate::gpu_bridge::viewport_resolution_in_pixels(painter.clip_rect(), pixels_from_points); + anyhow::ensure!(resolution_in_pixel[0] > 0 && resolution_in_pixel[1] > 0); + + let camera_position_space = space_from_ui.transform_pos(painter.clip_rect().min); + + let top_left_position = glam::vec2(camera_position_space.x, camera_position_space.y); + let target_config = re_renderer::view_builder::TargetConfiguration { + name: "tensor_view".into(), + resolution_in_pixel, + view_from_world: macaw::IsoTransform::from_translation(-top_left_position.extend(0.0)), + projection_from_view: re_renderer::view_builder::Projection::Orthographic { + camera_mode: re_renderer::view_builder::OrthographicCameraMode::TopLeftCornerAndExtendZ, + vertical_world_size: space_from_pixel * resolution_in_pixel[1] as f32, + far_plane_distance: 1000.0, + }, + pixels_from_point: pixels_from_points, + auto_size_config: Default::default(), + outline_config: None, + }; + + // TODO(andreas): separate setup for viewbuilder doesn't make sense. + let mut view_builder = ViewBuilder::default(); + view_builder.setup_view(render_ctx, target_config)?; + + view_builder.queue_draw(&re_renderer::renderer::RectangleDrawData::new( + render_ctx, + &[textured_rectangle], + )?); + + let command_buffer = view_builder.draw(render_ctx, re_renderer::Rgba::TRANSPARENT)?; + + painter.add(crate::gpu_bridge::renderer_paint_callback( + render_ctx, + command_buffer, + view_builder, + painter.clip_rect(), + painter.ctx().pixels_per_point(), + )); + + Ok(()) +} diff --git a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs index 0cedd7495fff..9700725123c5 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs @@ -10,13 +10,12 @@ use super::{ SpatialNavigationMode, ViewSpatialState, }; use crate::{ + gpu_bridge, misc::{HoveredSpace, SpaceViewHighlights}, ui::{ view_spatial::{ ui::outline_config, - ui_renderer_bridge::{ - fill_view_builder, get_viewport, renderer_paint_callback, ScreenBackground, - }, + ui_renderer_bridge::{fill_view_builder, ScreenBackground}, SceneSpatial, }, SpaceViewId, @@ -381,7 +380,7 @@ fn view_2d_scrollable( return response; } }; - painter.add(renderer_paint_callback( + painter.add(gpu_bridge::renderer_paint_callback( ctx.render_ctx, command_buffer, view_builder, @@ -412,7 +411,8 @@ fn setup_target_config( any_outlines: bool, ) -> anyhow::Result { let pixels_from_points = painter.ctx().pixels_per_point(); - let resolution_in_pixel = get_viewport(painter.clip_rect(), pixels_from_points); + let resolution_in_pixel = + gpu_bridge::viewport_resolution_in_pixels(painter.clip_rect(), pixels_from_points); anyhow::ensure!(resolution_in_pixel[0] > 0 && resolution_in_pixel[1] > 0); let camera_position_space = space_from_ui.transform_pos(painter.clip_rect().min); diff --git a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs index c464af8731e9..39fbc350d451 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs @@ -11,13 +11,12 @@ use re_renderer::{ }; use crate::{ + gpu_bridge, misc::{HoveredSpace, Item, SpaceViewHighlights}, ui::{ view_spatial::{ ui::{create_labels, outline_config, picking, screenshot_context_menu}, - ui_renderer_bridge::{ - fill_view_builder, get_viewport, renderer_paint_callback, ScreenBackground, - }, + ui_renderer_bridge::{fill_view_builder, ScreenBackground}, SceneSpatial, SpaceCamera3D, SpatialNavigationMode, }, SpaceViewId, @@ -315,7 +314,8 @@ pub fn view_3d( } // Determine view port resolution and position. - let resolution_in_pixel = get_viewport(rect, ui.ctx().pixels_per_point()); + let resolution_in_pixel = + gpu_bridge::viewport_resolution_in_pixels(rect, ui.ctx().pixels_per_point()); if resolution_in_pixel[0] == 0 || resolution_in_pixel[1] == 0 { return; } @@ -508,7 +508,7 @@ pub fn view_3d( return; } }; - ui.painter().add(renderer_paint_callback( + ui.painter().add(gpu_bridge::renderer_paint_callback( ctx.render_ctx, command_buffer, view_builder, diff --git a/crates/re_viewer/src/ui/view_spatial/ui_renderer_bridge.rs b/crates/re_viewer/src/ui/view_spatial/ui_renderer_bridge.rs index 5e84036d766b..513abdf82430 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_renderer_bridge.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_renderer_bridge.rs @@ -1,4 +1,3 @@ -use egui::mutex::Mutex; use re_renderer::{ renderer::{DepthCloudDrawData, GenericSkyboxDrawData, MeshDrawData, RectangleDrawData}, view_builder::ViewBuilder, @@ -7,13 +6,6 @@ use re_renderer::{ use super::scene::SceneSpatialPrimitives; -pub fn get_viewport(clip_rect: egui::Rect, pixels_from_point: f32) -> [u32; 2] { - let min = (clip_rect.min.to_vec2() * pixels_from_point).round(); - let max = (clip_rect.max.to_vec2() * pixels_from_point).round(); - let resolution = max - min; - [resolution.x as u32, resolution.y as u32] -} - pub enum ScreenBackground { GenericSkybox, ClearColor(re_renderer::Rgba), @@ -57,58 +49,3 @@ pub fn fill_view_builder( Ok(command_buffer) } - -slotmap::new_key_type! { pub struct ViewBuilderHandle; } - -type ViewBuilderMap = slotmap::SlotMap; - -pub fn renderer_paint_callback( - render_ctx: &mut RenderContext, - command_buffer: wgpu::CommandBuffer, - view_builder: ViewBuilder, - clip_rect: egui::Rect, - pixels_from_point: f32, -) -> egui::PaintCallback { - crate::profile_function!(); - - // egui paint callback are copyable / not a FnOnce (this in turn is because egui primitives can be callbacks and are copyable) - let command_buffer = std::sync::Arc::new(Mutex::new(Some(command_buffer))); - - let composition_view_builder_map = render_ctx - .active_frame - .per_frame_data_helper - .entry::() - .or_insert_with(Default::default); - let view_builder_handle = composition_view_builder_map.insert(view_builder); - - let screen_position = (clip_rect.min.to_vec2() * pixels_from_point).round(); - let screen_position = glam::vec2(screen_position.x, screen_position.y); - - egui::PaintCallback { - rect: clip_rect, - callback: std::sync::Arc::new( - egui_wgpu::CallbackFn::new() - .prepare( - move |_device, _queue, _encoder, _paint_callback_resources| { - let mut command_buffer = command_buffer.lock(); - vec![std::mem::replace(&mut *command_buffer, None) - .expect("egui_wgpu prepare callback called more than once")] - }, - ) - .paint(move |_info, render_pass, paint_callback_resources| { - crate::profile_scope!("paint"); - // TODO(andreas): This should work as well but doesn't work in the 3d view. - // Looks like a bug in egui, but unclear what's going on. - //let clip_rect = info.clip_rect_in_pixels(); - - let ctx = paint_callback_resources.get::().unwrap(); - ctx.active_frame - .per_frame_data_helper - .get::() - .unwrap()[view_builder_handle] - .composite(ctx, render_pass, screen_position) - .expect("Failed compositing view builder with main target."); - }), - ), - } -} diff --git a/crates/re_viewer/src/ui/view_tensor/mod.rs b/crates/re_viewer/src/ui/view_tensor/mod.rs index 87df6d5f397e..9c54bf671b05 100644 --- a/crates/re_viewer/src/ui/view_tensor/mod.rs +++ b/crates/re_viewer/src/ui/view_tensor/mod.rs @@ -1,4 +1,4 @@ -mod gpu; +mod tensor_slice_to_gpu; mod scene; pub(crate) use self::scene::SceneTensor; diff --git a/crates/re_viewer/src/ui/view_tensor/gpu.rs b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs similarity index 60% rename from crates/re_viewer/src/ui/view_tensor/gpu.rs rename to crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs index 3eec620720e4..7cfa544034f8 100644 --- a/crates/re_viewer/src/ui/view_tensor/gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs @@ -2,11 +2,7 @@ use re_log_types::{ component_types::{Tensor, TensorCastError}, TensorDataType, }; -use re_renderer::{ - renderer::{ColormappedTexture, RectangleDrawData, TextureFilterMag, TextureFilterMin}, - resource_managers::Texture2DCreationDesc, - view_builder::{TargetConfiguration, ViewBuilder}, -}; +use re_renderer::{renderer::ColormappedTexture, resource_managers::Texture2DCreationDesc}; use crate::misc::caches::TensorStats; @@ -179,92 +175,3 @@ fn to_texture_desc( height: height as u32, }) } - -// ---------------------------------------------------------------------------- - -pub fn paint( - render_ctx: &mut re_renderer::RenderContext, - painter: &egui::Painter, - slice_size: egui::Vec2, - image_position_on_screen: egui::Rect, - colormapped_texture: ColormappedTexture, - texture_options: egui::TextureOptions, -) -> anyhow::Result<()> { - crate::profile_function!(); - - let space_rect = egui::Rect::from_min_size(egui::Pos2::ZERO, slice_size); - - let textured_rectangle = re_renderer::renderer::TexturedRect { - top_left_corner_position: glam::Vec3::ZERO, - extent_u: glam::Vec3::X * slice_size.x, - extent_v: glam::Vec3::Y * slice_size.y, - colormapped_texture, - texture_filter_magnification: match texture_options.magnification { - egui::TextureFilter::Nearest => TextureFilterMag::Nearest, - egui::TextureFilter::Linear => TextureFilterMag::Linear, - }, - texture_filter_minification: match texture_options.minification { - egui::TextureFilter::Nearest => TextureFilterMin::Nearest, - egui::TextureFilter::Linear => TextureFilterMin::Linear, - }, - multiplicative_tint: egui::Rgba::WHITE, - depth_offset: 0, - outline_mask: Default::default(), - }; - - // ------------------------------------------------------------------------ - - let pixels_from_points = painter.ctx().pixels_per_point(); - let ui_from_space = egui::emath::RectTransform::from_to(space_rect, image_position_on_screen); - let space_from_ui = ui_from_space.inverse(); - let space_from_points = space_from_ui.scale().y; - let points_from_pixels = 1.0 / painter.ctx().pixels_per_point(); - let space_from_pixel = space_from_points * points_from_pixels; - - let resolution_in_pixel = get_viewport(painter.clip_rect(), pixels_from_points); - anyhow::ensure!(resolution_in_pixel[0] > 0 && resolution_in_pixel[1] > 0); - - let camera_position_space = space_from_ui.transform_pos(painter.clip_rect().min); - - let top_left_position = glam::vec2(camera_position_space.x, camera_position_space.y); - let target_config = TargetConfiguration { - name: "tensor_view".into(), - resolution_in_pixel, - view_from_world: macaw::IsoTransform::from_translation(-top_left_position.extend(0.0)), - projection_from_view: re_renderer::view_builder::Projection::Orthographic { - camera_mode: re_renderer::view_builder::OrthographicCameraMode::TopLeftCornerAndExtendZ, - vertical_world_size: space_from_pixel * resolution_in_pixel[1] as f32, - far_plane_distance: 1000.0, - }, - pixels_from_point: pixels_from_points, - auto_size_config: Default::default(), - outline_config: None, - }; - - // TODO(andreas): separate setup for viewbuilder doesn't make sense. - let mut view_builder = ViewBuilder::default(); - view_builder.setup_view(render_ctx, target_config)?; - - view_builder.queue_draw(&RectangleDrawData::new(render_ctx, &[textured_rectangle])?); - - let command_buffer = view_builder.draw(render_ctx, re_renderer::Rgba::TRANSPARENT)?; - - painter.add( - crate::ui::view_spatial::ui_renderer_bridge::renderer_paint_callback( - render_ctx, - command_buffer, - view_builder, - painter.clip_rect(), - painter.ctx().pixels_per_point(), - ), - ); - - Ok(()) -} - -fn get_viewport(clip_rect: egui::Rect, pixels_from_point: f32) -> [u32; 2] { - let min = (clip_rect.min.to_vec2() * pixels_from_point).round(); - let max = (clip_rect.max.to_vec2() * pixels_from_point).round(); - let resolution = max - min; - [resolution.x as u32, resolution.y as u32] -} diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index 7ab85bfe5cab..80fd36e4052f 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -182,8 +182,12 @@ fn paint_tensor_slice( crate::profile_function!(); let tensor_stats = ctx.cache.tensor_stats(tensor); - let colormapped_texture = - super::gpu::colormapped_texture(ctx.render_ctx, tensor, tensor_stats, state)?; + let colormapped_texture = super::tensor_slice_to_gpu::colormapped_texture( + ctx.render_ctx, + tensor, + tensor_stats, + state, + )?; let texture = ctx .render_ctx .texture_manager_2d @@ -212,7 +216,7 @@ fn paint_tensor_slice( let rect = response.rect; let image_rect = egui::Rect::from_min_max(rect.min + margin, rect.max); - super::gpu::paint( + crate::gpu_bridge::render_image( ctx.render_ctx, &painter, img_size, From 0b42f5628942f5b807ac88636f79e9302105906b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 09:26:22 +0200 Subject: [PATCH 13/27] Simnplify API --- crates/re_viewer/src/gpu_bridge/mod.rs | 15 ++++++++------- crates/re_viewer/src/ui/view_tensor/ui.rs | 8 +++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index 26523766023f..335e225552a7 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -87,11 +87,11 @@ pub fn renderer_paint_callback( } } +/// Render the given image, respecting the clip rectangle of the given painter. pub fn render_image( render_ctx: &mut re_renderer::RenderContext, painter: &egui::Painter, - image_size_in_space: egui::Vec2, - image_position_on_screen: egui::Rect, + image_rect_on_screen: egui::Rect, colormapped_texture: ColormappedTexture, texture_options: egui::TextureOptions, ) -> anyhow::Result<()> { @@ -99,12 +99,13 @@ pub fn render_image( use re_renderer::renderer::{TextureFilterMag, TextureFilterMin}; - let space_rect = egui::Rect::from_min_size(egui::Pos2::ZERO, image_size_in_space); + // Where in "world space" to paint the image. + let space_rect = egui::Rect::from_min_size(egui::Pos2::ZERO, image_rect_on_screen.size()); let textured_rectangle = re_renderer::renderer::TexturedRect { - top_left_corner_position: glam::Vec3::ZERO, - extent_u: glam::Vec3::X * image_size_in_space.x, - extent_v: glam::Vec3::Y * image_size_in_space.y, + top_left_corner_position: glam::vec3(space_rect.min.x, space_rect.min.y, 0.0), + extent_u: glam::Vec3::X * space_rect.width(), + extent_v: glam::Vec3::Y * space_rect.height(), colormapped_texture, texture_filter_magnification: match texture_options.magnification { egui::TextureFilter::Nearest => TextureFilterMag::Nearest, @@ -122,7 +123,7 @@ pub fn render_image( // ------------------------------------------------------------------------ let pixels_from_points = painter.ctx().pixels_per_point(); - let ui_from_space = egui::emath::RectTransform::from_to(space_rect, image_position_on_screen); + let ui_from_space = egui::emath::RectTransform::from_to(space_rect, image_rect_on_screen); let space_from_ui = ui_from_space.inverse(); let space_from_points = space_from_ui.scale().y; let points_from_pixels = 1.0 / painter.ctx().pixels_per_point(); diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index 80fd36e4052f..e99bb0483c29 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -196,13 +196,12 @@ fn paint_tensor_slice( let width = size.width; let height = size.height; - let margin = egui::Vec2::ZERO; let img_size = egui::vec2(width as _, height as _); let img_size = Vec2::max(Vec2::splat(1.0), img_size); // better safe than sorry let desired_size = match state.texture_settings.scaling { - TextureScaling::Original => img_size + margin, + TextureScaling::Original => img_size, TextureScaling::Fill => { - let desired_size = ui.available_size() - margin; + let desired_size = ui.available_size(); if state.texture_settings.keep_aspect_ratio { let scale = (desired_size / img_size).min_elem(); img_size * scale @@ -214,12 +213,11 @@ fn paint_tensor_slice( let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::hover()); let rect = response.rect; - let image_rect = egui::Rect::from_min_max(rect.min + margin, rect.max); + let image_rect = egui::Rect::from_min_max(rect.min, rect.max); crate::gpu_bridge::render_image( ctx.render_ctx, &painter, - img_size, image_rect, colormapped_texture, state.texture_settings.options, From f2488ee931b0e6dad54180a5147f15469b4755a1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 09:34:38 +0200 Subject: [PATCH 14/27] Create ViewBuilder::new --- crates/re_renderer/examples/2d.rs | 72 ++++++++--------- crates/re_renderer/examples/depth_cloud.rs | 78 +++++++++---------- crates/re_renderer/examples/multiview.rs | 3 +- crates/re_renderer/examples/outlines.rs | 53 ++++++------- crates/re_renderer/examples/picking.rs | 42 +++++----- crates/re_renderer/src/view_builder.rs | 18 ++--- crates/re_viewer/src/gpu_bridge/mod.rs | 4 +- crates/re_viewer/src/ui/view_spatial/ui_2d.rs | 7 +- crates/re_viewer/src/ui/view_spatial/ui_3d.rs | 7 +- 9 files changed, 123 insertions(+), 161 deletions(-) diff --git a/crates/re_renderer/examples/2d.rs b/crates/re_renderer/examples/2d.rs index 4cebee363cc4..34f7250b1b55 100644 --- a/crates/re_renderer/examples/2d.rs +++ b/crates/re_renderer/examples/2d.rs @@ -225,25 +225,22 @@ impl framework::Example for Render2D { vec![ // 2d view to the left { - let mut view_builder = ViewBuilder::default(); - view_builder - .setup_view( - re_ctx, - TargetConfiguration { - name: "2D".into(), - resolution_in_pixel: splits[0].resolution_in_pixel, - view_from_world: macaw::IsoTransform::IDENTITY, - projection_from_view: Projection::Orthographic { - camera_mode: - view_builder::OrthographicCameraMode::TopLeftCornerAndExtendZ, - vertical_world_size: splits[0].resolution_in_pixel[1] as f32, - far_plane_distance: 1000.0, - }, - pixels_from_point, - ..Default::default() + let mut view_builder = ViewBuilder::new( + re_ctx, + TargetConfiguration { + name: "2D".into(), + resolution_in_pixel: splits[0].resolution_in_pixel, + view_from_world: macaw::IsoTransform::IDENTITY, + projection_from_view: Projection::Orthographic { + camera_mode: + view_builder::OrthographicCameraMode::TopLeftCornerAndExtendZ, + vertical_world_size: splits[0].resolution_in_pixel[1] as f32, + far_plane_distance: 1000.0, }, - ) - .unwrap(); + pixels_from_point, + ..Default::default() + }, + ); view_builder.queue_draw(&line_strip_draw_data); view_builder.queue_draw(&point_draw_data); view_builder.queue_draw(&rectangle_draw_data); @@ -258,7 +255,6 @@ impl framework::Example for Render2D { }, // and 3d view of the same scene to the right { - let mut view_builder = ViewBuilder::default(); let seconds_since_startup = time.seconds_since_startup(); let camera_rotation_center = screen_size.extend(0.0) * 0.5; let camera_position = glam::vec3( @@ -267,27 +263,25 @@ impl framework::Example for Render2D { seconds_since_startup.cos(), ) * screen_size.x.max(screen_size.y) + camera_rotation_center; - view_builder - .setup_view( - re_ctx, - view_builder::TargetConfiguration { - name: "3D".into(), - resolution_in_pixel: splits[1].resolution_in_pixel, - view_from_world: macaw::IsoTransform::look_at_rh( - camera_position, - camera_rotation_center, - glam::Vec3::Y, - ) - .unwrap(), - projection_from_view: Projection::Perspective { - vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, - near_plane_distance: 0.01, - }, - pixels_from_point, - ..Default::default() + let mut view_builder = ViewBuilder::new( + re_ctx, + view_builder::TargetConfiguration { + name: "3D".into(), + resolution_in_pixel: splits[1].resolution_in_pixel, + view_from_world: macaw::IsoTransform::look_at_rh( + camera_position, + camera_rotation_center, + glam::Vec3::Y, + ) + .unwrap(), + projection_from_view: Projection::Perspective { + vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, + near_plane_distance: 0.01, }, - ) - .unwrap(); + pixels_from_point, + ..Default::default() + }, + ); let command_buffer = view_builder .queue_draw(&line_strip_draw_data) .queue_draw(&point_draw_data) diff --git a/crates/re_renderer/examples/depth_cloud.rs b/crates/re_renderer/examples/depth_cloud.rs index cbd79faa8a25..2003292731e6 100644 --- a/crates/re_renderer/examples/depth_cloud.rs +++ b/crates/re_renderer/examples/depth_cloud.rs @@ -110,28 +110,25 @@ impl RenderDepthClouds { builder.to_draw_data(re_ctx).unwrap() }; - let mut view_builder = ViewBuilder::default(); - view_builder - .setup_view( - re_ctx, - view_builder::TargetConfiguration { - name: "Point Cloud".into(), - resolution_in_pixel, - view_from_world: IsoTransform::look_at_rh( - self.camera_position, - Vec3::ZERO, - Vec3::Y, - ) - .unwrap(), - projection_from_view: Projection::Perspective { - vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, - near_plane_distance: 0.01, - }, - pixels_from_point, - ..Default::default() + let mut view_builder = ViewBuilder::new( + re_ctx, + view_builder::TargetConfiguration { + name: "Point Cloud".into(), + resolution_in_pixel, + view_from_world: IsoTransform::look_at_rh( + self.camera_position, + Vec3::ZERO, + Vec3::Y, + ) + .unwrap(), + projection_from_view: Projection::Perspective { + vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, + near_plane_distance: 0.01, }, - ) - .unwrap(); + pixels_from_point, + ..Default::default() + }, + ); let command_buffer = view_builder .queue_draw(&GenericSkyboxDrawData::new(re_ctx)) @@ -191,28 +188,25 @@ impl RenderDepthClouds { ) .unwrap(); - let mut view_builder = ViewBuilder::default(); - view_builder - .setup_view( - re_ctx, - view_builder::TargetConfiguration { - name: "Depth Cloud".into(), - resolution_in_pixel, - view_from_world: IsoTransform::look_at_rh( - self.camera_position, - Vec3::ZERO, - Vec3::Y, - ) - .unwrap(), - projection_from_view: Projection::Perspective { - vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, - near_plane_distance: 0.01, - }, - pixels_from_point, - ..Default::default() + let mut view_builder = ViewBuilder::new( + re_ctx, + view_builder::TargetConfiguration { + name: "Depth Cloud".into(), + resolution_in_pixel, + view_from_world: IsoTransform::look_at_rh( + self.camera_position, + Vec3::ZERO, + Vec3::Y, + ) + .unwrap(), + projection_from_view: Projection::Perspective { + vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, + near_plane_distance: 0.01, }, - ) - .unwrap(); + pixels_from_point, + ..Default::default() + }, + ); let command_buffer = view_builder .queue_draw(&GenericSkyboxDrawData::new(re_ctx)) diff --git a/crates/re_renderer/examples/multiview.rs b/crates/re_renderer/examples/multiview.rs index 35d234152b3b..48b77e24388f 100644 --- a/crates/re_renderer/examples/multiview.rs +++ b/crates/re_renderer/examples/multiview.rs @@ -210,8 +210,7 @@ impl Multiview { draw_data: &D, index: u32, ) -> (ViewBuilder, wgpu::CommandBuffer) { - let mut view_builder = ViewBuilder::default(); - view_builder.setup_view(re_ctx, target_cfg).unwrap(); + let mut view_builder = ViewBuilder::new(re_ctx, target_cfg); if self .take_screenshot_next_frame_for_view diff --git a/crates/re_renderer/examples/outlines.rs b/crates/re_renderer/examples/outlines.rs index b7b106a60426..2cdc2ed6e87c 100644 --- a/crates/re_renderer/examples/outlines.rs +++ b/crates/re_renderer/examples/outlines.rs @@ -40,8 +40,6 @@ impl framework::Example for Outlines { time: &framework::Time, pixels_from_point: f32, ) -> Vec { - let mut view_builder = ViewBuilder::default(); - if !self.is_paused { self.seconds_since_startup += time.last_frame_duration.as_secs_f32(); } @@ -49,35 +47,30 @@ impl framework::Example for Outlines { // TODO(#1426): unify camera logic between examples. let camera_position = glam::vec3(1.0, 3.5, 7.0); - view_builder - .setup_view( - re_ctx, - TargetConfiguration { - name: "OutlinesDemo".into(), - resolution_in_pixel: resolution, - view_from_world: macaw::IsoTransform::look_at_rh( - camera_position, - glam::Vec3::ZERO, - glam::Vec3::Y, - ) - .unwrap(), - projection_from_view: Projection::Perspective { - vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, - near_plane_distance: 0.01, - }, - pixels_from_point, - outline_config: Some(OutlineConfig { - outline_radius_pixel: (seconds_since_startup * 2.0).sin().abs() * 10.0 - + 2.0, - color_layer_a: re_renderer::Rgba::from_rgb(1.0, 0.6, 0.0), - color_layer_b: re_renderer::Rgba::from_rgba_unmultiplied( - 0.25, 0.3, 1.0, 0.5, - ), - }), - ..Default::default() + let mut view_builder = ViewBuilder::new( + re_ctx, + TargetConfiguration { + name: "OutlinesDemo".into(), + resolution_in_pixel: resolution, + view_from_world: macaw::IsoTransform::look_at_rh( + camera_position, + glam::Vec3::ZERO, + glam::Vec3::Y, + ) + .unwrap(), + projection_from_view: Projection::Perspective { + vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, + near_plane_distance: 0.01, }, - ) - .unwrap(); + pixels_from_point, + outline_config: Some(OutlineConfig { + outline_radius_pixel: (seconds_since_startup * 2.0).sin().abs() * 10.0 + 2.0, + color_layer_a: re_renderer::Rgba::from_rgb(1.0, 0.6, 0.0), + color_layer_b: re_renderer::Rgba::from_rgba_unmultiplied(0.25, 0.3, 1.0, 0.5), + }), + ..Default::default() + }, + ); let outline_mask_large_mesh = match ((seconds_since_startup * 0.5) as u64) % 5 { 0 => OutlineMaskPreference::NONE, diff --git a/crates/re_renderer/examples/picking.rs b/crates/re_renderer/examples/picking.rs index ee3413dae42d..afcb442ffbba 100644 --- a/crates/re_renderer/examples/picking.rs +++ b/crates/re_renderer/examples/picking.rs @@ -118,33 +118,29 @@ impl framework::Example for Picking { } } - let mut view_builder = ViewBuilder::default(); - // TODO(#1426): unify camera logic between examples. let camera_position = glam::vec3(1.0, 3.5, 7.0); - view_builder - .setup_view( - re_ctx, - TargetConfiguration { - name: "OutlinesDemo".into(), - resolution_in_pixel: resolution, - view_from_world: macaw::IsoTransform::look_at_rh( - camera_position, - glam::Vec3::ZERO, - glam::Vec3::Y, - ) - .unwrap(), - projection_from_view: Projection::Perspective { - vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, - near_plane_distance: 0.01, - }, - pixels_from_point, - outline_config: None, - ..Default::default() + let mut view_builder = ViewBuilder::new( + re_ctx, + TargetConfiguration { + name: "OutlinesDemo".into(), + resolution_in_pixel: resolution, + view_from_world: macaw::IsoTransform::look_at_rh( + camera_position, + glam::Vec3::ZERO, + glam::Vec3::Y, + ) + .unwrap(), + projection_from_view: Projection::Perspective { + vertical_fov: 70.0 * std::f32::consts::TAU / 360.0, + near_plane_distance: 0.01, }, - ) - .unwrap(); + pixels_from_point, + outline_config: None, + ..Default::default() + }, + ); // Use an uneven number of pixels for the picking rect so that there is a clearly defined middle-pixel. // (for this sample a size of 1 would be sufficient, but for a real application you'd want to use a larger size to allow snapping) diff --git a/crates/re_renderer/src/view_builder.rs b/crates/re_renderer/src/view_builder.rs index 704f0a22f7be..2dc937504d80 100644 --- a/crates/re_renderer/src/view_builder.rs +++ b/crates/re_renderer/src/view_builder.rs @@ -248,13 +248,11 @@ impl ViewBuilder { }, }); - pub fn setup_view( - &mut self, - ctx: &mut RenderContext, - config: TargetConfiguration, - ) -> Result<&mut Self, ViewBuilderError> { + pub fn new(ctx: &mut RenderContext, config: TargetConfiguration) -> Self { crate::profile_function!(); + let mut slf = Self::default(); + // Can't handle 0 size resolution since this would imply creating zero sized textures. assert_ne!(config.resolution_in_pixel[0], 0); assert_ne!(config.resolution_in_pixel[1], 0); @@ -297,7 +295,7 @@ impl ViewBuilder { }, ); - self.outline_mask_processor = config.outline_config.as_ref().map(|outline_config| { + slf.outline_mask_processor = config.outline_config.as_ref().map(|outline_config| { OutlineMaskProcessor::new( ctx, outline_config, @@ -306,10 +304,10 @@ impl ViewBuilder { ) }); - self.queue_draw(&CompositorDrawData::new( + slf.queue_draw(&CompositorDrawData::new( ctx, &main_target_resolved, - self.outline_mask_processor + slf.outline_mask_processor .as_ref() .map(|p| p.final_voronoi_texture()), &config.outline_config, @@ -453,7 +451,7 @@ impl ViewBuilder { frame_uniform_buffer, ); - self.setup = Some(ViewTargetSetup { + slf.setup = Some(ViewTargetSetup { name: config.name, bind_group_0, main_target_msaa: hdr_render_target_msaa, @@ -463,7 +461,7 @@ impl ViewBuilder { frame_uniform_buffer_content, }); - Ok(self) + slf } fn draw_phase<'a>( diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index 335e225552a7..2c9e58df2122 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -150,9 +150,7 @@ pub fn render_image( outline_config: None, }; - // TODO(andreas): separate setup for viewbuilder doesn't make sense. - let mut view_builder = ViewBuilder::default(); - view_builder.setup_view(render_ctx, target_config)?; + let mut view_builder = ViewBuilder::new(render_ctx, target_config); view_builder.queue_draw(&re_renderer::renderer::RectangleDrawData::new( render_ctx, diff --git a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs index 9700725123c5..ca790381a47a 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs @@ -321,12 +321,7 @@ fn view_2d_scrollable( return response; }; - // TODO(andreas): separate setup for viewbuilder doesn't make sense. - let mut view_builder = ViewBuilder::default(); - if let Err(err) = view_builder.setup_view(ctx.render_ctx, target_config) { - re_log::error!("Failed to setup view: {}", err); - return response; - } + let mut view_builder = ViewBuilder::new(ctx.render_ctx, target_config); // Create labels now since their shapes participate are added to scene.ui for picking. let label_shapes = create_labels( diff --git a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs index 39fbc350d451..86cb34e808f4 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs @@ -340,12 +340,7 @@ pub fn view_3d( .then(|| outline_config(ui.ctx())), }; - let mut view_builder = ViewBuilder::default(); - // TODO(andreas): separate setup_view doesn't make sense, add a `new` method instead. - if let Err(err) = view_builder.setup_view(ctx.render_ctx, target_config) { - re_log::error!("Failed to setup view: {}", err); - return; - } + let mut view_builder = ViewBuilder::new(ctx.render_ctx, target_config); // Create labels now since their shapes participate are added to scene.ui for picking. let label_shapes = create_labels( From 5c0fc569586a20900b389bf1666d24d65226f960 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 09:37:09 +0200 Subject: [PATCH 15/27] Fix missing colon in lint.py --- scripts/lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/lint.py b/scripts/lint.py index 594748c94056..375e156ed263 100755 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -270,7 +270,7 @@ def lint_file(filepath: str, args: Any) -> int: errors, lines_out = lint_vertical_spacing(lines_in) for error in errors: - print(f"{filepath}{error}") + print(f"{filepath}:{error}") if args.fix and lines_in != lines_out: with open(filepath, "w") as f: From cbd277c90eda38aab810596ce2b8bf3a19c8bac1 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 09:37:17 +0200 Subject: [PATCH 16/27] fix typos and formatting --- crates/re_viewer/src/gpu_bridge/mod.rs | 1 + crates/re_viewer/src/ui/view_spatial/mod.rs | 2 +- crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index 2c9e58df2122..45e2f3b687f5 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -43,6 +43,7 @@ pub fn renderer_paint_callback( crate::profile_function!(); slotmap::new_key_type! { pub struct ViewBuilderHandle; } + type ViewBuilderMap = slotmap::SlotMap; // egui paint callback are copyable / not a FnOnce (this in turn is because egui primitives can be callbacks and are copyable) diff --git a/crates/re_viewer/src/ui/view_spatial/mod.rs b/crates/re_viewer/src/ui/view_spatial/mod.rs index 1d805ce3ab41..240c83ae2023 100644 --- a/crates/re_viewer/src/ui/view_spatial/mod.rs +++ b/crates/re_viewer/src/ui/view_spatial/mod.rs @@ -5,7 +5,7 @@ mod space_camera_3d; mod ui; mod ui_2d; mod ui_3d; -pub mod ui_renderer_bridge; // TODO: move reusable parts into its own module +pub mod ui_renderer_bridge; pub use self::scene::{Image, MeshSource, MeshSourceData, SceneSpatial, UiLabel, UiLabelTarget}; pub use self::space_camera_3d::SpaceCamera3D; diff --git a/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs index 7cfa544034f8..c0227aa5c0ba 100644 --- a/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs @@ -23,7 +23,7 @@ pub enum TensorUploadError { #[error("Missing a range.")] MissingRange, - #[error("Non-finite range of vlaues")] + #[error("Non-finite range of values")] NonfiniteRange, } From 08e753c4552a2ae88d99fd71f012383bbdf7d81e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 09:44:14 +0200 Subject: [PATCH 17/27] Disable texture filtering options for tensors for now --- crates/re_viewer/src/ui/view_tensor/ui.rs | 43 +++++++++++++---------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index e99bb0483c29..57e87db8f663 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -349,27 +349,32 @@ impl TextureSettings { }); ui.end_row(); - re_ui.grid_left_hand_label(ui, "Filtering"); - - fn tf_to_string(tf: egui::TextureFilter) -> &'static str { - match tf { - egui::TextureFilter::Nearest => "Nearest", - egui::TextureFilter::Linear => "Linear", + // TODO(#1612): support texture filtering again + if false { + re_ui + .grid_left_hand_label(ui, "Filtering") + .on_hover_text("Filtering to use when magnifying"); + + fn tf_to_string(tf: egui::TextureFilter) -> &'static str { + match tf { + egui::TextureFilter::Nearest => "Nearest", + egui::TextureFilter::Linear => "Linear", + } } - } - egui::ComboBox::from_id_source("texture_filter") - .selected_text(tf_to_string(options.magnification)) - .show_ui(ui, |ui| { - ui.style_mut().wrap = Some(false); - ui.set_min_width(64.0); + egui::ComboBox::from_id_source("texture_filter") + .selected_text(tf_to_string(options.magnification)) + .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.set_min_width(64.0); - let mut selectable_value = |ui: &mut egui::Ui, e| { - ui.selectable_value(&mut options.magnification, e, tf_to_string(e)) - }; - selectable_value(ui, egui::TextureFilter::Linear); - selectable_value(ui, egui::TextureFilter::Nearest); - }); - ui.end_row(); + let mut selectable_value = |ui: &mut egui::Ui, e| { + ui.selectable_value(&mut options.magnification, e, tf_to_string(e)) + }; + selectable_value(ui, egui::TextureFilter::Linear); + selectable_value(ui, egui::TextureFilter::Nearest); + }); + ui.end_row(); + } } } From f5107388a95526287ee9d26e07152b228e260374 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 09:55:56 +0200 Subject: [PATCH 18/27] Update docstrings --- crates/re_renderer/src/view_builder.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/re_renderer/src/view_builder.rs b/crates/re_renderer/src/view_builder.rs index 2dc937504d80..2ebcd4a3b49e 100644 --- a/crates/re_renderer/src/view_builder.rs +++ b/crates/re_renderer/src/view_builder.rs @@ -32,8 +32,8 @@ struct QueuedDraw { #[derive(thiserror::Error, Debug)] pub enum ViewBuilderError { - #[error("ViewBuilder::setup_view needs to be called first.")] - ViewNotSetup, + #[error("ViewBuilder::new needs to be called first.")] + ViewNotSetup, // TODO(emilk): remove #[error("Screenshot was already scheduled.")] ScreenshotAlreadyScheduled, @@ -46,7 +46,7 @@ pub enum ViewBuilderError { /// Used to build up/collect various resources and then send them off for rendering of a single view. #[derive(Default)] pub struct ViewBuilder { - /// Result of [`ViewBuilder::setup_view`] - needs to be `Option` sine some of the fields don't have a default. + /// Result of [`ViewBuilder::new`] - needs to be `Option` since some of the fields don't have a default. setup: Option, queued_draws: Vec, @@ -519,7 +519,7 @@ impl ViewBuilder { let setup = self .setup .as_ref() - .context("ViewBuilder::setup_view wasn't called yet")?; + .context("ViewBuilder::new wasn't called yet")?; let mut encoder = ctx .device @@ -608,7 +608,7 @@ impl ViewBuilder { /// Schedules the taking of a screenshot. /// - /// Needs to be called after [`ViewBuilder::setup_view`] and before [`ViewBuilder::draw`]. + /// Needs to be called after [`ViewBuilder::new`] and before [`ViewBuilder::draw`]. /// Can only be called once per frame per [`ViewBuilder`]. /// /// Data from the screenshot needs to be retrieved via [`crate::ScreenshotProcessor::next_readback_result`]. @@ -653,7 +653,7 @@ impl ViewBuilder { /// Schedules the readback of a rectangle from the picking layer. /// - /// Needs to be called after [`ViewBuilder::setup_view`] and before [`ViewBuilder::draw`]. + /// Needs to be called after [`ViewBuilder::new`] and before [`ViewBuilder::draw`]. /// Can only be called once per frame per [`ViewBuilder`]. /// /// The result will still be valid if the rectangle is partially or fully outside of bounds. From a555bb1df8585d835eaa9ebc94d5f672e02671d3 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 10:04:41 +0200 Subject: [PATCH 19/27] Add profile scopes --- .../src/ui/view_tensor/tensor_slice_to_gpu.rs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs index c0227aa5c0ba..01ef9cda4c3f 100644 --- a/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs @@ -33,6 +33,8 @@ pub fn colormapped_texture( tensor_stats: &TensorStats, state: &ViewTensorState, ) -> Result { + crate::profile_function!(); + let (min, max) = range(tensor_stats)?; let texture = upload_texture_slice_to_gpu(render_ctx, tensor, state.slice())?; @@ -78,6 +80,8 @@ fn texture_desc_from_tensor( slice_selection: &SliceSelection, ) -> Result, TensorUploadError> { use wgpu::TextureFormat; + crate::profile_function!(); + match tensor.dtype() { TensorDataType::U8 => { let tensor = ndarray::ArrayViewD::::try_from(tensor)?; @@ -150,6 +154,8 @@ fn to_texture_desc( format: wgpu::TextureFormat, caster: impl Fn(From) -> To, ) -> Result, TensorUploadError> { + crate::profile_function!(); + use ndarray::Dimension as _; let slice = selected_tensor_slice(slice_selection, tensor); @@ -161,12 +167,17 @@ fn to_texture_desc( let mut pixels: Vec = vec![To::zeroed(); height * width]; let pixels_view = ndarray::ArrayViewMut2::from_shape(slice.raw_dim(), pixels.as_mut_slice()) .expect("Mismatched length."); - ndarray::Zip::from(pixels_view) - .and(slice) - .for_each(|pixel: &mut To, value: &From| { - *pixel = caster(*value); - }); + { + crate::profile_scope!("copy_from_slice"); + ndarray::Zip::from(pixels_view) + .and(slice) + .for_each(|pixel: &mut To, value: &From| { + *pixel = caster(*value); + }); + } + + crate::profile_scope!("pod_collect_to_vec"); Ok(Texture2DCreationDesc { label: "tensor_slice".into(), data: bytemuck::pod_collect_to_vec(&pixels).into(), From b025d08dbfd22d34794ef334f958ad19479aa6e0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 10:07:16 +0200 Subject: [PATCH 20/27] ViewBuilder cleanup --- crates/re_renderer/src/view_builder.rs | 87 ++++++++++++++------------ 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/crates/re_renderer/src/view_builder.rs b/crates/re_renderer/src/view_builder.rs index 2ebcd4a3b49e..844cf2125b80 100644 --- a/crates/re_renderer/src/view_builder.rs +++ b/crates/re_renderer/src/view_builder.rs @@ -43,8 +43,7 @@ pub enum ViewBuilderError { } /// The highest level rendering block in `re_renderer`. -/// Used to build up/collect various resources and then send them off for rendering of a single view. -#[derive(Default)] +/// Used to build up/collect various resources and then send them off for rendering of a single view. pub struct ViewBuilder { /// Result of [`ViewBuilder::new`] - needs to be `Option` since some of the fields don't have a default. setup: Option, @@ -251,8 +250,6 @@ impl ViewBuilder { pub fn new(ctx: &mut RenderContext, config: TargetConfiguration) -> Self { crate::profile_function!(); - let mut slf = Self::default(); - // Can't handle 0 size resolution since this would imply creating zero sized textures. assert_ne!(config.resolution_in_pixel[0], 0); assert_ne!(config.resolution_in_pixel[1], 0); @@ -295,24 +292,6 @@ impl ViewBuilder { }, ); - slf.outline_mask_processor = config.outline_config.as_ref().map(|outline_config| { - OutlineMaskProcessor::new( - ctx, - outline_config, - &config.name, - config.resolution_in_pixel, - ) - }); - - slf.queue_draw(&CompositorDrawData::new( - ctx, - &main_target_resolved, - slf.outline_mask_processor - .as_ref() - .map(|p| p.final_voronoi_texture()), - &config.outline_config, - )); - let aspect_ratio = config.resolution_in_pixel[0] as f32 / config.resolution_in_pixel[1] as f32; @@ -451,7 +430,25 @@ impl ViewBuilder { frame_uniform_buffer, ); - slf.setup = Some(ViewTargetSetup { + let outline_mask_processor = config.outline_config.as_ref().map(|outline_config| { + OutlineMaskProcessor::new( + ctx, + outline_config, + &config.name, + config.resolution_in_pixel, + ) + }); + + let first_draw = queued_draw(&CompositorDrawData::new( + ctx, + &main_target_resolved, + outline_mask_processor + .as_ref() + .map(|p| p.final_voronoi_texture()), + &config.outline_config, + )); + + let setup = ViewTargetSetup { name: config.name, bind_group_0, main_target_msaa: hdr_render_target_msaa, @@ -459,9 +456,15 @@ impl ViewBuilder { depth_buffer, resolution_in_pixel: config.resolution_in_pixel, frame_uniform_buffer_content, - }); + }; - slf + Self { + setup: Some(setup), + queued_draws: vec![first_draw], + outline_mask_processor, + screenshot_processor: Default::default(), + picking_processor: Default::default(), + } } fn draw_phase<'a>( @@ -489,21 +492,7 @@ impl ViewBuilder { draw_data: &D, ) -> &mut Self { crate::profile_function!(); - self.queued_draws.push(QueuedDraw { - draw_func: Box::new(move |ctx, phase, pass, draw_data| { - let renderers = ctx.renderers.read(); - let renderer = renderers - .get::() - .context("failed to retrieve renderer")?; - let draw_data = draw_data - .downcast_ref::() - .expect("passed wrong type of draw data"); - renderer.draw(&ctx.gpu_resources, phase, pass, draw_data) - }), - draw_data: Box::new(draw_data.clone()), - renderer_name: std::any::type_name::(), - participated_phases: D::Renderer::participated_phases(), - }); + self.queued_draws.push(queued_draw(draw_data)); self } @@ -750,3 +739,21 @@ impl ViewBuilder { Ok(()) } } + +fn queued_draw(draw_data: &D) -> QueuedDraw { + QueuedDraw { + draw_func: Box::new(move |ctx, phase, pass, draw_data| { + let renderers = ctx.renderers.read(); + let renderer = renderers + .get::() + .context("failed to retrieve renderer")?; + let draw_data = draw_data + .downcast_ref::() + .expect("passed wrong type of draw data"); + renderer.draw(&ctx.gpu_resources, phase, pass, draw_data) + }), + draw_data: Box::new(draw_data.clone()), + renderer_name: std::any::type_name::(), + participated_phases: D::Renderer::participated_phases(), + } +} From ce62b989c0ed7b42d603fd6bb8d5a6e6592143d2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 10:09:13 +0200 Subject: [PATCH 21/27] Make ViewBuilder::setup non-Option --- crates/re_renderer/src/view_builder.rs | 36 +++++++++----------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/crates/re_renderer/src/view_builder.rs b/crates/re_renderer/src/view_builder.rs index 844cf2125b80..15ff95a1448e 100644 --- a/crates/re_renderer/src/view_builder.rs +++ b/crates/re_renderer/src/view_builder.rs @@ -32,9 +32,6 @@ struct QueuedDraw { #[derive(thiserror::Error, Debug)] pub enum ViewBuilderError { - #[error("ViewBuilder::new needs to be called first.")] - ViewNotSetup, // TODO(emilk): remove - #[error("Screenshot was already scheduled.")] ScreenshotAlreadyScheduled, @@ -45,8 +42,7 @@ pub enum ViewBuilderError { /// The highest level rendering block in `re_renderer`. /// Used to build up/collect various resources and then send them off for rendering of a single view. pub struct ViewBuilder { - /// Result of [`ViewBuilder::new`] - needs to be `Option` since some of the fields don't have a default. - setup: Option, + setup: ViewTargetSetup, queued_draws: Vec, // TODO(andreas): Consider making "render processors" a "thing" by establishing a form of hardcoded/limited-flexibility render-graph @@ -459,7 +455,7 @@ impl ViewBuilder { }; Self { - setup: Some(setup), + setup, queued_draws: vec![first_draw], outline_mask_processor, screenshot_processor: Default::default(), @@ -505,10 +501,7 @@ impl ViewBuilder { ) -> anyhow::Result { crate::profile_function!(); - let setup = self - .setup - .as_ref() - .context("ViewBuilder::new wasn't called yet")?; + let setup = &self.setup; let mut encoder = ctx .device @@ -627,12 +620,10 @@ impl ViewBuilder { return Err(ViewBuilderError::ScreenshotAlreadyScheduled); }; - let setup = self.setup.as_ref().ok_or(ViewBuilderError::ViewNotSetup)?; - self.screenshot_processor = Some(ScreenshotProcessor::new( ctx, - &setup.name, - setup.resolution_in_pixel.into(), + &self.setup.name, + self.setup.resolution_in_pixel.into(), identifier, user_data, )); @@ -684,14 +675,12 @@ impl ViewBuilder { return Err(ViewBuilderError::PickingRectAlreadyScheduled); }; - let setup = self.setup.as_ref().ok_or(ViewBuilderError::ViewNotSetup)?; - let picking_processor = PickingLayerProcessor::new( ctx, - &setup.name, - setup.resolution_in_pixel.into(), + &self.setup.name, + self.setup.resolution_in_pixel.into(), picking_rect, - &setup.frame_uniform_buffer_content, + &self.setup.frame_uniform_buffer_content, show_debug_view, readback_identifier, readback_user_data, @@ -701,7 +690,7 @@ impl ViewBuilder { self.queue_draw(&DebugOverlayDrawData::new( ctx, &picking_processor.picking_target, - setup.resolution_in_pixel.into(), + self.setup.resolution_in_pixel.into(), picking_rect, )); } @@ -723,17 +712,16 @@ impl ViewBuilder { ) -> Result<(), ViewBuilderError> { crate::profile_function!(); - let setup = self.setup.as_ref().ok_or(ViewBuilderError::ViewNotSetup)?; pass.set_viewport( screen_position.x, screen_position.y, - setup.resolution_in_pixel[0] as f32, - setup.resolution_in_pixel[1] as f32, + self.setup.resolution_in_pixel[0] as f32, + self.setup.resolution_in_pixel[1] as f32, 0.0, 1.0, ); - pass.set_bind_group(0, &setup.bind_group_0, &[]); + pass.set_bind_group(0, &self.setup.bind_group_0, &[]); self.draw_phase(ctx, DrawPhase::Compositing, pass); Ok(()) From 2e01e7c0065389b95c4b6f46fffa5995d9f8236b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 10:13:57 +0200 Subject: [PATCH 22/27] Remove Result from thing that cannot fail --- crates/re_renderer/examples/framework.rs | 13 +++++-------- crates/re_renderer/src/view_builder.rs | 4 +--- crates/re_viewer/src/gpu_bridge/mod.rs | 3 +-- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/re_renderer/examples/framework.rs b/crates/re_renderer/examples/framework.rs index 47e0b42a3212..fef6e7544b5b 100644 --- a/crates/re_renderer/examples/framework.rs +++ b/crates/re_renderer/examples/framework.rs @@ -288,14 +288,11 @@ impl Application { }); for draw_result in &draw_results { - draw_result - .view_builder - .composite( - &self.re_ctx, - &mut composite_pass, - draw_result.target_location, - ) - .expect("Failed to composite view main surface"); + draw_result.view_builder.composite( + &self.re_ctx, + &mut composite_pass, + draw_result.target_location, + ); } }; diff --git a/crates/re_renderer/src/view_builder.rs b/crates/re_renderer/src/view_builder.rs index 15ff95a1448e..f9ad2ebf18cc 100644 --- a/crates/re_renderer/src/view_builder.rs +++ b/crates/re_renderer/src/view_builder.rs @@ -709,7 +709,7 @@ impl ViewBuilder { ctx: &'a RenderContext, pass: &mut wgpu::RenderPass<'a>, screen_position: glam::Vec2, - ) -> Result<(), ViewBuilderError> { + ) { crate::profile_function!(); pass.set_viewport( @@ -723,8 +723,6 @@ impl ViewBuilder { pass.set_bind_group(0, &self.setup.bind_group_0, &[]); self.draw_phase(ctx, DrawPhase::Compositing, pass); - - Ok(()) } } diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index 45e2f3b687f5..b59b2490c832 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -81,8 +81,7 @@ pub fn renderer_paint_callback( .per_frame_data_helper .get::() .unwrap()[view_builder_handle] - .composite(ctx, render_pass, screen_position) - .expect("Failed compositing view builder with main target."); + .composite(ctx, render_pass, screen_position); }), ), } From b204464f122453c51021312db5719c08699df41a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 11:09:06 +0200 Subject: [PATCH 23/27] Fix colormap numbering --- crates/re_renderer/src/colormap.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/re_renderer/src/colormap.rs b/crates/re_renderer/src/colormap.rs index d57609bd43e8..15cd98d5dc14 100644 --- a/crates/re_renderer/src/colormap.rs +++ b/crates/re_renderer/src/colormap.rs @@ -13,11 +13,11 @@ pub enum Colormap { /// Perceptually even #[default] Grayscale = 1, - Inferno = 6, - Magma = 5, + Inferno = 2, + Magma = 3, Plasma = 4, - Turbo = 2, - Viridis = 3, + Turbo = 5, + Viridis = 6, } impl Colormap { From 485e138e70471c03885bd3a08605cd6342a35aa8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 11:14:32 +0200 Subject: [PATCH 24/27] review cleanup --- crates/re_renderer/src/view_builder.rs | 8 ++++---- crates/re_viewer/src/ui/view_tensor/ui.rs | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/re_renderer/src/view_builder.rs b/crates/re_renderer/src/view_builder.rs index f9ad2ebf18cc..af4edc5287f7 100644 --- a/crates/re_renderer/src/view_builder.rs +++ b/crates/re_renderer/src/view_builder.rs @@ -435,7 +435,7 @@ impl ViewBuilder { ) }); - let first_draw = queued_draw(&CompositorDrawData::new( + let composition_draw = queued_draw(&CompositorDrawData::new( ctx, &main_target_resolved, outline_mask_processor @@ -456,7 +456,7 @@ impl ViewBuilder { Self { setup, - queued_draws: vec![first_draw], + queued_draws: vec![composition_draw], outline_mask_processor, screenshot_processor: Default::default(), picking_processor: Default::default(), @@ -590,7 +590,7 @@ impl ViewBuilder { /// Schedules the taking of a screenshot. /// - /// Needs to be called after [`ViewBuilder::new`] and before [`ViewBuilder::draw`]. + /// Needs to be called before [`ViewBuilder::draw`]. /// Can only be called once per frame per [`ViewBuilder`]. /// /// Data from the screenshot needs to be retrieved via [`crate::ScreenshotProcessor::next_readback_result`]. @@ -633,7 +633,7 @@ impl ViewBuilder { /// Schedules the readback of a rectangle from the picking layer. /// - /// Needs to be called after [`ViewBuilder::new`] and before [`ViewBuilder::draw`]. + /// Needs to be called before [`ViewBuilder::draw`]. /// Can only be called once per frame per [`ViewBuilder`]. /// /// The result will still be valid if the rectangle is partially or fully outside of bounds. diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index 57e87db8f663..256de56ce8eb 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -133,8 +133,6 @@ pub(crate) fn view_tensor( }); } - // tensor_ui(ctx, ui, state, tensor); - let dimension_labels = { let dm = &state.slice.dim_mapping; [ @@ -150,13 +148,13 @@ pub(crate) fn view_tensor( }; egui::ScrollArea::both().show(ui, |ui| { - if let Err(err) = fun_name(ctx, ui, state, tensor, dimension_labels) { + if let Err(err) = tensor_slice_ui(ctx, ui, state, tensor, dimension_labels) { ui.label(ctx.re_ui.error_text(err.to_string())); } }); } -fn fun_name( +fn tensor_slice_ui( ctx: &mut crate::misc::ViewerContext<'_>, ui: &mut egui::Ui, state: &mut ViewTensorState, From 5db63ab81dd60281ab296aed54b4a47865ba72ef Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 11:16:13 +0200 Subject: [PATCH 25/27] pass in debug_name --- crates/re_viewer/src/gpu_bridge/mod.rs | 3 ++- crates/re_viewer/src/ui/view_tensor/ui.rs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index b59b2490c832..090f628102b1 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -94,6 +94,7 @@ pub fn render_image( image_rect_on_screen: egui::Rect, colormapped_texture: ColormappedTexture, texture_options: egui::TextureOptions, + debug_name: &str, ) -> anyhow::Result<()> { crate::profile_function!(); @@ -137,7 +138,7 @@ pub fn render_image( let top_left_position = glam::vec2(camera_position_space.x, camera_position_space.y); let target_config = re_renderer::view_builder::TargetConfiguration { - name: "tensor_view".into(), + name: debug_name.into(), resolution_in_pixel, view_from_world: macaw::IsoTransform::from_translation(-top_left_position.extend(0.0)), projection_from_view: re_renderer::view_builder::Projection::Orthographic { diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index 256de56ce8eb..f9adcaad42c2 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -213,12 +213,14 @@ fn paint_tensor_slice( let rect = response.rect; let image_rect = egui::Rect::from_min_max(rect.min, rect.max); + let debug_name = "tensor_slice"; crate::gpu_bridge::render_image( ctx.render_ctx, &painter, image_rect, colormapped_texture, state.texture_settings.options, + debug_name, )?; Ok((response, painter, image_rect)) From 031a09b00aea64f0edcda2524fdb42cac72eb26a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 11:21:59 +0200 Subject: [PATCH 26/27] Unify the `range` function --- crates/re_viewer/src/gpu_bridge/mod.rs | 32 +++++++++++++++++++ .../re_viewer/src/gpu_bridge/tensor_to_gpu.rs | 7 +--- .../src/ui/view_tensor/tensor_slice_to_gpu.rs | 30 +++++------------ 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index 090f628102b1..f81ab8adb435 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -13,6 +13,38 @@ use re_renderer::{ RenderContext, ViewBuilder, }; +// ---------------------------------------------------------------------------- + +/// Errrors that can happen when supplying a tensor range to the GPU. +#[derive(thiserror::Error, Debug, PartialEq, Eq)] +pub enum RangeError { + /// This is weird. Should only happen with JPEGs, and those should have been decoded already + #[error("Missing a range.")] + MissingRange, + + #[error("Non-finite range of values")] + NonfiniteRange, +} + +/// Get a valid, finite range for the gpu to use. +pub fn range(tensor_stats: &crate::misc::caches::TensorStats) -> Result<[f32; 2], RangeError> { + let (min, max) = tensor_stats.range.ok_or(RangeError::MissingRange)?; + + let min = min as f32; + let max = max as f32; + + if !min.is_finite() || !max.is_finite() { + Err(RangeError::NonfiniteRange) + } else if min == max { + // uniform range. This can explode the colormapping, so let's map all colors to the middle: + Ok([min - 1.0, max + 1.0]) + } else { + Ok([min, max]) + } +} + +// ---------------------------------------------------------------------------- + pub fn viewport_resolution_in_pixels(clip_rect: egui::Rect, pixels_from_point: f32) -> [u32; 2] { let min = (clip_rect.min.to_vec2() * pixels_from_point).round(); let max = (clip_rect.max.to_vec2() * pixels_from_point).round(); diff --git a/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs b/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs index ad8713a829f7..f907b5b62fe9 100644 --- a/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs +++ b/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs @@ -108,12 +108,7 @@ fn color_tensor_to_gpu( } else if texture_format == TextureFormat::R8Snorm { [-1.0, 1.0] } else { - // For instance: 16-bit images. - // TODO(emilk): consider assuming [0-1] range for all float tensors. - let (min, max) = tensor_stats - .range - .ok_or_else(|| anyhow::anyhow!("missing tensor range. compressed?"))?; - [min as f32, max as f32] + crate::gpu_bridge::range(tensor_stats)? }; let color_mapper = if texture_format.describe().components == 1 { diff --git a/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs index 01ef9cda4c3f..0bb4d9f9d81c 100644 --- a/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs @@ -4,7 +4,10 @@ use re_log_types::{ }; use re_renderer::{renderer::ColormappedTexture, resource_managers::Texture2DCreationDesc}; -use crate::misc::caches::TensorStats; +use crate::{ + gpu_bridge::{range, RangeError}, + misc::caches::TensorStats, +}; use super::{ ui::{selected_tensor_slice, SliceSelection}, @@ -19,12 +22,8 @@ pub enum TensorUploadError { #[error("Expected a 2D slice")] Not2D, - /// This is weird. Should only happen with JPEGs, and those should have been decoded already - #[error("Missing a range.")] - MissingRange, - - #[error("Non-finite range of values")] - NonfiniteRange, + #[error(transparent)] + RangeError(#[from] RangeError), } pub fn colormapped_texture( @@ -35,14 +34,14 @@ pub fn colormapped_texture( ) -> Result { crate::profile_function!(); - let (min, max) = range(tensor_stats)?; + let range = range(tensor_stats)?; let texture = upload_texture_slice_to_gpu(render_ctx, tensor, state.slice())?; let color_mapping = state.color_mapping(); Ok(ColormappedTexture { texture, - range: [min as f32, max as f32], + range, gamma: color_mapping.gamma, color_mapper: Some(re_renderer::renderer::ColorMapper::Function( color_mapping.map, @@ -50,19 +49,6 @@ pub fn colormapped_texture( }) } -fn range(tensor_stats: &TensorStats) -> Result<(f64, f64), TensorUploadError> { - let (min, max) = tensor_stats.range.ok_or(TensorUploadError::MissingRange)?; - - if !min.is_finite() || !max.is_finite() { - Err(TensorUploadError::NonfiniteRange) - } else if min == max { - // uniform range. This can explode the colormapping, so let's map all colors to the middle: - Ok((min - 1.0, max + 1.0)) - } else { - Ok((min, max)) - } -} - fn upload_texture_slice_to_gpu( render_ctx: &mut re_renderer::RenderContext, tensor: &Tensor, From b1c4566075a2ee63c228e3003c8f78c8d9bac460 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 14 Apr 2023 11:22:46 +0200 Subject: [PATCH 27/27] typo --- crates/re_viewer/src/gpu_bridge/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index f81ab8adb435..0a70befc9057 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -15,7 +15,7 @@ use re_renderer::{ // ---------------------------------------------------------------------------- -/// Errrors that can happen when supplying a tensor range to the GPU. +/// Errors that can happen when supplying a tensor range to the GPU. #[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum RangeError { /// This is weird. Should only happen with JPEGs, and those should have been decoded already