use super::types::{SubHighlightGpuData, SubHighlightUniform, ViewportGpuResources};
use crate::interaction::sub_object::{SubObjectRef, SubSelectionRef};
impl ViewportGpuResources {
pub(crate) fn ensure_sub_highlight_pipelines(&mut self, device: &wgpu::Device) {
if self.sub_highlight_fill_pipeline.is_some() {
return;
}
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("sub_highlight_bgl"),
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: None,
},
count: None,
}],
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("sub_highlight_layout"),
bind_group_layouts: &[&self.camera_bind_group_layout, &bgl],
push_constant_ranges: &[],
});
let fill_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("sub_highlight_fill_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/sub_highlight_fill.wgsl").into(),
),
});
let edge_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("sub_highlight_edge_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/sub_highlight_edge.wgsl").into(),
),
});
let sprite_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("sub_highlight_sprite_shader"),
source: wgpu::ShaderSource::Wgsl(
include_str!("../shaders/sub_highlight_sprite.wgsl").into(),
),
});
let make_fill = |label: &'static str, fmt: wgpu::TextureFormat| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &fill_shader,
entry_point: Some("vs_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: 12,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x3],
}],
},
fragment: Some(wgpu::FragmentState {
module: &fill_shader,
entry_point: Some("fs_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: fmt,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: None,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::LessEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState { constant: -2, slope_scale: -1.0, clamp: 0.0 },
}),
multisample: wgpu::MultisampleState { count: 1, ..Default::default() },
multiview: None,
cache: None,
})
};
let make_edge = |label: &'static str, fmt: wgpu::TextureFormat| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &edge_shader,
entry_point: Some("vs_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: 24,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3],
}],
},
fragment: Some(wgpu::FragmentState {
module: &edge_shader,
entry_point: Some("fs_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: fmt,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: None,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::LessEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState { count: 1, ..Default::default() },
multiview: None,
cache: None,
})
};
let make_sprite = |label: &'static str, fmt: wgpu::TextureFormat| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some(label),
layout: Some(&layout),
vertex: wgpu::VertexState {
module: &sprite_shader,
entry_point: Some("vs_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
buffers: &[wgpu::VertexBufferLayout {
array_stride: 12,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &wgpu::vertex_attr_array![0 => Float32x3],
}],
},
fragment: Some(wgpu::FragmentState {
module: &sprite_shader,
entry_point: Some("fs_main"),
compilation_options: wgpu::PipelineCompilationOptions::default(),
targets: &[Some(wgpu::ColorTargetState {
format: fmt,
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
cull_mode: None,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth24PlusStencil8,
depth_write_enabled: false,
depth_compare: wgpu::CompareFunction::LessEqual,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState { count: 1, ..Default::default() },
multiview: None,
cache: None,
})
};
let ldr_fmt = self.target_format;
self.sub_highlight_fill_pipeline =
Some(make_fill("sub_highlight_fill_hdr", wgpu::TextureFormat::Rgba16Float));
self.sub_highlight_edge_pipeline =
Some(make_edge("sub_highlight_edge_hdr", wgpu::TextureFormat::Rgba16Float));
self.sub_highlight_sprite_pipeline =
Some(make_sprite("sub_highlight_sprite_hdr", wgpu::TextureFormat::Rgba16Float));
self.sub_highlight_fill_ldr_pipeline =
Some(make_fill("sub_highlight_fill_ldr", ldr_fmt));
self.sub_highlight_edge_ldr_pipeline =
Some(make_edge("sub_highlight_edge_ldr", ldr_fmt));
self.sub_highlight_sprite_ldr_pipeline =
Some(make_sprite("sub_highlight_sprite_ldr", ldr_fmt));
self.sub_highlight_bgl = Some(bgl);
}
pub(crate) fn build_sub_highlight(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
sel: &SubSelectionRef,
fill_color: [f32; 4],
edge_color: [f32; 4],
edge_width: f32,
vertex_size: f32,
viewport_width: f32,
viewport_height: f32,
) -> SubHighlightGpuData {
let mut fill_verts: Vec<[f32; 3]> = Vec::new();
let mut edge_data: Vec<f32> = Vec::new();
let mut sprite_pos: Vec<[f32; 3]> = Vec::new();
for (node_id, sub_ref) in &sel.items {
let model = sel
.model_matrices
.get(node_id)
.copied()
.unwrap_or(glam::Mat4::IDENTITY);
let xform = |lp: [f32; 3]| -> [f32; 3] {
(model * glam::Vec4::new(lp[0], lp[1], lp[2], 1.0))
.truncate()
.to_array()
};
match sub_ref {
SubObjectRef::Face(i) => {
if let Some((positions, indices)) = sel.mesh_lookup.get(node_id) {
let n_tri = indices.len() / 3;
let face_raw = *i as usize;
let face = if face_raw >= n_tri { face_raw - n_tri } else { face_raw };
let base = face * 3;
if base + 2 < indices.len() {
let ia = indices[base] as usize;
let ib = indices[base + 1] as usize;
let ic = indices[base + 2] as usize;
if ia < positions.len() && ib < positions.len() && ic < positions.len()
{
let a = xform(positions[ia]);
let b = xform(positions[ib]);
let c = xform(positions[ic]);
fill_verts.extend_from_slice(&[a, b, c]);
for (p0, p1) in [(a, b), (b, c), (c, a)] {
edge_data.extend_from_slice(&p0);
edge_data.extend_from_slice(&p1);
}
}
}
}
}
SubObjectRef::Vertex(v) => {
if let Some((positions, _)) = sel.mesh_lookup.get(node_id) {
if let Some(lp) = positions.get(*v as usize) {
sprite_pos.push(xform(*lp));
}
}
}
SubObjectRef::Point(i) => {
if let Some(pts) = sel.point_positions.get(node_id) {
if let Some(p) = pts.get(*i as usize) {
sprite_pos.push(*p);
}
}
}
SubObjectRef::Voxel(flat) => {
if let Some(info) = sel.voxel_lookup.get(node_id) {
let [nx, ny, nz] = info.dims;
if nx == 0 || ny == 0 || nz == 0 {
continue;
}
let flat = *flat;
let ix = flat % nx;
let iy = (flat / nx) % ny;
let iz = flat / (nx * ny);
let bbox_min = glam::Vec3::from(info.bbox_min);
let bbox_max = glam::Vec3::from(info.bbox_max);
let cell = (bbox_max - bbox_min)
/ glam::Vec3::new(nx as f32, ny as f32, nz as f32);
let lo = bbox_min + cell * glam::Vec3::new(ix as f32, iy as f32, iz as f32);
let hi = lo + cell;
let m = glam::Mat4::from_cols_array_2d(&info.model);
let xv = |lp: glam::Vec3| -> [f32; 3] {
m.transform_point3(lp).to_array()
};
let c = [
xv(glam::Vec3::new(lo.x, lo.y, lo.z)),
xv(glam::Vec3::new(hi.x, lo.y, lo.z)),
xv(glam::Vec3::new(hi.x, hi.y, lo.z)),
xv(glam::Vec3::new(lo.x, hi.y, lo.z)),
xv(glam::Vec3::new(lo.x, lo.y, hi.z)),
xv(glam::Vec3::new(hi.x, lo.y, hi.z)),
xv(glam::Vec3::new(hi.x, hi.y, hi.z)),
xv(glam::Vec3::new(lo.x, hi.y, hi.z)),
];
for (a, b) in [
(0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7), ] {
edge_data.extend_from_slice(&c[a]);
edge_data.extend_from_slice(&c[b]);
}
}
}
_ => {}
}
}
let make_buf = |label: &str, data: &[u8]| -> wgpu::Buffer {
let size = data.len().max(1) as u64;
let buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
if !data.is_empty() {
queue.write_buffer(&buf, 0, data);
}
buf
};
let fill_vertex_buf = make_buf(
"sub_hl_fill_vb",
bytemuck::cast_slice::<[f32; 3], u8>(&fill_verts),
);
let fill_vertex_count = fill_verts.len() as u32;
let edge_vertex_buf = make_buf(
"sub_hl_edge_vb",
bytemuck::cast_slice::<f32, u8>(&edge_data),
);
let edge_segment_count = (edge_data.len() / 6) as u32;
let sprite_vertex_buf = make_buf(
"sub_hl_sprite_vb",
bytemuck::cast_slice::<[f32; 3], u8>(&sprite_pos),
);
let sprite_point_count = sprite_pos.len() as u32;
let uniform = SubHighlightUniform {
fill_color,
edge_color,
edge_width,
vertex_size,
viewport_width,
viewport_height,
};
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("sub_hl_uniform"),
size: std::mem::size_of::<SubHighlightUniform>() as u64,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&uniform_buf, 0, bytemuck::cast_slice(&[uniform]));
let (fill_bind_group, edge_bind_group, sprite_bind_group) = {
let bgl = self.sub_highlight_bgl.as_ref().unwrap();
let binding = uniform_buf.as_entire_binding();
let fill_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("sub_hl_fill_bg"),
layout: bgl,
entries: &[wgpu::BindGroupEntry { binding: 0, resource: binding.clone() }],
});
let edge_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("sub_hl_edge_bg"),
layout: bgl,
entries: &[wgpu::BindGroupEntry { binding: 0, resource: binding.clone() }],
});
let sprite_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("sub_hl_sprite_bg"),
layout: bgl,
entries: &[wgpu::BindGroupEntry { binding: 0, resource: binding }],
});
(fill_bg, edge_bg, sprite_bg)
};
SubHighlightGpuData {
fill_vertex_buf,
fill_vertex_count,
edge_vertex_buf,
edge_segment_count,
sprite_vertex_buf,
sprite_point_count,
_uniform_buf: uniform_buf,
fill_bind_group,
edge_bind_group,
sprite_bind_group,
}
}
}