use super::*;
impl ViewportRenderer {
pub fn pick_scene_gpu(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
cursor: glam::Vec2,
frame: &FrameData,
) -> Option<crate::interaction::picking::GpuPickHit> {
let vp_w = frame.viewport_size[0] as u32;
let vp_h = frame.viewport_size[1] as u32;
if cursor.x < 0.0
|| cursor.y < 0.0
|| cursor.x >= frame.viewport_size[0]
|| cursor.y >= frame.viewport_size[1]
|| vp_w == 0
|| vp_h == 0
{
return None;
}
self.resources.ensure_pick_pipeline(device);
let pick_instances: Vec<PickInstance> = frame
.scene_items
.iter()
.enumerate()
.filter(|(_, item)| item.visible)
.map(|(idx, item)| {
let m = item.model;
PickInstance {
model_c0: m[0],
model_c1: m[1],
model_c2: m[2],
model_c3: m[3],
object_id: (idx + 1) as u32,
_pad: [0; 3],
}
})
.collect();
if pick_instances.is_empty() {
return None;
}
let visible_items: Vec<(usize, &SceneRenderItem)> = frame
.scene_items
.iter()
.enumerate()
.filter(|(_, item)| item.visible)
.collect();
let pick_instance_bytes = bytemuck::cast_slice(&pick_instances);
let pick_instance_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("pick_instance_buf"),
size: pick_instance_bytes.len().max(80) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&pick_instance_buf, 0, pick_instance_bytes);
let pick_instance_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("pick_instance_bg"),
layout: self
.resources
.pick_bind_group_layout_1
.as_ref()
.expect("ensure_pick_pipeline must be called first"),
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: pick_instance_buf.as_entire_binding(),
}],
});
let camera_uniform = CameraUniform {
view_proj: (frame.camera_proj * frame.camera_view).to_cols_array_2d(),
eye_pos: frame.camera_uniform.eye_pos,
_pad: 0.0,
forward: frame
.camera_view
.inverse()
.transform_vector3(-glam::Vec3::Z)
.normalize_or_zero()
.to_array(),
_pad1: 0.0,
};
let camera_bytes = bytemuck::bytes_of(&camera_uniform);
let pick_camera_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("pick_camera_buf"),
size: std::mem::size_of::<CameraUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&pick_camera_buf, 0, camera_bytes);
let pick_camera_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("pick_camera_bg"),
layout: self
.resources
.pick_camera_bgl
.as_ref()
.expect("ensure_pick_pipeline must be called first"),
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: pick_camera_buf.as_entire_binding(),
}],
});
let pick_id_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("pick_id_texture"),
size: wgpu::Extent3d {
width: vp_w,
height: vp_h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R32Uint,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let pick_id_view = pick_id_texture.create_view(&wgpu::TextureViewDescriptor::default());
let pick_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("pick_depth_color_texture"),
size: wgpu::Extent3d {
width: vp_w,
height: vp_h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::R32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let pick_depth_view =
pick_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
let depth_stencil_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("pick_ds_texture"),
size: wgpu::Extent3d {
width: vp_w,
height: vp_h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth24PlusStencil8,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let depth_stencil_view =
depth_stencil_texture.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("pick_pass_encoder"),
});
{
let mut pick_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("pick_pass"),
color_attachments: &[
Some(wgpu::RenderPassColorAttachment {
view: &pick_id_view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
}),
store: wgpu::StoreOp::Store,
},
}),
Some(wgpu::RenderPassColorAttachment {
view: &pick_depth_view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 0.0,
}),
store: wgpu::StoreOp::Store,
},
}),
],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &depth_stencil_view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
timestamp_writes: None,
occlusion_query_set: None,
});
pick_pass.set_pipeline(
self.resources
.pick_pipeline
.as_ref()
.expect("ensure_pick_pipeline must be called first"),
);
pick_pass.set_bind_group(0, &pick_camera_bg, &[]);
pick_pass.set_bind_group(1, &pick_instance_bg, &[]);
for (instance_slot, (_, item)) in visible_items.iter().enumerate() {
let Some(mesh) = self
.resources
.mesh_store
.get(crate::resources::mesh_store::MeshId(item.mesh_index))
else {
continue;
};
pick_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
pick_pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
let slot = instance_slot as u32;
pick_pass.draw_indexed(0..mesh.index_count, 0, slot..slot + 1);
}
}
let bytes_per_row_aligned = 256u32;
let id_staging = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("pick_id_staging"),
size: bytes_per_row_aligned as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let depth_staging = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("pick_depth_staging"),
size: bytes_per_row_aligned as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let px = cursor.x as u32;
let py = cursor.y as u32;
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &pick_id_texture,
mip_level: 0,
origin: wgpu::Origin3d { x: px, y: py, z: 0 },
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &id_staging,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row_aligned),
rows_per_image: Some(1),
},
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: &pick_depth_texture,
mip_level: 0,
origin: wgpu::Origin3d { x: px, y: py, z: 0 },
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &depth_staging,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row_aligned),
rows_per_image: Some(1),
},
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
queue.submit(std::iter::once(encoder.finish()));
let (tx_id, rx_id) = std::sync::mpsc::channel::<Result<(), wgpu::BufferAsyncError>>();
let (tx_dep, rx_dep) = std::sync::mpsc::channel::<Result<(), wgpu::BufferAsyncError>>();
id_staging
.slice(..)
.map_async(wgpu::MapMode::Read, move |r| {
let _ = tx_id.send(r);
});
depth_staging
.slice(..)
.map_async(wgpu::MapMode::Read, move |r| {
let _ = tx_dep.send(r);
});
device
.poll(wgpu::PollType::Wait {
submission_index: None,
timeout: Some(std::time::Duration::from_secs(5)),
})
.unwrap();
let _ = rx_id.recv().unwrap_or(Err(wgpu::BufferAsyncError));
let _ = rx_dep.recv().unwrap_or(Err(wgpu::BufferAsyncError));
let object_id = {
let data = id_staging.slice(..).get_mapped_range();
u32::from_le_bytes([data[0], data[1], data[2], data[3]])
};
id_staging.unmap();
let depth = {
let data = depth_staging.slice(..).get_mapped_range();
f32::from_le_bytes([data[0], data[1], data[2], data[3]])
};
depth_staging.unmap();
if object_id == 0 {
return None;
}
Some(crate::interaction::picking::GpuPickHit {
object_id: (object_id - 1) as u64,
depth,
})
}
}