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::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_bind_group_layout_1
110 .as_ref()
111 .expect("ensure_pick_pipeline must be called first"),
112 entries: &[wgpu::BindGroupEntry {
113 binding: 0,
114 resource: pick_instance_buf.as_entire_binding(),
115 }],
116 });
117
118 let camera_uniform = frame.camera.render_camera.camera_uniform();
120 let camera_bytes = bytemuck::bytes_of(&camera_uniform);
121 let pick_camera_buf = device.create_buffer(&wgpu::BufferDescriptor {
122 label: Some("pick_camera_buf"),
123 size: std::mem::size_of::<CameraUniform>() as u64,
124 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
125 mapped_at_creation: false,
126 });
127 queue.write_buffer(&pick_camera_buf, 0, camera_bytes);
128
129 let pick_camera_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
130 label: Some("pick_camera_bg"),
131 layout: self
132 .resources
133 .pick_camera_bgl
134 .as_ref()
135 .expect("ensure_pick_pipeline must be called first"),
136 entries: &[
137 wgpu::BindGroupEntry {
138 binding: 0,
139 resource: pick_camera_buf.as_entire_binding(),
140 },
141 wgpu::BindGroupEntry {
142 binding: 6,
143 resource: self.resources.clip_volume_uniform_buf.as_entire_binding(),
144 },
145 ],
146 });
147
148 let pick_id_texture = device.create_texture(&wgpu::TextureDescriptor {
150 label: Some("pick_id_texture"),
151 size: wgpu::Extent3d {
152 width: vp_w,
153 height: vp_h,
154 depth_or_array_layers: 1,
155 },
156 mip_level_count: 1,
157 sample_count: 1,
158 dimension: wgpu::TextureDimension::D2,
159 format: wgpu::TextureFormat::R32Uint,
160 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
161 view_formats: &[],
162 });
163 let pick_id_view = pick_id_texture.create_view(&wgpu::TextureViewDescriptor::default());
164
165 let pick_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
166 label: Some("pick_depth_colour_texture"),
167 size: wgpu::Extent3d {
168 width: vp_w,
169 height: vp_h,
170 depth_or_array_layers: 1,
171 },
172 mip_level_count: 1,
173 sample_count: 1,
174 dimension: wgpu::TextureDimension::D2,
175 format: wgpu::TextureFormat::R32Float,
176 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
177 view_formats: &[],
178 });
179 let pick_depth_view =
180 pick_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
181
182 let depth_stencil_texture = device.create_texture(&wgpu::TextureDescriptor {
183 label: Some("pick_ds_texture"),
184 size: wgpu::Extent3d {
185 width: vp_w,
186 height: vp_h,
187 depth_or_array_layers: 1,
188 },
189 mip_level_count: 1,
190 sample_count: 1,
191 dimension: wgpu::TextureDimension::D2,
192 format: wgpu::TextureFormat::Depth24PlusStencil8,
193 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
194 view_formats: &[],
195 });
196 let depth_stencil_view =
197 depth_stencil_texture.create_view(&wgpu::TextureViewDescriptor::default());
198
199 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
201 label: Some("pick_pass_encoder"),
202 });
203 {
204 let mut pick_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
205 label: Some("pick_pass"),
206 color_attachments: &[
207 Some(wgpu::RenderPassColorAttachment {
208 view: &pick_id_view,
209 resolve_target: None,
210 depth_slice: None,
211 ops: wgpu::Operations {
212 load: wgpu::LoadOp::Clear(wgpu::Color {
213 r: 0.0,
214 g: 0.0,
215 b: 0.0,
216 a: 0.0,
217 }),
218 store: wgpu::StoreOp::Store,
219 },
220 }),
221 Some(wgpu::RenderPassColorAttachment {
222 view: &pick_depth_view,
223 resolve_target: None,
224 depth_slice: None,
225 ops: wgpu::Operations {
226 load: wgpu::LoadOp::Clear(wgpu::Color {
227 r: 1.0,
228 g: 0.0,
229 b: 0.0,
230 a: 0.0,
231 }),
232 store: wgpu::StoreOp::Store,
233 },
234 }),
235 ],
236 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
237 view: &depth_stencil_view,
238 depth_ops: Some(wgpu::Operations {
239 load: wgpu::LoadOp::Clear(1.0),
240 store: wgpu::StoreOp::Store,
241 }),
242 stencil_ops: None,
243 }),
244 timestamp_writes: None,
245 occlusion_query_set: None,
246 });
247
248 pick_pass.set_pipeline(
249 self.resources
250 .pick_pipeline
251 .as_ref()
252 .expect("ensure_pick_pipeline must be called first"),
253 );
254 pick_pass.set_bind_group(0, &pick_camera_bg, &[]);
255 pick_pass.set_bind_group(1, &pick_instance_bg, &[]);
256
257 for (instance_slot, item) in pickable_items.iter().enumerate() {
260 let Some(mesh) = self.resources.mesh_store.get(item.mesh_id) else {
261 continue;
262 };
263 pick_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
264 pick_pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
265 let slot = instance_slot as u32;
266 pick_pass.draw_indexed(0..mesh.index_count, 0, slot..slot + 1);
267 }
268 }
269
270 let bytes_per_row_aligned = 256u32; let id_staging = device.create_buffer(&wgpu::BufferDescriptor {
275 label: Some("pick_id_staging"),
276 size: bytes_per_row_aligned as u64,
277 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
278 mapped_at_creation: false,
279 });
280 let depth_staging = device.create_buffer(&wgpu::BufferDescriptor {
281 label: Some("pick_depth_staging"),
282 size: bytes_per_row_aligned as u64,
283 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
284 mapped_at_creation: false,
285 });
286
287 let px = (cursor.x * ppp).round() as u32;
289 let py = (cursor.y * ppp).round() as u32;
290
291 encoder.copy_texture_to_buffer(
292 wgpu::TexelCopyTextureInfo {
293 texture: &pick_id_texture,
294 mip_level: 0,
295 origin: wgpu::Origin3d { x: px, y: py, z: 0 },
296 aspect: wgpu::TextureAspect::All,
297 },
298 wgpu::TexelCopyBufferInfo {
299 buffer: &id_staging,
300 layout: wgpu::TexelCopyBufferLayout {
301 offset: 0,
302 bytes_per_row: Some(bytes_per_row_aligned),
303 rows_per_image: Some(1),
304 },
305 },
306 wgpu::Extent3d {
307 width: 1,
308 height: 1,
309 depth_or_array_layers: 1,
310 },
311 );
312 encoder.copy_texture_to_buffer(
313 wgpu::TexelCopyTextureInfo {
314 texture: &pick_depth_texture,
315 mip_level: 0,
316 origin: wgpu::Origin3d { x: px, y: py, z: 0 },
317 aspect: wgpu::TextureAspect::All,
318 },
319 wgpu::TexelCopyBufferInfo {
320 buffer: &depth_staging,
321 layout: wgpu::TexelCopyBufferLayout {
322 offset: 0,
323 bytes_per_row: Some(bytes_per_row_aligned),
324 rows_per_image: Some(1),
325 },
326 },
327 wgpu::Extent3d {
328 width: 1,
329 height: 1,
330 depth_or_array_layers: 1,
331 },
332 );
333
334 queue.submit(std::iter::once(encoder.finish()));
335
336 let (tx_id, rx_id) = std::sync::mpsc::channel::<Result<(), wgpu::BufferAsyncError>>();
338 let (tx_dep, rx_dep) = std::sync::mpsc::channel::<Result<(), wgpu::BufferAsyncError>>();
339 id_staging
340 .slice(..)
341 .map_async(wgpu::MapMode::Read, move |r| {
342 let _ = tx_id.send(r);
343 });
344 depth_staging
345 .slice(..)
346 .map_async(wgpu::MapMode::Read, move |r| {
347 let _ = tx_dep.send(r);
348 });
349 device
350 .poll(wgpu::PollType::Wait {
351 submission_index: None,
352 timeout: Some(std::time::Duration::from_secs(5)),
353 })
354 .unwrap();
355 let _ = rx_id.recv().unwrap_or(Err(wgpu::BufferAsyncError));
356 let _ = rx_dep.recv().unwrap_or(Err(wgpu::BufferAsyncError));
357
358 let object_id = {
359 let data = id_staging.slice(..).get_mapped_range();
360 u32::from_le_bytes([data[0], data[1], data[2], data[3]])
361 };
362 id_staging.unmap();
363
364 let depth = {
365 let data = depth_staging.slice(..).get_mapped_range();
366 f32::from_le_bytes([data[0], data[1], data[2], data[3]])
367 };
368 depth_staging.unmap();
369
370 if object_id == 0 {
372 return None;
373 }
374
375 Some(crate::interaction::picking::GpuPickHit {
376 object_id: PickId(object_id as u64),
377 depth,
378 })
379 }
380}