Another project
0

Configure Feed

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

at main 15 kB View raw
1use wgpu::util::DeviceExt; 2 3use bone_types::{Camera3, Point3, ProjectionKind, UnitVec3, Vec3}; 4 5use crate::RenderTargets; 6use crate::ViewportRegion; 7use crate::gpu::{Gpu, PICK_FORMAT}; 8use crate::lower_f32; 9use crate::pick::PickId; 10use crate::pipelines::solid::DEPTH_FORMAT; 11use crate::scene::{EdgeScene, GenuineEdge, SilhouetteCandidate}; 12 13const DEPTH_BIAS_NDC_PER_PX: f32 = 0.001_5; 14const GENUINE_OFFSET_PX: f32 = 2.0; 15const SILHOUETTE_OFFSET_PX: f32 = 3.0; 16const EDGE_HALF_WIDTH_PX: f32 = 0.75; 17 18const STYLE_DASHED: u32 = 1 << 0; 19const STYLE_HIDDEN: u32 = 1 << 1; 20 21#[derive(Copy, Clone)] 22enum EdgeKind { 23 Genuine, 24 Silhouette, 25 Crease, 26} 27 28impl EdgeKind { 29 const fn style_bits(self) -> u32 { 30 match self { 31 Self::Genuine => 0, 32 Self::Silhouette => 1 << 2, 33 Self::Crease => 2 << 2, 34 } 35 } 36} 37 38#[derive(Copy, Clone)] 39enum EdgeLayer { 40 Visible, 41 Hidden, 42} 43 44impl EdgeLayer { 45 const fn extra_style_bits(self) -> u32 { 46 match self { 47 Self::Visible => 0, 48 Self::Hidden => STYLE_DASHED | STYLE_HIDDEN, 49 } 50 } 51 52 const fn depth_compare(self) -> wgpu::CompareFunction { 53 match self { 54 Self::Visible => wgpu::CompareFunction::LessEqual, 55 Self::Hidden => wgpu::CompareFunction::Greater, 56 } 57 } 58} 59 60#[derive(Copy, Clone)] 61pub(crate) enum HiddenEdges { 62 Omit, 63 Dashed, 64} 65 66#[repr(C)] 67#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 68struct EdgeInstance { 69 a: [f32; 3], 70 b: [f32; 3], 71 half_width_px: f32, 72 depth_offset_px: f32, 73 style_bits: u32, 74 pick_id: u32, 75} 76 77const INSTANCE_STRIDE: u64 = core::mem::size_of::<EdgeInstance>() as u64; 78 79#[repr(C, align(16))] 80#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 81struct EdgeFrame { 82 clip_from_world: [f32; 16], 83 edge_color: [f32; 4], 84 hidden_color: [f32; 4], 85 viewport_px: [f32; 2], 86 dash_period_px: f32, 87 dash_on_ratio: f32, 88 depth_bias_ndc: f32, 89 _pad: [f32; 3], 90} 91 92const UNIFORM_SIZE: u64 = core::mem::size_of::<EdgeFrame>() as u64; 93 94const INSTANCE_ATTRS: [wgpu::VertexAttribute; 6] = wgpu::vertex_attr_array![ 95 0 => Float32x3, 96 1 => Float32x3, 97 2 => Float32, 98 3 => Float32, 99 4 => Uint32, 100 5 => Uint32, 101]; 102 103#[derive(Copy, Clone)] 104pub(crate) enum EdgeProjection { 105 Orthographic { toward_viewer: Vec3 }, 106 Perspective { eye: Point3 }, 107} 108 109impl EdgeProjection { 110 #[must_use] 111 pub fn from_camera(camera: Camera3) -> Self { 112 match camera.projection().kind() { 113 ProjectionKind::Orthographic { .. } => Self::Orthographic { 114 toward_viewer: camera.eye() - camera.target(), 115 }, 116 ProjectionKind::Perspective { .. } => Self::Perspective { eye: camera.eye() }, 117 } 118 } 119 120 fn view_at(self, point: Point3) -> Vec3 { 121 match self { 122 Self::Orthographic { toward_viewer } => toward_viewer, 123 Self::Perspective { eye } => eye - point, 124 } 125 } 126} 127 128#[derive(Copy, Clone)] 129pub(crate) struct EdgeView { 130 pub clip_from_world: [f32; 16], 131 pub projection: EdgeProjection, 132 pub viewport_px: [f32; 2], 133 pub crease_threshold_rad: f64, 134 pub dash_period_px: f32, 135 pub dash_on_ratio: f32, 136 pub edge_color: [f32; 4], 137 pub hidden_color: [f32; 4], 138 pub region: ViewportRegion, 139} 140 141pub(crate) struct Edge3dPipeline { 142 device: wgpu::Device, 143 queue: wgpu::Queue, 144 visible: wgpu::RenderPipeline, 145 hidden: wgpu::RenderPipeline, 146 uniform_buffer: wgpu::Buffer, 147 bind_group: wgpu::BindGroup, 148} 149 150impl Edge3dPipeline { 151 #[must_use] 152 pub fn new(gpu: &Gpu, color_format: wgpu::TextureFormat) -> Self { 153 let device = gpu.device().clone(); 154 let queue = gpu.queue().clone(); 155 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { 156 label: Some("bone-render:edge3d-shader"), 157 source: wgpu::ShaderSource::Wgsl(include_str!("edge_3d.wgsl").into()), 158 }); 159 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 160 label: Some("bone-render:edge3d-bgl"), 161 entries: &[wgpu::BindGroupLayoutEntry { 162 binding: 0, 163 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, 164 ty: wgpu::BindingType::Buffer { 165 ty: wgpu::BufferBindingType::Uniform, 166 has_dynamic_offset: false, 167 min_binding_size: wgpu::BufferSize::new(UNIFORM_SIZE), 168 }, 169 count: None, 170 }], 171 }); 172 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 173 label: Some("bone-render:edge3d-layout"), 174 bind_group_layouts: &[Some(&bind_group_layout)], 175 immediate_size: 0, 176 }); 177 let visible = build_pipeline( 178 &device, 179 &pipeline_layout, 180 &shader, 181 color_format, 182 EdgeLayer::Visible.depth_compare(), 183 ); 184 let hidden = build_pipeline( 185 &device, 186 &pipeline_layout, 187 &shader, 188 color_format, 189 EdgeLayer::Hidden.depth_compare(), 190 ); 191 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { 192 label: Some("bone-render:edge3d-uniform"), 193 size: UNIFORM_SIZE, 194 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 195 mapped_at_creation: false, 196 }); 197 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { 198 label: Some("bone-render:edge3d-bg"), 199 layout: &bind_group_layout, 200 entries: &[wgpu::BindGroupEntry { 201 binding: 0, 202 resource: uniform_buffer.as_entire_binding(), 203 }], 204 }); 205 Self { 206 device, 207 queue, 208 visible, 209 hidden, 210 uniform_buffer, 211 bind_group, 212 } 213 } 214 215 pub fn draw( 216 &self, 217 encoder: &mut wgpu::CommandEncoder, 218 targets: RenderTargets<'_>, 219 depth_view: &wgpu::TextureView, 220 scene: &EdgeScene, 221 view: EdgeView, 222 hidden: HiddenEdges, 223 ) { 224 let visible = build_instances(scene, view, EdgeLayer::Visible.extra_style_bits()); 225 let occluded = match hidden { 226 HiddenEdges::Dashed => { 227 build_instances(scene, view, EdgeLayer::Hidden.extra_style_bits()) 228 } 229 HiddenEdges::Omit => Vec::new(), 230 }; 231 if visible.is_empty() && occluded.is_empty() { 232 return; 233 } 234 let uniform = EdgeFrame { 235 clip_from_world: view.clip_from_world, 236 edge_color: view.edge_color, 237 hidden_color: view.hidden_color, 238 viewport_px: view.viewport_px, 239 dash_period_px: view.dash_period_px, 240 dash_on_ratio: view.dash_on_ratio, 241 depth_bias_ndc: DEPTH_BIAS_NDC_PER_PX, 242 _pad: [0.0; 3], 243 }; 244 self.queue 245 .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform)); 246 let visible_buffer = (!visible.is_empty()).then(|| self.instance_buffer(&visible)); 247 let occluded_buffer = (!occluded.is_empty()).then(|| self.instance_buffer(&occluded)); 248 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 249 label: Some("bone-render:edge3d-pass"), 250 color_attachments: &[ 251 Some(wgpu::RenderPassColorAttachment { 252 view: targets.color, 253 resolve_target: None, 254 depth_slice: None, 255 ops: wgpu::Operations { 256 load: wgpu::LoadOp::Load, 257 store: wgpu::StoreOp::Store, 258 }, 259 }), 260 Some(wgpu::RenderPassColorAttachment { 261 view: targets.pick, 262 resolve_target: None, 263 depth_slice: None, 264 ops: wgpu::Operations { 265 load: wgpu::LoadOp::Load, 266 store: wgpu::StoreOp::Store, 267 }, 268 }), 269 ], 270 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { 271 view: depth_view, 272 depth_ops: Some(wgpu::Operations { 273 load: wgpu::LoadOp::Load, 274 store: wgpu::StoreOp::Store, 275 }), 276 stencil_ops: None, 277 }), 278 timestamp_writes: None, 279 occlusion_query_set: None, 280 multiview_mask: None, 281 }); 282 crate::apply_viewport_region(&mut pass, view.region); 283 pass.set_bind_group(0, &self.bind_group, &[]); 284 if let Some(buffer) = &visible_buffer { 285 record_layer(&mut pass, &self.visible, buffer, visible.len()); 286 } 287 if let Some(buffer) = &occluded_buffer { 288 record_layer(&mut pass, &self.hidden, buffer, occluded.len()); 289 } 290 } 291 292 fn instance_buffer(&self, instances: &[EdgeInstance]) -> wgpu::Buffer { 293 self.device 294 .create_buffer_init(&wgpu::util::BufferInitDescriptor { 295 label: Some("bone-render:edge3d-instances"), 296 contents: bytemuck::cast_slice(instances), 297 usage: wgpu::BufferUsages::VERTEX, 298 }) 299 } 300} 301 302fn record_layer( 303 pass: &mut wgpu::RenderPass<'_>, 304 pipeline: &wgpu::RenderPipeline, 305 buffer: &wgpu::Buffer, 306 len: usize, 307) { 308 let Ok(count) = u32::try_from(len) else { 309 panic!("edge instance count {len} exceeds u32::MAX"); 310 }; 311 pass.set_pipeline(pipeline); 312 pass.set_vertex_buffer(0, buffer.slice(..)); 313 pass.draw(0..6, 0..count); 314} 315 316impl core::fmt::Debug for Edge3dPipeline { 317 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 318 f.debug_struct("Edge3dPipeline").finish_non_exhaustive() 319 } 320} 321 322fn build_pipeline( 323 device: &wgpu::Device, 324 layout: &wgpu::PipelineLayout, 325 shader: &wgpu::ShaderModule, 326 color_format: wgpu::TextureFormat, 327 depth_compare: wgpu::CompareFunction, 328) -> wgpu::RenderPipeline { 329 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 330 label: Some("bone-render:edge3d-pipeline"), 331 layout: Some(layout), 332 vertex: wgpu::VertexState { 333 module: shader, 334 entry_point: Some("vs"), 335 compilation_options: wgpu::PipelineCompilationOptions::default(), 336 buffers: &[wgpu::VertexBufferLayout { 337 array_stride: INSTANCE_STRIDE, 338 step_mode: wgpu::VertexStepMode::Instance, 339 attributes: &INSTANCE_ATTRS, 340 }], 341 }, 342 fragment: Some(wgpu::FragmentState { 343 module: shader, 344 entry_point: Some("fs"), 345 compilation_options: wgpu::PipelineCompilationOptions::default(), 346 targets: &[ 347 Some(wgpu::ColorTargetState { 348 format: color_format, 349 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), 350 write_mask: wgpu::ColorWrites::ALL, 351 }), 352 Some(wgpu::ColorTargetState { 353 format: PICK_FORMAT, 354 blend: None, 355 write_mask: wgpu::ColorWrites::ALL, 356 }), 357 ], 358 }), 359 primitive: wgpu::PrimitiveState { 360 topology: wgpu::PrimitiveTopology::TriangleList, 361 strip_index_format: None, 362 front_face: wgpu::FrontFace::Ccw, 363 cull_mode: None, 364 polygon_mode: wgpu::PolygonMode::Fill, 365 conservative: false, 366 unclipped_depth: false, 367 }, 368 depth_stencil: Some(wgpu::DepthStencilState { 369 format: DEPTH_FORMAT, 370 depth_write_enabled: Some(false), 371 depth_compare: Some(depth_compare), 372 stencil: wgpu::StencilState::default(), 373 bias: wgpu::DepthBiasState::default(), 374 }), 375 multisample: wgpu::MultisampleState::default(), 376 multiview_mask: None, 377 cache: None, 378 }) 379} 380 381fn build_instances(scene: &EdgeScene, view: EdgeView, extra_bits: u32) -> Vec<EdgeInstance> { 382 let genuine = scene 383 .genuine() 384 .iter() 385 .copied() 386 .map(|edge| genuine_instance(edge, view.crease_threshold_rad, extra_bits)); 387 let silhouette = scene 388 .silhouettes() 389 .iter() 390 .copied() 391 .filter(|candidate| straddles(*candidate, view.projection)) 392 .map(|candidate| silhouette_instance(candidate, extra_bits)); 393 genuine.chain(silhouette).collect() 394} 395 396fn genuine_instance(edge: GenuineEdge, threshold_rad: f64, extra_bits: u32) -> EdgeInstance { 397 let kind = if edge.crease().radians() >= threshold_rad { 398 EdgeKind::Crease 399 } else { 400 EdgeKind::Genuine 401 }; 402 instance( 403 edge.a(), 404 edge.b(), 405 edge.pick(), 406 kind, 407 GENUINE_OFFSET_PX, 408 extra_bits, 409 ) 410} 411 412fn silhouette_instance(candidate: SilhouetteCandidate, extra_bits: u32) -> EdgeInstance { 413 instance( 414 candidate.a(), 415 candidate.b(), 416 candidate.pick(), 417 EdgeKind::Silhouette, 418 SILHOUETTE_OFFSET_PX, 419 extra_bits, 420 ) 421} 422 423fn instance( 424 a: Point3, 425 b: Point3, 426 pick: PickId, 427 kind: EdgeKind, 428 depth_offset_px: f32, 429 extra_bits: u32, 430) -> EdgeInstance { 431 EdgeInstance { 432 a: lower_point(a), 433 b: lower_point(b), 434 half_width_px: EDGE_HALF_WIDTH_PX, 435 depth_offset_px, 436 style_bits: kind.style_bits() | extra_bits, 437 pick_id: pick.raw(), 438 } 439} 440 441fn lower_point(p: Point3) -> [f32; 3] { 442 let (x, y, z) = p.coords_mm(); 443 [lower_f32(x), lower_f32(y), lower_f32(z)] 444} 445 446fn straddles(candidate: SilhouetteCandidate, projection: EdgeProjection) -> bool { 447 let view = projection.view_at(midpoint(candidate.a(), candidate.b())); 448 let front_a = dot_unit_vec(candidate.normal_a(), view); 449 let front_b = dot_unit_vec(candidate.normal_b(), view); 450 front_a * front_b < 0.0 451} 452 453fn midpoint(a: Point3, b: Point3) -> Point3 { 454 let (ax, ay, az) = a.coords_mm(); 455 let (bx, by, bz) = b.coords_mm(); 456 Point3::from_mm(0.5 * (ax + bx), 0.5 * (ay + by), 0.5 * (az + bz)) 457} 458 459fn dot_unit_vec(unit: UnitVec3, vec: Vec3) -> f64 { 460 let (ux, uy, uz) = unit.components(); 461 let (vx, vy, vz) = vec.coords_mm(); 462 ux * vx + uy * vy + uz * vz 463}