1use super::*;
5
6impl ViewportRenderer {
7 pub fn pick_scene_gpu(
31 &mut self,
32 device: &wgpu::Device,
33 queue: &wgpu::Queue,
34 cursor: glam::Vec2,
35 frame: &FrameData,
36 ) -> Option<crate::interaction::query::picking::GpuPickHit> {
37 if self.runtime_mode == crate::renderer::stats::RuntimeMode::Playback
40 && self.frame_counter % 4 != 0
41 {
42 return None;
43 }
44
45 let scene_items: &[SceneRenderItem] = match &frame.scene.surfaces {
47 SurfaceSubmission::Flat(items) => items.as_ref(),
48 };
49
50 let ppp = frame.camera.pixels_per_point;
51 let vp_w = (frame.camera.viewport_size[0] * ppp).round() as u32;
52 let vp_h = (frame.camera.viewport_size[1] * ppp).round() as u32;
53
54 if cursor.x < 0.0
56 || cursor.y < 0.0
57 || cursor.x >= frame.camera.viewport_size[0]
58 || cursor.y >= frame.camera.viewport_size[1]
59 || vp_w == 0
60 || vp_h == 0
61 {
62 return None;
63 }
64
65 self.resources.ensure_pick_pipeline(device);
67
68 let pickable_items: Vec<&SceneRenderItem> = scene_items
72 .iter()
73 .filter(|item| !item.settings.hidden && item.settings.pick_id != PickId::NONE)
74 .collect();
75
76 let pick_instances: Vec<PickInstance> = pickable_items
77 .iter()
78 .map(|item| {
79 let m = item.model;
80 PickInstance {
81 model_c0: m[0],
82 model_c1: m[1],
83 model_c2: m[2],
84 model_c3: m[3],
85 object_id: item.settings.pick_id.0 as u32,
86 _pad: [0; 3],
87 }
88 })
89 .collect();
90
91 if pick_instances.is_empty() {
92 return None;
93 }
94
95 let pick_instance_bytes = bytemuck::cast_slice(&pick_instances);
97 let pick_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
98 label: Some("pick_instance_buf"),
99 size: pick_instance_bytes.len().max(80) as u64,
100 usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
101 mapped_at_creation: false,
102 });
103 queue.write_buffer(&pick_instance_buf, 0, pick_instance_bytes);
104
105 let pick_instance_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
106 label: Some("pick_instance_bg"),
107 layout: self
108 .resources
109 .pick
110 .bind_group_layout_1
111 .as_ref()
112 .expect("ensure_pick_pipeline must be called first"),
113 entries: &[wgpu::BindGroupEntry {
114 binding: 0,
115 resource: pick_instance_buf.as_entire_binding(),
116 }],
117 });
118
119 let camera_uniform = frame.camera.render_camera.camera_uniform();
121 let camera_bytes = bytemuck::bytes_of(&camera_uniform);
122 let pick_camera_buf = device.create_buffer(&wgpu::BufferDescriptor {
123 label: Some("pick_camera_buf"),
124 size: std::mem::size_of::<CameraUniform>() as u64,
125 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
126 mapped_at_creation: false,
127 });
128 queue.write_buffer(&pick_camera_buf, 0, camera_bytes);
129
130 let pick_camera_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
131 label: Some("pick_camera_bg"),
132 layout: self
133 .resources
134 .pick
135 .camera_bgl
136 .as_ref()
137 .expect("ensure_pick_pipeline must be called first"),
138 entries: &[
139 wgpu::BindGroupEntry {
140 binding: 0,
141 resource: pick_camera_buf.as_entire_binding(),
142 },
143 wgpu::BindGroupEntry {
144 binding: 6,
145 resource: self.resources.clip_volume_uniform_buf.as_entire_binding(),
146 },
147 ],
148 });
149
150 let pick_id_texture = device.create_texture(&wgpu::TextureDescriptor {
152 label: Some("pick_id_texture"),
153 size: wgpu::Extent3d {
154 width: vp_w,
155 height: vp_h,
156 depth_or_array_layers: 1,
157 },
158 mip_level_count: 1,
159 sample_count: 1,
160 dimension: wgpu::TextureDimension::D2,
161 format: wgpu::TextureFormat::R32Uint,
162 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
163 view_formats: &[],
164 });
165 let pick_id_view = pick_id_texture.create_view(&wgpu::TextureViewDescriptor::default());
166
167 let pick_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
168 label: Some("pick_depth_colour_texture"),
169 size: wgpu::Extent3d {
170 width: vp_w,
171 height: vp_h,
172 depth_or_array_layers: 1,
173 },
174 mip_level_count: 1,
175 sample_count: 1,
176 dimension: wgpu::TextureDimension::D2,
177 format: wgpu::TextureFormat::R32Float,
178 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
179 view_formats: &[],
180 });
181 let pick_depth_view =
182 pick_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
183
184 let depth_stencil_texture = device.create_texture(&wgpu::TextureDescriptor {
185 label: Some("pick_ds_texture"),
186 size: wgpu::Extent3d {
187 width: vp_w,
188 height: vp_h,
189 depth_or_array_layers: 1,
190 },
191 mip_level_count: 1,
192 sample_count: 1,
193 dimension: wgpu::TextureDimension::D2,
194 format: wgpu::TextureFormat::Depth24PlusStencil8,
195 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
196 view_formats: &[],
197 });
198 let depth_stencil_view =
199 depth_stencil_texture.create_view(&wgpu::TextureViewDescriptor::default());
200
201 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
203 label: Some("pick_pass_encoder"),
204 });
205 {
206 let mut pick_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
207 label: Some("pick_pass"),
208 color_attachments: &[
209 Some(wgpu::RenderPassColorAttachment {
210 view: &pick_id_view,
211 resolve_target: None,
212 depth_slice: None,
213 ops: wgpu::Operations {
214 load: wgpu::LoadOp::Clear(wgpu::Color {
215 r: 0.0,
216 g: 0.0,
217 b: 0.0,
218 a: 0.0,
219 }),
220 store: wgpu::StoreOp::Store,
221 },
222 }),
223 Some(wgpu::RenderPassColorAttachment {
224 view: &pick_depth_view,
225 resolve_target: None,
226 depth_slice: None,
227 ops: wgpu::Operations {
228 load: wgpu::LoadOp::Clear(wgpu::Color {
229 r: 1.0,
230 g: 0.0,
231 b: 0.0,
232 a: 0.0,
233 }),
234 store: wgpu::StoreOp::Store,
235 },
236 }),
237 ],
238 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
239 view: &depth_stencil_view,
240 depth_ops: Some(wgpu::Operations {
241 load: wgpu::LoadOp::Clear(1.0),
242 store: wgpu::StoreOp::Store,
243 }),
244 stencil_ops: None,
245 }),
246 timestamp_writes: None,
247 occlusion_query_set: None,
248 });
249
250 pick_pass.set_pipeline(
251 self.resources
252 .pick
253 .pipeline
254 .as_ref()
255 .expect("ensure_pick_pipeline must be called first"),
256 );
257 pick_pass.set_bind_group(0, &pick_camera_bg, &[]);
258 pick_pass.set_bind_group(1, &pick_instance_bg, &[]);
259
260 for (instance_slot, item) in pickable_items.iter().enumerate() {
263 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
264 continue;
265 };
266 pick_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
267 pick_pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
268 let slot = instance_slot as u32;
269 pick_pass.draw_indexed(0..mesh.index_count, 0, slot..slot + 1);
270 }
271 }
272
273 let bytes_per_row_aligned = 256u32; let id_staging = device.create_buffer(&wgpu::BufferDescriptor {
278 label: Some("pick_id_staging"),
279 size: bytes_per_row_aligned as u64,
280 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
281 mapped_at_creation: false,
282 });
283 let depth_staging = device.create_buffer(&wgpu::BufferDescriptor {
284 label: Some("pick_depth_staging"),
285 size: bytes_per_row_aligned as u64,
286 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
287 mapped_at_creation: false,
288 });
289
290 let px = (cursor.x * ppp).round() as u32;
292 let py = (cursor.y * ppp).round() as u32;
293
294 encoder.copy_texture_to_buffer(
295 wgpu::TexelCopyTextureInfo {
296 texture: &pick_id_texture,
297 mip_level: 0,
298 origin: wgpu::Origin3d { x: px, y: py, z: 0 },
299 aspect: wgpu::TextureAspect::All,
300 },
301 wgpu::TexelCopyBufferInfo {
302 buffer: &id_staging,
303 layout: wgpu::TexelCopyBufferLayout {
304 offset: 0,
305 bytes_per_row: Some(bytes_per_row_aligned),
306 rows_per_image: Some(1),
307 },
308 },
309 wgpu::Extent3d {
310 width: 1,
311 height: 1,
312 depth_or_array_layers: 1,
313 },
314 );
315 encoder.copy_texture_to_buffer(
316 wgpu::TexelCopyTextureInfo {
317 texture: &pick_depth_texture,
318 mip_level: 0,
319 origin: wgpu::Origin3d { x: px, y: py, z: 0 },
320 aspect: wgpu::TextureAspect::All,
321 },
322 wgpu::TexelCopyBufferInfo {
323 buffer: &depth_staging,
324 layout: wgpu::TexelCopyBufferLayout {
325 offset: 0,
326 bytes_per_row: Some(bytes_per_row_aligned),
327 rows_per_image: Some(1),
328 },
329 },
330 wgpu::Extent3d {
331 width: 1,
332 height: 1,
333 depth_or_array_layers: 1,
334 },
335 );
336
337 queue.submit(std::iter::once(encoder.finish()));
338
339 let (tx_id, rx_id) = std::sync::mpsc::channel::<Result<(), wgpu::BufferAsyncError>>();
341 let (tx_dep, rx_dep) = std::sync::mpsc::channel::<Result<(), wgpu::BufferAsyncError>>();
342 id_staging
343 .slice(..)
344 .map_async(wgpu::MapMode::Read, move |r| {
345 let _ = tx_id.send(r);
346 });
347 depth_staging
348 .slice(..)
349 .map_async(wgpu::MapMode::Read, move |r| {
350 let _ = tx_dep.send(r);
351 });
352 device
353 .poll(wgpu::PollType::Wait {
354 submission_index: None,
355 timeout: Some(std::time::Duration::from_secs(5)),
356 })
357 .unwrap();
358 let _ = rx_id.recv().unwrap_or(Err(wgpu::BufferAsyncError));
359 let _ = rx_dep.recv().unwrap_or(Err(wgpu::BufferAsyncError));
360
361 let object_id = {
362 let data = id_staging.slice(..).get_mapped_range();
363 u32::from_le_bytes([data[0], data[1], data[2], data[3]])
364 };
365 id_staging.unmap();
366
367 let depth = {
368 let data = depth_staging.slice(..).get_mapped_range();
369 f32::from_le_bytes([data[0], data[1], data[2], data[3]])
370 };
371 depth_staging.unmap();
372
373 if object_id == 0 {
375 return None;
376 }
377
378 Some(crate::interaction::query::picking::GpuPickHit {
379 object_id: PickId(object_id as u64),
380 depth,
381 })
382 }
383}