Skip to content

Commit c54abe0

Browse files
authored
GPU based mesh picking in viewer (#1737)
* picking layer support for meshes * move out view builder from render bridge into ui code * remove now pointless create_scene_paint_callback method * move screenshot taking out of renderer bridge, create view builder earlier in the ui build up process * fix some issues in mesh picking and add mesh to picking example * use gpu for mesh picking in the viewer * debug option for debugging picking overlay * no longer discard "classic" picking information * placeholder picking layer implementations for remaining opaque primitives * fix temporal gaps in gpu picking report
1 parent 6dbc5b9 commit c54abe0

27 files changed

+547
-214
lines changed

crates/re_log_types/src/hash.rs

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ impl Hash64 {
1818
Self(hash(value))
1919
}
2020

21+
/// From an existing u64. Use this only for data conversions.
22+
#[inline]
23+
pub fn from_u64(i: u64) -> Self {
24+
Self(i)
25+
}
26+
2127
#[inline]
2228
pub fn hash64(&self) -> u64 {
2329
self.0

crates/re_log_types/src/path/entity_path.rs

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ impl EntityPathHash {
1414
/// Sometimes used as the hash of `None`.
1515
pub const NONE: EntityPathHash = EntityPathHash(Hash64::ZERO);
1616

17+
/// From an existing u64. Use this only for data conversions.
18+
#[inline]
19+
pub fn from_u64(i: u64) -> Self {
20+
Self(Hash64::from_u64(i))
21+
}
22+
1723
#[inline]
1824
pub fn hash64(&self) -> u64 {
1925
self.0.hash64()

crates/re_renderer/examples/picking.rs

+48-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use itertools::Itertools as _;
22
use rand::Rng;
33
use re_renderer::{
4+
renderer::MeshInstance,
45
view_builder::{Projection, TargetConfiguration, ViewBuilder},
5-
Color32, GpuReadbackIdentifier, IntRect, PickingLayerInstanceId, PickingLayerProcessor,
6-
PointCloudBuilder, Size,
6+
Color32, GpuReadbackIdentifier, IntRect, PickingLayerId, PickingLayerInstanceId,
7+
PickingLayerProcessor, PointCloudBuilder, Size,
78
};
89

910
mod framework;
@@ -18,6 +19,8 @@ struct PointSet {
1819
struct Picking {
1920
point_sets: Vec<PointSet>,
2021
picking_position: glam::UVec2,
22+
model_mesh_instances: Vec<MeshInstance>,
23+
mesh_is_hovered: bool,
2124
}
2225

2326
fn random_color(rnd: &mut impl rand::Rng) -> Color32 {
@@ -34,6 +37,12 @@ fn random_color(rnd: &mut impl rand::Rng) -> Color32 {
3437
/// Identifiers don't need to be unique and we don't have anything interesting to distinguish here!
3538
const READBACK_IDENTIFIER: GpuReadbackIdentifier = 0;
3639

40+
/// Mesh ID used for picking. Uses the entire 64bit range for testing.
41+
const MESH_ID: PickingLayerId = PickingLayerId {
42+
object: re_renderer::PickingLayerObjectId(0x1234_5678_9012_3456),
43+
instance: re_renderer::PickingLayerInstanceId(0x3456_1234_5678_9012),
44+
};
45+
3746
impl framework::Example for Picking {
3847
fn title() -> &'static str {
3948
"Picking"
@@ -43,10 +52,10 @@ impl framework::Example for Picking {
4352
self.picking_position = position_in_pixel;
4453
}
4554

46-
fn new(_re_ctx: &mut re_renderer::RenderContext) -> Self {
55+
fn new(re_ctx: &mut re_renderer::RenderContext) -> Self {
4756
let mut rnd = <rand::rngs::StdRng as rand::SeedableRng>::seed_from_u64(42);
4857
let random_point_range = -5.0_f32..5.0_f32;
49-
let point_count = 10000;
58+
let point_count = 1000;
5059

5160
// Split point cloud into several batches to test picking of multiple objects.
5261
let point_sets = (0..2)
@@ -72,9 +81,13 @@ impl framework::Example for Picking {
7281
})
7382
.collect_vec();
7483

84+
let model_mesh_instances = crate::framework::load_rerun_mesh(re_ctx);
85+
7586
Picking {
7687
point_sets,
88+
model_mesh_instances,
7789
picking_position: glam::UVec2::ZERO,
90+
mesh_is_hovered: false,
7891
}
7992
}
8093

@@ -93,7 +106,12 @@ impl framework::Example for Picking {
93106
+ (picking_result.rect.extent.y / 2) * picking_result.rect.extent.x)
94107
as usize];
95108

96-
if picked_pixel.object.0 != 0 {
109+
self.mesh_is_hovered = false;
110+
if picked_pixel == MESH_ID {
111+
self.mesh_is_hovered = true;
112+
} else if picked_pixel.object.0 != 0
113+
&& picked_pixel.object.0 <= self.point_sets.len() as u64
114+
{
97115
let point_set = &mut self.point_sets[picked_pixel.object.0 as usize - 1];
98116
point_set.radii[picked_pixel.instance.0 as usize] = Size::new_scene(0.1);
99117
point_set.colors[picked_pixel.instance.0 as usize] = Color32::DEBUG_COLOR;
@@ -139,10 +157,9 @@ impl framework::Example for Picking {
139157
.schedule_picking_rect(re_ctx, picking_rect, READBACK_IDENTIFIER, (), false)
140158
.unwrap();
141159

142-
let mut builder = PointCloudBuilder::<()>::new(re_ctx);
143-
160+
let mut point_builder = PointCloudBuilder::<()>::new(re_ctx);
144161
for (i, point_set) in self.point_sets.iter().enumerate() {
145-
builder
162+
point_builder
146163
.batch(format!("Random Points {i}"))
147164
.picking_object_id(re_renderer::PickingLayerObjectId(i as u64 + 1)) // offset by one since 0=default=no hit
148165
.add_points(
@@ -153,7 +170,29 @@ impl framework::Example for Picking {
153170
.colors(point_set.colors.iter().cloned())
154171
.picking_instance_ids(point_set.picking_ids.iter().cloned());
155172
}
156-
view_builder.queue_draw(&builder.to_draw_data(re_ctx).unwrap());
173+
view_builder.queue_draw(&point_builder.to_draw_data(re_ctx).unwrap());
174+
175+
let instances = self
176+
.model_mesh_instances
177+
.iter()
178+
.map(|instance| MeshInstance {
179+
gpu_mesh: instance.gpu_mesh.clone(),
180+
mesh: None,
181+
world_from_mesh: glam::Affine3A::from_translation(glam::vec3(0.0, 0.0, 0.0)),
182+
picking_layer_id: MESH_ID,
183+
additive_tint: if self.mesh_is_hovered {
184+
Color32::DEBUG_COLOR
185+
} else {
186+
Color32::TRANSPARENT
187+
},
188+
..Default::default()
189+
})
190+
.collect_vec();
191+
192+
view_builder.queue_draw(&re_renderer::renderer::GenericSkyboxDrawData::new(re_ctx));
193+
view_builder
194+
.queue_draw(&re_renderer::renderer::MeshDrawData::new(re_ctx, &instances).unwrap());
195+
157196
view_builder.queue_draw(&re_renderer::renderer::GenericSkyboxDrawData::new(re_ctx));
158197

159198
let command_buffer = view_builder

crates/re_renderer/shader/depth_cloud.wgsl

+9
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 {
139139
return vec4(in.point_color.rgb, coverage);
140140
}
141141

142+
@fragment
143+
fn fs_main_picking_layer(in: VertexOut) -> @location(0) UVec4 {
144+
let coverage = sphere_quad_coverage(in.pos_in_world, in.point_radius, in.point_pos_in_world);
145+
if coverage <= 0.5 {
146+
discard;
147+
}
148+
return UVec4(0u, 0u, 0u, 0u); // TODO(andreas): Implement picking layer id pass-through.
149+
}
150+
142151
@fragment
143152
fn fs_main_outline_mask(in: VertexOut) -> @location(0) UVec2 {
144153
// Output is an integer target, can't use coverage therefore.

crates/re_renderer/shader/instanced_mesh.wgsl

+24-5
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,26 @@ struct MaterialUniformBuffer {
1515
var<uniform> material: MaterialUniformBuffer;
1616

1717
struct VertexOut {
18-
@builtin(position) position: Vec4,
19-
@location(0) color: Vec4, // 0-1 linear space with unmultiplied/separate alpha
20-
@location(1) texcoord: Vec2,
21-
@location(2) normal_world_space: Vec3,
22-
@location(3) additive_tint_rgb: Vec3, // 0-1 linear space
18+
@builtin(position)
19+
position: Vec4,
20+
21+
@location(0)
22+
color: Vec4, // 0-1 linear space with unmultiplied/separate alpha
23+
24+
@location(1)
25+
texcoord: Vec2,
26+
27+
@location(2)
28+
normal_world_space: Vec3,
29+
30+
@location(3) @interpolate(flat)
31+
additive_tint_rgb: Vec3, // 0-1 linear space
32+
2333
@location(4) @interpolate(flat)
2434
outline_mask_ids: UVec2,
35+
36+
@location(5) @interpolate(flat)
37+
picking_layer_id: UVec4,
2538
};
2639

2740
@vertex
@@ -44,6 +57,7 @@ fn vs_main(in_vertex: VertexIn, in_instance: InstanceIn) -> VertexOut {
4457
out.normal_world_space = world_normal;
4558
out.additive_tint_rgb = linear_from_srgb(in_instance.additive_tint_srgb.rgb);
4659
out.outline_mask_ids = in_instance.outline_mask_ids;
60+
out.picking_layer_id = in_instance.picking_layer_id;
4761

4862
return out;
4963
}
@@ -70,6 +84,11 @@ fn fs_main_shaded(in: VertexOut) -> @location(0) Vec4 {
7084
}
7185
}
7286

87+
@fragment
88+
fn fs_main_picking_layer(in: VertexOut) -> @location(0) UVec4 {
89+
return in.picking_layer_id;
90+
}
91+
7392
@fragment
7493
fn fs_main_outline_mask(in: VertexOut) -> @location(0) UVec2 {
7594
return in.outline_mask_ids;

crates/re_renderer/shader/lines.wgsl

+9
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,15 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 {
299299
return Vec4(in.color.rgb * shading, coverage);
300300
}
301301

302+
@fragment
303+
fn fs_main_picking_layer(in: VertexOut) -> @location(0) UVec4 {
304+
var coverage = compute_coverage(in);
305+
if coverage < 0.5 {
306+
discard;
307+
}
308+
return UVec4(0u, 0u, 0u, 0u); // TODO(andreas): Implement picking layer id pass-through.
309+
}
310+
302311
@fragment
303312
fn fs_main_outline_mask(in: VertexOut) -> @location(0) UVec2 {
304313
// Output is an integer target, can't use coverage therefore.

crates/re_renderer/shader/mesh_vertex.wgsl

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ struct InstanceIn {
1717
@location(8) world_from_mesh_normal_row_1: Vec3,
1818
@location(9) world_from_mesh_normal_row_2: Vec3,
1919
@location(10) additive_tint_srgb: Vec4,
20-
@location(11) outline_mask_ids: UVec2,
20+
@location(11) picking_layer_id: UVec4,
21+
@location(12) outline_mask_ids: UVec2,
2122
};

crates/re_renderer/shader/rectangle.wgsl

+5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 {
5454
return texture_color * rect_info.multiplicative_tint;
5555
}
5656

57+
@fragment
58+
fn fs_main_picking_layer(in: VertexOut) -> @location(0) UVec4 {
59+
return UVec4(0u, 0u, 0u, 0u); // TODO(andreas): Implement picking layer id pass-through.
60+
}
61+
5762
@fragment
5863
fn fs_main_outline_mask(in: VertexOut) -> @location(0) UVec2 {
5964
return rect_info.outline_mask;

crates/re_renderer/src/draw_phases/picking_layer.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//!
66
//! The picking layer is a RGBA texture with 32bit per channel, the red & green channel are used for the [`PickingLayerObjectId`],
77
//! the blue & alpha channel are used for the [`PickingLayerInstanceId`].
8+
//! (Keep in mind that GPUs are little endian, so R will have the lower bytes and G the higher ones)
89
//!
910
//! In order to accomplish small render targets, the projection matrix is cropped to only render the area of interest.
1011
@@ -43,27 +44,38 @@ struct ReadbackBeltMetadata<T: 'static + Send + Sync> {
4344
/// Typically used to identify higher level objects
4445
/// Some renderers might allow to change this part of the picking identifier only at a coarse grained level.
4546
#[repr(C)]
46-
#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug)]
47+
#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug, PartialEq, Eq)]
4748
pub struct PickingLayerObjectId(pub u64);
4849

4950
/// The second 64bit of the picking layer.
5051
///
5152
/// Typically used to identify instances.
5253
/// Some renderers might allow to change only this part of the picking identifier at a fine grained level.
5354
#[repr(C)]
54-
#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug)]
55+
#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug, PartialEq, Eq)]
5556
pub struct PickingLayerInstanceId(pub u64);
5657

5758
/// Combination of `PickingLayerObjectId` and `PickingLayerInstanceId`.
5859
///
5960
/// This is the same memory order as it is found in the GPU picking layer texture.
6061
#[repr(C)]
61-
#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug)]
62+
#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug, PartialEq, Eq)]
6263
pub struct PickingLayerId {
6364
pub object: PickingLayerObjectId,
6465
pub instance: PickingLayerInstanceId,
6566
}
6667

68+
impl From<PickingLayerId> for [u32; 4] {
69+
fn from(val: PickingLayerId) -> Self {
70+
[
71+
val.object.0 as u32,
72+
(val.object.0 >> 32) as u32,
73+
val.instance.0 as u32,
74+
(val.instance.0 >> 32) as u32,
75+
]
76+
}
77+
}
78+
6779
/// Manages the rendering of the picking layer pass, its render targets & readback buffer.
6880
///
6981
/// The view builder creates this for every frame that requests a picking result.

crates/re_renderer/src/renderer/depth_cloud.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::{
2424
GpuRenderPipelineHandle, GpuTexture, PipelineLayoutDesc, RenderPipelineDesc, TextureDesc,
2525
TextureRowDataInfo,
2626
},
27-
ColorMap, OutlineMaskPreference,
27+
ColorMap, OutlineMaskPreference, PickingLayerProcessor,
2828
};
2929

3030
use super::{
@@ -387,6 +387,7 @@ fn create_and_upload_texture<T: bytemuck::Pod>(
387387

388388
pub struct DepthCloudRenderer {
389389
render_pipeline_color: GpuRenderPipelineHandle,
390+
render_pipeline_picking_layer: GpuRenderPipelineHandle,
390391
render_pipeline_outline_mask: GpuRenderPipelineHandle,
391392
bind_group_layout: GpuBindGroupLayoutHandle,
392393
}
@@ -395,7 +396,11 @@ impl Renderer for DepthCloudRenderer {
395396
type RendererDrawData = DepthCloudDrawData;
396397

397398
fn participated_phases() -> &'static [DrawPhase] {
398-
&[DrawPhase::OutlineMask, DrawPhase::Opaque]
399+
&[
400+
DrawPhase::Opaque,
401+
DrawPhase::PickingLayer,
402+
DrawPhase::OutlineMask,
403+
]
399404
}
400405

401406
fn create_renderer<Fs: FileSystem>(
@@ -480,7 +485,19 @@ impl Renderer for DepthCloudRenderer {
480485
&pools.pipeline_layouts,
481486
&pools.shader_modules,
482487
);
483-
488+
let render_pipeline_picking_layer = pools.render_pipelines.get_or_create(
489+
device,
490+
&RenderPipelineDesc {
491+
label: "DepthCloudRenderer::render_pipeline_picking_layer".into(),
492+
fragment_entrypoint: "fs_main_picking_layer".into(),
493+
render_targets: smallvec![Some(PickingLayerProcessor::PICKING_LAYER_FORMAT.into())],
494+
depth_stencil: PickingLayerProcessor::PICKING_LAYER_DEPTH_STATE,
495+
multisample: PickingLayerProcessor::PICKING_LAYER_MSAA_STATE,
496+
..render_pipeline_desc_color.clone()
497+
},
498+
&pools.pipeline_layouts,
499+
&pools.shader_modules,
500+
);
484501
let render_pipeline_outline_mask = pools.render_pipelines.get_or_create(
485502
device,
486503
&RenderPipelineDesc {
@@ -500,6 +517,7 @@ impl Renderer for DepthCloudRenderer {
500517

501518
DepthCloudRenderer {
502519
render_pipeline_color,
520+
render_pipeline_picking_layer,
503521
render_pipeline_outline_mask,
504522
bind_group_layout,
505523
}
@@ -518,8 +536,9 @@ impl Renderer for DepthCloudRenderer {
518536
}
519537

520538
let pipeline_handle = match phase {
521-
DrawPhase::OutlineMask => self.render_pipeline_outline_mask,
522539
DrawPhase::Opaque => self.render_pipeline_color,
540+
DrawPhase::PickingLayer => self.render_pipeline_picking_layer,
541+
DrawPhase::OutlineMask => self.render_pipeline_outline_mask,
523542
_ => unreachable!("We were called on a phase we weren't subscribed to: {phase:?}"),
524543
};
525544
let pipeline = pools.render_pipelines.get_resource(pipeline_handle)?;

0 commit comments

Comments
 (0)