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

Size boosted outlines for points & lines, color & size tweaking #1667

Merged
merged 10 commits into from
Mar 23, 2023
2 changes: 1 addition & 1 deletion crates/re_renderer/examples/2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ impl framework::Example for Render2D {
splits[0].resolution_in_pixel[1] as f32,
);

let mut line_strip_builder = LineStripSeriesBuilder::<()>::default();
let mut line_strip_builder = LineStripSeriesBuilder::<()>::new(re_ctx);

// Blue rect outline around the bottom right quarter.
{
Expand Down
2 changes: 1 addition & 1 deletion crates/re_renderer/examples/depth_cloud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ impl framework::Example for RenderDepthClouds {
let world_from_model = rotation * translation_center * scale;

let frame_draw_data = {
let mut builder = LineStripSeriesBuilder::<()>::default();
let mut builder = LineStripSeriesBuilder::<()>::new(re_ctx);
{
let mut line_batch = builder.batch("frame").world_from_obj(world_from_model);
line_batch.add_box_outline(glam::Affine3A::from_scale_rotation_translation(
Expand Down
2 changes: 1 addition & 1 deletion crates/re_renderer/examples/multiview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ fn build_lines(re_ctx: &mut RenderContext, seconds_since_startup: f32) -> LineDr
// Calculate some points that look nice for an animated line.
let lorenz_points = lorenz_points(seconds_since_startup);

let mut builder = LineStripSeriesBuilder::<()>::default();
let mut builder = LineStripSeriesBuilder::<()>::new(re_ctx);
{
let mut batch = builder.batch("lines without transform");

Expand Down
3 changes: 2 additions & 1 deletion crates/re_renderer/shader/depth_cloud.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
let point_data = compute_point_data(quad_idx);

// Span quad
let quad = sphere_quad_span(vertex_idx, point_data.pos_in_world, point_data.unresolved_radius);
// TODO(andreas): Implement outline-mask size boost for depth cloud as well.
let quad = sphere_quad_span(vertex_idx, point_data.pos_in_world, point_data.unresolved_radius, 0.0);
Wumpf marked this conversation as resolved.
Show resolved Hide resolved

var out: VertexOut;
out.pos_in_clip = frame.projection_from_world * Vec4(quad.pos_in_world, 1.0);
Expand Down
25 changes: 20 additions & 5 deletions crates/re_renderer/shader/lines.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ var line_strip_texture: texture_2d<f32>;
@group(1) @binding(1)
var position_data_texture: texture_2d<u32>;

struct DrawDataUniformBuffer {
size_boost_in_points: f32,
};
@group(1) @binding(2)
var<uniform> draw_data: DrawDataUniformBuffer;

struct BatchUniformBuffer {
world_from_obj: Mat4,
outline_mask_ids: UVec2,
Expand Down Expand Up @@ -159,11 +165,6 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
// Data valid for the entire strip that this vertex belongs to.
let strip_data = read_strip_data(pos_data_current.strip_index);

// Resolve radius.
// (slight inaccuracy: End caps are going to adjust their center_position)
let camera_ray = camera_ray_to_world_pos(center_position);
let strip_radius = unresolved_size_to_world(strip_data.unresolved_radius, length(camera_ray.origin - center_position), frame.auto_size_lines);

// Active flags are all flags that we react to at the current vertex.
// I.e. cap flags are only active in the respective cap triangle.
var currently_active_flags = strip_data.flags & (~(CAP_START_TRIANGLE | CAP_END_TRIANGLE | CAP_START_ROUND | CAP_END_ROUND));
Expand All @@ -189,6 +190,20 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
quad_dir = normalize(pos_data_quad_end.pos - pos_data_quad_begin.pos);
}

// Resolve radius.
// (slight inaccuracy: End caps are going to adjust their center_position)
let camera_ray = camera_ray_to_world_pos(center_position);
let camera_distance = distance(camera_ray.origin, center_position);
var strip_radius = unresolved_size_to_world(strip_data.unresolved_radius, camera_distance, frame.auto_size_lines);
if draw_data.size_boost_in_points > 0.0 {
let size_boost = point_size_to_world(draw_data.size_boost_in_points, camera_distance);
strip_radius += size_boost;
// Push out positions as well along the quad dir.
// This is not only, but especially important if there's no miters on a line-strip (TODO(#829)),
Wumpf marked this conversation as resolved.
Show resolved Hide resolved
// as this would enhance gaps between lines otherwise.
center_position += quad_dir * (size_boost * select(-1.0, 1.0, is_at_quad_end));
}

var active_radius = strip_radius;
// If this is a triangle cap, we blow up our ("virtual") quad by twice the size.
if has_any_flag(currently_active_flags, CAP_START_TRIANGLE | CAP_END_TRIANGLE) {
Expand Down
8 changes: 7 additions & 1 deletion crates/re_renderer/shader/point_cloud.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ var position_data_texture: texture_2d<f32>;
@group(1) @binding(1)
var color_texture: texture_2d<f32>;

struct DrawDataUniformBuffer {
size_boost_in_points: f32,
};
@group(1) @binding(2)
var<uniform> draw_data: DrawDataUniformBuffer;

struct BatchUniformBuffer {
world_from_obj: Mat4,
flags: u32,
Expand Down Expand Up @@ -63,7 +69,7 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
let point_data = read_data(quad_idx);

// Span quad
let quad = sphere_quad_span(vertex_idx, point_data.pos, point_data.unresolved_radius);
let quad = sphere_quad_span(vertex_idx, point_data.pos, point_data.unresolved_radius, draw_data.size_boost_in_points);

// Output, transform to projection space and done.
var out: VertexOut;
Expand Down
7 changes: 5 additions & 2 deletions crates/re_renderer/shader/utils/size.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
#import <camera.wgsl>


fn point_size_to_world(size_in_points: f32, camera_distance: f32) -> f32 {
Wumpf marked this conversation as resolved.
Show resolved Hide resolved
let pixel_size = frame.pixels_from_point * size_in_points;
return approx_pixel_world_size_at(camera_distance) * pixel_size;
}

fn unresolved_size_to_world(_unresolved_size: f32, camera_distance: f32, auto_size: f32) -> f32 {
// Resolve auto size.
Expand All @@ -23,6 +27,5 @@ fn unresolved_size_to_world(_unresolved_size: f32, camera_distance: f32, auto_si
}

// Negative size indicates size in points.
let pixel_size = frame.pixels_from_point * (-unresolved_size);
return approx_pixel_world_size_at(camera_distance) * pixel_size;
return point_size_to_world(-unresolved_size, camera_distance);
}
5 changes: 3 additions & 2 deletions crates/re_renderer/shader/utils/sphere_quad.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,12 @@ struct SphereQuadData {
/// Span a quad onto which perspective correct spheres can be drawn.
///
/// Spanning is done in perspective or orthographically depending of the state of the global cam.
fn sphere_quad_span(vertex_idx: u32, point_pos: Vec3, point_unresolved_radius: f32) -> SphereQuadData {
fn sphere_quad_span(vertex_idx: u32, point_pos: Vec3, point_unresolved_radius: f32, size_boost_in_points: f32) -> SphereQuadData {
// Resolve radius to a world size. We need the camera distance for this, which is useful later on.
let to_camera = frame.camera_position - point_pos;
let camera_distance = length(to_camera);
let radius = unresolved_size_to_world(point_unresolved_radius, camera_distance, frame.auto_size_points);
let radius = unresolved_size_to_world(point_unresolved_radius, camera_distance, frame.auto_size_points) +
point_size_to_world(size_boost_in_points, camera_distance);

// Basic properties of the vertex we're at.
let local_idx = vertex_idx % 6u;
Expand Down
36 changes: 33 additions & 3 deletions crates/re_renderer/src/line_strip_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
LineBatchInfo, LineDrawData, LineStripFlags, LineStripInfo, LineVertex,
OutlineMaskPreference,
},
Color32, DebugLabel, Size,
Color32, DebugLabel, RenderContext, Size,
};

/// Builder for a vector of line strips, making it easy to create [`crate::renderer::LineDrawData`].
Expand All @@ -14,7 +14,6 @@ use crate::{
/// of writing to a GPU readable memory location.
/// This will require some ahead of time size limit, but should be feasible.
/// But before that we first need to sort out cpu->gpu transfers better by providing staging buffers.
#[derive(Default)]
pub struct LineStripSeriesBuilder<PerStripUserData> {
pub vertices: Vec<LineVertex>,

Expand All @@ -23,12 +22,36 @@ pub struct LineStripSeriesBuilder<PerStripUserData> {
pub strip_user_data: Vec<PerStripUserData>,

pub batches: Vec<LineBatchInfo>,

pub(crate) size_boost_in_points_for_outlines: f32,
}

impl<PerStripUserData> LineStripSeriesBuilder<PerStripUserData>
where
PerStripUserData: Default + Copy,
{
// TODO(andreas): ctx not yet needed since we don't write to GPU yet, but soon needed.
pub fn new(_ctx: &RenderContext) -> Self {
const RESERVE_SIZE: usize = 512;

Self {
vertices: Vec::with_capacity(RESERVE_SIZE * 2),
strips: Vec::with_capacity(RESERVE_SIZE),
strip_user_data: Vec::with_capacity(RESERVE_SIZE),
batches: Vec::with_capacity(16),
size_boost_in_points_for_outlines: 0.0,
}
}

/// Boosts the size of the points by the given amount of ui-points for the purpose of drawing outlines.
pub fn size_boost_in_points_for_outlines(
mut self,
size_boost_in_points_for_outlines: f32,
) -> Self {
self.size_boost_in_points_for_outlines = size_boost_in_points_for_outlines;
self
}

/// Start of a new batch.
pub fn batch(
&mut self,
Expand Down Expand Up @@ -65,7 +88,14 @@ where

/// Finalizes the builder and returns a line draw data with all the lines added so far.
pub fn to_draw_data(&self, ctx: &mut crate::context::RenderContext) -> LineDrawData {
LineDrawData::new(ctx, &self.vertices, &self.strips, &self.batches).unwrap()
LineDrawData::new(
ctx,
&self.vertices,
&self.strips,
&self.batches,
self.size_boost_in_points_for_outlines,
)
.unwrap()
}

/// Iterates over all line strips batches together with their strips and their respective vertices.
Expand Down
14 changes: 13 additions & 1 deletion crates/re_renderer/src/point_cloud_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ pub struct PointCloudBuilder<PerPointUserData> {
pub user_data: Vec<PerPointUserData>,

pub(crate) batches: Vec<PointCloudBatchInfo>,

pub(crate) size_boost_in_points_for_outlines: f32,
}

impl<PerPointUserData> PointCloudBuilder<PerPointUserData>
where
PerPointUserData: Default + Copy,
{
pub fn new(ctx: &mut RenderContext) -> Self {
pub fn new(ctx: &RenderContext) -> Self {
const RESERVE_SIZE: usize = 512;

// TODO(andreas): Be more resourceful about the size allocated here. Typically we know in advance!
Expand All @@ -37,9 +39,19 @@ where
color_buffer,
user_data: Vec::with_capacity(RESERVE_SIZE),
batches: Vec::with_capacity(16),
size_boost_in_points_for_outlines: 0.0,
}
}

/// Boosts the size of the points by the given amount of ui-points for the purpose of drawing outlines.
pub fn size_boost_in_points_for_outlines(
mut self,
size_boost_in_points_for_outlines: f32,
) -> Self {
self.size_boost_in_points_for_outlines = size_boost_in_points_for_outlines;
self
}

/// Start of a new batch.
#[inline]
pub fn batch(
Expand Down
71 changes: 64 additions & 7 deletions crates/re_renderer/src/renderer/lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ pub mod gpu_data {
}
static_assertions::assert_eq_size!(LineStripInfo, [u32; 2]);

/// Uniform buffer that changes once per draw data rendering.
#[repr(C, align(256))]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct DrawDataUniformBuffer {
pub size_boost_in_points: wgpu_buffer_types::F32RowPadded,
pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 1],
}

/// Uniform buffer that changes for every batch of line strips.
#[repr(C, align(256))]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
Expand All @@ -184,6 +192,7 @@ struct LineStripBatch {
#[derive(Clone)]
pub struct LineDrawData {
bind_group_all_lines: Option<GpuBindGroup>,
bind_group_all_lines_outline_mask: Option<GpuBindGroup>,
batches: Vec<LineStripBatch>,
}

Expand Down Expand Up @@ -312,9 +321,11 @@ impl LineDrawData {
/// If no batches are passed, all lines are assumed to be in a single batch with identity transform.
pub fn new(
ctx: &mut RenderContext,
// TODO(andreas): Take LineBuilder directly
vertices: &[gpu_data::LineVertex],
strips: &[LineStripInfo],
batches: &[LineBatchInfo],
size_boost_in_points_for_outlines: f32,
) -> Result<Self, LineDrawDataError> {
let mut renderers = ctx.renderers.write();
let line_renderer = renderers.get_or_create::<_, LineRenderer>(
Expand All @@ -327,6 +338,7 @@ impl LineDrawData {
if strips.is_empty() {
return Ok(LineDrawData {
bind_group_all_lines: None,
bind_group_all_lines_outline_mask: None,
batches: Vec::new(),
});
}
Expand Down Expand Up @@ -495,14 +507,43 @@ impl LineDrawData {
},
);

let draw_data_uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
ctx,
"LineDrawData::DrawDataUniformBuffer".into(),
[
gpu_data::DrawDataUniformBuffer {
size_boost_in_points: 0.0.into(),
end_padding: Default::default(),
},
gpu_data::DrawDataUniformBuffer {
size_boost_in_points: size_boost_in_points_for_outlines.into(),
end_padding: Default::default(),
},
]
.into_iter(),
);
let bind_group_all_lines = ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label: "line draw data".into(),
label: "LineDrawData::bind_group_all_lines".into(),
entries: smallvec![
BindGroupEntry::DefaultTextureView(position_data_texture.handle),
BindGroupEntry::DefaultTextureView(line_strip_texture.handle),
draw_data_uniform_buffer_bindings[0].clone(),
],
layout: line_renderer.bind_group_layout_all_lines,
},
);
let bind_group_all_lines_outline_mask = ctx.gpu_resources.bind_groups.alloc(
&ctx.device,
&ctx.gpu_resources,
&BindGroupDesc {
label: "LineDrawData::bind_group_all_lines_outline_mask".into(),
entries: smallvec![
BindGroupEntry::DefaultTextureView(position_data_texture.handle),
BindGroupEntry::DefaultTextureView(line_strip_texture.handle),
draw_data_uniform_buffer_bindings[1].clone(),
],
layout: line_renderer.bind_group_layout_all_lines,
},
Expand Down Expand Up @@ -597,6 +638,7 @@ impl LineDrawData {

Ok(LineDrawData {
bind_group_all_lines: Some(bind_group_all_lines),
bind_group_all_lines_outline_mask: Some(bind_group_all_lines_outline_mask),
batches: batches_internal,
})
}
Expand Down Expand Up @@ -679,6 +721,18 @@ impl Renderer for LineRenderer {
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(std::mem::size_of::<
gpu_data::DrawDataUniformBuffer,
>() as _),
},
count: None,
},
],
},
);
Expand Down Expand Up @@ -792,15 +846,18 @@ impl Renderer for LineRenderer {
pass: &mut wgpu::RenderPass<'a>,
draw_data: &'a Self::RendererDrawData,
) -> anyhow::Result<()> {
let Some(bind_group_all_lines) = &draw_data.bind_group_all_lines else {
let (pipeline_handle, bind_group_all_lines) = match phase {
DrawPhase::OutlineMask => (
self.render_pipeline_outline_mask,
&draw_data.bind_group_all_lines_outline_mask,
),
DrawPhase::Opaque => (self.render_pipeline_color, &draw_data.bind_group_all_lines),
_ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
};
let Some(bind_group_all_lines) = bind_group_all_lines else {
return Ok(()); // No lines submitted.
};

let pipeline_handle = match phase {
DrawPhase::OutlineMask => self.render_pipeline_outline_mask,
DrawPhase::Opaque => self.render_pipeline_color,
_ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
};
let pipeline = pools.render_pipelines.get_resource(pipeline_handle)?;

pass.set_pipeline(pipeline);
Expand Down
Loading