use std::num::NonZeroU64;
use super::RenderEngine;
#[derive(Debug, Clone)]
pub struct PickRange {
pub global_start: u32,
pub count: u32,
pub type_name: String,
pub name: String,
}
impl RenderEngine {
pub fn assign_pick_range(&mut self, type_name: &str, name: &str, num_elements: u32) -> u32 {
let key = (type_name.to_string(), name.to_string());
if let Some(range) = self.pick_ranges.get(&key) {
return range.global_start;
}
let global_start = self.next_global_index;
self.next_global_index += num_elements;
let range = PickRange {
global_start,
count: num_elements,
type_name: type_name.to_string(),
name: name.to_string(),
};
self.pick_ranges.insert(key, range);
global_start
}
pub fn remove_pick_range(&mut self, type_name: &str, name: &str) {
let key = (type_name.to_string(), name.to_string());
self.pick_ranges.remove(&key);
}
pub fn lookup_global_index(&self, global_index: u32) -> Option<(&str, &str, u32)> {
for range in self.pick_ranges.values() {
if global_index >= range.global_start && global_index < range.global_start + range.count
{
let local = global_index - range.global_start;
return Some((&range.type_name, &range.name, local));
}
}
None
}
pub fn get_pick_range_start(&self, type_name: &str, name: &str) -> Option<u32> {
let key = (type_name.to_string(), name.to_string());
self.pick_ranges.get(&key).map(|r| r.global_start)
}
pub fn init_pick_buffers(&mut self, width: u32, height: u32) {
if self.pick_buffer_size == (width, height) && self.pick_texture.is_some() {
return;
}
let device = &self.device;
let pick_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Pick Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let pick_texture_view = pick_texture.create_view(&wgpu::TextureViewDescriptor::default());
let pick_depth_texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("Pick Depth Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth24Plus,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let pick_depth_view =
pick_depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
let pick_staging_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Pick Staging Buffer"),
size: 256, usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
self.pick_texture = Some(pick_texture);
self.pick_texture_view = Some(pick_texture_view);
self.pick_depth_texture = Some(pick_depth_texture);
self.pick_depth_view = Some(pick_depth_view);
self.pick_staging_buffer = Some(pick_staging_buffer);
self.pick_buffer_size = (width, height);
}
pub(crate) fn init_pick_pipeline(&mut self) {
let shader_source = include_str!("../shaders/pick.wgsl");
let shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Pick Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let bind_group_layout =
self.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Pick Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(272),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(16),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let pipeline_layout = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Pick Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = self
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("PointCloud Pick Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: None, write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..wgpu::PrimitiveState::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
self.point_pick_pipeline = Some(pipeline);
self.pick_bind_group_layout = Some(bind_group_layout);
}
pub fn pick_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
self.pick_bind_group_layout
.as_ref()
.expect("pick pipeline not initialized")
}
pub fn point_pick_pipeline(&self) -> &wgpu::RenderPipeline {
self.point_pick_pipeline
.as_ref()
.expect("pick pipeline not initialized")
}
pub fn curve_network_pick_pipeline(&self) -> &wgpu::RenderPipeline {
self.curve_network_pick_pipeline
.as_ref()
.expect("curve network pick pipeline not initialized")
}
pub fn init_curve_network_pick_pipeline(&mut self) {
let shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("CurveNetwork Pick Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/pick_curve.wgsl").into()),
});
let bind_group_layout = self
.pick_bind_group_layout
.as_ref()
.expect("pick bind group layout not initialized");
let pipeline_layout = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("CurveNetwork Pick Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = self
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("CurveNetwork Pick Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: None, write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::LineList,
..wgpu::PrimitiveState::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
self.curve_network_pick_pipeline = Some(pipeline);
}
pub fn has_curve_network_pick_pipeline(&self) -> bool {
self.curve_network_pick_pipeline.is_some()
}
pub fn init_curve_network_tube_pick_pipeline(&mut self) {
let shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("CurveNetwork Tube Pick Shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/pick_curve_tube.wgsl").into(),
),
});
let bind_group_layout =
self.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("CurveNetwork Tube Pick Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(272),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(16),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let pipeline_layout = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("CurveNetwork Tube Pick Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = self
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("CurveNetwork Tube Pick Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[
wgpu::VertexBufferLayout {
array_stride: 32, step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x4,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Uint32x4,
offset: 16,
shader_location: 1,
},
],
},
],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: None, write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..wgpu::PrimitiveState::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
self.curve_network_tube_pick_pipeline = Some(pipeline);
self.curve_network_tube_pick_bind_group_layout = Some(bind_group_layout);
}
pub fn has_curve_network_tube_pick_pipeline(&self) -> bool {
self.curve_network_tube_pick_pipeline.is_some()
}
pub fn curve_network_tube_pick_pipeline(&self) -> &wgpu::RenderPipeline {
self.curve_network_tube_pick_pipeline
.as_ref()
.expect("curve network tube pick pipeline not initialized")
}
pub fn curve_network_tube_pick_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
self.curve_network_tube_pick_bind_group_layout
.as_ref()
.expect("curve network tube pick bind group layout not initialized")
}
pub fn init_mesh_pick_pipeline(&mut self) {
let shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Mesh Pick Shader"),
source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/pick_mesh.wgsl").into()),
});
let bind_group_layout =
self.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Mesh Pick Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(272),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(80),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let pipeline_layout = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Mesh Pick Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = self
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("SurfaceMesh Pick Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..wgpu::PrimitiveState::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
self.mesh_pick_pipeline = Some(pipeline);
self.mesh_pick_bind_group_layout = Some(bind_group_layout);
}
pub fn has_mesh_pick_pipeline(&self) -> bool {
self.mesh_pick_pipeline.is_some()
}
pub fn mesh_pick_pipeline(&self) -> &wgpu::RenderPipeline {
self.mesh_pick_pipeline
.as_ref()
.expect("mesh pick pipeline not initialized")
}
pub fn mesh_pick_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
self.mesh_pick_bind_group_layout
.as_ref()
.expect("mesh pick bind group layout not initialized")
}
pub fn init_gridcube_pick_pipeline(&mut self) {
let shader = self
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Gridcube Pick Shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/pick_gridcube.wgsl").into(),
),
});
let bind_group_layout =
self.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Gridcube Pick Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(272),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(80),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let pipeline_layout = self
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Gridcube Pick Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = self
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Gridcube Pick Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
..wgpu::PrimitiveState::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24Plus,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
self.gridcube_pick_pipeline = Some(pipeline);
self.gridcube_pick_bind_group_layout = Some(bind_group_layout);
}
pub fn has_gridcube_pick_pipeline(&self) -> bool {
self.gridcube_pick_pipeline.is_some()
}
pub fn gridcube_pick_pipeline(&self) -> &wgpu::RenderPipeline {
self.gridcube_pick_pipeline
.as_ref()
.expect("gridcube pick pipeline not initialized")
}
pub fn gridcube_pick_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
self.gridcube_pick_bind_group_layout
.as_ref()
.expect("gridcube pick bind group layout not initialized")
}
pub fn pick_at(&self, x: u32, y: u32) -> Option<u32> {
let pick_texture = self.pick_texture.as_ref()?;
let staging_buffer = self.pick_staging_buffer.as_ref()?;
let (width, height) = self.pick_buffer_size;
if x >= width || y >= height {
return None;
}
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Pick Readback Encoder"),
});
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture: pick_texture,
mip_level: 0,
origin: wgpu::Origin3d { x, y, z: 0 },
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: staging_buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(256), rows_per_image: Some(1),
},
},
wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
);
self.queue.submit(std::iter::once(encoder.finish()));
let buffer_slice = staging_buffer.slice(..4);
let (tx, rx) = std::sync::mpsc::channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
tx.send(result).unwrap();
});
let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
rx.recv().unwrap().ok()?;
let data = buffer_slice.get_mapped_range();
let pixel: [u8; 4] = [data[0], data[1], data[2], data[3]];
drop(data);
staging_buffer.unmap();
let global_index = crate::pick::color_to_index(pixel[0], pixel[1], pixel[2]);
Some(global_index)
}
pub fn pick_texture_view(&self) -> Option<&wgpu::TextureView> {
self.pick_texture_view.as_ref()
}
pub fn pick_depth_view(&self) -> Option<&wgpu::TextureView> {
self.pick_depth_view.as_ref()
}
pub fn begin_pick_pass<'a>(
&'a self,
encoder: &'a mut wgpu::CommandEncoder,
) -> Option<wgpu::RenderPass<'a>> {
let pick_view = self.pick_texture_view.as_ref()?;
let pick_depth = self.pick_depth_view.as_ref()?;
Some(encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Pick Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: pick_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), store: wgpu::StoreOp::Store,
},
depth_slice: None,
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: pick_depth,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
..Default::default()
}))
}
}