Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement outlines for line renderer & use them for select & hover of "line-like" primitives in Viewer #1553

Merged
merged 10 commits into from
Mar 10, 2023
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ egui_dock = "0.4"
egui_extras = "0.21.0"
egui-wgpu = "0.21.0"
emath = "0.21.0"
enumset = "1.0.12"
epaint = "0.21.0"
glam = "0.22"
gltf = "1.1"
Expand Down
1 change: 1 addition & 0 deletions crates/re_renderer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ bytemuck = { version = "1.12", features = ["derive"] }
clean-path = "0.2"
document-features = "0.2"
ecolor = { workspace = true, features = ["bytemuck"] }
enumset.workspace = true
glam = { workspace = true, features = ["bytemuck"] }
half = { workspace = true, features = ["bytemuck"] }
itertools = "0.10"
Expand Down
10 changes: 5 additions & 5 deletions crates/re_renderer/examples/outlines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct Outlines {
}

struct MeshProperties {
outline_mask: OutlineMaskPreference,
outline_mask_ids: OutlineMaskPreference,
position: glam::Vec3,
rotation: glam::Quat,
}
Expand Down Expand Up @@ -89,17 +89,17 @@ impl framework::Example for Outlines {

let mesh_properties = [
MeshProperties {
outline_mask: outline_mask_large_mesh,
outline_mask_ids: outline_mask_large_mesh,
position: glam::Vec3::ZERO,
rotation: glam::Quat::IDENTITY,
},
MeshProperties {
outline_mask: OutlineMaskPreference::some(1, 0),
outline_mask_ids: OutlineMaskPreference::some(1, 0),
position: glam::vec3(2.0, 0.0, -3.0),
rotation: glam::Quat::from_rotation_y(seconds_since_startup),
},
MeshProperties {
outline_mask: OutlineMaskPreference::some(0, 1),
outline_mask_ids: OutlineMaskPreference::some(0, 1),
position: glam::vec3(-2.0, 1.0, 3.0),
rotation: glam::Quat::from_rotation_x(seconds_since_startup),
},
Expand All @@ -117,7 +117,7 @@ impl framework::Example for Outlines {
props.rotation,
props.position,
) * instance.world_from_mesh,
outline_mask: props.outline_mask,
outline_mask_ids: props.outline_mask_ids,
..Default::default()
})
})
Expand Down
6 changes: 3 additions & 3 deletions crates/re_renderer/shader/instanced_mesh.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct VertexOut {
@location(2) additive_tint_rgb: Vec3,

@location(3) @interpolate(flat)
outline_mask: UVec2,
outline_mask_ids: UVec2,
};

@vertex
Expand All @@ -41,7 +41,7 @@ fn vs_main(in_vertex: VertexIn, in_instance: InstanceIn) -> VertexOut {
out.texcoord = in_vertex.texcoord;
out.normal_world_space = world_normal;
out.additive_tint_rgb = linear_from_srgb(in_instance.additive_tint_srgb.rgb);
out.outline_mask = in_instance.outline_mask;
out.outline_mask_ids = in_instance.outline_mask_ids;

return out;
}
Expand All @@ -63,5 +63,5 @@ fn fs_main_shaded(in: VertexOut) -> @location(0) Vec4 {

@fragment
fn fs_main_outline_mask(in: VertexOut) -> @location(0) UVec2 {
return in.outline_mask;
return in.outline_mask_ids;
}
29 changes: 23 additions & 6 deletions crates/re_renderer/shader/lines.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var position_data_texture: texture_2d<u32>;

struct BatchUniformBuffer {
world_from_obj: Mat4,
outline_mask_ids: UVec2,
};
@group(2) @binding(0)
var<uniform> batch: BatchUniformBuffer;
Expand Down Expand Up @@ -222,9 +223,7 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
return out;
}

@fragment
fn fs_main(in: VertexOut) -> @location(0) Vec4 {

fn compute_coverage(in: VertexOut) -> f32{
var coverage = 1.0;
if has_any_flag(in.currently_active_flags, CAP_START_ROUND | CAP_END_ROUND) {
let distance_to_skeleton = length(in.position_world - in.closest_strip_position);
Expand All @@ -234,11 +233,17 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 {
// If we do only outwards, rectangle outlines won't line up nicely
let half_pixel_world_size = pixel_world_size * 0.5;
let signed_distance_to_border = distance_to_skeleton - in.active_radius;
if signed_distance_to_border > half_pixel_world_size {
discard;
}
coverage = 1.0 - saturate((signed_distance_to_border + half_pixel_world_size) / pixel_world_size);
}
return coverage;
}

@fragment
fn fs_main(in: VertexOut) -> @location(0) Vec4 {
var coverage = compute_coverage(in);
if coverage < 0.00001 {
discard;
}

// TODO(andreas): lighting setup
var shading = 1.0;
Expand All @@ -250,3 +255,15 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 {

return Vec4(in.color.rgb * shading, coverage);
}

@fragment
fn fs_main_outline_mask(in: VertexOut) -> @location(0) UVec2 {
// Output is an integer target, can't use coverage therefore.
// But we still want to discard fragments where coverage is low.
// Since the outline extends a bit, a very low cut off tends to look better.
var coverage = compute_coverage(in);
if coverage < 1.0 {
discard;
}
return batch.outline_mask_ids;
}
2 changes: 1 addition & 1 deletion crates/re_renderer/shader/mesh_vertex.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ struct InstanceIn {
@location(7) world_from_mesh_normal_row_1: Vec3,
@location(8) world_from_mesh_normal_row_2: Vec3,
@location(9) additive_tint_srgb: Vec4,
@location(10) outline_mask: UVec2,
@location(10) outline_mask_ids: UVec2,
};
4 changes: 4 additions & 0 deletions crates/re_renderer/src/allocator/uniform_buffer_fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ pub fn create_and_fill_uniform_buffer_batch<T: bytemuck::Pod>(
#[allow(clippy::let_unit_value)]
let _ = UniformBufferAlignmentCheck::<T>::CHECK;

if content.len() == 0 {
return vec![];
}

let num_buffers = content.len() as u64;
let element_size = std::mem::size_of::<T>() as u64;

Expand Down
87 changes: 68 additions & 19 deletions crates/re_renderer/src/line_strip_builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use std::ops::Range;

use crate::{
renderer::{LineBatchInfo, LineDrawData, LineStripFlags, LineStripInfo, LineVertex},
renderer::{
LineBatchInfo, LineDrawData, LineStripFlags, LineStripInfo, LineVertex,
OutlineMaskPreference,
},
Color32, DebugLabel, Size,
};

Expand Down Expand Up @@ -33,6 +38,8 @@ where
label: label.into(),
world_from_obj: glam::Mat4::IDENTITY,
line_vertex_count: 0,
overall_outline_mask_ids: OutlineMaskPreference::NONE,
additional_outline_mask_ids_vertex_ranges: Vec::new(),
});

LineBatchBuilder(self)
Expand Down Expand Up @@ -135,23 +142,35 @@ where
self
}

/// Sets an outline mask for every element in the batch.
#[inline]
pub fn outline_mask_ids(mut self, outline_mask_ids: OutlineMaskPreference) -> Self {
self.batch_mut().overall_outline_mask_ids = outline_mask_ids;
self
}

/// Adds a 3D series of line connected points.
pub fn add_strip(
&mut self,
points: impl Iterator<Item = glam::Vec3>,
) -> LineStripBuilder<'_, PerStripUserData> {
let old_len = self.0.strips.len();
let strip_index = old_len as _;
let old_strip_count = self.0.strips.len();
let old_vertex_count = self.0.vertices.len();
let strip_index = old_strip_count as _;

self.add_vertices(points, strip_index);
let new_vertex_count = self.0.vertices.len();

debug_assert_eq!(self.0.strips.len(), self.0.strip_user_data.len());
self.0.strips.push(LineStripInfo::default());
self.0.strip_user_data.push(PerStripUserData::default());
let new_strip_count = self.0.strips.len();

LineStripBuilder {
strips: &mut self.0.strips[old_len..],
user_data: &mut self.0.strip_user_data[old_len..],
builder: self.0,
outline_mask_ids: OutlineMaskPreference::NONE,
vertex_range: old_vertex_count..new_vertex_count,
strip_range: old_strip_count..new_strip_count,
}
}

Expand All @@ -170,18 +189,19 @@ where
&mut self,
segments: impl Iterator<Item = (glam::Vec3, glam::Vec3)>,
) -> LineStripBuilder<'_, PerStripUserData> {
let mut num_strips = self.0.strips.len() as u32;
let old_strip_count = self.0.strips.len();
let old_vertex_count = self.0.vertices.len();
let mut strip_index = old_strip_count as u32;

// It's tempting to assign the same strip to all vertices, after all they share
// color/radius/tag properties.
// However, if we don't assign different strip indices, we don't know when a strip (==segment) starts and ends.
for (a, b) in segments {
self.add_vertices([a, b].into_iter(), num_strips);
num_strips += 1;
self.add_vertices([a, b].into_iter(), strip_index);
strip_index += 1;
}

let old_len = self.0.strips.len();
let num_strips_added = num_strips as usize - old_len;
let new_vertex_count = self.0.vertices.len();
let num_strips_added = strip_index as usize - old_strip_count;

debug_assert_eq!(self.0.strips.len(), self.0.strip_user_data.len());
self.0
Expand All @@ -190,10 +210,13 @@ where
self.0
.strip_user_data
.extend(std::iter::repeat(PerStripUserData::default()).take(num_strips_added));
let new_strip_count = self.0.strips.len();

LineStripBuilder {
strips: &mut self.0.strips[old_len..],
user_data: &mut self.0.strip_user_data[old_len..],
builder: self.0,
outline_mask_ids: OutlineMaskPreference::NONE,
vertex_range: old_vertex_count..new_vertex_count,
strip_range: old_strip_count..new_strip_count,
}
}

Expand Down Expand Up @@ -345,8 +368,10 @@ where
}

pub struct LineStripBuilder<'a, PerStripUserData> {
strips: &'a mut [LineStripInfo],
user_data: &'a mut [PerStripUserData],
builder: &'a mut LineStripSeriesBuilder<PerStripUserData>,
outline_mask_ids: OutlineMaskPreference,
vertex_range: Range<usize>,
strip_range: Range<usize>,
}

impl<'a, PerStripUserData> LineStripBuilder<'a, PerStripUserData>
Expand All @@ -355,36 +380,60 @@ where
{
#[inline]
pub fn radius(self, radius: Size) -> Self {
for strip in self.strips.iter_mut() {
for strip in self.builder.strips[self.strip_range.clone()].iter_mut() {
strip.radius = radius;
}
self
}

#[inline]
pub fn color(self, color: Color32) -> Self {
for strip in self.strips.iter_mut() {
for strip in self.builder.strips[self.strip_range.clone()].iter_mut() {
strip.color = color;
}
self
}

#[inline]
pub fn flags(self, flags: LineStripFlags) -> Self {
for strip in self.strips.iter_mut() {
for strip in self.builder.strips[self.strip_range.clone()].iter_mut() {
strip.flags = flags;
}
self
}

/// Sets an individual outline mask ids.
/// Note that this has a relatively high performance impact.
#[inline]
pub fn outline_mask_ids(mut self, outline_mask_ids: OutlineMaskPreference) -> Self {
self.outline_mask_ids = outline_mask_ids;
self
}

/// Adds user data for every strip this builder adds.
///
/// User data is currently not available on the GPU.
#[inline]
pub fn user_data(self, user_data: PerStripUserData) -> Self {
for d in self.user_data.iter_mut() {
for d in self.builder.strip_user_data[self.strip_range.clone()].iter_mut() {
*d = user_data.clone();
}
self
}
}

impl<'a, PerStripUserData> Drop for LineStripBuilder<'a, PerStripUserData> {
fn drop(&mut self) {
if self.outline_mask_ids.is_some() {
self.builder
.batches
.last_mut()
.unwrap()
.additional_outline_mask_ids_vertex_ranges
.push((
self.vertex_range.start as u32..self.vertex_range.end as u32,
self.outline_mask_ids,
));
}
}
}
Loading