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 = match &scene.line_art {
80 Some(_) => crate::thumbnail::line_art::BACKGROUND,
81 None => params.settings.background,
82 };
83 let targets = self.targets.as_ref().expect("targets ensured");
84 let mut encoder = self
85 .device
86 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
87 label: Some("brep-render frame"),
88 });
89 {
90 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
91 label: Some("scene"),
92 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
93 view: &targets.msaa_view,
94 depth_slice: None,
95 resolve_target: Some(resolve_view),
96 ops: wgpu::Operations {
97 load: wgpu::LoadOp::Clear(wgpu::Color {
98 r: bg[0] as f64,
99 g: bg[1] as f64,
100 b: bg[2] as f64,
101 a: 1.0,
102 }),
103 store: wgpu::StoreOp::Store,
104 },
105 })],
106 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
107 view: &targets.depth_view,
108 depth_ops: Some(wgpu::Operations {
109 load: wgpu::LoadOp::Clear(1.0),
110 store: wgpu::StoreOp::Discard,
111 }),
112 stencil_ops: None,
113 }),
114 timestamp_writes: None,
115 occlusion_query_set: None,
116 multiview_mask: None,
117 });
118 pass.set_bind_group(0, &self.globals_bind, &[]);
119 pass.set_bind_group(2, &self.identity_model.bind, &[0]);
122
123 let visible_solids: Vec<(&SolidDisplay, &GpuSolid)> = gpu_scene
124 .order
125 .iter()
126 .filter_map(|name| {
127 let solid = scene.solid(name)?;
128 if !solid.visible {
129 return None;
130 }
131 Some((solid, gpu_scene.solids.get(name)?))
132 })
133 .collect();
134
135 if params.settings.wireframe && params.settings.show_faces {
142 pass.set_pipeline(&self.wire_pipeline);
143 for (solid, gpu_solid) in &visible_solids {
144 if gpu_solid.wire_index_count == 0 {
145 continue;
146 }
147 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
148 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
149 pass.set_index_buffer(
150 gpu_solid.wire_index_buf.slice(..),
151 wgpu::IndexFormat::Uint32,
152 );
153 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
154 if !solid.visibility.any_face_hidden() {
156 pass.draw_indexed(0..gpu_solid.wire_index_count, 0, 0..1);
157 continue;
158 }
159 let mut run: Option<(u32, u32)> = None; let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<(u32, u32)>| {
168 if let Some((first, count)) = run.take() {
169 if count > 0 {
170 pass.draw_indexed(first..first + count, 0, 0..1);
171 }
172 }
173 };
174 for (index, _face) in solid.faces.iter().enumerate() {
175 let range = &gpu_solid.faces[index];
176 if range.wire_count == 0 {
177 continue;
178 }
179 let first = range.wire_first;
180 let count = range.wire_count;
181 if !solid.visibility.is_face_visible(index) {
182 flush(&mut pass, &mut run);
183 continue;
184 }
185 match &mut run {
186 Some((run_first, run_count)) if *run_first + *run_count == first => {
187 *run_count += count;
188 }
189 _ => {
190 flush(&mut pass, &mut run);
191 run = Some((first, count));
192 }
193 }
194 }
195 flush(&mut pass, &mut run);
196 }
197 }
198
199 if !params.settings.wireframe && params.settings.show_faces {
204 pass.set_pipeline(&self.mesh_pipeline);
205 for (solid, gpu_solid) in &visible_solids {
206 if solid.mesh.indices.is_empty() || translucent(solid) {
207 continue;
208 }
209 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
210 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
211 pass.set_index_buffer(gpu_solid.index_buf.slice(..), wgpu::IndexFormat::Uint32);
212 if let Some(look) = &scene.line_art {
215 let style = if look.placed.contains(&solid.name) { &self.styles.line_art_placed_face } else { &self.styles.line_art_adding_face };
216 if !has_emphasis {
217 pass.set_bind_group(1, &style.bind, &[]);
218 draw_visible_faces(&mut pass, solid, gpu_solid);
219 continue;
220 }
221 for (index, face) in solid.faces.iter().enumerate() {
224 let range = &gpu_solid.faces[index];
225 if range.index_count == 0 || !solid.visibility.is_face_visible(index) {
226 continue;
227 }
228 let bind = match params.emphasis.face_state(&solid.name, &face.name) {
229 EmphasisState::Base => &style.bind,
230 EmphasisState::Selected => &self.styles.face_selected.bind,
231 EmphasisState::Hovered => &self.styles.face_hovered.bind,
232 };
233 pass.set_bind_group(1, bind, &[]);
234 pass.draw_indexed(range.first_index..range.first_index + range.index_count, 0, 0..1);
235 }
236 continue;
237 }
238 let any_face_hidden = solid.visibility.any_face_hidden();
246 let any_face_colored = !gpu_solid.face_styles.is_empty();
247 if !has_emphasis && !any_face_hidden && !any_face_colored {
248 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
249 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
250 continue;
251 }
252 let style_for = |state: EmphasisState, slot: Option<u32>| match state {
258 EmphasisState::Base => match slot.and_then(|s| gpu_solid.face_styles.get(s as usize)) {
259 Some(style) => &style.bind,
260 None => &gpu_solid.base_style.bind,
261 },
262 EmphasisState::Selected => &self.styles.face_selected.bind,
263 EmphasisState::Hovered => &self.styles.face_hovered.bind,
264 };
265 type FaceRun = (EmphasisState, Option<u32>, u32, u32); let mut run: Option<FaceRun> = None;
267 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<FaceRun>| {
268 if let Some((state, slot, first, count)) = run.take() {
269 if count > 0 {
270 pass.set_bind_group(1, style_for(state, slot), &[]);
271 pass.draw_indexed(first..first + count, 0, 0..1);
272 }
273 }
274 };
275 for (index, face) in solid.faces.iter().enumerate() {
276 let range = &gpu_solid.faces[index];
277 if range.index_count == 0 {
278 continue;
279 }
280 if !solid.visibility.is_face_visible(index) {
283 flush(&mut pass, &mut run);
284 continue;
285 }
286 let state = if has_emphasis {
287 params.emphasis.face_state(&solid.name, &face.name)
288 } else {
289 EmphasisState::Base
290 };
291 let slot = if state == EmphasisState::Base {
292 range.style
293 } else {
294 None
295 };
296 match &mut run {
297 Some((run_state, run_slot, first, count))
298 if *run_state == state
299 && *run_slot == slot
300 && *first + *count == range.first_index =>
301 {
302 *count += range.index_count;
303 }
304 _ => {
305 flush(&mut pass, &mut run);
306 run = Some((state, slot, range.first_index, range.index_count));
307 }
308 }
309 }
310 flush(&mut pass, &mut run);
311 }
312 }
313
314 if let (Some(look), false) = (&scene.line_art, params.settings.wireframe) {
319 pass.set_pipeline(&self.hull_pipeline);
320 for (solid, gpu_solid) in &visible_solids {
321 if solid.mesh.indices.is_empty() || translucent(solid) {
322 continue;
323 }
324 let style = if look.placed.contains(&solid.name) { &self.styles.line_art_placed_outline } else { &self.styles.line_art_adding_outline };
325 pass.set_bind_group(1, &style.bind, &[]);
326 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
327 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
328 pass.set_index_buffer(gpu_solid.index_buf.slice(..), wgpu::IndexFormat::Uint32);
329 draw_visible_faces(&mut pass, solid, gpu_solid);
330 }
331 }
332
333 if params.settings.show_edges
336 && params.settings.show_occluded_edges
337 && params.settings.hidden_edge_alpha > 0.0
338 && scene.line_art.is_none()
339 {
340 pass.set_pipeline(&self.edge_hidden_pipeline);
341 pass.set_bind_group(1, &self.styles.edge_hidden.bind, &[]);
342 for (solid, gpu_solid) in &visible_solids {
343 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
344 if gpu_solid.occludable_edge_instances == 0 {
345 continue;
346 }
347 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
348 pass.set_vertex_buffer(0, edge_buf.slice(..));
349 if solid.visibility.any_edge_hidden() {
350 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Real);
352 } else {
353 pass.draw(0..6, 0..gpu_solid.occludable_edge_instances);
355 }
356 }
357 }
358
359 if params.settings.show_edges {
362 let aux_shown = params.world_per_pixel <= AUX_EDGE_MAX_WORLD_PER_PIXEL;
363 pass.set_pipeline(&self.edge_visible_pipeline);
364 for (solid, gpu_solid) in &visible_solids {
365 let Some(edge_buf) = &gpu_solid.edge_buf else { continue };
366 if gpu_solid.edge_instances == 0 {
367 continue;
368 }
369 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
370 pass.set_vertex_buffer(0, edge_buf.slice(..));
371 let any_edge_hidden = solid.visibility.any_edge_hidden();
372 let split = gpu_solid.occludable_edge_instances;
377 if let Some(look) = &scene.line_art {
380 let style = if look.placed.contains(&solid.name) { &self.styles.line_art_placed_edge } else { &self.styles.line_art_adding_edge };
381 if !has_emphasis {
382 pass.set_bind_group(1, &style.bind, &[]);
383 if any_edge_hidden {
384 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Real);
385 } else if split > 0 {
386 pass.draw(0..6, 0..split);
387 }
388 continue;
389 }
390 for (index, edge) in solid.edges.iter().enumerate() {
391 let range = &gpu_solid.edges[index];
392 if range.instance_count == 0 || edge.aux || !solid.visibility.is_edge_visible(index) {
393 continue;
394 }
395 let bind = match params.emphasis.edge_state(&solid.name, &edge.name) {
396 EmphasisState::Base => &style.bind,
397 EmphasisState::Selected => &self.styles.edge_selected.bind,
398 EmphasisState::Hovered => &self.styles.edge_hovered.bind,
399 };
400 pass.set_bind_group(1, bind, &[]);
401 pass.draw(0..6, range.first_instance..range.first_instance + range.instance_count);
402 }
403 continue;
404 }
405 if !has_emphasis {
406 pass.set_bind_group(1, &self.styles.edge_base.bind, &[]);
407 if any_edge_hidden {
408 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Real);
409 if aux_shown {
410 pass.set_bind_group(1, &self.styles.edge_aux.bind, &[]);
411 draw_visible_edge_ranges(&mut pass, solid, gpu_solid, EdgeFilter::Aux);
412 }
413 } else {
414 if split > 0 {
415 pass.draw(0..6, 0..split);
416 }
417 if aux_shown && split < gpu_solid.edge_instances {
418 pass.set_bind_group(1, &self.styles.edge_aux.bind, &[]);
419 pass.draw(0..6, split..gpu_solid.edge_instances);
420 }
421 }
422 continue;
423 }
424 let style_for = |(state, aux): (EmphasisState, bool)| match state {
427 EmphasisState::Base if aux => &self.styles.edge_aux.bind,
428 EmphasisState::Base => &self.styles.edge_base.bind,
429 EmphasisState::Selected => &self.styles.edge_selected.bind,
430 EmphasisState::Hovered => &self.styles.edge_hovered.bind,
431 };
432 let mut run: Option<((EmphasisState, bool), u32, u32)> = None;
433 let flush = |pass: &mut wgpu::RenderPass, run: &mut Option<((EmphasisState, bool), u32, u32)>| {
434 if let Some((state, first, count)) = run.take() {
435 if count > 0 {
436 pass.set_bind_group(1, style_for(state), &[]);
437 pass.draw(0..6, first..first + count);
438 }
439 }
440 };
441 for (index, edge) in solid.edges.iter().enumerate() {
442 let range = &gpu_solid.edges[index];
443 if range.instance_count == 0 {
444 continue;
445 }
446 if !solid.visibility.is_edge_visible(index) || (edge.aux && !aux_shown) {
448 flush(&mut pass, &mut run);
449 continue;
450 }
451 let state = (params.emphasis.edge_state(&solid.name, &edge.name), edge.aux);
452 match &mut run {
453 Some((run_state, first, count))
454 if *run_state == state
455 && *first + *count == range.first_instance =>
456 {
457 *count += range.instance_count;
458 }
459 _ => {
460 flush(&mut pass, &mut run);
461 run = Some((state, range.first_instance, range.instance_count));
462 }
463 }
464 }
465 flush(&mut pass, &mut run);
466 }
467 }
468
469 if has_emphasis {
471 pass.set_pipeline(&self.edge_visible_pipeline);
480 pass.set_bind_group(1, &self.styles.boundary.bind, &[]);
481 for (_, gpu_solid) in &visible_solids {
482 let Some(boundary) = &gpu_solid.boundary else { continue };
483 let Some(buf) = &boundary.buf else { continue };
484 if boundary.count == 0 {
485 continue;
486 }
487 pass.set_bind_group(2, &self.identity_model.bind, &[0]);
488 pass.set_vertex_buffer(0, buf.slice(..));
489 pass.draw(0..6, 0..boundary.count);
490 }
491 }
492
493 if params.settings.show_vertices && params.settings.vertex_size_px > 0.0 && scene.line_art.is_none() {
496 pass.set_pipeline(&self.point_pipeline);
497 for (solid, gpu_solid) in &visible_solids {
498 let Some(point_buf) = &gpu_solid.point_buf else { continue };
499 if gpu_solid.point_count == 0 {
500 continue;
501 }
502 if solid.visibility.all_vertices_hidden(solid.vertices.len()) {
508 continue;
509 }
510 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
511 pass.set_vertex_buffer(0, point_buf.slice(..));
512 let any_vertex_hidden = solid.visibility.any_vertex_hidden();
513 if has_emphasis || any_vertex_hidden {
514 let tol = 1e-9_f64.max(params.world_per_pixel * 1e-3);
515 let mut base_run: Option<(u32, u32)> = None;
516 let mut emphasized: Vec<(EmphasisState, u32)> = Vec::new();
517 for (index, vertex) in solid.vertices.iter().enumerate() {
518 if !solid.visibility.is_vertex_visible(index) {
520 if let Some((first, count)) = base_run.take() {
521 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
522 pass.draw(0..6, first..first + count);
523 }
524 continue;
525 }
526 let state = if has_emphasis {
527 params.emphasis.vertex_state(&solid.name, vertex.position, tol)
528 } else {
529 EmphasisState::Base
530 };
531 if state == EmphasisState::Base {
532 match &mut base_run {
533 Some((first, count)) if *first + *count == index as u32 => {
534 *count += 1
535 }
536 _ => {
537 if let Some((first, count)) = base_run.take() {
538 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
539 pass.draw(0..6, first..first + count);
540 }
541 base_run = Some((index as u32, 1));
542 }
543 }
544 } else {
545 emphasized.push((state, index as u32));
546 }
547 }
548 if let Some((first, count)) = base_run {
549 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
550 pass.draw(0..6, first..first + count);
551 }
552 for (state, index) in emphasized {
553 let style = match state {
554 EmphasisState::Selected => &self.styles.point_selected.bind,
555 _ => &self.styles.point_hovered.bind,
556 };
557 pass.set_bind_group(1, style, &[]);
558 pass.draw(0..6, index..index + 1);
559 }
560 } else {
561 pass.set_bind_group(1, &self.styles.point_base.bind, &[]);
562 pass.draw(0..6, 0..gpu_solid.point_count);
563 }
564 }
565 }
566
567 if draw_axes {
569 if let Some(axis_buf) = &gpu_scene.axis_buf {
570 pass.set_pipeline(&self.edge_visible_pipeline);
571 pass.set_bind_group(2, &self.identity_model.bind, &[0]);
572 pass.set_vertex_buffer(0, axis_buf.slice(..));
573 for (index, style) in [
574 &self.styles.axis_x,
575 &self.styles.axis_y,
576 &self.styles.axis_z,
577 ]
578 .iter()
579 .enumerate()
580 {
581 pass.set_bind_group(1, &style.bind, &[]);
582 let i = index as u32;
583 pass.draw(0..6, i..i + 1);
584 }
585 }
586 }
587 if !params.settings.wireframe && params.settings.show_faces {
592 let mut any = false;
593 for (solid, gpu_solid) in visible_solids.iter().filter(|(solid, _)| translucent(solid) && !solid.mesh.indices.is_empty()) {
594 if !any {
595 pass.set_pipeline(&self.context_pipeline);
596 any = true;
597 }
598 bind_pose(&mut pass, gpu_scene.models.as_ref(), &self.identity_model, gpu_solid.model_slot);
599 pass.set_vertex_buffer(0, gpu_solid.vertex_buf.slice(..));
600 pass.set_index_buffer(gpu_solid.index_buf.slice(..), wgpu::IndexFormat::Uint32);
601 pass.set_bind_group(1, &gpu_solid.base_style.bind, &[]);
602 if !solid.visibility.any_face_hidden() {
603 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
604 continue;
605 }
606 for (index, face) in gpu_solid.faces.iter().enumerate() {
607 if solid.visibility.is_face_visible(index) {
608 pass.draw_indexed(face.first_index..face.first_index + face.index_count, 0, 0..1);
609 }
610 }
611 }
612 }
613 if let Some((gpu_context, context_scene)) = context {
614 self.context_style.write(&self.queue, context_scene.tint.unwrap_or(super::WORK_PART_CONTEXT_RGBA), [0.0; 4]);
617 pass.set_pipeline(&self.context_pipeline);
618 pass.set_bind_group(1, &self.context_style.bind, &[]);
619 for solid in context_scene.solids().iter().filter(|solid| solid.visible) {
620 let Some(gpu) = gpu_context.solids.get(&solid.name) else { continue };
621 bind_pose(&mut pass, gpu_context.models.as_ref(), &self.identity_model, gpu.model_slot);
622 pass.set_vertex_buffer(0, gpu.vertex_buf.slice(..));
623 pass.set_index_buffer(gpu.index_buf.slice(..), wgpu::IndexFormat::Uint32);
624 if !solid.visibility.any_face_hidden() {
625 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
626 continue;
627 }
628 for (index, face) in gpu.faces.iter().enumerate() {
629 if solid.visibility.is_face_visible(index) {
630 pass.draw_indexed(face.first_index..face.first_index + face.index_count, 0, 0..1);
631 }
632 }
633 }
634 for (solid_name, face_name, selected) in &context_scene.highlight {
637 let Some(solid) = context_scene.solid(solid_name) else { continue };
638 let Some(gpu) = gpu_context.solids.get(solid_name) else { continue };
639 let Some(index) = solid.faces.iter().position(|face| &face.name == face_name) else {
640 let edge = solid.edges.iter().position(|edge| &edge.name == face_name);
641 let (Some(index), Some(edge_buf)) = (edge, &gpu.edge_buf) else { continue };
642 let Some(range) = gpu.edges.get(index) else { continue };
643 pass.set_pipeline(&self.edge_visible_pipeline);
644 pass.set_bind_group(1, if *selected { &self.styles.edge_selected.bind } else { &self.styles.edge_hovered.bind }, &[]);
645 bind_pose(&mut pass, gpu_context.models.as_ref(), &self.identity_model, gpu.model_slot);
646 pass.set_vertex_buffer(0, edge_buf.slice(..));
647 pass.draw(0..6, range.first_instance..range.first_instance + range.instance_count);
648 pass.set_pipeline(&self.context_pipeline);
649 continue;
650 };
651 let Some(range) = gpu.faces.get(index) else { continue };
652 pass.set_bind_group(1, if *selected { &self.context_selected.bind } else { &self.context_hovered.bind }, &[]);
653 bind_pose(&mut pass, gpu_context.models.as_ref(), &self.identity_model, gpu.model_slot);
654 pass.set_vertex_buffer(0, gpu.vertex_buf.slice(..));
655 pass.set_index_buffer(gpu.index_buf.slice(..), wgpu::IndexFormat::Uint32);
656 pass.draw_indexed(range.first_index..range.first_index + range.index_count, 0, 0..1);
657 }
658 }
659 }
660
661 if let Some(overlay) = params.overlay {
666 let make_tris = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
667 let verts = overlay_tri_verts(ov);
668 (!verts.is_empty()).then(|| {
669 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
670 label: Some("overlay tris"),
671 contents: bytemuck::cast_slice(&verts),
672 usage: wgpu::BufferUsages::VERTEX,
673 })
674 })
675 };
676 let make_lines = |ov: &brep_gizmos::Overlay| -> Option<wgpu::Buffer> {
677 let insts = overlay_line_insts(ov);
678 (!insts.is_empty()).then(|| {
679 self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
680 label: Some("overlay lines"),
681 contents: bytemuck::cast_slice(&insts),
682 usage: wgpu::BufferUsages::VERTEX,
683 })
684 })
685 };
686
687 let main_tri_count = overlay.main.tris.len() as u32;
688 let main_line_count = (overlay.main.lines.len() / 2) as u32;
689 let main_tri_buf = make_tris(&overlay.main);
690 let main_line_buf = make_lines(&overlay.main);
691 if main_tri_buf.is_some() || main_line_buf.is_some() {
692 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
693 label: Some("overlay-main"),
694 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
695 view: &targets.msaa_view,
696 depth_slice: None,
697 resolve_target: Some(resolve_view),
698 ops: wgpu::Operations {
699 load: wgpu::LoadOp::Load,
700 store: wgpu::StoreOp::Store,
701 },
702 })],
703 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
704 view: &targets.depth_view,
705 depth_ops: Some(wgpu::Operations {
706 load: wgpu::LoadOp::Clear(1.0),
707 store: wgpu::StoreOp::Discard,
708 }),
709 stencil_ops: None,
710 }),
711 timestamp_writes: None,
712 occlusion_query_set: None,
713 multiview_mask: None,
714 });
715 pass.set_bind_group(0, &self.globals_bind, &[]);
716 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
717 if let Some(buf) = &main_tri_buf {
718 pass.set_vertex_buffer(0, buf.slice(..));
719 let plane_verts = (overlay.plane_tri_verts as u32).min(main_tri_count);
724 if plane_verts > 0 {
725 pass.set_pipeline(&self.overlay_tri_nodepth_pipeline);
726 pass.draw(0..plane_verts, 0..1);
727 }
728 if plane_verts < main_tri_count {
729 pass.set_pipeline(&self.overlay_tri_pipeline);
730 pass.draw(plane_verts..main_tri_count, 0..1);
731 }
732 }
733 if let Some(buf) = &main_line_buf {
734 pass.set_pipeline(&self.overlay_line_pipeline);
735 pass.set_vertex_buffer(0, buf.slice(..));
736 pass.draw(0..6, 0..main_line_count);
737 }
738 }
739
740 if let Some(vc) = &overlay.viewcube {
741 let dpr = params.dpr.max(1e-3);
742 let mut x = (vc.rect_css[0] * dpr).max(0.0);
743 let mut y = (vc.rect_css[1] * dpr).max(0.0);
744 let mut w = (vc.rect_css[2] * dpr).max(1.0);
745 let mut h = (vc.rect_css[3] * dpr).max(1.0);
746 w = w.min(width as f32 - x).max(1.0);
748 h = h.min(height as f32 - y).max(1.0);
749 x = x.min(width as f32 - w).max(0.0);
750 y = y.min(height as f32 - h).max(0.0);
751
752 let vc_globals = Globals {
753 view_proj: vc.view_proj,
754 viewport: [w, h, dpr, 0.0],
755 forward: [vc.forward[0], vc.forward[1], vc.forward[2], 0.0],
756 };
757 self.queue
758 .write_buffer(&self.vc_globals_buf, 0, bytemuck::bytes_of(&vc_globals));
759
760 let vc_tri_count = vc.overlay.tris.len() as u32;
761 let vc_line_count = (vc.overlay.lines.len() / 2) as u32;
762 let vc_tri_buf = make_tris(&vc.overlay);
763 let vc_line_buf = make_lines(&vc.overlay);
764 if vc_tri_buf.is_some() || vc_line_buf.is_some() {
765 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
766 label: Some("overlay-viewcube"),
767 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
768 view: &targets.msaa_view,
769 depth_slice: None,
770 resolve_target: Some(resolve_view),
771 ops: wgpu::Operations {
772 load: wgpu::LoadOp::Load,
773 store: wgpu::StoreOp::Store,
774 },
775 })],
776 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
777 view: &targets.depth_view,
778 depth_ops: Some(wgpu::Operations {
779 load: wgpu::LoadOp::Clear(1.0),
780 store: wgpu::StoreOp::Discard,
781 }),
782 stencil_ops: None,
783 }),
784 timestamp_writes: None,
785 occlusion_query_set: None,
786 multiview_mask: None,
787 });
788 pass.set_viewport(x, y, w, h, 0.0, 1.0);
789 pass.set_scissor_rect(x as u32, y as u32, w as u32, h as u32);
790 pass.set_bind_group(0, &self.vc_globals_bind, &[]);
791 pass.set_bind_group(1, &self.styles.overlay_line.bind, &[]);
792 if let Some(buf) = &vc_tri_buf {
793 pass.set_pipeline(&self.overlay_tri_pipeline);
794 pass.set_vertex_buffer(0, buf.slice(..));
795 pass.draw(0..vc_tri_count, 0..1);
796 }
797 if let Some(buf) = &vc_line_buf {
798 pass.set_pipeline(&self.overlay_line_pipeline);
799 pass.set_vertex_buffer(0, buf.slice(..));
800 pass.draw(0..6, 0..vc_line_count);
801 }
802 }
803 }
804 }
805
806 self.queue.submit([encoder.finish()]);
807 }
808
809 #[cfg(not(target_arch = "wasm32"))]
812 pub fn render_to_png(
813 &mut self,
814 scene: &RenderScene,
815 camera: &Camera,
816 width: u32,
817 height: u32,
818 ) -> Result<Vec<u8>, String> {
819 let settings = RenderSettings::artifact();
820 let mut gpu_scene = self.upload_scene_with(scene, &settings);
821 self.render_gpu_scene_to_png(&mut gpu_scene, scene, camera, width, height)
822 }
823
824 #[cfg(not(target_arch = "wasm32"))]
828 pub(crate) fn render_gpu_scene_to_png(
829 &mut self,
830 gpu_scene: &mut GpuScene,
831 scene: &RenderScene,
832 camera: &Camera,
833 width: u32,
834 height: u32,
835 ) -> Result<Vec<u8>, String> {
836 let settings = RenderSettings::artifact();
837 let rgb = self.render_gpu_scene_to_rgb(gpu_scene, scene, camera, width, height, &settings)?;
838 encode_png(&rgb, width, height)
839 }
840
841 #[cfg(not(target_arch = "wasm32"))]
846 pub fn render_gpu_scene_to_rgb(
847 &mut self,
848 gpu_scene: &mut GpuScene,
849 scene: &RenderScene,
850 camera: &Camera,
851 width: u32,
852 height: u32,
853 settings: &RenderSettings,
854 ) -> Result<Vec<u8>, String> {
855 let emphasis = Emphasis::default();
856 let params = FrameParams {
857 camera,
858 width,
859 height,
860 dpr: 1.0,
861 settings,
862 emphasis: &emphasis,
863 world_per_pixel: 0.0,
864 overlay: None,
865 };
866 let resolve = self.device.create_texture(&wgpu::TextureDescriptor {
867 label: Some("resolve"),
868 size: wgpu::Extent3d {
869 width,
870 height,
871 depth_or_array_layers: 1,
872 },
873 mip_level_count: 1,
874 sample_count: 1,
875 dimension: wgpu::TextureDimension::D2,
876 format: self.format,
877 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
878 view_formats: &[],
879 });
880 let resolve_view = resolve.create_view(&Default::default());
881 self.render_to_view(gpu_scene, scene, ¶ms, &resolve_view);
882
883 let bytes_per_row = (width * 4).div_ceil(256) * 256;
885 let readback = self.device.create_buffer(&wgpu::BufferDescriptor {
886 label: Some("readback"),
887 size: bytes_per_row as u64 * height as u64,
888 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
889 mapped_at_creation: false,
890 });
891 let mut encoder = self
892 .device
893 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
894 label: Some("readback"),
895 });
896 encoder.copy_texture_to_buffer(
897 wgpu::TexelCopyTextureInfo {
898 texture: &resolve,
899 mip_level: 0,
900 origin: wgpu::Origin3d::ZERO,
901 aspect: wgpu::TextureAspect::All,
902 },
903 wgpu::TexelCopyBufferInfo {
904 buffer: &readback,
905 layout: wgpu::TexelCopyBufferLayout {
906 offset: 0,
907 bytes_per_row: Some(bytes_per_row),
908 rows_per_image: None,
909 },
910 },
911 wgpu::Extent3d {
912 width,
913 height,
914 depth_or_array_layers: 1,
915 },
916 );
917 self.queue.submit([encoder.finish()]);
918
919 let slice = readback.slice(..);
920 let (sender, receiver) = std::sync::mpsc::channel();
921 slice.map_async(wgpu::MapMode::Read, move |result| {
922 let _ = sender.send(result);
923 });
924 self.device
925 .poll(wgpu::PollType::wait_indefinitely())
926 .map_err(|error| format!("wgpu poll: {error:?}"))?;
927 receiver
928 .recv()
929 .map_err(|_| "readback callback dropped".to_string())?
930 .map_err(|error| format!("readback map failed: {error:?}"))?;
931
932 let data = slice.get_mapped_range();
933 let mut rgb = Vec::with_capacity((width * height * 3) as usize);
934 for row in 0..height {
935 let start = (row * bytes_per_row) as usize;
936 for col in 0..width as usize {
937 let px = start + col * 4;
938 rgb.extend_from_slice(&data[px..px + 3]);
939 }
940 }
941 drop(data);
942 readback.unmap();
943 Ok(rgb)
944 }
945}
946
947const AUX_EDGE_MAX_WORLD_PER_PIXEL: f64 = 0.1;
958
959#[derive(Clone, Copy, PartialEq, Eq)]
961enum EdgeFilter {
962 Real,
964 Aux,
966}
967
968fn draw_visible_faces(pass: &mut wgpu::RenderPass<'_>, solid: &SolidDisplay, gpu_solid: &GpuSolid) {
971 if !solid.visibility.any_face_hidden() {
972 pass.draw_indexed(0..solid.mesh.indices.len() as u32, 0, 0..1);
973 return;
974 }
975 let mut run: Option<(u32, u32)> = None; for index in 0..solid.faces.len() {
977 let range = &gpu_solid.faces[index];
978 if range.index_count == 0 {
979 continue;
980 }
981 if !solid.visibility.is_face_visible(index) {
982 if let Some((first, count)) = run.take() {
983 pass.draw_indexed(first..first + count, 0, 0..1);
984 }
985 continue;
986 }
987 match &mut run {
988 Some((first, count)) if *first + *count == range.first_index => *count += range.index_count,
989 _ => {
990 if let Some((first, count)) = run.take() {
991 pass.draw_indexed(first..first + count, 0, 0..1);
992 }
993 run = Some((range.first_index, range.index_count));
994 }
995 }
996 }
997 if let Some((first, count)) = run {
998 pass.draw_indexed(first..first + count, 0, 0..1);
999 }
1000}
1001
1002fn draw_visible_edge_ranges(
1003 pass: &mut wgpu::RenderPass<'_>,
1004 solid: &SolidDisplay,
1005 gpu_solid: &GpuSolid,
1006 filter: EdgeFilter,
1007) {
1008 let mut run: Option<(u32, u32)> = None; for (index, edge) in solid.edges.iter().enumerate() {
1010 let range = &gpu_solid.edges[index];
1011 if range.instance_count == 0 {
1012 continue;
1013 }
1014 if !solid.visibility.is_edge_visible(index) || edge.aux != (filter == EdgeFilter::Aux) {
1015 if let Some((first, count)) = run.take() {
1016 pass.draw(0..6, first..first + count);
1017 }
1018 continue;
1019 }
1020 match &mut run {
1021 Some((first, count)) if *first + *count == range.first_instance => {
1022 *count += range.instance_count;
1023 }
1024 _ => {
1025 if let Some((first, count)) = run.take() {
1026 pass.draw(0..6, first..first + count);
1027 }
1028 run = Some((range.first_instance, range.instance_count));
1029 }
1030 }
1031 }
1032 if let Some((first, count)) = run {
1033 pass.draw(0..6, first..first + count);
1034 }
1035}
1036
1037fn overlay_tri_verts(ov: &brep_gizmos::Overlay) -> Vec<OverlayTriVertex> {
1039 ov.tris
1040 .iter()
1041 .map(|v| OverlayTriVertex {
1042 position: v.pos,
1043 normal: v.normal,
1044 color: v.color,
1045 })
1046 .collect()
1047}
1048
1049fn overlay_line_insts(ov: &brep_gizmos::Overlay) -> Vec<OverlayLineInstance> {
1052 ov.lines
1053 .chunks_exact(2)
1054 .map(|pair| OverlayLineInstance {
1055 p0: pair[0].pos,
1056 p1: pair[1].pos,
1057 color: pair[0].color,
1058 })
1059 .collect()
1060}
1061
1062fn bind_pose(pass: &mut wgpu::RenderPass<'_>, models: Option<&ModelArena>, identity: &IdentityModel, slot: u32) {
1065 match models {
1066 Some(models) => pass.set_bind_group(2, &models.bind, &[ModelArena::offset(slot)]),
1067 None => pass.set_bind_group(2, &identity.bind, &[0]),
1068 }
1069}