1use super::*;
2
3impl RenderCore {
4 pub fn render_to_view(
8 &mut self,
9 gpu_scene: &mut GpuScene,
10 scene: &RenderScene,
11 params: &FrameParams,
12 resolve_view: &wgpu::TextureView,
13 ) {
14 let width = params.width.max(1);
15 let height = params.height.max(1);
16 self.ensure_targets(width, height);
17 self.write_global_styles(params.settings);
18
19 let globals = Globals {
20 view_proj: params.camera.view_proj,
21 viewport: [width as f32, height as f32, params.dpr.max(1e-3), 0.0],
22 forward: [
23 params.camera.forward[0],
24 params.camera.forward[1],
25 params.camera.forward[2],
26 0.0,
27 ],
28 };
29 self.queue
30 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
31
32 let axis_len = params.settings.axis_length_px as f64 * params.world_per_pixel;
34 let draw_axes = params.settings.axis_length_px > 0.0 && axis_len.is_finite() && axis_len > 0.0;
35 if draw_axes {
36 let l = axis_len as f32;
37 let segments = [
38 EdgeInstance { p0: [0.0; 3], p1: [l, 0.0, 0.0] },
39 EdgeInstance { p0: [0.0; 3], p1: [0.0, l, 0.0] },
40 EdgeInstance { p0: [0.0; 3], p1: [0.0, 0.0, l] },
41 ];
42 match &gpu_scene.axis_buf {
43 Some(buf) => self.queue.write_buffer(buf, 0, bytemuck::cast_slice(&segments)),
44 None => {
45 gpu_scene.axis_buf = Some(self.device.create_buffer_init(
46 &wgpu::util::BufferInitDescriptor {
47 label: Some("world axes"),
48 contents: bytemuck::cast_slice(&segments),
49 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
50 },
51 ));
52 }
53 }
54 }
55
56 let has_emphasis = !params.emphasis.is_empty();
58 for solid in scene.solids() {
59 if let Some(gpu_solid) = gpu_scene.solids.get_mut(&solid.name) {
60 self.sync_boundary(gpu_solid, solid, params.emphasis);
61 }
62 }
63
64 let bg = params.settings.background;
65 let targets = self.targets.as_ref().expect("targets ensured");
66 let mut encoder = self
67 .device
68 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
69 label: Some("brep-render frame"),
70 });
71 {
72 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
73 label: Some("scene"),
74 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
75 view: &targets.msaa_view,
76 depth_slice: None,
77 resolve_target: Some(resolve_view),
78 ops: wgpu::Operations {
79 load: wgpu::LoadOp::Clear(wgpu::Color {
80 r: bg[0] as f64,
81 g: bg[1] as f64,
82 b: bg[2] as f64,
83 a: 1.0,
84 }),
85 store: wgpu::StoreOp::Store,
86 },
87 })],
88 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
89 view: &targets.depth_view,
90 depth_ops: Some(wgpu::Operations {
91 load: wgpu::LoadOp::Clear(1.0),
92 store: wgpu::StoreOp::Discard,
93 }),
94 stencil_ops: None,
95 }),
96 timestamp_writes: None,
97 occlusion_query_set: None,
98 multiview_mask: None,
99 });
100 pass.set_bind_group(0, &self.globals_bind, &[]);
101
102 let visible_solids: Vec<(&SolidDisplay, &GpuSolid)> = gpu_scene
103 .order
104 .iter()
105 .filter_map(|name| {
106 let solid = scene.solid(name)?;
107 if !solid.visible {
108 return None;
109 }
110 Some((solid, gpu_scene.solids.get(name)?))
111 })
112 .collect();
113
114 if params.settings.wireframe && params.settings.show_faces {
121 pass.set_pipeline(&self.wire_pipeline);
122 for (solid, gpu_solid) in &visible_solids {
123 if gpu_solid.wire_index_count == 0 {
124 continue;
125 }
126 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
127 pass.set_index_buffer(
128 gpu_solid.wire_index_buf.slice(..),
129 wgpu::IndexFormat::Uint32,
130 );
131 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
132 if !solid.visibility.any_face_hidden() {
134 pass.draw_indexed(0..gpu_solid.wire_index_count, 0, 0..1);
135 continue;
136 }
137 let mut run: Option<(u32, u32)> = None; let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<(u32, u32)>| {
146 if let Some((first, count)) = run.take() {
147 if count > 0 {
148 pass.draw_indexed(first..first + count, 0, 0..1);
149 }
150 }
151 };
152 for (index, _face) in solid.faces.iter().enumerate() {
153 let range = &gpu_solid.faces[index];
154 if range.wire_count == 0 {
155 continue;
156 }
157 let first = range.wire_first;
158 let count = range.wire_count;
159 if !solid.visibility.is_face_visible(index) {
160 flush(&mut pass, &mut run);
161 continue;
162 }
163 match &mut run {
164 Some((run_first, run_count)) if *run_first + *run_count == first => {
165 *run_count += count;
166 }
167 _ => {
168 flush(&mut pass, &mut run);
169 run = Some((first, count));
170 }
171 }
172 }
173 flush(&mut pass, &mut run);
174 }
175 }
176
177 if !params.settings.wireframe && params.settings.show_faces {
182 pass.set_pipeline(&self.mesh_pipeline);
183 for (solid, gpu_solid) in &visible_solids {
184 if solid.mesh.indices.is_empty() {
185 continue;
186 }
187 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
188 pass.set_index_buffer(gpu_solid.index_buf.slice(..), wgpu::IndexFormat::Uint32);
189 let any_face_hidden = solid.visibility.any_face_hidden();
197 let any_face_colored = !gpu_solid.face_styles.is_empty();
198 if !has_emphasis && !any_face_hidden && !any_face_colored {
199 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
200 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
201 continue;
202 }
203 let style_for = |state: EmphasisState, slot: Option<u32>| match state {
209 EmphasisState::Base => match slot.and_then(|s| gpu_solid.face_styles.get(s as usize)) {
210 Some(style) => &style.bind,
211 None => &gpu_solid.base_style.bind,
212 },
213 EmphasisState::Selected => &self.styles.face_selected.bind,
214 EmphasisState::Hovered => &self.styles.face_hovered.bind,
215 };
216 type FaceRun = (EmphasisState, Option<u32>, u32, u32); let mut run: Option<FaceRun> = None;
218 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<FaceRun>| {
219 if let Some((state, slot, first, count)) = run.take() {
220 if count > 0 {
221 pass.set_bind_group(1, style_for(state, slot), &[]);
222 pass.draw_indexed(first..first + count, 0, 0..1);
223 }
224 }
225 };
226 for (index, face) in solid.faces.iter().enumerate() {
227 let range = &gpu_solid.faces[index];
228 if range.index_count == 0 {
229 continue;
230 }
231 if !solid.visibility.is_face_visible(index) {
234 flush(&mut pass, &mut run);
235 continue;
236 }
237 let state = if has_emphasis {
238 params.emphasis.face_state(&solid.name, &face.name)
239 } else {
240 EmphasisState::Base
241 };
242 let slot = if state == EmphasisState::Base {
243 range.style
244 } else {
245 None
246 };
247 match &mut run {
248 Some((run_state, run_slot, first, count))
249 if *run_state == state
250 && *run_slot == slot
251 && *first + *count == range.first_index =>
252 {
253 *count += range.index_count;
254 }
255 _ => {
256 flush(&mut pass, &mut run);
257 run = Some((state, slot, range.first_index, range.index_count));
258 }
259 }
260 }
261 flush(&mut pass, &mut run);
262 }
263 }
264
265 if params.settings.show_edges
268 && params.settings.show_occluded_edges
269 && params.settings.hidden_edge_alpha > 0.0
270 {
271 pass.set_pipeline(&self.edge_hidden_pipeline);
272 pass.set_bind_group(1, &self.styles.edge_hidden.bind, &[]);
273 for (solid, gpu_solid) in &visible_solids {
274 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
275 if gpu_solid.occludable_edge_instances == 0 {
276 continue;
277 }
278 pass.set_vertex_buffer(0, edge_buf.slice(..));
279 if solid.visibility.any_edge_hidden() {
280 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Real);
282 } else {
283 pass.draw(0..6, 0..gpu_solid.occludable_edge_instances);
285 }
286 }
287 }
288
289 if params.settings.show_edges {
292 let aux_shown = params.world_per_pixel <= AUX_EDGE_MAX_WORLD_PER_PIXEL;
293 pass.set_pipeline(&self.edge_visible_pipeline);
294 for (solid, gpu_solid) in &visible_solids {
295 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
296 if gpu_solid.edge_instances == 0 {
297 continue;
298 }
299 pass.set_vertex_buffer(0, edge_buf.slice(..));
300 let any_edge_hidden = solid.visibility.any_edge_hidden();
301 let split = gpu_solid.occludable_edge_instances;
306 if !has_emphasis {
307 pass.set_bind_group(1, &self.styles.edge_base.bind, &[]);
308 if any_edge_hidden {
309 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Real);
310 if aux_shown {
311 pass.set_bind_group(1, &self.styles.edge_aux.bind, &[]);
312 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Aux);
313 }
314 } else {
315 if split > 0 {
316 pass.draw(0..6, 0..split);
317 }
318 if aux_shown && split < gpu_solid.edge_instances {
319 pass.set_bind_group(1, &self.styles.edge_aux.bind, &[]);
320 pass.draw(0..6, split..gpu_solid.edge_instances);
321 }
322 }
323 continue;
324 }
325 let style_for = |(state, aux): (EmphasisState, bool)| match state {
328 EmphasisState::Base if aux => &self.styles.edge_aux.bind,
329 EmphasisState::Base => &self.styles.edge_base.bind,
330 EmphasisState::Selected => &self.styles.edge_selected.bind,
331 EmphasisState::Hovered => &self.styles.edge_hovered.bind,
332 };
333 let mut run: Option<((EmphasisState, bool), u32, u32)> = None;
334 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<((EmphasisState, bool), u32, u32)>| {
335 if let Some((state, first, count)) = run.take() {
336 if count > 0 {
337 pass.set_bind_group(1, style_for(state), &[]);
338 pass.draw(0..6, first..first + count);
339 }
340 }
341 };
342 for (index, edge) in solid.edges.iter().enumerate() {
343 let range = &gpu_solid.edges[index];
344 if range.instance_count == 0 {
345 continue;
346 }
347 if !solid.visibility.is_edge_visible(index) || (edge.aux && !aux_shown) {
349 flush(&mut pass, &mut run);
350 continue;
351 }
352 let state = (params.emphasis.edge_state(&solid.name, &edge.name), edge.aux);
353 match &mut run {
354 Some((run_state, first, count))
355 if *run_state == state
356 && *first + *count == range.first_instance =>
357 {
358 *count += range.instance_count;
359 }
360 _ => {
361 flush(&mut pass, &mut run);
362 run = Some((state, range.first_instance, range.instance_count));
363 }
364 }
365 }
366 flush(&mut pass, &mut run);
367 }
368 }
369
370 if has_emphasis {
372 pass.set_pipeline(&self.edge_visible_pipeline);
381 pass.set_bind_group(1, &self.styles.boundary.bind, &[]);
382 for (_, gpu_solid) in &visible_solids {
383 let Some(boundary) = &gpu_solid.boundary else { continue };
384 let Some(buf) = &boundary.buf else { continue };
385 if boundary.count == 0 {
386 continue;
387 }
388 pass.set_vertex_buffer(0, buf.slice(..));
389 pass.draw(0..6, 0..boundary.count);
390 }
391 }
392
393 if params.settings.show_vertices && params.settings.vertex_size_px > 0.0 {
395 pass.set_pipeline(&self.point_pipeline);
396 for (solid, gpu_solid) in &visible_solids {
397 let Some(point_buf) = &gpu_solid.point_buf else { continue };
398 if gpu_solid.point_count == 0 {
399 continue;
400 }
401 if solid.visibility.all_vertices_hidden(solid.vertices.len()) {
407 continue;
408 }
409 pass.set_vertex_buffer(0, point_buf.slice(..));
410 let any_vertex_hidden = solid.visibility.any_vertex_hidden();
411 if has_emphasis || any_vertex_hidden {
412 let tol = 1e-9_f64.max(params.world_per_pixel * 1e-3);
413 let mut base_run: Option<(u32, u32)> = None;
414 let mut emphasized: Vec<(EmphasisState, u32)> = Vec::new();
415 for (index, vertex) in solid.vertices.iter().enumerate() {
416 if !solid.visibility.is_vertex_visible(index) {
418 if let Some((first, count)) = base_run.take() {
419 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
420 pass.draw(0..6, first..first + count);
421 }
422 continue;
423 }
424 let state = if has_emphasis {
425 params.emphasis.vertex_state(&solid.name, vertex.position, tol)
426 } else {
427 EmphasisState::Base
428 };
429 if state == EmphasisState::Base {
430 match &mut base_run {
431 Some((first, count)) if *first + *count == index as u32 => {
432 *count += 1
433 }
434 _ => {
435 if let Some((first, count)) = base_run.take() {
436 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
437 pass.draw(0..6, first..first + count);
438 }
439 base_run = Some((index as u32, 1));
440 }
441 }
442 } else {
443 emphasized.push((state, index as u32));
444 }
445 }
446 if let Some((first, count)) = base_run {
447 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
448 pass.draw(0..6, first..first + count);
449 }
450 for (state, index) in emphasized {
451 let style = match state {
452 EmphasisState::Selected => &self.styles.point_selected.bind,
453 _ => &self.styles.point_hovered.bind,
454 };
455 pass.set_bind_group(1, style, &[]);
456 pass.draw(0..6, index..index + 1);
457 }
458 } else {
459 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
460 pass.draw(0..6, 0..gpu_solid.point_count);
461 }
462 }
463 }
464
465 if draw_axes {
467 if let Some(axis_buf) = &gpu_scene.axis_buf {
468 pass.set_pipeline(&self.edge_visible_pipeline);
469 pass.set_vertex_buffer(0, axis_buf.slice(..));
470 for (index, style) in [
471 &self.styles.axis_x,
472 &self.styles.axis_y,
473 &self.styles.axis_z,
474 ]
475 .iter()
476 .enumerate()
477 {
478 pass.set_bind_group(1, &style.bind, &[]);
479 let i = index as u32;
480 pass.draw(0..6, i..i + 1);
481 }
482 }
483 }
484 }
485
486 if let Some(overlay) = params.overlay {
491 let make_tris = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
492 let verts = overlay_tri_verts(ov);
493 (!verts.is_empty()).then(|| {
494 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
495 label: Some("overlay tris"),
496 contents: bytemuck::cast_slice(&verts),
497 usage: wgpu::BufferUsages::VERTEX,
498 })
499 })
500 };
501 let make_lines = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
502 let insts = overlay_line_insts(ov);
503 (!insts.is_empty()).then(|| {
504 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
505 label: Some("overlay lines"),
506 contents: bytemuck::cast_slice(&insts),
507 usage: wgpu::BufferUsages::VERTEX,
508 })
509 })
510 };
511
512 let main_tri_count = overlay.main.tris.len() as u32;
513 let main_line_count = (overlay.main.lines.len() / 2) as u32;
514 let main_tri_buf = make_tris(&overlay.main);
515 let main_line_buf = make_lines(&overlay.main);
516 if main_tri_buf.is_some() || main_line_buf.is_some() {
517 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
518 label: Some("overlay-main"),
519 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
520 view: &targets.msaa_view,
521 depth_slice: None,
522 resolve_target: Some(resolve_view),
523 ops: wgpu::Operations {
524 load: wgpu::LoadOp::Load,
525 store: wgpu::StoreOp::Store,
526 },
527 })],
528 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
529 view: &targets.depth_view,
530 depth_ops: Some(wgpu::Operations {
531 load: wgpu::LoadOp::Clear(1.0),
532 store: wgpu::StoreOp::Discard,
533 }),
534 stencil_ops: None,
535 }),
536 timestamp_writes: None,
537 occlusion_query_set: None,
538 multiview_mask: None,
539 });
540 pass.set_bind_group(0, &self.globals_bind, &[]);
541 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
542 if let Some(buf) = &main_tri_buf {
543 pass.set_vertex_buffer(0, buf.slice(..));
544 let plane_verts = (overlay.plane_tri_verts as u32).min(main_tri_count);
549 if plane_verts > 0 {
550 pass.set_pipeline(&self.overlay_tri_nodepth_pipeline);
551 pass.draw(0..plane_verts, 0..1);
552 }
553 if plane_verts < main_tri_count {
554 pass.set_pipeline(&self.overlay_tri_pipeline);
555 pass.draw(plane_verts..main_tri_count, 0..1);
556 }
557 }
558 if let Some(buf) = &main_line_buf {
559 pass.set_pipeline(&self.overlay_line_pipeline);
560 pass.set_vertex_buffer(0, buf.slice(..));
561 pass.draw(0..6, 0..main_line_count);
562 }
563 }
564
565 if let Some(vc) = &overlay.viewcube {
566 let dpr = params.dpr.max(1e-3);
567 let mut x = (vc.rect_css[0] * dpr).max(0.0);
568 let mut y = (vc.rect_css[1] * dpr).max(0.0);
569 let mut w = (vc.rect_css[2] * dpr).max(1.0);
570 let mut h = (vc.rect_css[3] * dpr).max(1.0);
571 w = w.min(width as f32 - x).max(1.0);
573 h = h.min(height as f32 - y).max(1.0);
574 x = x.min(width as f32 - w).max(0.0);
575 y = y.min(height as f32 - h).max(0.0);
576
577 let vc_globals = Globals {
578 view_proj: vc.view_proj,
579 viewport: [w, h, dpr, 0.0],
580 forward: [vc.forward[0], vc.forward[1], vc.forward[2], 0.0],
581 };
582 self.queue
583 .write_buffer(&self.vc_globals_buf, 0, bytemuck::bytes_of(&vc_globals));
584
585 let vc_tri_count = vc.overlay.tris.len() as u32;
586 let vc_line_count = (vc.overlay.lines.len() / 2) as u32;
587 let vc_tri_buf = make_tris(&vc.overlay);
588 let vc_line_buf = make_lines(&vc.overlay);
589 if vc_tri_buf.is_some() || vc_line_buf.is_some() {
590 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
591 label: Some("overlay-viewcube"),
592 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
593 view: &targets.msaa_view,
594 depth_slice: None,
595 resolve_target: Some(resolve_view),
596 ops: wgpu::Operations {
597 load: wgpu::LoadOp::Load,
598 store: wgpu::StoreOp::Store,
599 },
600 })],
601 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
602 view: &targets.depth_view,
603 depth_ops: Some(wgpu::Operations {
604 load: wgpu::LoadOp::Clear(1.0),
605 store: wgpu::StoreOp::Discard,
606 }),
607 stencil_ops: None,
608 }),
609 timestamp_writes: None,
610 occlusion_query_set: None,
611 multiview_mask: None,
612 });
613 pass.set_viewport(x, y, w, h, 0.0, 1.0);
614 pass.set_scissor_rect(x as u32, y as u32, w as u32, h as u32);
615 pass.set_bind_group(0, &self.vc_globals_bind, &[]);
616 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
617 if let Some(buf) = &vc_tri_buf {
618 pass.set_pipeline(&self.overlay_tri_pipeline);
619 pass.set_vertex_buffer(0, buf.slice(..));
620 pass.draw(0..vc_tri_count, 0..1);
621 }
622 if let Some(buf) = &vc_line_buf {
623 pass.set_pipeline(&self.overlay_line_pipeline);
624 pass.set_vertex_buffer(0, buf.slice(..));
625 pass.draw(0..6, 0..vc_line_count);
626 }
627 }
628 }
629 }
630
631 self.queue.submit([encoder.finish()]);
632 }
633
634 #[cfg(not(target_arch = "wasm32"))]
637 pub fn render_to_png(
638 &mut self,
639 scene: &RenderScene,
640 camera: &Camera,
641 width: u32,
642 height: u32,
643 ) -> Result<Vec<u8>, String> {
644 let settings = RenderSettings::artifact();
645 let emphasis = Emphasis::default();
646 let mut gpu_scene = self.upload_scene_with(scene, &settings);
647 let params = FrameParams {
648 camera,
649 width,
650 height,
651 dpr: 1.0,
652 settings: &settings,
653 emphasis: &emphasis,
654 world_per_pixel: 0.0,
655 overlay: None,
656 };
657 let resolve = self.device.create_texture(&wgpu::TextureDescriptor {
658 label: Some("resolve"),
659 size: wgpu::Extent3d {
660 width,
661 height,
662 depth_or_array_layers: 1,
663 },
664 mip_level_count: 1,
665 sample_count: 1,
666 dimension: wgpu::TextureDimension::D2,
667 format: self.format,
668 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
669 view_formats: &[],
670 });
671 let resolve_view = resolve.create_view(&Default::default());
672 self.render_to_view(&mut gpu_scene, scene, ¶ms, &resolve_view);
673
674 let bytes_per_row = (width * 4).div_ceil(256) * 256;
676 let readback = self.device.create_buffer(&wgpu::BufferDescriptor {
677 label: Some("readback"),
678 size: bytes_per_row as u64 * height as u64,
679 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
680 mapped_at_creation: false,
681 });
682 let mut encoder = self
683 .device
684 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
685 label: Some("readback"),
686 });
687 encoder.copy_texture_to_buffer(
688 wgpu::TexelCopyTextureInfo {
689 texture: &resolve,
690 mip_level: 0,
691 origin: wgpu::Origin3d::ZERO,
692 aspect: wgpu::TextureAspect::All,
693 },
694 wgpu::TexelCopyBufferInfo {
695 buffer: &readback,
696 layout: wgpu::TexelCopyBufferLayout {
697 offset: 0,
698 bytes_per_row: Some(bytes_per_row),
699 rows_per_image: None,
700 },
701 },
702 wgpu::Extent3d {
703 width,
704 height,
705 depth_or_array_layers: 1,
706 },
707 );
708 self.queue.submit([encoder.finish()]);
709
710 let slice = readback.slice(..);
711 let (sender, receiver) = std::sync::mpsc::channel();
712 slice.map_async(wgpu::MapMode::Read, move |result| {
713 let _ = sender.send(result);
714 });
715 self.device
716 .poll(wgpu::PollType::wait_indefinitely())
717 .map_err(|error| format!("wgpu poll: {error:?}"))?;
718 receiver
719 .recv()
720 .map_err(|_| "readback callback dropped".to_string())?
721 .map_err(|error| format!("readback map failed: {error:?}"))?;
722
723 let data = slice.get_mapped_range();
724 let mut rgb = Vec::with_capacity((width * height * 3) as usize);
725 for row in 0..height {
726 let start = (row * bytes_per_row) as usize;
727 for col in 0..width as usize {
728 let px = start + col * 4;
729 rgb.extend_from_slice(&data[px..px + 3]);
730 }
731 }
732 drop(data);
733 readback.unmap();
734
735 encode_png(&rgb, width, height)
736 }
737}
738
739const AUX_EDGE_MAX_WORLD_PER_PIXEL: f64 = 0.1;
750
751#[derive(Clone, Copy, PartialEq, Eq)]
753enum EdgeFilter {
754 Real,
756 Aux,
758}
759
760fn draw_visible_edge_ranges(
761 pass: &mut wgpu::RenderPass<'_>,
762 solid: &SolidDisplay,
763 gpu_solid: &GpuSolid,
764 filter: EdgeFilter,
765) {
766 let mut run: Option<(u32, u32)> = None; for (index, edge) in solid.edges.iter().enumerate() {
768 let range = &gpu_solid.edges[index];
769 if range.instance_count == 0 {
770 continue;
771 }
772 if !solid.visibility.is_edge_visible(index) || edge.aux != (filter == EdgeFilter::Aux) {
773 if let Some((first, count)) = run.take() {
774 pass.draw(0..6, first..first + count);
775 }
776 continue;
777 }
778 match &mut run {
779 Some((first, count)) if *first + *count == range.first_instance => {
780 *count += range.instance_count;
781 }
782 _ => {
783 if let Some((first, count)) = run.take() {
784 pass.draw(0..6, first..first + count);
785 }
786 run = Some((range.first_instance, range.instance_count));
787 }
788 }
789 }
790 if let Some((first, count)) = run {
791 pass.draw(0..6, first..first + count);
792 }
793}
794
795fn overlay_tri_verts(ov: &brep_gizmos::Overlay) -> Vec<OverlayTriVertex> {
797 ov.tris
798 .iter()
799 .map(|v| OverlayTriVertex {
800 position: v.pos,
801 normal: v.normal,
802 color: v.color,
803 })
804 .collect()
805}
806
807fn overlay_line_insts(ov: &brep_gizmos::Overlay) -> Vec<OverlayLineInstance> {
810 ov.lines
811 .chunks_exact(2)
812 .map(|pair| OverlayLineInstance {
813 p0: pair[0].pos,
814 p1: pair[1].pos,
815 color: pair[0].color,
816 })
817 .collect()
818}