Skip to content

Commit

Permalink
2D layering fixes (#2080)
Browse files Browse the repository at this point in the history
* Fix images with same render order not layering transparently

* nicer draw order sample

* every draw order given out is individual again to avoid z fighting

* spelling
  • Loading branch information
Wumpf authored May 15, 2023
1 parent 223e0bd commit a64edaa
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 65 deletions.
6 changes: 3 additions & 3 deletions crates/re_log_types/src/component_types/tensor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1359,7 +1359,7 @@ fn test_tensor_shape_utilities() {
assert!(tensor.is_vector());
assert!(tensor.is_shaped_like_an_image());
}
// Color/Grey 2x4 images
// Color/Gray 2x4 images
for shape in [
vec![4, 2],
vec![4, 2, 1],
Expand All @@ -1381,7 +1381,7 @@ fn test_tensor_shape_utilities() {
assert!(tensor.is_shaped_like_an_image());
}

// gray 1x4 images
// Gray 1x4 images
for shape in [
vec![4, 1],
vec![4, 1, 1],
Expand All @@ -1396,7 +1396,7 @@ fn test_tensor_shape_utilities() {
assert!(tensor.is_shaped_like_an_image());
}

// gray 4x1 images
// Gray 4x1 images
for shape in [
vec![1, 4],
vec![1, 4, 1],
Expand Down
4 changes: 3 additions & 1 deletion crates/re_renderer/src/view_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,9 @@ impl ViewBuilder {
pub const MAIN_TARGET_DEFAULT_DEPTH_STATE: Option<wgpu::DepthStencilState> =
Some(wgpu::DepthStencilState {
format: Self::MAIN_TARGET_DEPTH_FORMAT,
depth_compare: wgpu::CompareFunction::Greater,
// It's important to set the depth test to GreaterEqual, not to Greater.
// This way, we ensure that objects that are drawn later with the exact same depth value, can overwrite earlier ones!
depth_compare: wgpu::CompareFunction::GreaterEqual,
depth_write_enabled: true,
stencil: wgpu::StencilState {
front: wgpu::StencilFaceState::IGNORE,
Expand Down
92 changes: 53 additions & 39 deletions crates/re_viewer/src/ui/view_spatial/scene/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use re_log_types::{
};
use re_renderer::{renderer::TexturedRect, Color32, OutlineMaskPreference, Size};
use re_viewer_context::{auto_color, AnnotationMap, Annotations, SceneQuery, ViewerContext};
use smallvec::smallvec;
use smallvec::SmallVec;

use crate::misc::{mesh_loader::LoadedMesh, SpaceViewHighlights, TransformCache};
Expand Down Expand Up @@ -41,6 +42,9 @@ pub struct Image {

/// Textured rectangle for the renderer.
pub textured_rect: TexturedRect,

/// Draw order value used.
pub draw_order: DrawOrder,
}

pub enum UiLabelTarget {
Expand Down Expand Up @@ -149,54 +153,64 @@ impl SceneSpatial {
}

// Push in default draw orders. All of them using the none hash.
entities_per_draw_order
.entry(DrawOrder::DEFAULT_BOX2D)
.or_default()
.push(DrawOrderTarget::DefaultBox2D);
entities_per_draw_order
.entry(DrawOrder::DEFAULT_IMAGE)
.or_default()
.push(DrawOrderTarget::DefaultImage);
entities_per_draw_order
.entry(DrawOrder::DEFAULT_LINES2D)
.or_default()
.push(DrawOrderTarget::DefaultLines2D);
entities_per_draw_order
.entry(DrawOrder::DEFAULT_POINTS2D)
.or_default()
.push(DrawOrderTarget::DefaultPoints);
entities_per_draw_order.insert(
DrawOrder::DEFAULT_BOX2D,
smallvec![DrawOrderTarget::DefaultBox2D],
);
entities_per_draw_order.insert(
DrawOrder::DEFAULT_IMAGE,
smallvec![DrawOrderTarget::DefaultImage],
);
entities_per_draw_order.insert(
DrawOrder::DEFAULT_LINES2D,
smallvec![DrawOrderTarget::DefaultLines2D],
);
entities_per_draw_order.insert(
DrawOrder::DEFAULT_POINTS2D,
smallvec![DrawOrderTarget::DefaultPoints],
);

// Determine re_renderer draw order from this.
//
// We give objects with the same `DrawOrder` still a different depth offset
// in order to avoid z-fighting artifacts when rendering in 3D.
// (for pure 2D this isn't necessary)
//
// We want to be as tightly around 0 as possible.
let mut offsets = EntityDepthOffsets::default();
let mut draw_order = -((entities_per_draw_order.len() / 2) as re_renderer::DepthOffset);
let num_entities_with_draw_order: usize = entities_per_draw_order
.values()
.map(|entities| entities.len())
.sum();
let mut draw_order = -((num_entities_with_draw_order / 2) as re_renderer::DepthOffset);
offsets.per_entity = entities_per_draw_order
.into_values()
.flat_map(move |targets| {
let pairs = targets
.flat_map(|targets| {
targets
.into_iter()
.filter_map(|target| match target {
DrawOrderTarget::Entity(entity) => Some((entity, draw_order)),
DrawOrderTarget::DefaultBox2D => {
offsets.box2d = draw_order;
None
}
DrawOrderTarget::DefaultLines2D => {
offsets.lines2d = draw_order;
None
}
DrawOrderTarget::DefaultImage => {
offsets.image = draw_order;
None
}
DrawOrderTarget::DefaultPoints => {
offsets.points = draw_order;
None
.filter_map(|target| {
draw_order += 1;
match target {
DrawOrderTarget::Entity(entity) => Some((entity, draw_order)),
DrawOrderTarget::DefaultBox2D => {
offsets.box2d = draw_order;
None
}
DrawOrderTarget::DefaultLines2D => {
offsets.lines2d = draw_order;
None
}
DrawOrderTarget::DefaultImage => {
offsets.image = draw_order;
None
}
DrawOrderTarget::DefaultPoints => {
offsets.points = draw_order;
None
}
}
})
.collect::<SmallVec<[_; 4]>>();
draw_order += 1;
pairs
.collect::<SmallVec<[_; 4]>>()
})
.collect();

Expand Down
42 changes: 25 additions & 17 deletions crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use itertools::Itertools;
use re_data_store::{query_latest_single, EntityPath, EntityProperties};
use re_log_types::{
component_types::{ColorRGBA, InstanceKey, Tensor, TensorData, TensorDataMeaning},
Component, DecodedTensor, Transform,
Component, DecodedTensor, DrawOrder, Transform,
};
use re_query::{query_primary_with_history, EntityView, QueryError};
use re_renderer::{
Expand Down Expand Up @@ -113,21 +113,21 @@ fn handle_image_layering(scene: &mut SceneSpatial) {
);

fn is_plane_similar(a: macaw::Plane3, b: macaw::Plane3) -> bool {
a.normal.dot(b.normal) >= 0.99 && (a.d - b.d) >= 0.01
a.normal.dot(b.normal) > 0.99 && (a.d - b.d).abs() < 0.01
}
let has_same_depth_offset = rectangle_group.last().map_or(true, |last_image| {
last_image.textured_rect.options.depth_offset
== image.textured_rect.options.depth_offset
});

// Are the image planes too unsimilar? Then this is a new group.
if !rectangle_group.is_empty()
&& (!is_plane_similar(prev_plane, cur_plane) || !has_same_depth_offset)
{

// Use draw order, not depth offset since depth offset might change when draw order does not.
let has_same_draw_order = rectangle_group
.last()
.map_or(true, |last_image| last_image.draw_order == image.draw_order);

// If the planes are similar, add them to the same group, otherwise start a new group.
if has_same_draw_order && is_plane_similar(prev_plane, cur_plane) {
rectangle_group.push(image);
} else {
let previous_group = std::mem::replace(&mut rectangle_group, vec![image]);
return Some(previous_group);
}
rectangle_group.push(image);
}
if !rectangle_group.is_empty() {
Some(rectangle_group.drain(..).collect())
Expand All @@ -150,8 +150,9 @@ fn handle_image_layering(scene: &mut SceneSpatial) {
let opacity = if idx == 0 {
1.0
} else {
// avoid precision problems in framebuffer
1.0 / total_num_images.at_most(20) as f32
}; // avoid precision problems in framebuffer
};
image.textured_rect.options.multiplicative_tint = image
.textured_rect
.options
Expand Down Expand Up @@ -181,9 +182,10 @@ impl ImagesPart {
crate::profile_function!();

// Instance ids of tensors refer to entries inside the tensor.
for (tensor, color) in itertools::izip!(
for (tensor, color, draw_order) in itertools::izip!(
entity_view.iter_primary()?,
entity_view.iter_component::<ColorRGBA>()?
entity_view.iter_component::<ColorRGBA>()?,
entity_view.iter_component::<DrawOrder>()?
) {
crate::profile_scope!("loop_iter");
let Some(tensor) = tensor else { continue; };
Expand Down Expand Up @@ -251,6 +253,7 @@ impl ImagesPart {
ent_path: ent_path.clone(),
tensor,
textured_rect,
draw_order: draw_order.unwrap_or(DrawOrder::DEFAULT_IMAGE),
});
}
}
Expand Down Expand Up @@ -401,13 +404,18 @@ impl ScenePart for ImagesPart {
continue;
};

match query_primary_with_history::<Tensor, 3>(
match query_primary_with_history::<Tensor, 4>(
&ctx.log_db.entity_db.data_store,
&query.timeline,
&query.latest_at,
&props.visible_history,
ent_path,
[Tensor::name(), InstanceKey::name(), ColorRGBA::name()],
[
Tensor::name(),
InstanceKey::name(),
ColorRGBA::name(),
DrawOrder::name(),
],
)
.and_then(|entities| {
for entity in entities {
Expand Down
36 changes: 31 additions & 5 deletions examples/rust/api_demo/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use std::{
f32::consts::{PI, TAU},
};

use itertools::Itertools;
use rerun::{
components::{
AnnotationContext, AnnotationInfo, Box3D, ClassDescription, ClassId, ColorRGBA, DrawOrder,
Expand Down Expand Up @@ -284,23 +285,48 @@ fn demo_rects(rec_stream: &RecordingStream) -> anyhow::Result<()> {
Ok(())
}

fn colored_tensor<F: Fn(usize, usize) -> [u8; 3]>(
width: usize,
height: usize,
pos_to_color: F,
) -> ndarray::Array3<u8> {
let pos_to_color = &pos_to_color; // lambda borrow workaround.
ndarray::Array3::from_shape_vec(
(height, width, 3),
(0..height)
.flat_map(|y| (0..width).flat_map(move |x| pos_to_color(x, y)))
.collect_vec(),
)
.unwrap()
}

fn demo_2d_layering(rec_stream: &RecordingStream) -> anyhow::Result<()> {
use ndarray::prelude::*;

// Add several overlapping images
let img = Array::<u8, _>::from_elem((512, 512, 3).f(), 64);
// Add several overlapping images.
// Large dark gray in the background
let img = Array::<u8, _>::from_elem((512, 512, 1).f(), 64);
MsgSender::new("2d_layering/background")
.with_timepoint(sim_time(1.0))
.with_component(&[Tensor::try_from(img.as_standard_layout().view())?])?
.with_component(&[DrawOrder(0.0)])?
.send(rec_stream)?;
let img = Array::<u8, _>::from_elem((256, 256, 3).f(), 128);
MsgSender::new("2d_layering/middle")
// Smaller gradient in the middle
let img = colored_tensor(256, 256, |x, y| [x as u8, y as u8, 0]);
MsgSender::new("2d_layering/middle_red")
.with_timepoint(sim_time(1.0))
.with_component(&[Tensor::try_from(img.as_standard_layout().view())?])?
.with_component(&[DrawOrder(1.0)])?
.send(rec_stream)?;
// Slightly smaller red in the middle, on the same layer as the previous.
let img = colored_tensor(192, 192, |_, _| [0, 0, 255]);
MsgSender::new("2d_layering/middle_blue")
.with_timepoint(sim_time(1.0))
.with_component(&[Tensor::try_from(img.as_standard_layout().view())?])?
.with_component(&[DrawOrder(1.0)])?
.send(rec_stream)?;
let img = Array::<u8, _>::from_elem((128, 128, 3).f(), 255);
// Small white on top.
let img = Array::<u8, _>::from_elem((128, 128, 1).f(), 255);
MsgSender::new("2d_layering/top")
.with_timepoint(sim_time(1.0))
.with_component(&[Tensor::try_from(img.as_standard_layout().view())?])?
Expand Down

1 comment on commit a64edaa

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rust Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.25.

Benchmark suite Current: a64edaa Previous: 223e0bd Ratio
datastore/num_rows=1000/num_instances=1000/packed=false/latest_at/default 371 ns/iter (± 3) 280 ns/iter (± 3) 1.32
datastore/num_rows=1000/num_instances=1000/packed=false/latest_at_missing/primary/default 273 ns/iter (± 0) 204 ns/iter (± 0) 1.34
datastore/num_rows=1000/num_instances=1000/packed=false/latest_at_missing/secondaries/default 421 ns/iter (± 0) 323 ns/iter (± 0) 1.30
datastore/num_rows=1000/num_instances=1000/gc/default 2447148 ns/iter (± 4947) 1617628 ns/iter (± 5707) 1.51
mono_points_arrow_batched/generate_messages 4042736 ns/iter (± 115541) 3102065 ns/iter (± 76275) 1.30
mono_points_arrow_batched/encode_log_msg 1390577 ns/iter (± 10007) 1070327 ns/iter (± 40217) 1.30
mono_points_arrow_batched/decode_log_msg 728432 ns/iter (± 1995) 433307 ns/iter (± 1571) 1.68
arrow_mono_points/insert 2317079195 ns/iter (± 5539456) 1522629127 ns/iter (± 3990245) 1.52
arrow_mono_points/query 1232716 ns/iter (± 9226) 865135 ns/iter (± 1026) 1.42
arrow_batch_points/query 16847 ns/iter (± 73) 12920 ns/iter (± 8) 1.30
arrow_batch_vecs/query 387406 ns/iter (± 1938) 295266 ns/iter (± 796) 1.31

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.