Another project
0

Configure Feed

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

feat(render): display-mode

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (Jun 6, 2026, 9:40 AM +0300) commit be9a9020 parent c3ec5e84 change-id tnpspksv
+435 -189
+131 -21
crates/bone-render/src/lib.rs
··· 20 20 EntityKindTag, PickAperture, PickId, PickIdError, PickIndex, PickQuery, PickedItem, Picker, 21 21 }; 22 22 pub use pipelines::{ 23 - ArcPipeline, ChromeInstance, ChromePipeline, ChromeTextPipeline, Edge3dPipeline, 24 - EdgeProjection, EdgeView, FaceFill, GlyphPipeline, GridPipeline, LinesPipeline, 25 - SdfGlyphInstance, SolidPipeline, SolidView, TextPipeline, 23 + ArcPipeline, ChromeInstance, ChromePipeline, ChromeTextPipeline, GlyphPipeline, GridPipeline, 24 + LinesPipeline, SdfGlyphInstance, TextPipeline, 25 + }; 26 + pub(crate) use pipelines::{ 27 + Edge3dPipeline, EdgeProjection, EdgeView, FaceFill, HiddenEdges, SolidPipeline, SolidView, 26 28 }; 27 29 pub use preview::{PreviewArc, PreviewCircle, SketchPreview}; 28 30 pub use scene::{ ··· 30 32 ScenePoint, SceneRelationGlyph, SilhouetteCandidate, SketchScene, SolidScene, 31 33 }; 32 34 pub use snapshot::{ 33 - ClearColor, GlyphStyle, GridStyle, SnapshotFrame, StrokeStyle, Style, TextStyle, decode_png, 34 - encode_png, 35 + ClearColor, EdgeStyle, GlyphStyle, GridStyle, SnapshotFrame, StrokeStyle, Style, TextStyle, 36 + decode_png, encode_png, 35 37 }; 36 38 pub use surface::{SurfaceContext, SurfaceError}; 37 39 ··· 217 219 camera: bone_types::Camera3, 218 220 style: &Style, 219 221 ) -> Result<SnapshotFrame> { 220 - self.render_with_edges( 222 + self.render_display( 221 223 ctx, 222 224 scene, 223 225 &EdgeScene::empty(), 224 226 camera, 225 227 style, 226 - FaceFill::Shaded, 228 + bone_types::DisplayMode::ShadedNoEdges, 227 229 ) 228 230 } 229 231 230 - pub fn render_with_edges( 232 + pub fn render_display( 231 233 &mut self, 232 234 ctx: &OffscreenContext, 233 235 scene: &SolidScene, 234 236 edges: &EdgeScene, 235 237 camera: bone_types::Camera3, 236 238 style: &Style, 237 - fill: FaceFill, 239 + mode: bone_types::DisplayMode, 238 240 ) -> Result<SnapshotFrame> { 241 + let plan = DisplayPlan::for_mode(mode); 239 242 let extent = ctx.extent(); 240 243 let clip_from_world = camera3::clip_from_world(camera, extent)?; 241 244 let (ex, ey, ez) = camera.eye().coords_mm(); ··· 254 257 .create_view(&wgpu::TextureViewDescriptor::default()); 255 258 let solid = &self.solid; 256 259 let edge_pipeline = &self.edges; 257 - let solid_view = SolidView { 258 - clip_from_world, 259 - eye_world, 260 - shading: self.shading, 261 - fill, 262 - }; 260 + let shading = self.shading; 263 261 let edge_view = EdgeView { 264 262 clip_from_world, 265 263 projection: EdgeProjection::from_camera(camera), ··· 268 266 lower_f32(f64::from(extent.height().value())), 269 267 ], 270 268 crease_threshold_rad: CREASE_THRESHOLD_RAD, 271 - dash_period_px: EDGE_DASH_DISABLED_PX, 272 - dash_on_ratio: 0.0, 269 + dash_period_px: HIDDEN_DASH_PERIOD_PX, 270 + dash_on_ratio: HIDDEN_DASH_ON_RATIO, 271 + edge_color: style.edges().visible().to_rgba_array(), 272 + hidden_color: style.edges().hidden().to_rgba_array(), 273 273 }; 274 274 ctx.render(|encoder, color_view, pick_view| { 275 275 let targets = RenderTargets::new(color_view, pick_view); 276 - solid.draw(encoder, targets, &depth_view, scene, solid_view, style); 277 - if !edges.is_empty() { 278 - edge_pipeline.draw(encoder, targets, &depth_view, edges, edge_view); 276 + match plan.solid { 277 + Some(fill) => solid.draw( 278 + encoder, 279 + targets, 280 + &depth_view, 281 + scene, 282 + SolidView { 283 + clip_from_world, 284 + eye_world, 285 + shading, 286 + fill, 287 + }, 288 + style, 289 + ), 290 + None => clear_solid_targets(encoder, targets, &depth_view, style), 291 + } 292 + if plan.edges && !edges.is_empty() { 293 + edge_pipeline.draw(encoder, targets, &depth_view, edges, edge_view, plan.hidden); 279 294 } 280 295 }) 281 296 } 282 297 } 283 298 299 + struct DisplayPlan { 300 + solid: Option<FaceFill>, 301 + edges: bool, 302 + hidden: HiddenEdges, 303 + } 304 + 305 + impl DisplayPlan { 306 + const fn for_mode(mode: bone_types::DisplayMode) -> Self { 307 + use bone_types::DisplayMode; 308 + match mode { 309 + DisplayMode::Wireframe => Self { 310 + solid: None, 311 + edges: true, 312 + hidden: HiddenEdges::Omit, 313 + }, 314 + DisplayMode::HiddenLineRemoved => Self { 315 + solid: Some(FaceFill::Occluder), 316 + edges: true, 317 + hidden: HiddenEdges::Omit, 318 + }, 319 + DisplayMode::HiddenLineGray => Self { 320 + solid: Some(FaceFill::Occluder), 321 + edges: true, 322 + hidden: HiddenEdges::Dashed, 323 + }, 324 + DisplayMode::ShadedWithEdges => Self { 325 + solid: Some(FaceFill::Shaded), 326 + edges: true, 327 + hidden: HiddenEdges::Omit, 328 + }, 329 + DisplayMode::ShadedNoEdges => Self { 330 + solid: Some(FaceFill::Shaded), 331 + edges: false, 332 + hidden: HiddenEdges::Omit, 333 + }, 334 + } 335 + } 336 + } 337 + 338 + pub(crate) fn solid_clear_color_attachments<'a>( 339 + targets: RenderTargets<'a>, 340 + style: &Style, 341 + ) -> [Option<wgpu::RenderPassColorAttachment<'a>>; 2] { 342 + [ 343 + Some(wgpu::RenderPassColorAttachment { 344 + view: targets.color, 345 + resolve_target: None, 346 + depth_slice: None, 347 + ops: wgpu::Operations { 348 + load: wgpu::LoadOp::Clear(style.background().into()), 349 + store: wgpu::StoreOp::Store, 350 + }, 351 + }), 352 + Some(wgpu::RenderPassColorAttachment { 353 + view: targets.pick, 354 + resolve_target: None, 355 + depth_slice: None, 356 + ops: wgpu::Operations { 357 + load: wgpu::LoadOp::Clear(gpu::pick_clear_color()), 358 + store: wgpu::StoreOp::Store, 359 + }, 360 + }), 361 + ] 362 + } 363 + 364 + pub(crate) fn solid_clear_depth_attachment( 365 + depth_view: &wgpu::TextureView, 366 + ) -> wgpu::RenderPassDepthStencilAttachment<'_> { 367 + wgpu::RenderPassDepthStencilAttachment { 368 + view: depth_view, 369 + depth_ops: Some(wgpu::Operations { 370 + load: wgpu::LoadOp::Clear(1.0), 371 + store: wgpu::StoreOp::Store, 372 + }), 373 + stencil_ops: None, 374 + } 375 + } 376 + 377 + fn clear_solid_targets( 378 + encoder: &mut wgpu::CommandEncoder, 379 + targets: RenderTargets<'_>, 380 + depth_view: &wgpu::TextureView, 381 + style: &Style, 382 + ) { 383 + let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 384 + label: Some("bone-render:solid-clear"), 385 + color_attachments: &solid_clear_color_attachments(targets, style), 386 + depth_stencil_attachment: Some(solid_clear_depth_attachment(depth_view)), 387 + timestamp_writes: None, 388 + occlusion_query_set: None, 389 + multiview_mask: None, 390 + }); 391 + } 392 + 284 393 const CREASE_THRESHOLD_RAD: f64 = core::f64::consts::FRAC_PI_6; 285 - const EDGE_DASH_DISABLED_PX: f32 = 0.0; 394 + const HIDDEN_DASH_PERIOD_PX: f32 = 6.0; 395 + const HIDDEN_DASH_ON_RATIO: f32 = 0.5; 286 396 287 397 fn depth_texture(device: &wgpu::Device, extent: ViewportExtent) -> wgpu::Texture { 288 398 device.create_texture(&wgpu::TextureDescriptor {
+171 -82
crates/bone-render/src/pipelines/edge_3d.rs
··· 9 9 use crate::pipelines::solid::DEPTH_FORMAT; 10 10 use crate::scene::{EdgeScene, GenuineEdge, SilhouetteCandidate}; 11 11 12 - const EDGE_COLOR: [f32; 4] = [0.90, 0.90, 0.93, 1.0]; 13 - const HIDDEN_COLOR: [f32; 4] = [0.45, 0.45, 0.50, 1.0]; 14 12 const DEPTH_BIAS_NDC_PER_PX: f32 = 0.001_5; 15 13 const GENUINE_OFFSET_PX: f32 = 2.0; 16 14 const SILHOUETTE_OFFSET_PX: f32 = 3.0; 17 15 const EDGE_HALF_WIDTH_PX: f32 = 0.75; 16 + 17 + const STYLE_DASHED: u32 = 1 << 0; 18 + const STYLE_HIDDEN: u32 = 1 << 1; 18 19 19 20 #[derive(Copy, Clone)] 20 21 enum EdgeKind { ··· 33 34 } 34 35 } 35 36 37 + #[derive(Copy, Clone)] 38 + enum EdgeLayer { 39 + Visible, 40 + Hidden, 41 + } 42 + 43 + impl EdgeLayer { 44 + const fn extra_style_bits(self) -> u32 { 45 + match self { 46 + Self::Visible => 0, 47 + Self::Hidden => STYLE_DASHED | STYLE_HIDDEN, 48 + } 49 + } 50 + 51 + const fn depth_compare(self) -> wgpu::CompareFunction { 52 + match self { 53 + Self::Visible => wgpu::CompareFunction::LessEqual, 54 + Self::Hidden => wgpu::CompareFunction::Greater, 55 + } 56 + } 57 + } 58 + 59 + #[derive(Copy, Clone)] 60 + pub(crate) enum HiddenEdges { 61 + Omit, 62 + Dashed, 63 + } 64 + 36 65 #[repr(C)] 37 66 #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 38 67 struct EdgeInstance { ··· 71 100 ]; 72 101 73 102 #[derive(Copy, Clone)] 74 - pub enum EdgeProjection { 103 + pub(crate) enum EdgeProjection { 75 104 Orthographic { toward_viewer: Vec3 }, 76 105 Perspective { eye: Point3 }, 77 106 } ··· 96 125 } 97 126 98 127 #[derive(Copy, Clone)] 99 - pub struct EdgeView { 128 + pub(crate) struct EdgeView { 100 129 pub clip_from_world: [f32; 16], 101 130 pub projection: EdgeProjection, 102 131 pub viewport_px: [f32; 2], 103 132 pub crease_threshold_rad: f64, 104 133 pub dash_period_px: f32, 105 134 pub dash_on_ratio: f32, 135 + pub edge_color: [f32; 4], 136 + pub hidden_color: [f32; 4], 106 137 } 107 138 108 - pub struct Edge3dPipeline { 139 + pub(crate) struct Edge3dPipeline { 109 140 device: wgpu::Device, 110 141 queue: wgpu::Queue, 111 - pipeline: wgpu::RenderPipeline, 142 + visible: wgpu::RenderPipeline, 143 + hidden: wgpu::RenderPipeline, 112 144 uniform_buffer: wgpu::Buffer, 113 145 bind_group: wgpu::BindGroup, 114 146 } ··· 140 172 bind_group_layouts: &[Some(&bind_group_layout)], 141 173 immediate_size: 0, 142 174 }); 143 - let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 144 - label: Some("bone-render:edge3d-pipeline"), 145 - layout: Some(&pipeline_layout), 146 - vertex: wgpu::VertexState { 147 - module: &shader, 148 - entry_point: Some("vs"), 149 - compilation_options: wgpu::PipelineCompilationOptions::default(), 150 - buffers: &[wgpu::VertexBufferLayout { 151 - array_stride: INSTANCE_STRIDE, 152 - step_mode: wgpu::VertexStepMode::Instance, 153 - attributes: &INSTANCE_ATTRS, 154 - }], 155 - }, 156 - fragment: Some(wgpu::FragmentState { 157 - module: &shader, 158 - entry_point: Some("fs"), 159 - compilation_options: wgpu::PipelineCompilationOptions::default(), 160 - targets: &[ 161 - Some(wgpu::ColorTargetState { 162 - format: color_format, 163 - blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), 164 - write_mask: wgpu::ColorWrites::ALL, 165 - }), 166 - Some(wgpu::ColorTargetState { 167 - format: PICK_FORMAT, 168 - blend: None, 169 - write_mask: wgpu::ColorWrites::ALL, 170 - }), 171 - ], 172 - }), 173 - primitive: wgpu::PrimitiveState { 174 - topology: wgpu::PrimitiveTopology::TriangleList, 175 - strip_index_format: None, 176 - front_face: wgpu::FrontFace::Ccw, 177 - cull_mode: None, 178 - polygon_mode: wgpu::PolygonMode::Fill, 179 - conservative: false, 180 - unclipped_depth: false, 181 - }, 182 - depth_stencil: Some(wgpu::DepthStencilState { 183 - format: DEPTH_FORMAT, 184 - depth_write_enabled: Some(false), 185 - depth_compare: Some(wgpu::CompareFunction::LessEqual), 186 - stencil: wgpu::StencilState::default(), 187 - bias: wgpu::DepthBiasState::default(), 188 - }), 189 - multisample: wgpu::MultisampleState::default(), 190 - multiview_mask: None, 191 - cache: None, 192 - }); 175 + let visible = build_pipeline( 176 + &device, 177 + &pipeline_layout, 178 + &shader, 179 + color_format, 180 + EdgeLayer::Visible.depth_compare(), 181 + ); 182 + let hidden = build_pipeline( 183 + &device, 184 + &pipeline_layout, 185 + &shader, 186 + color_format, 187 + EdgeLayer::Hidden.depth_compare(), 188 + ); 193 189 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { 194 190 label: Some("bone-render:edge3d-uniform"), 195 191 size: UNIFORM_SIZE, ··· 207 203 Self { 208 204 device, 209 205 queue, 210 - pipeline, 206 + visible, 207 + hidden, 211 208 uniform_buffer, 212 209 bind_group, 213 210 } ··· 220 217 depth_view: &wgpu::TextureView, 221 218 scene: &EdgeScene, 222 219 view: EdgeView, 220 + hidden: HiddenEdges, 223 221 ) { 224 - let instances = build_instances(scene, view); 225 - if instances.is_empty() { 222 + let visible = build_instances(scene, view, EdgeLayer::Visible.extra_style_bits()); 223 + let occluded = match hidden { 224 + HiddenEdges::Dashed => { 225 + build_instances(scene, view, EdgeLayer::Hidden.extra_style_bits()) 226 + } 227 + HiddenEdges::Omit => Vec::new(), 228 + }; 229 + if visible.is_empty() && occluded.is_empty() { 226 230 return; 227 231 } 228 232 let uniform = EdgeFrame { 229 233 clip_from_world: view.clip_from_world, 230 - edge_color: EDGE_COLOR, 231 - hidden_color: HIDDEN_COLOR, 234 + edge_color: view.edge_color, 235 + hidden_color: view.hidden_color, 232 236 viewport_px: view.viewport_px, 233 237 dash_period_px: view.dash_period_px, 234 238 dash_on_ratio: view.dash_on_ratio, ··· 237 241 }; 238 242 self.queue 239 243 .write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniform)); 240 - let instance_buffer = self 241 - .device 242 - .create_buffer_init(&wgpu::util::BufferInitDescriptor { 243 - label: Some("bone-render:edge3d-instances"), 244 - contents: bytemuck::cast_slice(&instances), 245 - usage: wgpu::BufferUsages::VERTEX, 246 - }); 244 + let visible_buffer = (!visible.is_empty()).then(|| self.instance_buffer(&visible)); 245 + let occluded_buffer = (!occluded.is_empty()).then(|| self.instance_buffer(&occluded)); 247 246 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 248 247 label: Some("bone-render:edge3d-pass"), 249 248 color_attachments: &[ ··· 278 277 occlusion_query_set: None, 279 278 multiview_mask: None, 280 279 }); 281 - let len = instances.len(); 282 - let Ok(count) = u32::try_from(len) else { 283 - panic!("edge instance count {len} exceeds u32::MAX"); 284 - }; 285 - pass.set_pipeline(&self.pipeline); 286 280 pass.set_bind_group(0, &self.bind_group, &[]); 287 - pass.set_vertex_buffer(0, instance_buffer.slice(..)); 288 - pass.draw(0..6, 0..count); 281 + if let Some(buffer) = &visible_buffer { 282 + record_layer(&mut pass, &self.visible, buffer, visible.len()); 283 + } 284 + if let Some(buffer) = &occluded_buffer { 285 + record_layer(&mut pass, &self.hidden, buffer, occluded.len()); 286 + } 287 + } 288 + 289 + fn instance_buffer(&self, instances: &[EdgeInstance]) -> wgpu::Buffer { 290 + self.device 291 + .create_buffer_init(&wgpu::util::BufferInitDescriptor { 292 + label: Some("bone-render:edge3d-instances"), 293 + contents: bytemuck::cast_slice(instances), 294 + usage: wgpu::BufferUsages::VERTEX, 295 + }) 289 296 } 290 297 } 291 298 299 + fn record_layer( 300 + pass: &mut wgpu::RenderPass<'_>, 301 + pipeline: &wgpu::RenderPipeline, 302 + buffer: &wgpu::Buffer, 303 + len: usize, 304 + ) { 305 + let Ok(count) = u32::try_from(len) else { 306 + panic!("edge instance count {len} exceeds u32::MAX"); 307 + }; 308 + pass.set_pipeline(pipeline); 309 + pass.set_vertex_buffer(0, buffer.slice(..)); 310 + pass.draw(0..6, 0..count); 311 + } 312 + 292 313 impl core::fmt::Debug for Edge3dPipeline { 293 314 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 294 315 f.debug_struct("Edge3dPipeline").finish_non_exhaustive() 295 316 } 296 317 } 297 318 298 - fn build_instances(scene: &EdgeScene, view: EdgeView) -> Vec<EdgeInstance> { 319 + fn build_pipeline( 320 + device: &wgpu::Device, 321 + layout: &wgpu::PipelineLayout, 322 + shader: &wgpu::ShaderModule, 323 + color_format: wgpu::TextureFormat, 324 + depth_compare: wgpu::CompareFunction, 325 + ) -> wgpu::RenderPipeline { 326 + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { 327 + label: Some("bone-render:edge3d-pipeline"), 328 + layout: Some(layout), 329 + vertex: wgpu::VertexState { 330 + module: shader, 331 + entry_point: Some("vs"), 332 + compilation_options: wgpu::PipelineCompilationOptions::default(), 333 + buffers: &[wgpu::VertexBufferLayout { 334 + array_stride: INSTANCE_STRIDE, 335 + step_mode: wgpu::VertexStepMode::Instance, 336 + attributes: &INSTANCE_ATTRS, 337 + }], 338 + }, 339 + fragment: Some(wgpu::FragmentState { 340 + module: shader, 341 + entry_point: Some("fs"), 342 + compilation_options: wgpu::PipelineCompilationOptions::default(), 343 + targets: &[ 344 + Some(wgpu::ColorTargetState { 345 + format: color_format, 346 + blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING), 347 + write_mask: wgpu::ColorWrites::ALL, 348 + }), 349 + Some(wgpu::ColorTargetState { 350 + format: PICK_FORMAT, 351 + blend: None, 352 + write_mask: wgpu::ColorWrites::ALL, 353 + }), 354 + ], 355 + }), 356 + primitive: wgpu::PrimitiveState { 357 + topology: wgpu::PrimitiveTopology::TriangleList, 358 + strip_index_format: None, 359 + front_face: wgpu::FrontFace::Ccw, 360 + cull_mode: None, 361 + polygon_mode: wgpu::PolygonMode::Fill, 362 + conservative: false, 363 + unclipped_depth: false, 364 + }, 365 + depth_stencil: Some(wgpu::DepthStencilState { 366 + format: DEPTH_FORMAT, 367 + depth_write_enabled: Some(false), 368 + depth_compare: Some(depth_compare), 369 + stencil: wgpu::StencilState::default(), 370 + bias: wgpu::DepthBiasState::default(), 371 + }), 372 + multisample: wgpu::MultisampleState::default(), 373 + multiview_mask: None, 374 + cache: None, 375 + }) 376 + } 377 + 378 + fn build_instances(scene: &EdgeScene, view: EdgeView, extra_bits: u32) -> Vec<EdgeInstance> { 299 379 let genuine = scene 300 380 .genuine() 301 381 .iter() 302 382 .copied() 303 - .map(|edge| genuine_instance(edge, view.crease_threshold_rad)); 383 + .map(|edge| genuine_instance(edge, view.crease_threshold_rad, extra_bits)); 304 384 let silhouette = scene 305 385 .silhouettes() 306 386 .iter() 307 387 .copied() 308 388 .filter(|candidate| straddles(*candidate, view.projection)) 309 - .map(silhouette_instance); 389 + .map(|candidate| silhouette_instance(candidate, extra_bits)); 310 390 genuine.chain(silhouette).collect() 311 391 } 312 392 313 - fn genuine_instance(edge: GenuineEdge, threshold_rad: f64) -> EdgeInstance { 393 + fn genuine_instance(edge: GenuineEdge, threshold_rad: f64, extra_bits: u32) -> EdgeInstance { 314 394 let kind = if edge.crease().radians() >= threshold_rad { 315 395 EdgeKind::Crease 316 396 } else { 317 397 EdgeKind::Genuine 318 398 }; 319 - instance(edge.a(), edge.b(), edge.pick(), kind, GENUINE_OFFSET_PX) 399 + instance( 400 + edge.a(), 401 + edge.b(), 402 + edge.pick(), 403 + kind, 404 + GENUINE_OFFSET_PX, 405 + extra_bits, 406 + ) 320 407 } 321 408 322 - fn silhouette_instance(candidate: SilhouetteCandidate) -> EdgeInstance { 409 + fn silhouette_instance(candidate: SilhouetteCandidate, extra_bits: u32) -> EdgeInstance { 323 410 instance( 324 411 candidate.a(), 325 412 candidate.b(), 326 413 candidate.pick(), 327 414 EdgeKind::Silhouette, 328 415 SILHOUETTE_OFFSET_PX, 416 + extra_bits, 329 417 ) 330 418 } 331 419 ··· 335 423 pick: PickId, 336 424 kind: EdgeKind, 337 425 depth_offset_px: f32, 426 + extra_bits: u32, 338 427 ) -> EdgeInstance { 339 428 EdgeInstance { 340 429 a: lower_point(a), 341 430 b: lower_point(b), 342 431 half_width_px: EDGE_HALF_WIDTH_PX, 343 432 depth_offset_px, 344 - style_bits: kind.style_bits(), 433 + style_bits: kind.style_bits() | extra_bits, 345 434 pick_id: pick.raw(), 346 435 } 347 436 }
+2 -2
crates/bone-render/src/pipelines/mod.rs
··· 12 12 pub use arc::ArcPipeline; 13 13 pub use chrome::{ChromeInstance, ChromePipeline}; 14 14 pub use chrome_text::{ChromeTextPipeline, SdfGlyphInstance}; 15 - pub use edge_3d::{Edge3dPipeline, EdgeProjection, EdgeView}; 15 + pub(crate) use edge_3d::{Edge3dPipeline, EdgeProjection, EdgeView, HiddenEdges}; 16 16 pub use glyph::GlyphPipeline; 17 17 pub use grid::GridPipeline; 18 18 pub use lines::LinesPipeline; 19 - pub use solid::{FaceFill, SolidPipeline, SolidView}; 19 + pub(crate) use solid::{FaceFill, SolidPipeline, SolidView}; 20 20 pub use text::TextPipeline; 21 21 22 22 use crate::camera::Camera2;
+6 -32
crates/bone-render/src/pipelines/solid.rs
··· 3 3 use bone_types::ShadingModel; 4 4 5 5 use crate::RenderTargets; 6 - use crate::gpu::{Gpu, PICK_FORMAT, pick_clear_color}; 6 + use crate::gpu::{Gpu, PICK_FORMAT}; 7 7 use crate::lower_f32; 8 8 use crate::scene::SolidScene; 9 9 use crate::snapshot::Style; ··· 28 28 } 29 29 30 30 #[derive(Copy, Clone, Debug, PartialEq, Eq)] 31 - pub enum FaceFill { 31 + pub(crate) enum FaceFill { 32 32 Shaded, 33 33 Occluder, 34 34 } ··· 41 41 } 42 42 43 43 #[derive(Copy, Clone)] 44 - pub struct SolidView { 44 + pub(crate) struct SolidView { 45 45 pub clip_from_world: [f32; 16], 46 46 pub eye_world: [f32; 3], 47 47 pub shading: ShadingModel, ··· 80 80 2 => Uint32, 81 81 ]; 82 82 83 - pub struct SolidPipeline { 83 + pub(crate) struct SolidPipeline { 84 84 device: wgpu::Device, 85 85 queue: wgpu::Queue, 86 86 pipeline: wgpu::RenderPipeline, ··· 214 214 let indices = build_indices(scene); 215 215 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 216 216 label: Some("bone-render:solid-pass"), 217 - color_attachments: &[ 218 - Some(wgpu::RenderPassColorAttachment { 219 - view: targets.color, 220 - resolve_target: None, 221 - depth_slice: None, 222 - ops: wgpu::Operations { 223 - load: wgpu::LoadOp::Clear(style.background().into()), 224 - store: wgpu::StoreOp::Store, 225 - }, 226 - }), 227 - Some(wgpu::RenderPassColorAttachment { 228 - view: targets.pick, 229 - resolve_target: None, 230 - depth_slice: None, 231 - ops: wgpu::Operations { 232 - load: wgpu::LoadOp::Clear(pick_clear_color()), 233 - store: wgpu::StoreOp::Store, 234 - }, 235 - }), 236 - ], 237 - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { 238 - view: depth_view, 239 - depth_ops: Some(wgpu::Operations { 240 - load: wgpu::LoadOp::Clear(1.0), 241 - store: wgpu::StoreOp::Store, 242 - }), 243 - stencil_ops: None, 244 - }), 217 + color_attachments: &crate::solid_clear_color_attachments(targets, style), 218 + depth_stencil_attachment: Some(crate::solid_clear_depth_attachment(depth_view)), 245 219 timestamp_writes: None, 246 220 occlusion_query_set: None, 247 221 multiview_mask: None,
+40
crates/bone-render/src/snapshot.rs
··· 270 270 } 271 271 272 272 #[derive(Copy, Clone, Debug, PartialEq)] 273 + pub struct EdgeStyle { 274 + visible: ClearColor, 275 + hidden: ClearColor, 276 + } 277 + 278 + impl EdgeStyle { 279 + pub const DEFAULT: Self = Self { 280 + visible: ClearColor::opaque(0.90, 0.90, 0.93), 281 + hidden: ClearColor::opaque(0.249, 0.274, 0.300), 282 + }; 283 + 284 + #[must_use] 285 + pub const fn new(visible: ClearColor, hidden: ClearColor) -> Self { 286 + Self { visible, hidden } 287 + } 288 + 289 + #[must_use] 290 + pub const fn visible(self) -> ClearColor { 291 + self.visible 292 + } 293 + 294 + #[must_use] 295 + pub const fn hidden(self) -> ClearColor { 296 + self.hidden 297 + } 298 + } 299 + 300 + #[derive(Copy, Clone, Debug, PartialEq)] 273 301 pub struct Style { 274 302 background: ClearColor, 275 303 grid: GridStyle, 276 304 strokes: StrokeStyle, 277 305 glyphs: GlyphStyle, 278 306 text: TextStyle, 307 + edges: EdgeStyle, 279 308 } 280 309 281 310 impl Style { ··· 287 316 strokes: StrokeStyle::DEFAULT, 288 317 glyphs: GlyphStyle::DEFAULT, 289 318 text: TextStyle::DEFAULT, 319 + edges: EdgeStyle::DEFAULT, 290 320 } 291 321 } 292 322 ··· 296 326 } 297 327 298 328 #[must_use] 329 + pub const fn edges(self) -> EdgeStyle { 330 + self.edges 331 + } 332 + 333 + #[must_use] 299 334 pub const fn grid(self) -> GridStyle { 300 335 self.grid 301 336 } ··· 318 353 #[must_use] 319 354 pub const fn with_text(self, text: TextStyle) -> Self { 320 355 Self { text, ..self } 356 + } 357 + 358 + #[must_use] 359 + pub const fn with_edges(self, edges: EdgeStyle) -> Self { 360 + Self { edges, ..self } 321 361 } 322 362 } 323 363
+85 -52
crates/bone-render/tests/edges.rs
··· 4 4 evaluate_extrude, 5 5 }; 6 6 use bone_render::{ 7 - EdgeScene, FaceFill, OffscreenContext, SnapshotFrame, SolidRenderer, SolidScene, Style, 8 - frame_isometric, 7 + EdgeScene, OffscreenContext, SnapshotFrame, SolidRenderer, SolidScene, Style, frame_isometric, 9 8 }; 10 9 use bone_types::{ 11 - Aabb3, Angle, AngleTolerance, Camera3, ChordHeightTolerance, FeatureId, Length, Plane3, Point2, 12 - Point3, PositiveLength, Projection, SketchEntityId, SketchId, Tolerance, UnitVec3, millimeter, 13 - radian, 10 + Aabb3, Angle, AngleTolerance, Camera3, ChordHeightTolerance, DisplayMode, FeatureId, Length, 11 + Plane3, Point2, Point3, PositiveLength, Projection, SketchEntityId, SketchId, Tolerance, 12 + UnitVec3, millimeter, radian, 14 13 }; 15 14 use slotmap::{Key, SlotMap}; 16 15 ··· 20 19 21 20 const TOLERANCE: Tolerance = Tolerance::new(1.0e-9); 22 21 const UPDATE_ENV: &str = "BONE_UPDATE_EDGES_GOLDENS"; 22 + const DISPLAY_ENV: &str = "BONE_UPDATE_DISPLAY_GOLDENS"; 23 23 const CHORD_MM: f64 = 0.05; 24 24 const ANGLE_RAD: f64 = 0.2; 25 25 ··· 182 182 faces: &SolidScene, 183 183 edges: &EdgeScene, 184 184 camera: Camera3, 185 - fill: FaceFill, 185 + mode: DisplayMode, 186 186 ) -> SnapshotFrame { 187 187 let mut renderer = SolidRenderer::new(ctx.gpu(), ctx.color_format()); 188 - let Ok(frame) = renderer.render_with_edges(ctx, faces, edges, camera, &Style::default(), fill) 188 + let Ok(frame) = renderer.render_display(ctx, faces, edges, camera, &Style::default(), mode) 189 189 else { 190 - panic!("SolidRenderer::render_with_edges failed"); 190 + panic!("SolidRenderer::render_display failed"); 191 191 }; 192 192 frame 193 193 } 194 194 195 - #[test] 196 - fn cube_shaded_with_edges_matches_golden() { 195 + fn cube_frame(mode: DisplayMode) -> SnapshotFrame { 197 196 let size = extent(256); 198 197 let ctx = make_context(size); 199 198 let solid = unit_cube(); 200 199 let (faces, edges) = scenes(&solid); 201 - let frame = render( 202 - &ctx, 203 - &faces, 204 - &edges, 205 - cube_camera(&solid, size), 206 - FaceFill::Shaded, 200 + render(&ctx, &faces, &edges, cube_camera(&solid, size), mode) 201 + } 202 + 203 + fn cylinder_frame(mode: DisplayMode) -> SnapshotFrame { 204 + let size = extent(256); 205 + let ctx = make_context(size); 206 + let solid = cylinder(); 207 + let (faces, edges) = scenes(&solid); 208 + let camera = framed( 209 + aabb_of(&solid), 210 + direction(1.0, 1.0, 0.6), 211 + UnitVec3::z_axis(), 207 212 ); 213 + render(&ctx, &faces, &edges, camera, mode) 214 + } 215 + 216 + #[test] 217 + fn cube_shaded_with_edges_matches_golden() { 208 218 check_golden( 209 - &frame, 219 + &cube_frame(DisplayMode::ShadedWithEdges), 210 220 "tests/goldens/shaded_with_edges_256.png", 211 221 UPDATE_ENV, 212 222 ); ··· 214 224 215 225 #[test] 216 226 fn cube_hidden_line_removed_matches_golden() { 217 - let size = extent(256); 218 - let ctx = make_context(size); 219 - let solid = unit_cube(); 220 - let (faces, edges) = scenes(&solid); 221 - let frame = render( 222 - &ctx, 223 - &faces, 224 - &edges, 225 - cube_camera(&solid, size), 226 - FaceFill::Occluder, 227 - ); 228 227 check_golden( 229 - &frame, 228 + &cube_frame(DisplayMode::HiddenLineRemoved), 230 229 "tests/goldens/hidden_line_removed_256.png", 231 230 UPDATE_ENV, 232 231 ); ··· 243 242 direction(1.0, 1.0, 0.6), 244 243 UnitVec3::z_axis(), 245 244 ); 246 - let frame = render(&ctx, &faces, &edges, camera, FaceFill::Shaded); 245 + let frame = render(&ctx, &faces, &edges, camera, DisplayMode::ShadedWithEdges); 247 246 check_golden( 248 247 &frame, 249 248 "tests/goldens/perspective_with_edges_256.png", ··· 253 252 254 253 #[test] 255 254 fn cylinder_shaded_with_edges_matches_golden() { 256 - let size = extent(256); 257 - let ctx = make_context(size); 258 - let solid = cylinder(); 259 - let (faces, edges) = scenes(&solid); 260 - let camera = framed( 261 - aabb_of(&solid), 262 - direction(1.0, 1.0, 0.6), 263 - UnitVec3::z_axis(), 264 - ); 265 - let frame = render(&ctx, &faces, &edges, camera, FaceFill::Shaded); 266 255 check_golden( 267 - &frame, 256 + &cylinder_frame(DisplayMode::ShadedWithEdges), 268 257 "tests/goldens/shaded_with_edges_cylinder_256.png", 269 258 UPDATE_ENV, 270 259 ); ··· 272 261 273 262 #[test] 274 263 fn cylinder_hidden_line_removed_matches_golden() { 275 - let size = extent(256); 276 - let ctx = make_context(size); 277 - let solid = cylinder(); 278 - let (faces, edges) = scenes(&solid); 279 - let camera = framed( 280 - aabb_of(&solid), 281 - direction(1.0, 1.0, 0.6), 282 - UnitVec3::z_axis(), 283 - ); 284 - let frame = render(&ctx, &faces, &edges, camera, FaceFill::Occluder); 285 264 check_golden( 286 - &frame, 265 + &cylinder_frame(DisplayMode::HiddenLineRemoved), 287 266 "tests/goldens/hidden_line_removed_cylinder_256.png", 288 267 UPDATE_ENV, 268 + ); 269 + } 270 + 271 + #[test] 272 + fn cube_wireframe_matches_golden() { 273 + check_golden( 274 + &cube_frame(DisplayMode::Wireframe), 275 + "tests/goldens/wireframe_256.png", 276 + DISPLAY_ENV, 277 + ); 278 + } 279 + 280 + #[test] 281 + fn cylinder_wireframe_matches_golden() { 282 + check_golden( 283 + &cylinder_frame(DisplayMode::Wireframe), 284 + "tests/goldens/wireframe_cylinder_256.png", 285 + DISPLAY_ENV, 286 + ); 287 + } 288 + 289 + #[test] 290 + fn cube_hidden_line_gray_matches_golden() { 291 + check_golden( 292 + &cube_frame(DisplayMode::HiddenLineGray), 293 + "tests/goldens/hidden_line_gray_256.png", 294 + DISPLAY_ENV, 295 + ); 296 + } 297 + 298 + #[test] 299 + fn cylinder_hidden_line_gray_matches_golden() { 300 + check_golden( 301 + &cylinder_frame(DisplayMode::HiddenLineGray), 302 + "tests/goldens/hidden_line_gray_cylinder_256.png", 303 + DISPLAY_ENV, 304 + ); 305 + } 306 + 307 + #[test] 308 + fn cube_shaded_no_edges_matches_golden() { 309 + check_golden( 310 + &cube_frame(DisplayMode::ShadedNoEdges), 311 + "tests/goldens/shaded_no_edges_256.png", 312 + DISPLAY_ENV, 313 + ); 314 + } 315 + 316 + #[test] 317 + fn cylinder_shaded_no_edges_matches_golden() { 318 + check_golden( 319 + &cylinder_frame(DisplayMode::ShadedNoEdges), 320 + "tests/goldens/shaded_no_edges_cylinder_256.png", 321 + DISPLAY_ENV, 289 322 ); 290 323 } 291 324
crates/bone-render/tests/goldens/hidden_line_gray_256.png

This is a binary file and will not be displayed.

crates/bone-render/tests/goldens/hidden_line_gray_cylinder_256.png

This is a binary file and will not be displayed.

crates/bone-render/tests/goldens/shaded_no_edges_256.png

This is a binary file and will not be displayed.

crates/bone-render/tests/goldens/shaded_no_edges_cylinder_256.png

This is a binary file and will not be displayed.

crates/bone-render/tests/goldens/wireframe_256.png

This is a binary file and will not be displayed.

crates/bone-render/tests/goldens/wireframe_cylinder_256.png

This is a binary file and will not be displayed.