-
Notifications
You must be signed in to change notification settings - Fork 373
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GPU based picking with points (#1721)
* 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
Showing
38 changed files
with
1,278 additions
and
182 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.
d84d2d1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rust Benchmark
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.