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 self.render_to_view_with_context(gpu_scene, scene, params, resolve_view, None);
15 }
16
17 pub fn render_to_view_with_context(
19 &mut self,
20 gpu_scene: &mut GpuScene,
21 scene: &RenderScene,
22 params: &FrameParams,
23 resolve_view: &wgpu::TextureView,
24 context: Option<(&GpuScene, &RenderScene)>,
25 ) {
26 let width = params.width.max(1);
27 let height = params.height.max(1);
28 self.ensure_targets(width, height);
29 self.write_global_styles(params.settings);
30
31 let globals = Globals {
32 view_proj: params.camera.view_proj,
33 viewport: [width as f32, height as f32, params.dpr.max(1e-3), 0.0],
34 forward: [
35 params.camera.forward[0],
36 params.camera.forward[1],
37 params.camera.forward[2],
38 0.0,
39 ],
40 };
41 self.queue
42 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
43
44 let axis_len = params.settings.axis_length_px as f64 * params.world_per_pixel;
46 let draw_axes = params.settings.axis_length_px > 0.0 && axis_len.is_finite() && axis_len > 0.0;
47 if draw_axes {
48 let l = axis_len as f32;
49 let segments = [
50 EdgeInstance { p0: [0.0; 3], p1: [l, 0.0, 0.0] },
51 EdgeInstance { p0: [0.0; 3], p1: [0.0, l, 0.0] },
52 EdgeInstance { p0: [0.0; 3], p1: [0.0, 0.0, l] },
53 ];
54 match &gpu_scene.axis_buf {
55 Some(buf) => self.queue.write_buffer(buf, 0, bytemuck::cast_slice(&segments)),
56 None => {
57 gpu_scene.axis_buf = Some(self.device.create_buffer_init(
58 &wgpu::util::BufferInitDescriptor {
59 label: Some("world axes"),
60 contents: bytemuck::cast_slice(&segments),
61 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
62 },
63 ));
64 }
65 }
66 }
67
68 let has_emphasis = !params.emphasis.is_empty();
70 let translucent = |solid: &SolidDisplay| solid.opacity.is_some_and(|opacity| opacity < 1.0);
72 for solid in scene.solids() {
73 if let Some(gpu_solid) = gpu_scene.solids.get_mut(&solid.name) {
74 self.sync_boundary(gpu_solid, solid, params.emphasis);
75 }
76 }
77
78 let bg = params.settings.background;
79 let targets = self.targets.as_ref().expect("targets ensured");
80 let mut encoder = self
81 .device
82 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
83 label: Some("brep-render frame"),
84 });
85 {
86 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
87 label: Some("scene"),
88 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
89 view: &targets.msaa_view,
90 depth_slice: None,
91 resolve_target: Some(resolve_view),
92 ops: wgpu::Operations {
93 load: wgpu::LoadOp::Clear(wgpu::Color {
94 r: bg[0] as f64,
95 g: bg[1] as f64,
96 b: bg[2] as f64,
97 a: 1.0,
98 }),
99 store: wgpu::StoreOp::Store,
100 },
101 })],
102 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
103 view: &targets.depth_view,
104 depth_ops: Some(wgpu::Operations {
105 load: wgpu::LoadOp::Clear(1.0),
106 store: wgpu::StoreOp::Discard,
107 }),
108 stencil_ops: None,
109 }),
110 timestamp_writes: None,
111 occlusion_query_set: None,
112 multiview_mask: None,
113 });
114 pass.set_bind_group(0, &self.globals_bind, &[]);
115 pass.set_bind_group(2, &self.identity_model.bind, &[0]);
118
119 let visible_solids: Vec<(&SolidDisplay, &GpuSolid)> = gpu_scene
120 .order
121 .iter()
122 .filter_map(|name| {
123 let solid = scene.solid(name)?;
124 if !solid.visible {
125 return None;
126 }
127 Some((solid, gpu_scene.solids.get(name)?))
128 })
129 .collect();
130
131 if params.settings.wireframe && params.settings.show_faces {
138 pass.set_pipeline(&self.wire_pipeline);
139 for (solid, gpu_solid) in &visible_solids {
140 if gpu_solid.wire_index_count == 0 {
141 continue;
142 }
143 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
144 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
145 pass.set_index_buffer(
146 gpu_solid.wire_index_buf.slice(..),
147 wgpu::IndexFormat::Uint32,
148 );
149 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
150 if !solid.visibility.any_face_hidden() {
152 pass.draw_indexed(0..gpu_solid.wire_index_count, 0, 0..1);
153 continue;
154 }
155 let mut run: Option<(u32, u32)> = None; let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<(u32, u32)>| {
164 if let Some((first, count)) = run.take() {
165 if count > 0 {
166 pass.draw_indexed(first..first + count, 0, 0..1);
167 }
168 }
169 };
170 for (index, _face) in solid.faces.iter().enumerate() {
171 let range = &gpu_solid.faces[index];
172 if range.wire_count == 0 {
173 continue;
174 }
175 let first = range.wire_first;
176 let count = range.wire_count;
177 if !solid.visibility.is_face_visible(index) {
178 flush(&mut pass, &mut run);
179 continue;
180 }
181 match &mut run {
182 Some((run_first, run_count)) if *run_first + *run_count == first => {
183 *run_count += count;
184 }
185 _ => {
186 flush(&mut pass, &mut run);
187 run = Some((first, count));
188 }
189 }
190 }
191 flush(&mut pass, &mut run);
192 }
193 }
194
195 if !params.settings.wireframe && params.settings.show_faces {
200 pass.set_pipeline(&self.mesh_pipeline);
201 for (solid, gpu_solid) in &visible_solids {
202 if solid.mesh.indices.is_empty() || translucent(solid) {
203 continue;
204 }
205 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
206 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
207 pass.set_index_buffer(gpu_solid.index_buf.slice(..), wgpu::IndexFormat::Uint32);
208 let any_face_hidden = solid.visibility.any_face_hidden();
216 let any_face_colored = !gpu_solid.face_styles.is_empty();
217 if !has_emphasis && !any_face_hidden && !any_face_colored {
218 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
219 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
220 continue;
221 }
222 let style_for = |state: EmphasisState, slot: Option<u32>| match state {
228 EmphasisState::Base => match slot.and_then(|s| gpu_solid.face_styles.get(s as usize)) {
229 Some(style) => &style.bind,
230 None => &gpu_solid.base_style.bind,
231 },
232 EmphasisState::Selected => &self.styles.face_selected.bind,
233 EmphasisState::Hovered => &self.styles.face_hovered.bind,
234 };
235 type FaceRun = (EmphasisState, Option<u32>, u32, u32); let mut run: Option<FaceRun> = None;
237 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<FaceRun>| {
238 if let Some((state, slot, first, count)) = run.take() {
239 if count > 0 {
240 pass.set_bind_group(1, style_for(state, slot), &[]);
241 pass.draw_indexed(first..first + count, 0, 0..1);
242 }
243 }
244 };
245 for (index, face) in solid.faces.iter().enumerate() {
246 let range = &gpu_solid.faces[index];
247 if range.index_count == 0 {
248 continue;
249 }
250 if !solid.visibility.is_face_visible(index) {
253 flush(&mut pass, &mut run);
254 continue;
255 }
256 let state = if has_emphasis {
257 params.emphasis.face_state(&solid.name, &face.name)
258 } else {
259 EmphasisState::Base
260 };
261 let slot = if state == EmphasisState::Base {
262 range.style
263 } else {
264 None
265 };
266 match &mut run {
267 Some((run_state, run_slot, first, count))
268 if *run_state == state
269 && *run_slot == slot
270 && *first + *count == range.first_index =>
271 {
272 *count += range.index_count;
273 }
274 _ => {
275 flush(&mut pass, &mut run);
276 run = Some((state, slot, range.first_index, range.index_count));
277 }
278 }
279 }
280 flush(&mut pass, &mut run);
281 }
282 }
283
284 if params.settings.show_edges
287 && params.settings.show_occluded_edges
288 && params.settings.hidden_edge_alpha > 0.0
289 {
290 pass.set_pipeline(&self.edge_hidden_pipeline);
291 pass.set_bind_group(1, &self.styles.edge_hidden.bind, &[]);
292 for (solid, gpu_solid) in &visible_solids {
293 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
294 if gpu_solid.occludable_edge_instances == 0 {
295 continue;
296 }
297 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
298 pass.set_vertex_buffer(0, edge_buf.slice(..));
299 if solid.visibility.any_edge_hidden() {
300 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Real);
302 } else {
303 pass.draw(0..6, 0..gpu_solid.occludable_edge_instances);
305 }
306 }
307 }
308
309 if params.settings.show_edges {
312 let aux_shown = params.world_per_pixel <= AUX_EDGE_MAX_WORLD_PER_PIXEL;
313 pass.set_pipeline(&self.edge_visible_pipeline);
314 for (solid, gpu_solid) in &visible_solids {
315 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
316 if gpu_solid.edge_instances == 0 {
317 continue;
318 }
319 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
320 pass.set_vertex_buffer(0, edge_buf.slice(..));
321 let any_edge_hidden = solid.visibility.any_edge_hidden();
322 let split = gpu_solid.occludable_edge_instances;
327 if !has_emphasis {
328 pass.set_bind_group(1, &self.styles.edge_base.bind, &[]);
329 if any_edge_hidden {
330 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Real);
331 if aux_shown {
332 pass.set_bind_group(1, &self.styles.edge_aux.bind, &[]);
333 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Aux);
334 }
335 } else {
336 if split > 0 {
337 pass.draw(0..6, 0..split);
338 }
339 if aux_shown && split < gpu_solid.edge_instances {
340 pass.set_bind_group(1, &self.styles.edge_aux.bind, &[]);
341 pass.draw(0..6, split..gpu_solid.edge_instances);
342 }
343 }
344 continue;
345 }
346 let style_for = |(state, aux): (EmphasisState, bool)| match state {
349 EmphasisState::Base if aux => &self.styles.edge_aux.bind,
350 EmphasisState::Base => &self.styles.edge_base.bind,
351 EmphasisState::Selected => &self.styles.edge_selected.bind,
352 EmphasisState::Hovered => &self.styles.edge_hovered.bind,
353 };
354 let mut run: Option<((EmphasisState, bool), u32, u32)> = None;
355 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<((EmphasisState, bool), u32, u32)>| {
356 if let Some((state, first, count)) = run.take() {
357 if count > 0 {
358 pass.set_bind_group(1, style_for(state), &[]);
359 pass.draw(0..6, first..first + count);
360 }
361 }
362 };
363 for (index, edge) in solid.edges.iter().enumerate() {
364 let range = &gpu_solid.edges[index];
365 if range.instance_count == 0 {
366 continue;
367 }
368 if !solid.visibility.is_edge_visible(index) || (edge.aux && !aux_shown) {
370 flush(&mut pass, &mut run);
371 continue;
372 }
373 let state = (params.emphasis.edge_state(&solid.name, &edge.name), edge.aux);
374 match &mut run {
375 Some((run_state, first, count))
376 if *run_state == state
377 && *first + *count == range.first_instance =>
378 {
379 *count += range.instance_count;
380 }
381 _ => {
382 flush(&mut pass, &mut run);
383 run = Some((state, range.first_instance, range.instance_count));
384 }
385 }
386 }
387 flush(&mut pass, &mut run);
388 }
389 }
390
391 if has_emphasis {
393 pass.set_pipeline(&self.edge_visible_pipeline);
402 pass.set_bind_group(1, &self.styles.boundary.bind, &[]);
403 for (_, gpu_solid) in &visible_solids {
404 let Some(boundary) = &gpu_solid.boundary else { continue };
405 let Some(buf) = &boundary.buf else { continue };
406 if boundary.count == 0 {
407 continue;
408 }
409 pass.set_bind_group(2, &self.identity_model.bind, &[0]);
410 pass.set_vertex_buffer(0, buf.slice(..));
411 pass.draw(0..6, 0..boundary.count);
412 }
413 }
414
415 if params.settings.show_vertices && params.settings.vertex_size_px > 0.0 {
417 pass.set_pipeline(&self.point_pipeline);
418 for (solid, gpu_solid) in &visible_solids {
419 let Some(point_buf) = &gpu_solid.point_buf else { continue };
420 if gpu_solid.point_count == 0 {
421 continue;
422 }
423 if solid.visibility.all_vertices_hidden(solid.vertices.len()) {
429 continue;
430 }
431 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
432 pass.set_vertex_buffer(0, point_buf.slice(..));
433 let any_vertex_hidden = solid.visibility.any_vertex_hidden();
434 if has_emphasis || any_vertex_hidden {
435 let tol = 1e-9_f64.max(params.world_per_pixel * 1e-3);
436 let mut base_run: Option<(u32, u32)> = None;
437 let mut emphasized: Vec<(EmphasisState, u32)> = Vec::new();
438 for (index, vertex) in solid.vertices.iter().enumerate() {
439 if !solid.visibility.is_vertex_visible(index) {
441 if let Some((first, count)) = base_run.take() {
442 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
443 pass.draw(0..6, first..first + count);
444 }
445 continue;
446 }
447 let state = if has_emphasis {
448 params.emphasis.vertex_state(&solid.name, vertex.position, tol)
449 } else {
450 EmphasisState::Base
451 };
452 if state == EmphasisState::Base {
453 match &mut base_run {
454 Some((first, count)) if *first + *count == index as u32 => {
455 *count += 1
456 }
457 _ => {
458 if let Some((first, count)) = base_run.take() {
459 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
460 pass.draw(0..6, first..first + count);
461 }
462 base_run = Some((index as u32, 1));
463 }
464 }
465 } else {
466 emphasized.push((state, index as u32));
467 }
468 }
469 if let Some((first, count)) = base_run {
470 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
471 pass.draw(0..6, first..first + count);
472 }
473 for (state, index) in emphasized {
474 let style = match state {
475 EmphasisState::Selected => &self.styles.point_selected.bind,
476 _ => &self.styles.point_hovered.bind,
477 };
478 pass.set_bind_group(1, style, &[]);
479 pass.draw(0..6, index..index + 1);
480 }
481 } else {
482 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
483 pass.draw(0..6, 0..gpu_solid.point_count);
484 }
485 }
486 }
487
488 if draw_axes {
490 if let Some(axis_buf) = &gpu_scene.axis_buf {
491 pass.set_pipeline(&self.edge_visible_pipeline);
492 pass.set_bind_group(2, &self.identity_model.bind, &[0]);
493 pass.set_vertex_buffer(0, axis_buf.slice(..));
494 for (index, style) in [
495 &self.styles.axis_x,
496 &self.styles.axis_y,
497 &self.styles.axis_z,
498 ]
499 .iter()
500 .enumerate()
501 {
502 pass.set_bind_group(1, &style.bind, &[]);
503 let i = index as u32;
504 pass.draw(0..6, i..i + 1);
505 }
506 }
507 }
508 if !params.settings.wireframe && params.settings.show_faces {
513 let mut any = false;
514 for (solid, gpu_solid) in visible_solids.iter().filter(|(solid, _)| translucent(solid) && !solid.mesh.indices.is_empty()) {
515 if !any {
516 pass.set_pipeline(&self.context_pipeline);
517 any = true;
518 }
519 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
520 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
521 pass.set_index_buffer(gpu_solid.index_buf.slice(..), wgpu::IndexFormat::Uint32);
522 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
523 if !solid.visibility.any_face_hidden() {
524 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
525 continue;
526 }
527 for (index, face) in gpu_solid.faces.iter().enumerate() {
528 if solid.visibility.is_face_visible(index) {
529 pass.draw_indexed(face.first_index..face.first_index + face.index_count, 0, 0..1);
530 }
531 }
532 }
533 }
534 if let Some((gpu_context, context_scene)) = context {
535 self.context_style.write(&self.queue, context_scene.tint.unwrap_or(super::WORK_PART_CONTEXT_RGBA), [0.0; 4]);
538 pass.set_pipeline(&self.context_pipeline);
539 pass.set_bind_group(1, &self.context_style.bind, &[]);
540 for solid in context_scene.solids().iter().filter(|solid| solid.visible) {
541 let Some(gpu) = gpu_context.solids.get(&solid.name) else { continue };
542 bind_pose(&mut pass, gpu_context.models.as_ref(), &self.identity_model, gpu.model_slot);
543 pass.set_vertex_buffer(0, gpu.vertex_buf.slice(..));
544 pass.set_index_buffer(gpu.index_buf.slice(..), wgpu::IndexFormat::Uint32);
545 if !solid.visibility.any_face_hidden() {
546 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
547 continue;
548 }
549 for (index, face) in gpu.faces.iter().enumerate() {
550 if solid.visibility.is_face_visible(index) {
551 pass.draw_indexed(face.first_index..face.first_index + face.index_count, 0, 0..1);
552 }
553 }
554 }
555 for (solid_name, face_name, selected) in &context_scene.highlight {
558 let Some(solid) = context_scene.solid(solid_name) else { continue };
559 let Some(gpu) = gpu_context.solids.get(solid_name) else { continue };
560 let Some(index) = solid.faces.iter().position(|face| &face.name == face_name) else {
561 let edge = solid.edges.iter().position(|edge| &edge.name == face_name);
562 let (Some(index), Some(edge_buf)) = (edge, &gpu.edge_buf) else { continue };
563 let Some(range) = gpu.edges.get(index) else { continue };
564 pass.set_pipeline(&self.edge_visible_pipeline);
565 pass.set_bind_group(1, if *selected { &self.styles.edge_selected.bind } else { &self.styles.edge_hovered.bind }, &[]);
566 bind_pose(&mut pass, gpu_context.models.as_ref(), &self.identity_model, gpu.model_slot);
567 pass.set_vertex_buffer(0, edge_buf.slice(..));
568 pass.draw(0..6, range.first_instance..range.first_instance + range.instance_count);
569 pass.set_pipeline(&self.context_pipeline);
570 continue;
571 };
572 let Some(range) = gpu.faces.get(index) else { continue };
573 pass.set_bind_group(1, if *selected { &self.context_selected.bind } else { &self.context_hovered.bind }, &[]);
574 bind_pose(&mut pass, gpu_context.models.as_ref(), &self.identity_model, gpu.model_slot);
575 pass.set_vertex_buffer(0, gpu.vertex_buf.slice(..));
576 pass.set_index_buffer(gpu.index_buf.slice(..), wgpu::IndexFormat::Uint32);
577 pass.draw_indexed(range.first_index..range.first_index + range.index_count, 0, 0..1);
578 }
579 }
580 }
581
582 if let Some(overlay) = params.overlay {
587 let make_tris = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
588 let verts = overlay_tri_verts(ov);
589 (!verts.is_empty()).then(|| {
590 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
591 label: Some("overlay tris"),
592 contents: bytemuck::cast_slice(&verts),
593 usage: wgpu::BufferUsages::VERTEX,
594 })
595 })
596 };
597 let make_lines = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
598 let insts = overlay_line_insts(ov);
599 (!insts.is_empty()).then(|| {
600 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
601 label: Some("overlay lines"),
602 contents: bytemuck::cast_slice(&insts),
603 usage: wgpu::BufferUsages::VERTEX,
604 })
605 })
606 };
607
608 let main_tri_count = overlay.main.tris.len() as u32;
609 let main_line_count = (overlay.main.lines.len() / 2) as u32;
610 let main_tri_buf = make_tris(&overlay.main);
611 let main_line_buf = make_lines(&overlay.main);
612 if main_tri_buf.is_some() || main_line_buf.is_some() {
613 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
614 label: Some("overlay-main"),
615 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
616 view: &targets.msaa_view,
617 depth_slice: None,
618 resolve_target: Some(resolve_view),
619 ops: wgpu::Operations {
620 load: wgpu::LoadOp::Load,
621 store: wgpu::StoreOp::Store,
622 },
623 })],
624 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
625 view: &targets.depth_view,
626 depth_ops: Some(wgpu::Operations {
627 load: wgpu::LoadOp::Clear(1.0),
628 store: wgpu::StoreOp::Discard,
629 }),
630 stencil_ops: None,
631 }),
632 timestamp_writes: None,
633 occlusion_query_set: None,
634 multiview_mask: None,
635 });
636 pass.set_bind_group(0, &self.globals_bind, &[]);
637 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
638 if let Some(buf) = &main_tri_buf {
639 pass.set_vertex_buffer(0, buf.slice(..));
640 let plane_verts = (overlay.plane_tri_verts as u32).min(main_tri_count);
645 if plane_verts > 0 {
646 pass.set_pipeline(&self.overlay_tri_nodepth_pipeline);
647 pass.draw(0..plane_verts, 0..1);
648 }
649 if plane_verts < main_tri_count {
650 pass.set_pipeline(&self.overlay_tri_pipeline);
651 pass.draw(plane_verts..main_tri_count, 0..1);
652 }
653 }
654 if let Some(buf) = &main_line_buf {
655 pass.set_pipeline(&self.overlay_line_pipeline);
656 pass.set_vertex_buffer(0, buf.slice(..));
657 pass.draw(0..6, 0..main_line_count);
658 }
659 }
660
661 if let Some(vc) = &overlay.viewcube {
662 let dpr = params.dpr.max(1e-3);
663 let mut x = (vc.rect_css[0] * dpr).max(0.0);
664 let mut y = (vc.rect_css[1] * dpr).max(0.0);
665 let mut w = (vc.rect_css[2] * dpr).max(1.0);
666 let mut h = (vc.rect_css[3] * dpr).max(1.0);
667 w = w.min(width as f32 - x).max(1.0);
669 h = h.min(height as f32 - y).max(1.0);
670 x = x.min(width as f32 - w).max(0.0);
671 y = y.min(height as f32 - h).max(0.0);
672
673 let vc_globals = Globals {
674 view_proj: vc.view_proj,
675 viewport: [w, h, dpr, 0.0],
676 forward: [vc.forward[0], vc.forward[1], vc.forward[2], 0.0],
677 };
678 self.queue
679 .write_buffer(&self.vc_globals_buf, 0, bytemuck::bytes_of(&vc_globals));
680
681 let vc_tri_count = vc.overlay.tris.len() as u32;
682 let vc_line_count = (vc.overlay.lines.len() / 2) as u32;
683 let vc_tri_buf = make_tris(&vc.overlay);
684 let vc_line_buf = make_lines(&vc.overlay);
685 if vc_tri_buf.is_some() || vc_line_buf.is_some() {
686 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
687 label: Some("overlay-viewcube"),
688 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
689 view: &targets.msaa_view,
690 depth_slice: None,
691 resolve_target: Some(resolve_view),
692 ops: wgpu::Operations {
693 load: wgpu::LoadOp::Load,
694 store: wgpu::StoreOp::Store,
695 },
696 })],
697 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
698 view: &targets.depth_view,
699 depth_ops: Some(wgpu::Operations {
700 load: wgpu::LoadOp::Clear(1.0),
701 store: wgpu::StoreOp::Discard,
702 }),
703 stencil_ops: None,
704 }),
705 timestamp_writes: None,
706 occlusion_query_set: None,
707 multiview_mask: None,
708 });
709 pass.set_viewport(x, y, w, h, 0.0, 1.0);
710 pass.set_scissor_rect(x as u32, y as u32, w as u32, h as u32);
711 pass.set_bind_group(0, &self.vc_globals_bind, &[]);
712 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
713 if let Some(buf) = &vc_tri_buf {
714 pass.set_pipeline(&self.overlay_tri_pipeline);
715 pass.set_vertex_buffer(0, buf.slice(..));
716 pass.draw(0..vc_tri_count, 0..1);
717 }
718 if let Some(buf) = &vc_line_buf {
719 pass.set_pipeline(&self.overlay_line_pipeline);
720 pass.set_vertex_buffer(0, buf.slice(..));
721 pass.draw(0..6, 0..vc_line_count);
722 }
723 }
724 }
725 }
726
727 self.queue.submit([encoder.finish()]);
728 }
729
730 #[cfg(not(target_arch = "wasm32"))]
733 pub fn render_to_png(
734 &mut self,
735 scene: &RenderScene,
736 camera: &Camera,
737 width: u32,
738 height: u32,
739 ) -> Result<Vec<u8>, String> {
740 let settings = RenderSettings::artifact();
741 let mut gpu_scene = self.upload_scene_with(scene, &settings);
742 self.render_gpu_scene_to_png(&mut gpu_scene, scene, camera, width, height)
743 }
744
745 #[cfg(not(target_arch = "wasm32"))]
749 pub(crate) fn render_gpu_scene_to_png(
750 &mut self,
751 gpu_scene: &mut GpuScene,
752 scene: &RenderScene,
753 camera: &Camera,
754 width: u32,
755 height: u32,
756 ) -> Result<Vec<u8>, String> {
757 let settings = RenderSettings::artifact();
758 let rgb = self.render_gpu_scene_to_rgb(gpu_scene, scene, camera, width, height, &settings)?;
759 encode_png(&rgb, width, height)
760 }
761
762 #[cfg(not(target_arch = "wasm32"))]
767 pub fn render_gpu_scene_to_rgb(
768 &mut self,
769 gpu_scene: &mut GpuScene,
770 scene: &RenderScene,
771 camera: &Camera,
772 width: u32,
773 height: u32,
774 settings: &RenderSettings,
775 ) -> Result<Vec<u8>, String> {
776 let emphasis = Emphasis::default();
777 let params = FrameParams {
778 camera,
779 width,
780 height,
781 dpr: 1.0,
782 settings,
783 emphasis: &emphasis,
784 world_per_pixel: 0.0,
785 overlay: None,
786 };
787 let resolve = self.device.create_texture(&wgpu::TextureDescriptor {
788 label: Some("resolve"),
789 size: wgpu::Extent3d {
790 width,
791 height,
792 depth_or_array_layers: 1,
793 },
794 mip_level_count: 1,
795 sample_count: 1,
796 dimension: wgpu::TextureDimension::D2,
797 format: self.format,
798 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
799 view_formats: &[],
800 });
801 let resolve_view = resolve.create_view(&Default::default());
802 self.render_to_view(gpu_scene, scene, ¶ms, &resolve_view);
803
804 let bytes_per_row = (width * 4).div_ceil(256) * 256;
806 let readback = self.device.create_buffer(&wgpu::BufferDescriptor {
807 label: Some("readback"),
808 size: bytes_per_row as u64 * height as u64,
809 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
810 mapped_at_creation: false,
811 });
812 let mut encoder = self
813 .device
814 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
815 label: Some("readback"),
816 });
817 encoder.copy_texture_to_buffer(
818 wgpu::TexelCopyTextureInfo {
819 texture: &resolve,
820 mip_level: 0,
821 origin: wgpu::Origin3d::ZERO,
822 aspect: wgpu::TextureAspect::All,
823 },
824 wgpu::TexelCopyBufferInfo {
825 buffer: &readback,
826 layout: wgpu::TexelCopyBufferLayout {
827 offset: 0,
828 bytes_per_row: Some(bytes_per_row),
829 rows_per_image: None,
830 },
831 },
832 wgpu::Extent3d {
833 width,
834 height,
835 depth_or_array_layers: 1,
836 },
837 );
838 self.queue.submit([encoder.finish()]);
839
840 let slice = readback.slice(..);
841 let (sender, receiver) = std::sync::mpsc::channel();
842 slice.map_async(wgpu::MapMode::Read, move |result| {
843 let _ = sender.send(result);
844 });
845 self.device
846 .poll(wgpu::PollType::wait_indefinitely())
847 .map_err(|error| format!("wgpu poll: {error:?}"))?;
848 receiver
849 .recv()
850 .map_err(|_| "readback callback dropped".to_string())?
851 .map_err(|error| format!("readback map failed: {error:?}"))?;
852
853 let data = slice.get_mapped_range();
854 let mut rgb = Vec::with_capacity((width * height * 3) as usize);
855 for row in 0..height {
856 let start = (row * bytes_per_row) as usize;
857 for col in 0..width as usize {
858 let px = start + col * 4;
859 rgb.extend_from_slice(&data[px..px + 3]);
860 }
861 }
862 drop(data);
863 readback.unmap();
864 Ok(rgb)
865 }
866}
867
868const AUX_EDGE_MAX_WORLD_PER_PIXEL: f64 = 0.1;
879
880#[derive(Clone, Copy, PartialEq, Eq)]
882enum EdgeFilter {
883 Real,
885 Aux,
887}
888
889fn draw_visible_edge_ranges(
890 pass: &mut wgpu::RenderPass<'_>,
891 solid: &SolidDisplay,
892 gpu_solid: &GpuSolid,
893 filter: EdgeFilter,
894) {
895 let mut run: Option<(u32, u32)> = None; for (index, edge) in solid.edges.iter().enumerate() {
897 let range = &gpu_solid.edges[index];
898 if range.instance_count == 0 {
899 continue;
900 }
901 if !solid.visibility.is_edge_visible(index) || edge.aux != (filter == EdgeFilter::Aux) {
902 if let Some((first, count)) = run.take() {
903 pass.draw(0..6, first..first + count);
904 }
905 continue;
906 }
907 match &mut run {
908 Some((first, count)) if *first + *count == range.first_instance => {
909 *count += range.instance_count;
910 }
911 _ => {
912 if let Some((first, count)) = run.take() {
913 pass.draw(0..6, first..first + count);
914 }
915 run = Some((range.first_instance, range.instance_count));
916 }
917 }
918 }
919 if let Some((first, count)) = run {
920 pass.draw(0..6, first..first + count);
921 }
922}
923
924fn overlay_tri_verts(ov: &brep_gizmos::Overlay) -> Vec<OverlayTriVertex> {
926 ov.tris
927 .iter()
928 .map(|v| OverlayTriVertex {
929 position: v.pos,
930 normal: v.normal,
931 color: v.color,
932 })
933 .collect()
934}
935
936fn overlay_line_insts(ov: &brep_gizmos::Overlay) -> Vec<OverlayLineInstance> {
939 ov.lines
940 .chunks_exact(2)
941 .map(|pair| OverlayLineInstance {
942 p0: pair[0].pos,
943 p1: pair[1].pos,
944 color: pair[0].color,
945 })
946 .collect()
947}
948
949fn bind_pose(pass: &mut wgpu::RenderPass<'_>, models: Option<&ModelArena>, identity: &IdentityModel, slot: u32) {
952 match models {
953 Some(models) => pass.set_bind_group(2, &models.bind, &[ModelArena::offset(slot)]),
954 None => pass.set_bind_group(2, &identity.bind, &[0]),
955 }
956}