Skip to content

Commit

Permalink
GPU based picking with points (#1721)
Browse files Browse the repository at this point in the history
* a working re_renderer example for picking in point clouds
* a debug overlay renderer
* a (incomplete) picking draw phase, fully implemented for point clouds
* the ability to schedule readback transfers for picking rects that are rendered on demand to a small texture
* refactor draw phase processing out to its own module (we should formalize things things some more later!)
  • Loading branch information
Wumpf authored Mar 29, 2023
1 parent f0c2fde commit d84d2d1
Show file tree
Hide file tree
Showing 38 changed files with 1,278 additions and 182 deletions.
11 changes: 10 additions & 1 deletion crates/re_renderer/examples/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ pub trait Example {
pixels_from_point: f32,
) -> Vec<ViewDrawResult>;

fn on_keyboard_input(&mut self, input: winit::event::KeyboardInput);
fn on_keyboard_input(&mut self, _input: winit::event::KeyboardInput) {}

fn on_cursor_moved(&mut self, _position_in_pixel: glam::UVec2) {}
}

#[allow(dead_code)]
Expand Down Expand Up @@ -205,6 +207,13 @@ impl<E: Example + 'static> Application<E> {
event: WindowEvent::KeyboardInput { input, .. },
..
} => self.example.on_keyboard_input(input),
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => self.example.on_cursor_moved(glam::uvec2(
position.x.round() as u32,
position.y.round() as u32,
)),
Event::WindowEvent {
event:
WindowEvent::ScaleFactorChanged {
Expand Down
19 changes: 5 additions & 14 deletions crates/re_renderer/examples/multiview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,17 +216,7 @@ impl Multiview {
{
let screenshot = self.scheduled_screenshots.swap_remove(index);

// Need to do a memcpy to remove the padding.
let row_info = screenshot.row_info;
let mut buffer = Vec::with_capacity(
(row_info.bytes_per_row_unpadded * screenshot.height) as usize,
);
for row in 0..screenshot.height {
let offset = (row_info.bytes_per_row_padded * row) as usize;
buffer.extend_from_slice(
&data[offset..(offset + row_info.bytes_per_row_unpadded as usize)],
);
}
re_log::info!("Received screenshot. Total bytes {}", data.len());

// Get next available file name.
let mut i = 1;
Expand All @@ -238,11 +228,12 @@ impl Multiview {
i += 1;
};

#[cfg(not(target_arch = "wasm32"))]
image::save_buffer(
filename,
&buffer,
screenshot.width,
screenshot.height,
&screenshot.row_info.remove_padding(data),
screenshot.extent.x,
screenshot.extent.y,
image::ColorType::Rgba8,
)
.expect("Failed to save screenshot");
Expand Down
3 changes: 2 additions & 1 deletion crates/re_renderer/examples/outlines.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use itertools::Itertools;
use re_renderer::{
renderer::{MeshInstance, OutlineConfig, OutlineMaskPreference},
renderer::MeshInstance,
view_builder::{Projection, TargetConfiguration, ViewBuilder},
OutlineConfig, OutlineMaskPreference,
};
use winit::event::{ElementState, VirtualKeyCode};

Expand Down
198 changes: 198 additions & 0 deletions crates/re_renderer/examples/picking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
use ahash::HashMap;
use itertools::Itertools as _;
use rand::Rng;
use re_renderer::{
view_builder::{Projection, TargetConfiguration, ViewBuilder},
Color32, GpuReadbackBufferIdentifier, IntRect, PickingLayerId, PickingLayerInstanceId,
PointCloudBuilder, RenderContext, ScheduledPickingRect, Size,
};

mod framework;

struct PointSet {
positions: Vec<glam::Vec3>,
radii: Vec<Size>,
colors: Vec<Color32>,
picking_ids: Vec<PickingLayerInstanceId>,
}

struct Picking {
point_sets: Vec<PointSet>,
scheduled_picking_rects: HashMap<GpuReadbackBufferIdentifier, ScheduledPickingRect>,
picking_position: glam::UVec2,
}

fn random_color(rnd: &mut impl rand::Rng) -> Color32 {
ecolor::Hsva {
h: rnd.gen::<f32>(),
s: rnd.gen::<f32>() * 0.5 + 0.5,
v: rnd.gen::<f32>() * 0.5 + 0.5,
a: 1.0,
}
.into()
}

impl Picking {
#[allow(clippy::unused_self)]
fn handle_incoming_picking_data(&mut self, re_ctx: &mut RenderContext, _time: f32) {
re_ctx
.gpu_readback_belt
.lock()
.receive_data(|data, identifier| {
if let Some(picking_rect_info) = self.scheduled_picking_rects.remove(&identifier) {
// TODO(andreas): Move this into a utility function?
let picking_data_without_padding =
picking_rect_info.row_info.remove_padding(data);
let picking_data: &[PickingLayerId] =
bytemuck::cast_slice(&picking_data_without_padding);

// Grab the middle pixel. usually we'd want to do something clever that snaps the the closest object of interest.
let picked_pixel = picking_data[(picking_rect_info.rect.extent.x / 2
+ (picking_rect_info.rect.extent.y / 2) * picking_rect_info.rect.extent.x)
as usize];

if picked_pixel.object.0 != 0 {
let point_set = &mut self.point_sets[picked_pixel.object.0 as usize - 1];
point_set.radii[picked_pixel.instance.0 as usize] = Size::new_scene(0.1);
point_set.colors[picked_pixel.instance.0 as usize] = Color32::DEBUG_COLOR;
}
} else {
re_log::error!("Received picking data for unknown identifier");
}
});
}
}

impl framework::Example for Picking {
fn title() -> &'static str {
"Picking"
}

fn on_cursor_moved(&mut self, position_in_pixel: glam::UVec2) {
self.picking_position = position_in_pixel;
}

fn new(_re_ctx: &mut re_renderer::RenderContext) -> Self {
let mut rnd = <rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(42);
let random_point_range = -5.0_f32..5.0_f32;
let point_count = 10000;

// Split point cloud into several batches to test picking of multiple objects.
let point_sets = (0..2)
.map(|_| PointSet {
positions: (0..point_count)
.map(|_| {
glam::vec3(
rnd.gen_range(random_point_range.clone()),
rnd.gen_range(random_point_range.clone()),
rnd.gen_range(random_point_range.clone()),
)
})
.collect_vec(),
radii: std::iter::repeat(Size::new_scene(0.08))
.take(point_count)
.collect_vec(),
colors: (0..point_count)
.map(|_| random_color(&mut rnd))
.collect_vec(),
picking_ids: (0..point_count as u64)
.map(PickingLayerInstanceId)
.collect_vec(),
})
.collect_vec();

Picking {
point_sets,
scheduled_picking_rects: HashMap::default(),
picking_position: glam::UVec2::ZERO,
}
}

fn draw(
&mut self,
re_ctx: &mut re_renderer::RenderContext,
resolution: [u32; 2],
time: &framework::Time,
pixels_from_point: f32,
) -> Vec<framework::ViewDrawResult> {
self.handle_incoming_picking_data(re_ctx, time.seconds_since_startup());

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()
},
)
.unwrap();

// 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)
let picking_rect_size = 31;

let picking_rect = view_builder
.schedule_picking_readback(
re_ctx,
IntRect::from_middle_and_extent(
self.picking_position.as_ivec2(),
glam::uvec2(picking_rect_size, picking_rect_size),
),
false,
)
.unwrap();
self.scheduled_picking_rects
.insert(picking_rect.identifier, picking_rect);

let mut builder = PointCloudBuilder::<()>::new(re_ctx);

for (i, point_set) in self.point_sets.iter().enumerate() {
builder
.batch(format!("Random Points {i}"))
.picking_object_id(re_renderer::PickingLayerObjectId(i as u64 + 1)) // offset by one since 0=default=no hit
.add_points(
point_set.positions.len(),
point_set.positions.iter().cloned(),
)
.radii(point_set.radii.iter().cloned())
.colors(point_set.colors.iter().cloned())
.picking_instance_ids(point_set.picking_ids.iter().cloned());
}
view_builder.queue_draw(&builder.to_draw_data(re_ctx).unwrap());
view_builder.queue_draw(&re_renderer::renderer::GenericSkyboxDrawData::new(re_ctx));

let command_buffer = view_builder
.draw(re_ctx, ecolor::Rgba::TRANSPARENT)
.unwrap();

vec![framework::ViewDrawResult {
view_builder,
command_buffer,
target_location: glam::Vec2::ZERO,
}]
}

fn on_keyboard_input(&mut self, _input: winit::event::KeyboardInput) {}
}

fn main() {
framework::start::<Picking>();
}
68 changes: 68 additions & 0 deletions crates/re_renderer/shader/debug_overlay.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Debug overlay shader
//
// Works together with `debug_overlay.rs` to display a texture on top of the screen.
// It is meant to be used as last part of the compositor phase in order to present the debug output unfiltered.
// It's sole purpose is for developing new rendering features and it should not be used in production!
//
// The fragment shader is a blueprint for handling different texture outputs.
// *Do* edit it on the fly for debugging purposes!

#import <./types.wgsl>
#import <./global_bindings.wgsl>

struct UniformBuffer {
screen_resolution: Vec2,
position_in_pixel: Vec2,
extent_in_pixel: Vec2,
mode: u32,
_padding: u32,
};
@group(1) @binding(0)
var<uniform> uniforms: UniformBuffer;

@group(1) @binding(1)
var debug_texture_float: texture_2d<f32>;
@group(1) @binding(2)
var debug_texture_uint: texture_2d<u32>;

// Mode options, see `DebugOverlayMode` in `debug_overlay.rs`
const ShowFloatTexture: u32 = 0u;
const ShowUintTexture: u32 = 1u;

struct VertexOutput {
@builtin(position) position: Vec4,
@location(0) texcoord: Vec2,
};

@vertex
fn main_vs(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
let texcoord = Vec2(f32(vertex_index / 2u), f32(vertex_index % 2u));

// This calculation could be simplified by pre-computing things on the CPU.
// But this is not the point here - we want to debug this and other things rapidly by editing the shader.
let screen_fraction = texcoord * (uniforms.extent_in_pixel / uniforms.screen_resolution) +
uniforms.position_in_pixel / uniforms.screen_resolution;
let screen_ndc = Vec2(screen_fraction.x * 2.0 - 1.0, 1.0 - screen_fraction.y * 2.0);

var out: VertexOutput;
out.position = Vec4(screen_ndc, 0.0, 1.0);
out.texcoord = texcoord;
return out;
}

@fragment
fn main_fs(in: VertexOutput) -> @location(0) Vec4 {
if uniforms.mode == ShowFloatTexture {
return Vec4(textureSample(debug_texture_float, nearest_sampler, in.texcoord).rgb, 1.0);
} else if uniforms.mode == ShowUintTexture {
let coords = IVec2(in.texcoord * Vec2(textureDimensions(debug_texture_uint).xy));
let raw_values = textureLoad(debug_texture_uint, coords, 0);

let num_color_levels = 20u;
let mapped_values = Vec4(raw_values % num_color_levels) / f32(num_color_levels - 1u);

return Vec4(mapped_values.rgb, 1.0);
} else {
return Vec4(1.0, 0.0, 1.0, 1.0);
}
}
Loading

1 comment on commit d84d2d1

@github-actions
Copy link

Choose a reason for hiding this comment

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

Rust Benchmark

Benchmark suite Current: d84d2d1 Previous: f0c2fde Ratio
datastore/insert/batch/rects/insert 595656 ns/iter (± 5248) 605599 ns/iter (± 2448) 0.98
datastore/latest_at/batch/rects/query 1827 ns/iter (± 64) 1836 ns/iter (± 6) 1.00
datastore/latest_at/missing_components/primary 276 ns/iter (± 3) 281 ns/iter (± 0) 0.98
datastore/latest_at/missing_components/secondaries 425 ns/iter (± 5) 435 ns/iter (± 0) 0.98
datastore/range/batch/rects/query 149051 ns/iter (± 2000) 151509 ns/iter (± 447) 0.98
mono_points_arrow/generate_message_bundles 42956054 ns/iter (± 846925) 43059300 ns/iter (± 528134) 1.00
mono_points_arrow/generate_messages 181906241 ns/iter (± 1670214) 167580898 ns/iter (± 1263022) 1.09
mono_points_arrow/encode_log_msg 219005854 ns/iter (± 1655460) 209851844 ns/iter (± 3733358) 1.04
mono_points_arrow/encode_total 443594955 ns/iter (± 3756558) 417361966 ns/iter (± 1812551) 1.06
mono_points_arrow/decode_log_msg 266153192 ns/iter (± 2562157) 251422372 ns/iter (± 810673) 1.06
mono_points_arrow/decode_message_bundles 94505495 ns/iter (± 1244586) 85077520 ns/iter (± 727163) 1.11
mono_points_arrow/decode_total 359343268 ns/iter (± 14241870) 338052305 ns/iter (± 1368801) 1.06
mono_points_arrow_batched/generate_message_bundles 31349750 ns/iter (± 1599319) 35444065 ns/iter (± 1258314) 0.88
mono_points_arrow_batched/generate_messages 8889953 ns/iter (± 501904) 9305538 ns/iter (± 761267) 0.96
mono_points_arrow_batched/encode_log_msg 1770994 ns/iter (± 15125) 1762443 ns/iter (± 3490) 1.00
mono_points_arrow_batched/encode_total 44236809 ns/iter (± 2346489) 47352681 ns/iter (± 2488976) 0.93
mono_points_arrow_batched/decode_log_msg 987788 ns/iter (± 5247) 985723 ns/iter (± 2709) 1.00
mono_points_arrow_batched/decode_message_bundles 16492553 ns/iter (± 1183928) 18418610 ns/iter (± 1256736) 0.90
mono_points_arrow_batched/decode_total 18039322 ns/iter (± 881815) 19568993 ns/iter (± 714999) 0.92
batch_points_arrow/generate_message_bundles 280540 ns/iter (± 3106) 288751 ns/iter (± 473) 0.97
batch_points_arrow/generate_messages 7621 ns/iter (± 122) 7800 ns/iter (± 19) 0.98
batch_points_arrow/encode_log_msg 381834 ns/iter (± 3251) 389641 ns/iter (± 1458) 0.98
batch_points_arrow/encode_total 683418 ns/iter (± 8730) 696104 ns/iter (± 1785) 0.98
batch_points_arrow/decode_log_msg 335913 ns/iter (± 2797) 337022 ns/iter (± 782) 1.00
batch_points_arrow/decode_message_bundles 2836 ns/iter (± 36) 2935 ns/iter (± 7) 0.97
batch_points_arrow/decode_total 347147 ns/iter (± 2060) 346135 ns/iter (± 731) 1.00
arrow_mono_points/insert 6801273546 ns/iter (± 16225597) 6133941953 ns/iter (± 22892058) 1.11
arrow_mono_points/query 1743930 ns/iter (± 21940) 1809200 ns/iter (± 7892) 0.96
arrow_batch_points/insert 2966999 ns/iter (± 25040) 3045916 ns/iter (± 11102) 0.97
arrow_batch_points/query 16890 ns/iter (± 200) 17082 ns/iter (± 49) 0.99
arrow_batch_vecs/insert 42739 ns/iter (± 335) 42955 ns/iter (± 110) 0.99
arrow_batch_vecs/query 497910 ns/iter (± 6017) 506159 ns/iter (± 354) 0.98
tuid/Tuid::random 34 ns/iter (± 0) 34 ns/iter (± 0) 1

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

Please sign in to comment.