1use re_log::debug_assert_eq;
13use re_mutex::Mutex;
14use smallvec::smallvec;
15
16use crate::allocator::create_and_fill_uniform_buffer;
17use crate::global_bindings::FrameUniformBuffer;
18use crate::rect::RectF32;
19use crate::texture_info::Texture2DBufferInfo;
20use crate::transform::{RectTransform, ndc_from_pixel};
21use crate::view_builder::ViewBuilder;
22use crate::wgpu_resources::{
23 BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuRenderPipelineHandle,
24 GpuRenderPipelinePoolAccessor, GpuTexture, GpuTextureHandle, PipelineLayoutDesc, PoolError,
25 RenderPipelineDesc, TextureDesc,
26};
27use crate::{
28 GpuReadbackBuffer, GpuReadbackIdentifier, Label, RectInt, RenderContext, include_shader_module,
29};
30
31pub struct PickingResult {
33 pub rect: RectInt,
36
37 pub picking_id_data: Vec<PickingLayerId>,
42
43 pub picking_depth_data: Vec<f32>,
50
51 world_from_cropped_projection: glam::Mat4,
53}
54
55impl PickingResult {
56 #[inline]
62 pub fn picked_world_position(&self, pos_on_picking_rect: glam::UVec2) -> glam::Vec3 {
63 let raw_depth = self.picking_depth_data
64 [(pos_on_picking_rect.y * self.rect.width() + pos_on_picking_rect.x) as usize];
65
66 self.world_from_cropped_projection.project_point3(
67 ndc_from_pixel(pos_on_picking_rect.as_vec2(), self.rect.extent).extend(raw_depth),
68 )
69 }
70
71 #[inline]
75 pub fn picked_id(&self, pos_on_picking_rect: glam::UVec2) -> PickingLayerId {
76 self.picking_id_data
77 [(pos_on_picking_rect.y * self.rect.width() + pos_on_picking_rect.x) as usize]
78 }
79}
80
81struct ReadbackBeltMetadata {
83 picking_rect: RectInt,
84 world_from_cropped_projection: glam::Mat4,
85
86 depth_readback_workaround_in_use: bool,
87}
88
89#[repr(C)]
94#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug, PartialEq, Eq)]
95pub struct PickingLayerObjectId(pub u64);
96
97#[repr(C)]
102#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug, PartialEq, Eq)]
103pub struct PickingLayerInstanceId(pub u64);
104
105impl re_byte_size::SizeBytes for PickingLayerInstanceId {
106 #[inline]
107 fn heap_size_bytes(&self) -> u64 {
108 0
109 }
110
111 #[inline]
112 fn is_pod() -> bool {
113 true
114 }
115}
116
117#[repr(C)]
121#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod, Default, Debug, PartialEq, Eq)]
122pub struct PickingLayerId {
123 pub object: PickingLayerObjectId,
124 pub instance: PickingLayerInstanceId,
125}
126
127impl From<PickingLayerId> for [u32; 4] {
128 fn from(val: PickingLayerId) -> Self {
129 [
130 val.object.0 as u32,
131 (val.object.0 >> 32) as u32,
132 val.instance.0 as u32,
133 (val.instance.0 >> 32) as u32,
134 ]
135 }
136}
137
138#[derive(thiserror::Error, Debug)]
139pub enum PickingLayerError {
140 #[error(transparent)]
141 ReadbackError(#[from] crate::allocator::GpuReadbackError),
142
143 #[error(transparent)]
144 ResourcePoolError(#[from] PoolError),
145}
146
147pub struct PickingLayerProcessor {
151 pub picking_target: GpuTexture,
152 picking_depth_target: GpuTexture,
153 readback_buffer: Mutex<GpuReadbackBuffer>,
154 bind_group_0: GpuBindGroup,
155
156 depth_readback_workaround: Option<DepthReadbackWorkaround>,
157}
158
159impl PickingLayerProcessor {
160 pub const PICKING_LAYER_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba32Uint;
162
163 pub const PICKING_LAYER_DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
165
166 pub const PICKING_LAYER_MSAA_STATE: wgpu::MultisampleState = wgpu::MultisampleState {
167 count: 1,
168 mask: !0,
169 alpha_to_coverage_enabled: false,
170 };
171
172 pub const PICKING_LAYER_DEPTH_STATE: Option<wgpu::DepthStencilState> =
173 Some(ViewBuilder::MAIN_TARGET_DEFAULT_DEPTH_STATE);
174
175 pub fn new(
185 ctx: &RenderContext,
186 view_name: &Label,
187 screen_resolution: glam::UVec2,
188 picking_rect: RectInt,
189 frame_uniform_buffer_content: &FrameUniformBuffer,
190 enable_picking_target_sampling: bool,
191 readback_identifier: GpuReadbackIdentifier,
192 ) -> Self {
193 let mut picking_target_usage =
194 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC;
195 picking_target_usage.set(
196 wgpu::TextureUsages::TEXTURE_BINDING,
197 enable_picking_target_sampling,
198 );
199
200 let picking_target = ctx.gpu_resources.textures.alloc(
201 &ctx.device,
202 &TextureDesc {
203 label: format!("{view_name} - PickingLayerProcessor").into(),
204 size: picking_rect.wgpu_extent(),
205 mip_level_count: 1,
206 sample_count: 1,
207 dimension: wgpu::TextureDimension::D2,
208 format: Self::PICKING_LAYER_FORMAT,
209 usage: picking_target_usage,
210 },
211 );
212
213 let direct_depth_readback = ctx.device_caps().tier.support_depth_readback();
214
215 let picking_depth_target = ctx.gpu_resources.textures.alloc(
216 &ctx.device,
217 &TextureDesc {
218 label: format!("{view_name} - picking_layer depth target").into(),
219 format: Self::PICKING_LAYER_DEPTH_FORMAT,
220 usage: if direct_depth_readback {
221 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC
222 } else {
223 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING
224 },
225 ..picking_target.creation_desc
226 },
227 );
228
229 let depth_readback_workaround = (!direct_depth_readback).then(|| {
230 DepthReadbackWorkaround::new(ctx, picking_rect.extent, picking_depth_target.handle)
231 });
232
233 let cropped_projection_from_projection = RectTransform {
234 region_of_interest: picking_rect.into(),
235 region: RectF32 {
236 min: glam::Vec2::ZERO,
237 extent: screen_resolution.as_vec2(),
238 },
239 }
240 .to_ndc_scale_and_translation();
241
242 let previous_projection_from_world: glam::Mat4 =
244 frame_uniform_buffer_content.projection_from_world.into();
245 let cropped_projection_from_world =
246 cropped_projection_from_projection * previous_projection_from_world;
247 let previous_projection_from_view: glam::Mat4 =
248 frame_uniform_buffer_content.projection_from_view.into();
249 let cropped_projection_from_view =
250 cropped_projection_from_projection * previous_projection_from_view;
251
252 let frame_uniform_buffer_content = FrameUniformBuffer {
253 projection_from_world: cropped_projection_from_world.into(),
254 projection_from_view: cropped_projection_from_view.into(),
255 ..*frame_uniform_buffer_content
256 };
257
258 let frame_uniform_buffer = create_and_fill_uniform_buffer(
259 ctx,
260 format!("{view_name} - picking_layer frame uniform buffer").into(),
261 frame_uniform_buffer_content,
262 );
263
264 let bind_group_0 = ctx.global_bindings.create_bind_group(
265 &ctx.gpu_resources,
266 &ctx.device,
267 frame_uniform_buffer,
268 );
269
270 let row_info_id =
271 Texture2DBufferInfo::new(Self::PICKING_LAYER_FORMAT, picking_rect.wgpu_extent());
272 let row_info_depth = Texture2DBufferInfo::new(
273 if direct_depth_readback {
274 Self::PICKING_LAYER_DEPTH_FORMAT
275 } else {
276 DepthReadbackWorkaround::READBACK_FORMAT
277 },
278 picking_rect.wgpu_extent(),
279 );
280
281 re_log::debug_assert!(
284 Self::PICKING_LAYER_FORMAT.block_copy_size(None).unwrap()
285 % Self::PICKING_LAYER_DEPTH_FORMAT
286 .block_copy_size(Some(wgpu::TextureAspect::DepthOnly))
287 .unwrap()
288 == 0
289 );
290 let buffer_size = row_info_id.buffer_size_padded + row_info_depth.buffer_size_padded;
291
292 let readback_buffer = Mutex::new(ctx.gpu_readback_belt.lock().allocate(
293 &ctx.device,
294 &ctx.gpu_resources.buffers,
295 buffer_size,
296 readback_identifier,
297 Box::new(ReadbackBeltMetadata {
298 picking_rect,
299 world_from_cropped_projection: cropped_projection_from_world.inverse(),
300 depth_readback_workaround_in_use: depth_readback_workaround.is_some(),
301 }),
302 ));
303
304 Self {
305 picking_target,
306 picking_depth_target,
307 readback_buffer,
308 bind_group_0,
309 depth_readback_workaround,
310 }
311 }
312
313 pub fn begin_render_pass<'a>(
314 &'a self,
315 view_name: &Label,
316 encoder: &'a mut wgpu::CommandEncoder,
317 ) -> wgpu::RenderPass<'a> {
318 re_tracing::profile_function!();
319
320 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
321 label: Label::from(format!("{view_name} - picking_layer pass")).wgpu_label(),
322 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
323 view: &self.picking_target.default_view,
324 depth_slice: None,
325 resolve_target: None,
326 ops: wgpu::Operations {
327 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
328 store: wgpu::StoreOp::Store, },
330 })],
331 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
332 view: &self.picking_depth_target.default_view,
333 depth_ops: Some(wgpu::Operations {
334 load: ViewBuilder::DEFAULT_DEPTH_CLEAR,
335 store: wgpu::StoreOp::Store, }),
337 stencil_ops: None,
338 }),
339 timestamp_writes: None,
340 occlusion_query_set: None,
341 multiview_mask: None,
342 });
343
344 pass.set_bind_group(0, &self.bind_group_0, &[]);
345
346 pass
347 }
348
349 pub fn end_render_pass(
350 &self,
351 encoder: &mut wgpu::CommandEncoder,
352 render_pipelines: &GpuRenderPipelinePoolAccessor<'_>,
353 ) -> Result<(), PickingLayerError> {
354 let extent = self.picking_target.texture.size();
355
356 let readable_depth_texture =
357 if let Some(depth_copy_workaround) = self.depth_readback_workaround.as_ref() {
358 depth_copy_workaround.copy_to_readable_texture(
359 encoder,
360 render_pipelines,
361 &self.bind_group_0,
362 )?
363 } else {
364 &self.picking_depth_target
365 };
366
367 self.readback_buffer.lock().read_multiple_texture2d(
368 encoder,
369 &[
370 (
371 wgpu::TexelCopyTextureInfo {
372 texture: &self.picking_target.texture,
373 mip_level: 0,
374 origin: wgpu::Origin3d::ZERO,
375 aspect: wgpu::TextureAspect::All,
376 },
377 extent,
378 ),
379 (
380 wgpu::TexelCopyTextureInfo {
381 texture: &readable_depth_texture.texture,
382 mip_level: 0,
383 origin: wgpu::Origin3d::ZERO,
384 aspect: if self.depth_readback_workaround.is_some() {
385 wgpu::TextureAspect::All
386 } else {
387 wgpu::TextureAspect::DepthOnly
388 },
389 },
390 extent,
391 ),
392 ],
393 )?;
394
395 Ok(())
396 }
397
398 pub fn readback_result(
404 ctx: &RenderContext,
405 identifier: GpuReadbackIdentifier,
406 ) -> Option<PickingResult> {
407 ctx.gpu_readback_belt
408 .lock()
409 .readback_newest_available(identifier, |data, metadata: Box<ReadbackBeltMetadata>| {
410 debug_assert_eq!(
412 Self::PICKING_LAYER_DEPTH_FORMAT
413 .block_copy_size(Some(wgpu::TextureAspect::DepthOnly))
414 .unwrap(),
415 std::mem::size_of::<f32>() as u32
416 );
417 debug_assert_eq!(
418 Self::PICKING_LAYER_FORMAT.block_copy_size(None).unwrap() as usize,
419 std::mem::size_of::<PickingLayerId>()
420 );
421
422 let buffer_info_id = Texture2DBufferInfo::new(
423 Self::PICKING_LAYER_FORMAT,
424 metadata.picking_rect.wgpu_extent(),
425 );
426 let buffer_info_depth = Texture2DBufferInfo::new(
427 if metadata.depth_readback_workaround_in_use {
428 DepthReadbackWorkaround::READBACK_FORMAT
429 } else {
430 Self::PICKING_LAYER_DEPTH_FORMAT
431 },
432 metadata.picking_rect.wgpu_extent(),
433 );
434
435 let picking_id_data = buffer_info_id
436 .remove_padding_and_convert(&data[..buffer_info_id.buffer_size_padded as _]);
437 let mut picking_depth_data = buffer_info_depth
438 .remove_padding_and_convert(&data[buffer_info_id.buffer_size_padded as _..]);
439
440 if metadata.depth_readback_workaround_in_use {
441 debug_assert_eq!(
444 DepthReadbackWorkaround::READBACK_FORMAT
445 .block_copy_size(None)
446 .unwrap() as usize,
447 std::mem::size_of::<f32>() * 4
448 );
449 picking_depth_data = picking_depth_data.into_iter().step_by(4).collect();
450 }
451
452 Some(PickingResult {
453 picking_id_data,
454 picking_depth_data,
455 rect: metadata.picking_rect,
456 world_from_cropped_projection: metadata.world_from_cropped_projection,
457 })
458 })
459 .flatten()
460 }
461}
462
463struct DepthReadbackWorkaround {
470 render_pipeline: GpuRenderPipelineHandle,
471 bind_group: GpuBindGroup,
472 readable_texture: GpuTexture,
473}
474
475impl DepthReadbackWorkaround {
476 const READBACK_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba32Float;
483
484 fn new(
485 ctx: &RenderContext,
486 extent: glam::UVec2,
487 depth_target_handle: GpuTextureHandle,
488 ) -> Self {
489 let readable_texture = ctx.gpu_resources.textures.alloc(
490 &ctx.device,
491 &TextureDesc {
492 label: "DepthCopyWorkaround::readable_texture".into(),
493 format: Self::READBACK_FORMAT,
494 usage: wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
495 size: wgpu::Extent3d {
496 width: extent.x,
497 height: extent.y,
498 depth_or_array_layers: 1,
499 },
500 mip_level_count: 1,
501 sample_count: 1,
502 dimension: wgpu::TextureDimension::D2,
503 },
504 );
505
506 let bind_group_layout = ctx.gpu_resources.bind_group_layouts.get_or_create(
507 &ctx.device,
508 &BindGroupLayoutDesc {
509 label: "DepthCopyWorkaround::bind_group_layout".into(),
510 entries: vec![wgpu::BindGroupLayoutEntry {
511 binding: 0,
512 visibility: wgpu::ShaderStages::FRAGMENT,
513 ty: wgpu::BindingType::Texture {
514 sample_type: wgpu::TextureSampleType::Float { filterable: false },
515 view_dimension: wgpu::TextureViewDimension::D2,
516 multisampled: false,
517 },
518 count: None,
519 }],
520 },
521 );
522
523 let bind_group = ctx.gpu_resources.bind_groups.alloc(
524 &ctx.device,
525 &ctx.gpu_resources,
526 &BindGroupDesc {
527 label: "DepthCopyWorkaround::bind_group".into(),
528 entries: smallvec![BindGroupEntry::DefaultTextureView(depth_target_handle)],
529 layout: bind_group_layout,
530 },
531 );
532
533 let render_pipeline = ctx.gpu_resources.render_pipelines.get_or_create(
534 ctx,
535 &RenderPipelineDesc {
536 label: "DepthCopyWorkaround::render_pipeline".into(),
537 pipeline_layout: ctx.gpu_resources.pipeline_layouts.get_or_create(
538 ctx,
539 &PipelineLayoutDesc {
540 label: "DepthCopyWorkaround::render_pipeline".into(),
541 entries: vec![ctx.global_bindings.layout, bind_group_layout],
542 },
543 ),
544 vertex_entrypoint: "main".into(),
545 vertex_handle: ctx.gpu_resources.shader_modules.get_or_create(
546 ctx,
547 &include_shader_module!("../../shader/screen_triangle.wgsl"),
548 ),
549 fragment_entrypoint: "main".into(),
550 fragment_handle: ctx.gpu_resources.shader_modules.get_or_create(
551 ctx,
552 &include_shader_module!("../../shader/copy_texture.wgsl"),
553 ),
554 vertex_buffers: smallvec![],
555 render_targets: smallvec![Some(readable_texture.texture.format().into())],
556 primitive: wgpu::PrimitiveState {
557 topology: wgpu::PrimitiveTopology::TriangleStrip,
558 cull_mode: None,
559 ..Default::default()
560 },
561 depth_stencil: None,
562 multisample: wgpu::MultisampleState::default(),
563 },
564 );
565
566 Self {
567 render_pipeline,
568 bind_group,
569 readable_texture,
570 }
571 }
572
573 fn copy_to_readable_texture(
574 &self,
575 encoder: &mut wgpu::CommandEncoder,
576 render_pipelines: &GpuRenderPipelinePoolAccessor<'_>,
577 global_binding_bind_group: &GpuBindGroup,
578 ) -> Result<&GpuTexture, PoolError> {
579 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
581 label: Label::from("Depth copy workaround").wgpu_label(),
582 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
583 view: &self.readable_texture.default_view,
584 depth_slice: None,
585 resolve_target: None,
586 ops: wgpu::Operations {
587 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
588 store: wgpu::StoreOp::Store, },
590 })],
591 depth_stencil_attachment: None,
592 timestamp_writes: None,
593 occlusion_query_set: None,
594 multiview_mask: None,
595 });
596
597 let pipeline = render_pipelines.get(self.render_pipeline)?;
598 pass.set_pipeline(pipeline);
599 pass.set_bind_group(0, global_binding_bind_group, &[]);
600 pass.set_bind_group(1, &self.bind_group, &[]);
601 pass.draw(0..3, 0..1);
602
603 Ok(&self.readable_texture)
604 }
605}