Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 11 kB View raw
1use wgpu::util::DeviceExt; 2 3use crate::RenderTargets; 4use crate::camera::Camera2; 5use crate::gpu::{Gpu, PICK_FORMAT}; 6use crate::pick::PickId; 7use crate::pipelines::{CONSTRUCTION_BIT, FRAME_UNIFORM_SIZE, build_frame_uniform}; 8use crate::preview::SketchPreview; 9use crate::scene::{SceneLine, ScenePoint, SketchScene}; 10use crate::snapshot::Style; 11use bone_types::Point2; 12 13#[repr(C)] 14#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 15struct LineInstance { 16 a: [f32; 2], 17 b: [f32; 2], 18 half_width_px: f32, 19 pick_id: u32, 20 style_bits: u32, 21} 22 23const INSTANCE_STRIDE: u64 = core::mem::size_of::<LineInstance>() as u64; 24 25pub struct LinesPipeline { 26 device: wgpu::Device, 27 queue: wgpu::Queue, 28 pipeline: wgpu::RenderPipeline, 29 uniform_buffer: wgpu::Buffer, 30 bind_group: wgpu::BindGroup, 31} 32 33impl LinesPipeline { 34 #[must_use] 35 pub fn new(gpu: &Gpu, color_format: wgpu::TextureFormat) -> Self { 36 let device = gpu.device().clone(); 37 let queue = gpu.queue().clone(); 38 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { 39 label: Some("bone-render:lines-shader"), 40 source: wgpu::ShaderSource::Wgsl(include_str!("lines.wgsl").into()), 41 }); 42 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 43 label: Some("bone-render:lines-bgl"), 44 entries: &[wgpu::BindGroupLayoutEntry { 45 binding: 0, 46 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, 47 ty: wgpu::BindingType::Buffer { 48 ty: wgpu::BufferBindingType::Uniform, 49 has_dynamic_offset: false, 50 min_binding_size: wgpu::BufferSize::new(FRAME_UNIFORM_SIZE), 51 }, 52 count: None, 53 }], 54 }); 55 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 56 label: Some("bone-render:lines-layout"), 57 bind_group_layouts: &[Some(&bind_group_layout)], 58 immediate_size: 0, 59 }); 60 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 61 label: Some("bone-render:lines-pipeline"), 62 layout: Some(&pipeline_layout), 63 vertex: wgpu::VertexState { 64 module: &shader, 65 entry_point: Some("vs"), 66 compilation_options: wgpu::PipelineCompilationOptions::default(), 67 buffers: &[wgpu::VertexBufferLayout { 68 array_stride: INSTANCE_STRIDE, 69 step_mode: wgpu::VertexStepMode::Instance, 70 attributes: &INSTANCE_ATTRS, 71 }], 72 }, 73 fragment: Some(wgpu::FragmentState { 74 module: &shader, 75 entry_point: Some("fs"), 76 compilation_options: wgpu::PipelineCompilationOptions::default(), 77 targets: &[ 78 Some(wgpu::ColorTargetState { 79 format: color_format, 80 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), 81 write_mask: wgpu::ColorWrites::ALL, 82 }), 83 Some(wgpu::ColorTargetState { 84 format: PICK_FORMAT, 85 blend: None, 86 write_mask: wgpu::ColorWrites::ALL, 87 }), 88 ], 89 }), 90 primitive: wgpu::PrimitiveState { 91 topology: wgpu::PrimitiveTopology::TriangleList, 92 strip_index_format: None, 93 front_face: wgpu::FrontFace::Ccw, 94 cull_mode: None, 95 polygon_mode: wgpu::PolygonMode::Fill, 96 conservative: false, 97 unclipped_depth: false, 98 }, 99 depth_stencil: None, 100 multisample: wgpu::MultisampleState::default(), 101 multiview_mask: None, 102 cache: None, 103 }); 104 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { 105 label: Some("bone-render:lines-uniform"), 106 size: FRAME_UNIFORM_SIZE, 107 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 108 mapped_at_creation: false, 109 }); 110 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 111 label: Some("bone-render:lines-bg"), 112 layout: &bind_group_layout, 113 entries: &[wgpu::BindGroupEntry { 114 binding: 0, 115 resource: uniform_buffer.as_entire_binding(), 116 }], 117 }); 118 Self { 119 device, 120 queue, 121 pipeline, 122 uniform_buffer, 123 bind_group, 124 } 125 } 126 127 pub fn draw( 128 &self, 129 encoder: &mut wgpu::CommandEncoder, 130 targets: RenderTargets<'_>, 131 camera: Camera2, 132 style: &Style, 133 scene: &SketchScene, 134 preview: &SketchPreview, 135 ) { 136 let instances = build_instances(scene, preview, style); 137 if instances.is_empty() { 138 return; 139 } 140 let uniform = build_frame_uniform(camera, style); 141 self.queue 142 .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform)); 143 let instance_buffer = self 144 .device 145 .create_buffer_init(&wgpu::util::BufferInitDescriptor { 146 label: Some("bone-render:lines-instances"), 147 contents: bytemuck::cast_slice(&instances), 148 usage: wgpu::BufferUsages::VERTEX, 149 }); 150 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 151 label: Some("bone-render:lines-pass"), 152 color_attachments: &[ 153 Some(wgpu::RenderPassColorAttachment { 154 view: targets.color, 155 resolve_target: None, 156 depth_slice: None, 157 ops: wgpu::Operations { 158 load: wgpu::LoadOp::Load, 159 store: wgpu::StoreOp::Store, 160 }, 161 }), 162 Some(wgpu::RenderPassColorAttachment { 163 view: targets.pick, 164 resolve_target: None, 165 depth_slice: None, 166 ops: wgpu::Operations { 167 load: wgpu::LoadOp::Load, 168 store: wgpu::StoreOp::Store, 169 }, 170 }), 171 ], 172 depth_stencil_attachment: None, 173 timestamp_writes: None, 174 occlusion_query_set: None, 175 multiview_mask: None, 176 }); 177 pass.set_pipeline(&self.pipeline); 178 pass.set_bind_group(0, &self.bind_group, &[]); 179 pass.set_vertex_buffer(0, instance_buffer.slice(..)); 180 let len = instances.len(); 181 let Ok(count) = u32::try_from(len) else { 182 panic!("line instance count {len} exceeds u32::MAX"); 183 }; 184 pass.draw(0..6, 0..count); 185 } 186} 187 188impl core::fmt::Debug for LinesPipeline { 189 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 190 f.debug_struct("LinesPipeline").finish_non_exhaustive() 191 } 192} 193 194const INSTANCE_ATTRS: [wgpu::VertexAttribute; 5] = wgpu::vertex_attr_array![ 195 0 => Float32x2, 196 1 => Float32x2, 197 2 => Float32, 198 3 => Uint32, 199 4 => Uint32, 200]; 201 202fn build_instances( 203 scene: &SketchScene, 204 preview: &SketchPreview, 205 style: &Style, 206) -> Vec<LineInstance> { 207 let lines = scene.lines().iter().map(|l| line_instance(*l, style)); 208 let points = scene.points().iter().map(|p| point_instance(*p, style)); 209 let preview_segments = preview 210 .segments 211 .iter() 212 .map(|(a, b)| preview_line_instance(*a, *b, style)); 213 let preview_anchors = preview 214 .anchors 215 .iter() 216 .map(|p| preview_point_instance(*p, style)); 217 let preview_snap = preview 218 .snap 219 .iter() 220 .map(|p| preview_snap_instance(*p, style)); 221 lines 222 .chain(points) 223 .chain(preview_segments) 224 .chain(preview_anchors) 225 .chain(preview_snap) 226 .collect() 227} 228 229#[allow( 230 clippy::cast_possible_truncation, 231 reason = "mm coordinates fit f32 mantissa at CAD scales" 232)] 233fn line_instance(line: SceneLine, style: &Style) -> LineInstance { 234 let (ax, ay) = line.a().coords_mm(); 235 let (bx, by) = line.b().coords_mm(); 236 let bits = if line.for_construction() { 237 CONSTRUCTION_BIT 238 } else { 239 0 240 }; 241 LineInstance { 242 a: [ax as f32, ay as f32], 243 b: [bx as f32, by as f32], 244 half_width_px: style.strokes().stroke_width_px() * 0.5, 245 pick_id: line.pick().raw(), 246 style_bits: bits, 247 } 248} 249 250#[allow( 251 clippy::cast_possible_truncation, 252 reason = "mm coordinates fit f32 mantissa at CAD scales" 253)] 254fn point_instance(point: ScenePoint, style: &Style) -> LineInstance { 255 let (x, y) = point.at().coords_mm(); 256 let xy = [x as f32, y as f32]; 257 LineInstance { 258 a: xy, 259 b: xy, 260 half_width_px: style.strokes().point_radius_px(), 261 pick_id: point.pick().raw(), 262 style_bits: 0, 263 } 264} 265 266#[allow( 267 clippy::cast_possible_truncation, 268 reason = "mm coordinates fit f32 mantissa at CAD scales" 269)] 270fn preview_line_instance(a: Point2, b: Point2, style: &Style) -> LineInstance { 271 let (ax, ay) = a.coords_mm(); 272 let (bx, by) = b.coords_mm(); 273 LineInstance { 274 a: [ax as f32, ay as f32], 275 b: [bx as f32, by as f32], 276 half_width_px: style.strokes().stroke_width_px() * 0.5, 277 pick_id: PickId::NONE.raw(), 278 style_bits: 0, 279 } 280} 281 282#[allow( 283 clippy::cast_possible_truncation, 284 reason = "mm coordinates fit f32 mantissa at CAD scales" 285)] 286fn preview_point_instance(at: Point2, style: &Style) -> LineInstance { 287 let (x, y) = at.coords_mm(); 288 let xy = [x as f32, y as f32]; 289 LineInstance { 290 a: xy, 291 b: xy, 292 half_width_px: style.strokes().point_radius_px(), 293 pick_id: PickId::NONE.raw(), 294 style_bits: 0, 295 } 296} 297 298const SNAP_RADIUS_RATIO: f32 = 1.8; 299 300#[allow( 301 clippy::cast_possible_truncation, 302 reason = "mm coordinates fit f32 mantissa at CAD scales" 303)] 304fn preview_snap_instance(at: Point2, style: &Style) -> LineInstance { 305 let (x, y) = at.coords_mm(); 306 let xy = [x as f32, y as f32]; 307 LineInstance { 308 a: xy, 309 b: xy, 310 half_width_px: style.strokes().point_radius_px() * SNAP_RADIUS_RATIO, 311 pick_id: PickId::NONE.raw(), 312 style_bits: CONSTRUCTION_BIT, 313 } 314}