1use super::*;
2
3const AUX_EDGE_WIDTH_PX: f32 = 1.0;
5
6impl RenderCore {
7 fn face_base_color(solid: &SolidDisplay, settings: &RenderSettings) -> Rgba {
12 let mut color = Self::face_base_color_opaque(solid, settings);
13 if let Some(opacity) = solid.opacity {
14 color[3] = opacity.clamp(0.0, 1.0);
15 }
16 color
17 }
18
19 fn face_base_color_opaque(solid: &SolidDisplay, settings: &RenderSettings) -> Rgba {
20 if let Some(rgb) = solid.color_override {
21 return Self::opaque_linear(rgb);
22 }
23 match settings.face_color_mode {
24 FaceColorMode::HashedBySolid => {
25 let srgb = solid_color_srgb(&solid.name);
26 [
27 srgb_to_linear(srgb[0]) as f32,
28 srgb_to_linear(srgb[1]) as f32,
29 srgb_to_linear(srgb[2]) as f32,
30 1.0,
31 ]
32 }
33 FaceColorMode::Uniform => {
34 let c = settings.face_color;
35 [
36 srgb_to_linear(c[0] as f64) as f32,
37 srgb_to_linear(c[1] as f64) as f32,
38 srgb_to_linear(c[2] as f64) as f32,
39 c[3],
40 ]
41 }
42 }
43 }
44
45 fn opaque_linear(rgb: [f32; 3]) -> Rgba {
48 [
49 srgb_to_linear(rgb[0] as f64) as f32,
50 srgb_to_linear(rgb[1] as f64) as f32,
51 srgb_to_linear(rgb[2] as f64) as f32,
52 1.0,
53 ]
54 }
55
56 fn style_params(settings: &RenderSettings) -> [f32; 4] {
58 [if settings.flat_shading { 1.0 } else { 0.0 }, 0.0, 0.0, 0.0]
59 }
60
61 fn face_style_palette(
70 &self,
71 solid: &SolidDisplay,
72 settings: &RenderSettings,
73 ) -> (Vec<StyleBuf>, Vec<Option<u32>>) {
74 let mut styles: Vec<StyleBuf> = Vec::new();
75 let mut colors: Vec<[f32; 3]> = Vec::new();
76 let mut per_face: Vec<Option<u32>> = Vec::with_capacity(solid.faces.len());
77 let params = Self::style_params(settings);
78 for face in &solid.faces {
79 let Some(rgb) = face.color_override else {
80 per_face.push(None);
81 continue;
82 };
83 let slot = match colors.iter().position(|c| *c == rgb) {
84 Some(slot) => slot,
85 None => {
86 let style = StyleBuf::new(
87 &self.device,
88 &self.style_layout,
89 solid.name.as_str(),
90 );
91 style.write(&self.queue, Self::opaque_linear(rgb), params);
92 styles.push(style);
93 colors.push(rgb);
94 colors.len() - 1
95 }
96 };
97 per_face.push(Some(slot as u32));
98 }
99 (styles, per_face)
100 }
101
102 fn mesh_vertices(solid: &SolidDisplay) -> Vec<MeshVertex> {
104 solid
105 .mesh
106 .positions
107 .iter()
108 .zip(&solid.mesh.normals)
109 .map(|(&position, &normal)| MeshVertex { position, normal })
110 .collect()
111 }
112
113 fn edge_segments(solid: &SolidDisplay) -> (Vec<EdgeInstance>, Vec<EdgeRange>) {
116 let mut segments: Vec<EdgeInstance> = Vec::new();
117 let mut edges = Vec::with_capacity(solid.edges.len());
118 for edge in &solid.edges {
119 let first_instance = segments.len() as u32;
120 for pair in edge.polyline.windows(2) {
121 segments.push(EdgeInstance {
122 p0: pair[0],
123 p1: pair[1],
124 });
125 }
126 edges.push(EdgeRange {
127 first_instance,
128 instance_count: segments.len() as u32 - first_instance,
129 });
130 }
131 (segments, edges)
132 }
133
134 fn point_instances(solid: &SolidDisplay) -> Vec<PointInstance> {
136 solid
137 .vertices
138 .iter()
139 .map(|v| PointInstance {
140 center: [
141 v.position[0] as f32,
142 v.position[1] as f32,
143 v.position[2] as f32,
144 ],
145 })
146 .collect()
147 }
148
149 fn repose_solid(&self, existing: &GpuSolid, solid: &SolidDisplay) -> bool {
153 let vertices = Self::mesh_vertices(solid);
154 let (segments, edges) = Self::edge_segments(solid);
155 let points = Self::point_instances(solid);
156 let same = vertices.len() as u32 == existing.vertex_count
157 && segments.len() as u32 == existing.edge_instances
158 && edges.len() == existing.edges.len()
159 && points.len() as u32 == existing.point_count
160 && solid.faces.len() == existing.faces.len()
161 && existing.edge_buf.is_some() == !segments.is_empty()
162 && existing.point_buf.is_some() == !points.is_empty();
163 if !same {
164 return false;
165 }
166 self.queue.write_buffer(&existing.vertex_buf, 0, bytemuck::cast_slice(&vertices));
167 if let Some(buf) = &existing.edge_buf {
168 self.queue.write_buffer(buf, 0, bytemuck::cast_slice(&segments));
169 }
170 if let Some(buf) = &existing.point_buf {
171 self.queue.write_buffer(buf, 0, bytemuck::cast_slice(&points));
172 }
173 true
174 }
175
176 fn upload_solid(&self, solid: &SolidDisplay, settings: &RenderSettings) -> GpuSolid {
177 let vertices = Self::mesh_vertices(solid);
178 let vertex_buf = self
179 .device
180 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
181 label: Some(solid.name.as_str()),
182 contents: bytemuck::cast_slice(&vertices),
183 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
185 });
186 let index_buf = self
187 .device
188 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
189 label: Some(solid.name.as_str()),
190 contents: bytemuck::cast_slice(&solid.mesh.indices),
191 usage: wgpu::BufferUsages::INDEX,
192 });
193 let tri_total = solid.mesh.indices.len() / 3;
211 let mut wire_indices: Vec<u32> = Vec::with_capacity(solid.mesh.indices.len());
212 let mut wire_ranges: Vec<(u32, u32)> = Vec::with_capacity(solid.faces.len());
213 let mut covered = vec![false; tri_total];
214 let mut seen: HashSet<(u32, u32), BuildEdgeHasher> = HashSet::default();
215 let mut emit_triangle = |wire: &mut Vec<u32>,
216 seen: &mut HashSet<(u32, u32), BuildEdgeHasher>,
217 tri: usize| {
218 let idx = [
219 solid.mesh.indices[tri * 3],
220 solid.mesh.indices[tri * 3 + 1],
221 solid.mesh.indices[tri * 3 + 2],
222 ];
223 for k in 0..3 {
224 let (a, b) = (idx[k], idx[(k + 1) % 3]);
225 let key = if a < b { (a, b) } else { (b, a) };
226 if seen.insert(key) {
227 wire.push(a);
228 wire.push(b);
229 }
230 }
231 };
232 for face in &solid.faces {
233 let first = wire_indices.len() as u32;
234 seen.clear();
235 let start = (face.tri_start as usize).min(tri_total);
236 let end = (start + face.tri_count as usize).min(tri_total);
237 for tri in start..end {
238 covered[tri] = true;
239 emit_triangle(&mut wire_indices, &mut seen, tri);
240 }
241 wire_ranges.push((first, wire_indices.len() as u32 - first));
242 }
243 seen.clear();
247 for tri in 0..tri_total {
248 if !covered[tri] {
249 emit_triangle(&mut wire_indices, &mut seen, tri);
250 }
251 }
252 let wire_index_count = wire_indices.len() as u32;
253 let wire_index_buf = self
254 .device
255 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
256 label: Some(solid.name.as_str()),
257 contents: bytemuck::cast_slice(&wire_indices),
258 usage: wgpu::BufferUsages::INDEX,
259 });
260 let (face_styles, face_style_index) = self.face_style_palette(solid, settings);
261 let faces = solid
262 .faces
263 .iter()
264 .zip(&face_style_index)
265 .zip(&wire_ranges)
266 .map(|((face, style), &(wire_first, wire_count))| FaceRange {
267 first_index: face.tri_start * 3,
268 index_count: face.tri_count * 3,
269 wire_first,
270 wire_count,
271 style: *style,
272 })
273 .collect();
274
275 let (segments, edges) = Self::edge_segments(solid);
276 let occludable_edge_instances = solid
277 .edges
278 .iter()
279 .zip(&edges)
280 .find(|(edge, _)| edge.aux)
281 .map_or(segments.len() as u32, |(_, range): (_, &EdgeRange)| range.first_instance);
282 let edge_buf = (!segments.is_empty()).then(|| {
283 self.device
284 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
285 label: Some(solid.name.as_str()),
286 contents: bytemuck::cast_slice(&segments),
287 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
288 })
289 });
290
291 let points = Self::point_instances(solid);
292 let point_buf = (!points.is_empty()).then(|| {
293 self.device
294 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
295 label: Some(solid.name.as_str()),
296 contents: bytemuck::cast_slice(&points),
297 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
298 })
299 });
300
301 let base_style = StyleBuf::new(&self.device, &self.style_layout, solid.name.as_str());
302 base_style.write(
303 &self.queue,
304 Self::face_base_color(solid, settings),
305 Self::style_params(settings),
306 );
307
308 GpuSolid {
309 revision: solid.revision,
310 shape: Self::shape_of(solid),
311 vertex_count: vertices.len() as u32,
312 face_styles,
313 vertex_buf,
314 index_buf,
315 wire_index_buf,
316 wire_index_count,
317 faces,
318 edge_buf,
319 edges,
320 edge_instances: segments.len() as u32,
321 occludable_edge_instances,
322 point_buf,
323 point_count: points.len() as u32,
324 base_style,
325 boundary: None,
326 model_slot: 0,
330 holds_shape: solid.shape_revision == 0,
331 }
332 }
333
334 pub fn sync_scene(
338 &self,
339 gpu: &mut GpuScene,
340 scene: &RenderScene,
341 settings: &RenderSettings,
342 settings_generation: u64,
343 ) {
344 gpu.order.clear();
345 let models = gpu.models.get_or_insert_with(|| ModelArena::new(&self.device, &self.model_layout, scene.solids().len() as u32));
346 let mut seen: Vec<&str> = Vec::with_capacity(scene.solids().len());
347 for solid in scene.solids() {
348 gpu.order.push(solid.name.clone());
349 seen.push(solid.name.as_str());
350 let refresh_style = gpu.settings_generation != settings_generation;
351 match gpu.solids.get_mut(&solid.name) {
352 Some(existing) if existing.revision == solid.revision => {
353 if refresh_style {
354 self.restyle(existing, solid, settings);
355 }
356 }
357 Some(existing)
361 if existing.holds_shape
362 && existing.shape == Self::shape_of(solid)
363 && (solid.pose.is_some() || solid.shape_revision == 0) =>
364 {
365 models.set(existing.model_slot, solid.pose.as_ref());
366 existing.revision = solid.revision;
367 gpu.pose_writes += 1;
368 if refresh_style {
369 self.restyle(existing, solid, settings);
370 }
371 }
372 Some(existing) if existing.shape == Self::shape_of(solid) && self.repose_solid(existing, solid) => {
375 models.set(existing.model_slot, None);
377 existing.holds_shape = false;
378 existing.revision = solid.revision;
379 gpu.reposes += 1;
380 if refresh_style {
381 self.restyle(existing, solid, settings);
382 }
383 }
384 _ => {
385 let mut uploaded = self.upload_solid(solid, settings);
386 match gpu.solids.get(&solid.name) {
387 Some(replaced) => uploaded.model_slot = replaced.model_slot,
388 None => uploaded.model_slot = models.alloc(),
389 }
390 models.set(uploaded.model_slot, None);
391 gpu.solids.insert(solid.name.clone(), uploaded);
392 gpu.uploads += 1;
393 }
394 }
395 }
396 let seen: std::collections::HashSet<&str> = seen.into_iter().collect();
397 gpu.solids.retain(|name, solid| {
398 let keep = seen.contains(name.as_str());
399 if !keep {
400 models.release(solid.model_slot);
401 }
402 keep
403 });
404 models.flush(&self.device, &self.queue, &self.model_layout);
405 gpu.settings_generation = settings_generation;
406 }
407
408 fn shape_of(solid: &SolidDisplay) -> u64 {
411 if solid.shape_revision != 0 {
412 solid.shape_revision
413 } else {
414 solid.revision
415 }
416 }
417
418 fn restyle(&self, existing: &GpuSolid, solid: &SolidDisplay, settings: &RenderSettings) {
420 let params = Self::style_params(settings);
421 existing.base_style.write(&self.queue, Self::face_base_color(solid, settings), params);
422 let mut seen: Vec<[f32; 3]> = Vec::new();
426 for face in &solid.faces {
427 let Some(rgb) = face.color_override else { continue };
428 if seen.contains(&rgb) {
429 continue;
430 }
431 if let Some(style) = existing.face_styles.get(seen.len()) {
432 style.write(&self.queue, Self::opaque_linear(rgb), params);
433 }
434 seen.push(rgb);
435 }
436 }
437
438 pub fn upload_scene(&self, scene: &RenderScene) -> GpuScene {
440 self.upload_scene_with(scene, &RenderSettings::artifact())
441 }
442
443 pub fn upload_scene_with(&self, scene: &RenderScene, settings: &RenderSettings) -> GpuScene {
444 let mut gpu = GpuScene::default();
445 self.sync_scene(&mut gpu, scene, settings, 0);
446 gpu
447 }
448
449 pub(super) fn ensure_targets(&mut self, width: u32, height: u32) {
450 if let Some(targets) = &self.targets {
451 if targets.width == width && targets.height == height {
452 return;
453 }
454 }
455 let size = wgpu::Extent3d {
456 width,
457 height,
458 depth_or_array_layers: 1,
459 };
460 let msaa = self.device.create_texture(&wgpu::TextureDescriptor {
461 label: Some("msaa color"),
462 size,
463 mip_level_count: 1,
464 sample_count: SAMPLES,
465 dimension: wgpu::TextureDimension::D2,
466 format: self.format,
467 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
468 view_formats: &[],
469 });
470 let depth = self.device.create_texture(&wgpu::TextureDescriptor {
471 label: Some("depth"),
472 size,
473 mip_level_count: 1,
474 sample_count: SAMPLES,
475 dimension: wgpu::TextureDimension::D2,
476 format: DEPTH_FORMAT,
477 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
478 view_formats: &[],
479 });
480 self.targets = Some(CachedTargets {
481 width,
482 height,
483 msaa_view: msaa.create_view(&Default::default()),
484 depth_view: depth.create_view(&Default::default()),
485 });
486 }
487
488 pub(super) fn write_global_styles(&self, settings: &RenderSettings) {
489 let flat = if settings.flat_shading { 1.0 } else { 0.0 };
490 let face = |c: Rgba| -> Rgba {
491 [
492 srgb_to_linear(c[0] as f64) as f32,
493 srgb_to_linear(c[1] as f64) as f32,
494 srgb_to_linear(c[2] as f64) as f32,
495 c[3],
496 ]
497 };
498 let s = &self.styles;
499 let q = &self.queue;
500 s.face_selected
501 .write(q, face(settings.face_selected_color), [flat, 0.0, 0.0, 0.0]);
502 s.face_hovered
503 .write(q, face(settings.hover_color), [flat, 0.0, 0.0, 0.0]);
504 let translucent = |rgba: [f32; 4], alpha: f32| [rgba[0], rgba[1], rgba[2], alpha];
506 self.context_selected.write(q, translucent(settings.face_selected_color, 0.6), [0.0; 4]);
507 self.context_hovered.write(q, translucent(settings.hover_color, 0.45), [0.0; 4]);
508 let edge_params = [settings.edge_width_px, EDGE_NUDGE, 0.0, 0.0];
509 s.edge_base.write(q, settings.edge_color, edge_params);
510 s.edge_selected.write(q, settings.edge_selected_color, edge_params);
511 s.edge_hovered.write(q, settings.hover_color, edge_params);
512 let mut hidden = settings.edge_color;
513 hidden[3] = settings.hidden_edge_alpha;
514 s.edge_hidden.write(q, hidden, edge_params);
515 s.edge_aux.write(q, settings.edge_color, [AUX_EDGE_WIDTH_PX, EDGE_NUDGE, 0.0, 0.0]);
521 s.boundary.write(
524 q,
525 settings.edge_selected_color,
526 [settings.edge_width_px + 1.0, EDGE_NUDGE * 1.5, 0.0, 0.0],
527 );
528 let point_params = [settings.vertex_size_px, 0.0, 0.0, 0.0];
529 s.point_base.write(q, settings.vertex_color, point_params);
530 s.point_selected
531 .write(q, settings.vertex_selected_color, [settings.vertex_size_px + 1.0, 0.0, 0.0, 0.0]);
532 s.point_hovered
533 .write(q, settings.hover_color, [settings.vertex_size_px + 1.0, 0.0, 0.0, 0.0]);
534 let axis_params = [2.0, EDGE_NUDGE, 0.0, 0.0];
535 s.axis_x.write(q, [0.91, 0.30, 0.32, 1.0], axis_params);
536 s.axis_y.write(q, [0.27, 0.80, 0.42, 1.0], axis_params);
537 s.axis_z.write(q, [0.23, 0.51, 0.96, 1.0], axis_params);
538 s.overlay_line.write(q, [1.0, 1.0, 1.0, 1.0], [2.5, 0.0, 0.0, 0.0]);
540 use crate::thumbnail::line_art as la;
543 let srgb = |c: [f32; 3]| [c[0], c[1], c[2], 1.0];
544 s.line_art_placed_face.write(q, face(srgb(la::PLACED_FILL)), [0.0, 1.0, 0.0, 0.0]);
545 s.line_art_adding_face.write(q, face(srgb(la::ADDING_FILL)), [0.0, 1.0, 0.0, 0.0]);
546 s.line_art_placed_edge.write(q, srgb(la::PLACED_EDGE), [1.0, EDGE_NUDGE, 0.0, 0.0]);
547 s.line_art_adding_edge.write(q, srgb(la::INK), [1.2, EDGE_NUDGE, 0.0, 0.0]);
548 s.line_art_placed_outline.write(q, srgb(la::PLACED_OUTLINE), [LINE_ART_PLACED_OUTLINE_PX, LINE_ART_OUTLINE_DEPTH, 0.0, 0.0]);
549 s.line_art_adding_outline.write(q, srgb(la::INK), [LINE_ART_ADDING_OUTLINE_PX, LINE_ART_OUTLINE_DEPTH, 0.0, 0.0]);
550 }
551
552 pub(super) fn sync_boundary(&self, gpu_solid: &mut GpuSolid, solid: &SolidDisplay, emphasis: &Emphasis) {
556 if let Some(boundary) = &gpu_solid.boundary {
557 if boundary.emphasis_generation == emphasis.generation
558 && boundary.revision == solid.revision
559 {
560 return;
561 }
562 }
563 let mut segments: Vec<EdgeInstance> = Vec::new();
564 for face in &solid.faces {
565 if face.tri_count == 0 {
566 continue;
567 }
568 let state = emphasis.face_state(&solid.name, &face.name);
569 if state == EmphasisState::Base {
570 continue;
571 }
572 let mut edge_use: HashMap<(u32, u32), u32> = HashMap::new();
573 let start = face.tri_start as usize;
574 let end = (start + face.tri_count as usize).min(solid.mesh.indices.len() / 3);
575 for tri in start..end {
576 let idx = [
577 solid.mesh.indices[tri * 3],
578 solid.mesh.indices[tri * 3 + 1],
579 solid.mesh.indices[tri * 3 + 2],
580 ];
581 for k in 0..3 {
582 let a = idx[k];
583 let b = idx[(k + 1) % 3];
584 let key = if a < b { (a, b) } else { (b, a) };
585 *edge_use.entry(key).or_insert(0) += 1;
586 }
587 }
588 let mut boundary: Vec<(u32, u32)> = edge_use
589 .into_iter()
590 .filter_map(|(key, count)| (count == 1).then_some(key))
591 .collect();
592 boundary.sort_unstable();
593 for (a, b) in boundary {
594 segments.push(EdgeInstance {
595 p0: solid.mesh.positions[a as usize],
596 p1: solid.mesh.positions[b as usize],
597 });
598 }
599 }
600 let buf = (!segments.is_empty()).then(|| {
601 self.device
602 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
603 label: Some("emphasis boundary"),
604 contents: bytemuck::cast_slice(&segments),
605 usage: wgpu::BufferUsages::VERTEX,
606 })
607 });
608 gpu_solid.boundary = Some(BoundaryBuf {
609 emphasis_generation: emphasis.generation,
610 revision: solid.revision,
611 buf,
612 count: segments.len() as u32,
613 });
614 }
615}