1use crate::render_backend::stencil;
2use crate::render_backend::CpuBuffers;
3use crate::render_backend::RetainedImageDraw;
4use crate::render_backend::RetainedVectorResource;
5use crate::render_backend::SharedRetainedVectorResource;
6use crate::render_backend::MAX_BATCH_COLORS;
7use crate::render_backend::MAX_BATCH_GRADIENTS;
8use crate::render_backend::MAX_BATCH_MATERIALS;
9use crate::render_backend::MAX_BATCH_PRIMITIVES;
10use crate::render_backend::MAX_BATCH_TRANSFORMS;
11use crate::render_backend::MAX_SCENE_CLIPS;
12use crate::render_backend::MAX_SCENE_TRANSFORMS;
13use crate::render_backend::{RetainedBatchRun, RetainedDraw};
14use crate::Box2D;
15use crate::Image;
16use crate::Point2D;
17use crate::Transform2D;
18use crate::Vector2D;
19use lyon::geom::{CubicBezierSegment, QuadraticBezierSegment, Segment};
20use lyon::lyon_tessellation::BuffersBuilder;
21use lyon::lyon_tessellation::FillOptions;
22use lyon::lyon_tessellation::FillRule;
23use lyon::lyon_tessellation::FillTessellator;
24use lyon::lyon_tessellation::FillVertex;
25use lyon::lyon_tessellation::VertexBuffers;
26use lyon::math::Point as LyonPoint;
27use lyon::path::EndpointId;
28use lyon::path::Path;
29use lyon::path::PathEvent;
30use lyon::tessellation::StrokeOptions;
31use lyon::tessellation::StrokeTessellator;
32use lyon::tessellation::StrokeVertex;
33use lyon::tessellation::VertexSource;
34use pax_runtime_api::PathSmoothing;
35
36use crate::point;
37use crate::render_backend::data::GpuColor;
38use crate::render_backend::data::GpuGradient;
39use crate::render_backend::data::GpuMaterial;
40use crate::render_backend::data::GpuPrimitive;
41use crate::render_backend::data::GpuSceneLight;
42use crate::render_backend::data::GpuSceneLighting;
43use crate::render_backend::data::GpuTransform;
44use crate::render_backend::data::GpuVertex;
45use crate::render_backend::data::MAX_SCENE_LIGHTS;
46use crate::render_backend::CachedTextureResource;
47use crate::render_backend::CapturedFrame;
48use crate::render_backend::PrimitiveBatch;
49use crate::render_backend::PrimitiveBatchSegment;
50use crate::render_backend::RenderBackend;
51use crate::render_backend::ScissorRect;
52use crate::render_backend::VectorResourceDirty;
53use std::cell::RefCell;
54use std::collections::hash_map::DefaultHasher;
55use std::collections::{HashMap, HashSet};
56use std::hash::{Hash, Hasher};
57use std::rc::Rc;
58
59const DEFAULT_TESSELLATION_TOLERANCE: f32 = 0.1;
60
61pub struct AlphaMaskPaint {
63 pub path: Path,
64 pub transform: Transform2D,
65 pub fill: Fill,
66 pub opacity: f32,
67}
68const MAX_VECTOR_GEOMETRY_CACHE_BYTES: usize = 16 * 1024 * 1024;
69const MAX_VECTOR_RESOURCE_CACHE_BYTES: usize = 128 * 1024 * 1024;
70pub const NATIVE_VECTOR_RESOURCE_CACHE_BYTES: usize = 256 * 1024 * 1024;
71
72type SharedVectorGeometryCache = Rc<RefCell<VectorGeometryCache>>;
73type SharedVectorResourceCache = Rc<RefCell<VectorResourceCache>>;
74
75#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
77pub struct ResourceChurnStats {
78 pub flushes: u64,
79 pub retained_scene_resets: u64,
80 pub vector_batch_flushes: u64,
81 pub vector_buffer_rebuilds: u64,
82 pub vector_geometry_rebuilds: u64,
83 pub vector_geometry_cache_hits: u64,
84 pub vector_geometry_cache_misses: u64,
85 pub vector_geometry_cache_evictions: u64,
86 pub vector_geometry_cache_bytes: u64,
87 pub tessellated_vertices: u64,
88 pub tessellated_indices: u64,
89 pub cached_vertices_reused: u64,
90 pub cached_indices_reused: u64,
91 pub vector_resource_creates: u64,
92 pub vector_resource_updates: u64,
93 pub vector_resource_recreates: u64,
94 pub vector_resource_create_bytes: u64,
95 pub vector_resource_update_bytes: u64,
96 pub vector_resource_cache_hits: u64,
97 pub vector_resource_cache_misses: u64,
98 pub vector_resource_cache_evictions: u64,
99 pub vector_resource_cache_bytes: u64,
100 pub texture_creates: u64,
101 pub texture_upload_bytes: u64,
102 pub retained_nodes_considered: u64,
103 pub retained_nodes_visible: u64,
104 pub retained_draw_batches: u64,
105 pub retained_draws: u64,
106 pub retained_vector_draws: u64,
107 pub retained_image_draws: u64,
108}
109
110impl ResourceChurnStats {
111 pub fn merge(&mut self, other: Self) {
112 self.flushes += other.flushes;
113 self.retained_scene_resets += other.retained_scene_resets;
114 self.vector_batch_flushes += other.vector_batch_flushes;
115 self.vector_buffer_rebuilds += other.vector_buffer_rebuilds;
116 self.vector_geometry_rebuilds += other.vector_geometry_rebuilds;
117 self.vector_geometry_cache_hits += other.vector_geometry_cache_hits;
118 self.vector_geometry_cache_misses += other.vector_geometry_cache_misses;
119 self.vector_geometry_cache_evictions += other.vector_geometry_cache_evictions;
120 self.vector_geometry_cache_bytes = self
121 .vector_geometry_cache_bytes
122 .max(other.vector_geometry_cache_bytes);
123 self.tessellated_vertices += other.tessellated_vertices;
124 self.tessellated_indices += other.tessellated_indices;
125 self.cached_vertices_reused += other.cached_vertices_reused;
126 self.cached_indices_reused += other.cached_indices_reused;
127 self.vector_resource_creates += other.vector_resource_creates;
128 self.vector_resource_updates += other.vector_resource_updates;
129 self.vector_resource_recreates += other.vector_resource_recreates;
130 self.vector_resource_create_bytes += other.vector_resource_create_bytes;
131 self.vector_resource_update_bytes += other.vector_resource_update_bytes;
132 self.vector_resource_cache_hits += other.vector_resource_cache_hits;
133 self.vector_resource_cache_misses += other.vector_resource_cache_misses;
134 self.vector_resource_cache_evictions += other.vector_resource_cache_evictions;
135 self.vector_resource_cache_bytes = self
136 .vector_resource_cache_bytes
137 .max(other.vector_resource_cache_bytes);
138 self.texture_creates += other.texture_creates;
139 self.texture_upload_bytes += other.texture_upload_bytes;
140 self.retained_nodes_considered += other.retained_nodes_considered;
141 self.retained_nodes_visible += other.retained_nodes_visible;
142 self.retained_draw_batches += other.retained_draw_batches;
143 self.retained_draws += other.retained_draws;
144 self.retained_vector_draws += other.retained_vector_draws;
145 self.retained_image_draws += other.retained_image_draws;
146 }
147
148 pub fn has_resource_churn(&self) -> bool {
149 let mut stats = *self;
150 stats.flushes = 0;
151 stats.retained_nodes_considered = 0;
152 stats.retained_nodes_visible = 0;
153 stats.retained_draw_batches = 0;
154 stats.retained_draws = 0;
155 stats.retained_vector_draws = 0;
156 stats.retained_image_draws = 0;
157 stats != Self::default()
158 }
159}
160
161pub struct WgpuRenderer<'w> {
163 render_backend: RenderBackend<'w>,
164 scene: HashMap<u32, RetainedNode>,
165 transform_arena: TransformArena,
166 clip_arena: ClipArena,
167 clip_owner_keys: HashMap<u32, Vec<ClipArenaKey>>,
168 sorted_nodes: Vec<(i32, u32)>,
169 order_dirty: bool,
170 scene_dirty: bool,
171 cached_images: HashMap<String, CachedImageEntry>,
172 transform_stack: Vec<Transform2D>,
173 clip_stack: Vec<ClipReference>,
174 saves: Vec<SceneStateSave>,
175 current_node: Option<PendingNode>,
176 tolerance: f32,
177 vector_geometry_cache: SharedVectorGeometryCache,
178 vector_resource_cache: SharedVectorResourceCache,
179 resource_churn_stats: ResourceChurnStats,
180}
181
182struct CachedImageEntry {
183 texture: CachedTextureResource,
184 version: u64,
185}
186
187#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
188struct VectorGeometryCacheKey {
189 geometry_signature: u64,
190 tolerance_bits: u32,
191}
192
193struct CachedVectorGeometry {
194 vertices: Vec<GpuVertex>,
195 indices: Vec<u16>,
196 bytes: usize,
197 last_used: u64,
198}
199
200#[derive(Default)]
201struct VectorGeometryCacheInsertStats {
202 evicted_entries: u64,
203 current_bytes: usize,
204}
205
206struct VectorGeometryCache {
207 entries: HashMap<VectorGeometryCacheKey, CachedVectorGeometry>,
208 current_bytes: usize,
209 max_bytes: usize,
210 clock: u64,
211}
212
213impl Default for VectorGeometryCache {
214 fn default() -> Self {
215 Self {
216 entries: HashMap::new(),
217 current_bytes: 0,
218 max_bytes: MAX_VECTOR_GEOMETRY_CACHE_BYTES,
219 clock: 0,
220 }
221 }
222}
223
224impl VectorGeometryCache {
225 fn get(&mut self, key: &VectorGeometryCacheKey) -> Option<&CachedVectorGeometry> {
226 self.clock = self.clock.wrapping_add(1);
227 let entry = self.entries.get_mut(key)?;
228 entry.last_used = self.clock;
229 Some(entry)
230 }
231
232 fn insert(
233 &mut self,
234 key: VectorGeometryCacheKey,
235 vertices: Vec<GpuVertex>,
236 indices: Vec<u16>,
237 ) -> VectorGeometryCacheInsertStats {
238 let bytes = vector_geometry_bytes(vertices.len(), indices.len());
239 if bytes > self.max_bytes {
240 return VectorGeometryCacheInsertStats {
241 current_bytes: self.current_bytes,
242 ..Default::default()
243 };
244 }
245
246 if let Some(previous) = self.entries.remove(&key) {
247 self.current_bytes = self.current_bytes.saturating_sub(previous.bytes);
248 }
249
250 self.clock = self.clock.wrapping_add(1);
251 self.current_bytes += bytes;
252 self.entries.insert(
253 key,
254 CachedVectorGeometry {
255 vertices,
256 indices,
257 bytes,
258 last_used: self.clock,
259 },
260 );
261
262 let mut stats = VectorGeometryCacheInsertStats {
263 current_bytes: self.current_bytes,
264 ..Default::default()
265 };
266 while self.current_bytes > self.max_bytes {
267 let Some(oldest_key) = self
268 .entries
269 .iter()
270 .min_by_key(|(_, entry)| entry.last_used)
271 .map(|(key, _)| *key)
272 else {
273 break;
274 };
275 if let Some(removed) = self.entries.remove(&oldest_key) {
276 self.current_bytes = self.current_bytes.saturating_sub(removed.bytes);
277 stats.evicted_entries += 1;
278 }
279 }
280 stats.current_bytes = self.current_bytes;
281 stats
282 }
283}
284
285#[derive(Clone, Debug, PartialEq, Eq, Hash)]
286struct VectorResourceKey {
287 tolerance_bits: u32,
288 geometry_signatures: Vec<u64>,
289 fill_signature: u64,
290 primitive_signature: u64,
291 transform_layout_signature: u64,
292}
293
294struct CachedVectorResource {
295 resource: Rc<SharedRetainedVectorResource>,
296 bytes: usize,
297 last_used: u64,
298}
299
300#[derive(Default)]
301struct VectorResourceCacheStats {
302 evicted_entries: u64,
303 current_bytes: usize,
304}
305
306struct VectorResourceCache {
307 entries: HashMap<VectorResourceKey, CachedVectorResource>,
308 current_bytes: usize,
309 max_bytes: usize,
310 clock: u64,
311}
312
313impl Default for VectorResourceCache {
314 fn default() -> Self {
315 Self {
316 entries: HashMap::new(),
317 current_bytes: 0,
318 max_bytes: MAX_VECTOR_RESOURCE_CACHE_BYTES,
319 clock: 0,
320 }
321 }
322}
323
324impl VectorResourceCache {
325 fn set_max_bytes(&mut self, max_bytes: usize) -> VectorResourceCacheStats {
326 self.max_bytes = max_bytes.max(1);
327 self.evict_if_needed()
328 }
329
330 fn get(&mut self, key: &VectorResourceKey) -> Option<Rc<SharedRetainedVectorResource>> {
331 self.clock = self.clock.wrapping_add(1);
332 let entry = self.entries.get_mut(key)?;
333 entry.last_used = self.clock;
334 Some(Rc::clone(&entry.resource))
335 }
336
337 fn current_bytes(&self) -> usize {
338 self.current_bytes
339 }
340
341 fn insert(
342 &mut self,
343 key: VectorResourceKey,
344 resource: Rc<SharedRetainedVectorResource>,
345 bytes: u64,
346 ) -> VectorResourceCacheStats {
347 let bytes = bytes as usize;
348 if bytes > self.max_bytes {
349 return VectorResourceCacheStats {
350 current_bytes: self.current_bytes,
351 ..Default::default()
352 };
353 }
354
355 if let Some(previous) = self.entries.remove(&key) {
356 self.current_bytes = self.current_bytes.saturating_sub(previous.bytes);
357 }
358
359 self.clock = self.clock.wrapping_add(1);
360 self.current_bytes += bytes;
361 self.entries.insert(
362 key,
363 CachedVectorResource {
364 resource,
365 bytes,
366 last_used: self.clock,
367 },
368 );
369 self.evict_if_needed()
370 }
371
372 fn remove_if_same(
373 &mut self,
374 key: &VectorResourceKey,
375 resource: &Rc<SharedRetainedVectorResource>,
376 ) -> Option<CachedVectorResource> {
377 let entry = self.entries.get(key)?;
378 if !Rc::ptr_eq(&entry.resource, resource) {
379 return None;
380 }
381 let removed = self.entries.remove(key)?;
382 self.current_bytes = self.current_bytes.saturating_sub(removed.bytes);
383 Some(removed)
384 }
385
386 fn evict_if_needed(&mut self) -> VectorResourceCacheStats {
387 let mut stats = VectorResourceCacheStats {
388 current_bytes: self.current_bytes,
389 ..Default::default()
390 };
391 while self.current_bytes > self.max_bytes {
392 let Some(oldest_key) = self
393 .entries
394 .iter()
395 .filter(|(_, entry)| Rc::strong_count(&entry.resource) == 1)
396 .min_by_key(|(_, entry)| entry.last_used)
397 .map(|(key, _)| key.clone())
398 else {
399 break;
400 };
401 if let Some(removed) = self.entries.remove(&oldest_key) {
402 self.current_bytes = self.current_bytes.saturating_sub(removed.bytes);
403 stats.evicted_entries += 1;
404 }
405 }
406 stats.current_bytes = self.current_bytes;
407 stats
408 }
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
412struct TransformArenaKey {
413 node_id: u32,
414 op_index: u32,
415}
416
417struct TransformArena {
418 slots: Vec<GpuTransform>,
419 entries: HashMap<TransformArenaKey, u32>,
420 free_slots: Vec<u32>,
421 next_slot: u32,
422 dirty: bool,
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
426struct ClipArenaKey {
427 node_id: u32,
428 clip_index: u32,
429}
430
431#[derive(Clone, Copy, Debug, PartialEq)]
432enum ClipReference {
433 Alpha { owner: u32 },
434 Stencil { clip_id: u32 },
435 Scissor(ScissorRect),
436}
437
438struct ClipArenaEntry {
439 geometry_signature: u64,
440 geometry: VertexBuffers<stencil::Vertex, u16>,
441}
442
443struct ClipArena {
444 slots: Vec<GpuTransform>,
445 key_to_id: HashMap<ClipArenaKey, u32>,
446 entries: HashMap<u32, ClipArenaEntry>,
447 free_ids: Vec<u32>,
448 next_id: u32,
449 dirty: bool,
450}
451
452impl ClipArena {
453 fn new() -> Self {
454 Self {
455 slots: vec![GpuTransform::default(); MAX_SCENE_CLIPS],
456 key_to_id: HashMap::new(),
457 entries: HashMap::new(),
458 free_ids: Vec::new(),
459 next_id: 0,
460 dirty: true,
461 }
462 }
463
464 fn sync_clip(
465 &mut self,
466 node_id: u32,
467 clip_index: u32,
468 geometry_signature: u64,
469 geometry: VertexBuffers<stencil::Vertex, u16>,
470 transform: Transform2D,
471 ) -> Option<(ClipArenaKey, u32)> {
472 let key = ClipArenaKey {
473 node_id,
474 clip_index,
475 };
476 let clip_id = if let Some(clip_id) = self.key_to_id.get(&key).copied() {
477 clip_id
478 } else {
479 let Some(clip_id) = self.free_ids.pop().or_else(|| {
480 ((self.next_id as usize) < MAX_SCENE_CLIPS).then(|| {
481 let clip_id = self.next_id;
482 self.next_id += 1;
483 clip_id
484 })
485 }) else {
486 log::error!(
487 "clip arena capacity exceeded (capacity {})",
488 MAX_SCENE_CLIPS
489 );
490 return None;
491 };
492 self.key_to_id.insert(key, clip_id);
493 self.dirty = true;
494 clip_id
495 };
496
497 let value = GpuTransform {
498 transform: transform.to_arrays(),
499 ..GpuTransform::default()
500 };
501 if self.slots[clip_id as usize].transform != value.transform {
502 self.slots[clip_id as usize] = value;
503 self.dirty = true;
504 }
505
506 match self.entries.get_mut(&clip_id) {
507 Some(entry) => {
508 if entry.geometry_signature != geometry_signature {
509 entry.geometry_signature = geometry_signature;
510 entry.geometry = geometry;
511 }
512 }
513 None => {
514 self.entries.insert(
515 clip_id,
516 ClipArenaEntry {
517 geometry_signature,
518 geometry,
519 },
520 );
521 self.dirty = true;
522 }
523 }
524
525 Some((key, clip_id))
526 }
527
528 fn release_keys(&mut self, keys: &[ClipArenaKey]) {
529 for key in keys {
530 let Some(clip_id) = self.key_to_id.remove(key) else {
531 continue;
532 };
533 self.entries.remove(&clip_id);
534 self.slots[clip_id as usize] = GpuTransform::default();
535 self.free_ids.push(clip_id);
536 self.dirty = true;
537 }
538 }
539
540 fn get(&self, clip_id: u32) -> Option<&ClipArenaEntry> {
541 self.entries.get(&clip_id)
542 }
543
544 fn flush_to_gpu<'w>(&mut self, render_backend: &RenderBackend<'w>) {
545 if !self.dirty {
546 return;
547 }
548 render_backend.update_scene_clip_transforms(&self.slots);
549 self.dirty = false;
550 }
551}
552
553impl TransformArena {
554 fn new() -> Self {
555 Self {
556 slots: vec![GpuTransform::default(); MAX_SCENE_TRANSFORMS],
557 entries: HashMap::new(),
558 free_slots: Vec::new(),
559 next_slot: 1,
560 dirty: true,
561 }
562 }
563
564 fn sync_node(
565 &mut self,
566 node_id: u32,
567 local_transforms: &[GpuTransform],
568 ) -> (Vec<TransformArenaKey>, Vec<u32>) {
569 let mut keys = Vec::with_capacity(local_transforms.len().saturating_sub(1));
570 let mut ids = Vec::with_capacity(local_transforms.len());
571 ids.push(0);
572 for (transform_index, transform) in local_transforms.iter().enumerate().skip(1) {
573 let key = TransformArenaKey {
574 node_id,
575 op_index: (transform_index - 1) as u32,
576 };
577 let slot = self.entries.get(&key).copied().unwrap_or_else(|| {
578 let slot = self
579 .free_slots
580 .pop()
581 .or_else(|| {
582 ((self.next_slot as usize) < MAX_SCENE_TRANSFORMS).then(|| {
583 let slot = self.next_slot;
584 self.next_slot += 1;
585 slot
586 })
587 })
588 .unwrap_or_else(|| {
589 log::error!(
590 "transform arena capacity exceeded (capacity {})",
591 MAX_SCENE_TRANSFORMS
592 );
593 0
594 });
595 self.entries.insert(key, slot);
596 self.dirty = true;
597 slot
598 });
599 if self.slots[slot as usize].transform != transform.transform
600 || (self.slots[slot as usize].opacity - transform.opacity).abs() > f32::EPSILON
601 {
602 self.slots[slot as usize] = *transform;
603 self.dirty = true;
604 }
605 keys.push(key);
606 ids.push(slot);
607 }
608 (keys, ids)
609 }
610
611 fn release_keys(&mut self, keys: &[TransformArenaKey]) {
612 for key in keys {
613 if let Some(slot) = self.entries.remove(key) {
614 if slot != 0 {
615 self.free_slots.push(slot);
616 }
617 }
618 }
619 }
620
621 fn flush_to_gpu<'w>(&mut self, render_backend: &RenderBackend<'w>) {
622 if !self.dirty {
623 return;
624 }
625 render_backend.update_scene_transforms(&self.slots);
626 self.dirty = false;
627 }
628}
629
630impl<'w> WgpuRenderer<'w> {
631 pub fn new(render_backend: RenderBackend<'w>) -> Self {
633 Self {
634 render_backend,
635 tolerance: DEFAULT_TESSELLATION_TOLERANCE, scene: HashMap::new(),
637 transform_arena: TransformArena::new(),
638 clip_arena: ClipArena::new(),
639 clip_owner_keys: HashMap::new(),
640 sorted_nodes: Vec::new(),
641 order_dirty: false,
642 scene_dirty: false,
643 cached_images: HashMap::new(),
644 transform_stack: vec![Transform2D::identity()],
645 clip_stack: Vec::new(),
646 saves: vec![],
647 current_node: None,
648 vector_geometry_cache: Rc::new(RefCell::new(VectorGeometryCache::default())),
649 vector_resource_cache: Rc::new(RefCell::new(VectorResourceCache::default())),
650 resource_churn_stats: ResourceChurnStats::default(),
651 }
652 }
653
654 pub fn share_vector_caches_from(&mut self, other: &Self) {
656 self.vector_geometry_cache = Rc::clone(&other.vector_geometry_cache);
657 self.vector_resource_cache = Rc::clone(&other.vector_resource_cache);
658 }
659
660 pub fn set_vector_resource_cache_max_bytes(&mut self, max_bytes: usize) {
661 let stats = self
662 .vector_resource_cache
663 .borrow_mut()
664 .set_max_bytes(max_bytes);
665 self.merge_vector_resource_cache_stats(stats);
666 }
667
668 pub fn shared_gpu_context_id(&self) -> usize {
669 self.render_backend.shared_context_id()
670 }
671
672 pub fn take_resource_churn_stats(&mut self) -> ResourceChurnStats {
674 std::mem::take(&mut self.resource_churn_stats)
675 }
676
677 pub fn current_transform(&self) -> Transform2D {
679 self.transform_stack
680 .last()
681 .copied()
682 .unwrap_or_else(Transform2D::identity)
683 }
684
685 pub fn set_surface_transform(&mut self, transform: Transform2D) {
687 if let Some(base_transform) = self.transform_stack.first_mut() {
691 *base_transform = transform;
692 } else {
693 self.transform_stack.push(transform);
694 }
695 self.scene_dirty = true;
696 self.clip_stack.clear();
697 self.saves.clear();
698 }
699
700 pub fn reset_retained_scene(&mut self) {
702 self.resource_churn_stats.retained_scene_resets += 1;
703 self.scene.clear();
708 self.transform_arena = TransformArena::new();
709 self.clip_arena = ClipArena::new();
710 self.clip_owner_keys.clear();
711 self.sorted_nodes.clear();
712 self.order_dirty = false;
713 self.scene_dirty = true;
714 self.clip_stack.clear();
715 self.saves.clear();
716 self.current_node = None;
717 }
718
719 pub fn stroke_path(&mut self, path: Path, stroke: Stroke) {
721 self.stroke_path_with_opacity(path, stroke, 1.0);
722 }
723
724 pub fn stroke_path_with_opacity(&mut self, path: Path, stroke: Stroke, opacity: f32) {
726 self.stroke_path_with_material_and_opacity(path, stroke, Material::default(), opacity);
727 }
728
729 pub fn stroke_path_with_material_and_opacity(
731 &mut self,
732 path: Path,
733 stroke: Stroke,
734 material: Material,
735 opacity: f32,
736 ) {
737 self.stroke_path_with_draw_range_and_material_and_opacity(
738 path,
739 stroke,
740 material,
741 opacity,
742 DrawRange::disabled(),
743 );
744 }
745
746 pub fn stroke_path_with_material_and_opacity_and_smoothing(
748 &mut self,
749 path: Path,
750 stroke: Stroke,
751 material: Material,
752 opacity: f32,
753 smoothing: PathSmoothing,
754 ) {
755 self.stroke_path_with_draw_range_and_material_and_opacity_and_smoothing(
756 path,
757 stroke,
758 material,
759 opacity,
760 DrawRange::disabled(),
761 smoothing,
762 );
763 }
764
765 pub fn stroke_path_with_draw_range_and_material_and_opacity(
767 &mut self,
768 path: Path,
769 stroke: Stroke,
770 material: Material,
771 opacity: f32,
772 draw_range: DrawRange,
773 ) {
774 self.stroke_path_with_draw_range_and_material_and_opacity_and_smoothing(
775 path,
776 stroke,
777 material,
778 opacity,
779 draw_range,
780 PathSmoothing::None,
781 );
782 }
783
784 pub fn stroke_path_with_draw_range_and_material_and_opacity_and_smoothing(
786 &mut self,
787 path: Path,
788 stroke: Stroke,
789 material: Material,
790 opacity: f32,
791 draw_range: DrawRange,
792 smoothing: PathSmoothing,
793 ) {
794 let current_transform = self.current_transform();
795 let light_mask = self
796 .current_node
797 .as_ref()
798 .map(|node| node.light_mask)
799 .unwrap_or(0);
800 let geometry_signature = hash_vector_path(
801 &path,
802 PendingVectorOpKind::Stroke(stroke.weight, stroke.cap, stroke.join),
803 smoothing,
804 );
805 let Some(PendingNode {
806 kind: PendingNodeKind::Vector(buffers),
807 ..
808 }) = self.ensure_vector_node()
809 else {
810 return;
811 };
812 buffers.ops.push(PendingVectorOp {
813 path,
814 fill: stroke.fill,
815 material,
816 light_mask,
817 transform: current_transform,
818 opacity,
819 kind: PendingVectorOpKind::Stroke(stroke.weight, stroke.cap, stroke.join),
820 geometry_signature,
821 draw_range,
822 smoothing,
823 });
824 }
825
826 pub fn fill_path(&mut self, path: Path, fill: Fill) {
828 self.fill_path_with_opacity(path, fill, 1.0);
829 }
830
831 pub fn fill_path_with_opacity(&mut self, path: Path, fill: Fill, opacity: f32) {
833 self.fill_path_with_material_and_opacity(path, fill, Material::default(), opacity);
834 }
835
836 pub fn fill_path_with_material_and_opacity(
838 &mut self,
839 path: Path,
840 fill: Fill,
841 material: Material,
842 opacity: f32,
843 ) {
844 self.fill_path_with_material_and_opacity_and_smoothing(
845 path,
846 fill,
847 material,
848 opacity,
849 PathSmoothing::None,
850 );
851 }
852
853 pub fn fill_path_with_material_and_opacity_and_smoothing(
855 &mut self,
856 path: Path,
857 fill: Fill,
858 material: Material,
859 opacity: f32,
860 smoothing: PathSmoothing,
861 ) {
862 let current_transform = self.current_transform();
863 let light_mask = self
864 .current_node
865 .as_ref()
866 .map(|node| node.light_mask)
867 .unwrap_or(0);
868 let geometry_signature = hash_vector_path(&path, PendingVectorOpKind::Fill, smoothing);
869 let Some(PendingNode {
870 kind: PendingNodeKind::Vector(buffers),
871 ..
872 }) = self.ensure_vector_node()
873 else {
874 return;
875 };
876 buffers.ops.push(PendingVectorOp {
877 path,
878 fill,
879 material,
880 light_mask,
881 transform: current_transform,
882 opacity,
883 kind: PendingVectorOpKind::Fill,
884 geometry_signature,
885 draw_range: DrawRange::disabled(),
886 smoothing,
887 });
888 }
889
890 pub fn set_scene_lighting(&mut self, lighting: SceneLighting) {
891 self.render_backend
892 .set_scene_lighting(to_gpu_scene_lighting(&lighting));
893 self.scene_dirty = true;
894 }
895
896 pub fn clear(&mut self) {
897 self.scene_dirty = true;
898 self.render_backend.clear();
899 }
900
901 pub fn draw_image(&mut self, image_key: &str, image_version: u64, image: &Image, rect: Box2D) {
902 let needs_upload = self
903 .cached_images
904 .get(image_key)
905 .map(|entry| entry.version != image_version)
906 .unwrap_or(true);
907 if needs_upload {
908 self.resource_churn_stats.texture_creates += 1;
909 self.resource_churn_stats.texture_upload_bytes += image.rgba.len() as u64;
910 self.cached_images.insert(
911 image_key.to_owned(),
912 CachedImageEntry {
913 texture: self.render_backend.create_cached_texture(
914 &image.rgba,
915 image.pixel_width,
916 image.pixel_height,
917 ),
918 version: image_version,
919 },
920 );
921 }
922 let transform = self.current_transform();
923 let clip_stack = self.clip_stack.clone();
924 let bounds = transform_box(rect, &transform);
925 let draw = self
926 .render_backend
927 .create_image_draw(image_key.to_owned(), transform, rect);
928 let Some(current_node) = self.current_node.as_mut() else {
929 return;
930 };
931 current_node.kind = PendingNodeKind::Image(PendingImageNode {
932 draw,
933 clip_stack,
934 bounds,
935 });
936 }
937
938 pub fn flush(&mut self) {
939 self.flush_internal(false);
940 }
941
942 pub fn flush_deferred(&mut self) {
943 self.flush_internal(true);
944 }
945
946 fn flush_internal(&mut self, defer_submit: bool) {
947 self.resource_churn_stats.flushes += 1;
948 let active_alpha_masks = self
949 .scene
950 .values()
951 .flat_map(|node| node.clip_stack())
952 .filter_map(|clip| {
953 if let ClipReference::Alpha { owner } = clip {
954 Some(*owner)
955 } else {
956 None
957 }
958 })
959 .collect();
960 self.render_backend.alpha_masks.retain(&active_alpha_masks);
961 if !self.scene_dirty {
962 self.transform_stack.truncate(1);
963 self.clip_stack.clear();
964 self.saves.clear();
965 self.current_node = None;
966 return;
967 }
968 if self.order_dirty {
969 self.sorted_nodes = self
970 .scene
971 .iter()
972 .map(|(node_id, node)| (node.z_index(), *node_id))
973 .collect();
974 self.sorted_nodes.sort_unstable();
975 self.order_dirty = false;
976 }
977 if self.should_use_vector_scene_batch() {
978 self.resource_churn_stats.vector_batch_flushes += 1;
979 self.flush_vector_scene_batch(defer_submit);
980 self.scene_dirty = false;
981 self.transform_stack.truncate(1);
982 self.clip_stack.clear();
983 self.saves.clear();
984 self.current_node = None;
985 return;
986 }
987 self.render_backend.ensure_frame_cleared();
988 self.ensure_vector_resources_for_immediate_scene();
989 self.transform_arena.flush_to_gpu(&self.render_backend);
990 self.clip_arena.flush_to_gpu(&self.render_backend);
991 self.resource_churn_stats.retained_nodes_considered += self.sorted_nodes.len() as u64;
992 let viewport_bounds = self.viewport_bounds();
996 let mut current_clip_stack: Vec<u32> = Vec::new();
997 let mut current_batch: Vec<RetainedDraw<'_>> = Vec::new();
998 let mut retained_runs: Vec<RetainedBatchRun<'_>> = Vec::new();
999 let mut current_batch_clip_stack: Option<Vec<ClipReference>> = None;
1000 for (_, node_id) in &self.sorted_nodes {
1001 let Some(node) = self.scene.get(node_id) else {
1002 continue;
1003 };
1004 if !node.intersects_bounds(&viewport_bounds) {
1005 continue;
1006 }
1007 self.resource_churn_stats.retained_nodes_visible += 1;
1008 if let Some(batch_clip_stack) = current_batch_clip_stack.as_ref() {
1009 if !clip_stacks_match(batch_clip_stack, node.clip_stack()) {
1010 sync_retained_clip_stack(
1011 &mut self.render_backend,
1012 &mut retained_runs,
1013 &mut current_clip_stack,
1014 batch_clip_stack,
1015 &self.clip_arena,
1016 );
1017 let stencil_index = self.render_backend.get_clip_depth();
1018 let scissor = scissor_for_clip_stack(batch_clip_stack);
1019 record_retained_batch_stats(&mut self.resource_churn_stats, ¤t_batch);
1020 if !current_batch.is_empty() {
1021 retained_runs.push(RetainedBatchRun {
1022 alpha_mask: alpha_for_clip_stack(batch_clip_stack),
1023 stencil_index,
1024 scissor,
1025 draws: std::mem::take(&mut current_batch),
1026 });
1027 }
1028 current_batch_clip_stack = Some(node.clip_stack().to_vec());
1029 }
1030 } else {
1031 current_batch_clip_stack = Some(node.clip_stack().to_vec());
1032 }
1033 match node {
1034 RetainedNode::Vector(node) => {
1035 let Some(resource) = node.resource.as_ref() else {
1036 continue;
1037 };
1038 if !resource.is_drawable() {
1039 continue;
1040 }
1041 current_batch.push(RetainedDraw::Vector(resource));
1042 }
1043 RetainedNode::Image(node) => {
1044 let Some(texture) = self.cached_images.get(&node.draw.resource.image_key)
1045 else {
1046 continue;
1047 };
1048 current_batch.push(RetainedDraw::Image {
1049 texture: &texture.texture,
1050 draw: &node.draw,
1051 });
1052 }
1053 }
1054 }
1055 if let Some(batch_clip_stack) = current_batch_clip_stack.as_ref() {
1056 sync_retained_clip_stack(
1057 &mut self.render_backend,
1058 &mut retained_runs,
1059 &mut current_clip_stack,
1060 batch_clip_stack,
1061 &self.clip_arena,
1062 );
1063 let stencil_index = self.render_backend.get_clip_depth();
1064 let scissor = scissor_for_clip_stack(batch_clip_stack);
1065 record_retained_batch_stats(&mut self.resource_churn_stats, ¤t_batch);
1066 if !current_batch.is_empty() {
1067 retained_runs.push(RetainedBatchRun {
1068 alpha_mask: alpha_for_clip_stack(batch_clip_stack),
1069 stencil_index,
1070 scissor,
1071 draws: std::mem::take(&mut current_batch),
1072 });
1073 }
1074 }
1075 self.render_backend.draw_retained_batch_runs(&retained_runs);
1076 self.cached_images.retain(|image_key, _| {
1077 self.scene.values().any(|node| {
1078 matches!(
1079 node,
1080 RetainedNode::Image(image_node)
1081 if image_node.draw.resource.image_key == *image_key
1082 )
1083 })
1084 });
1085 let (active_clip_signatures, active_clip_ids) =
1086 collect_active_clip_resources(&self.scene, &self.clip_arena);
1087 self.render_backend
1088 .retain_stencil_resources(&active_clip_signatures, &active_clip_ids);
1089 self.finish_backend_frame(defer_submit);
1090 self.scene_dirty = false;
1091 self.transform_stack.truncate(1);
1092 self.clip_stack.clear();
1093 self.saves.clear();
1094 self.current_node = None;
1095 }
1096
1097 pub fn save(&mut self) {
1098 self.saves.push(SceneStateSave {
1099 transform_depth: self.transform_stack.len(),
1100 clip_depth: self.clip_stack.len(),
1101 });
1102 }
1103
1104 pub fn restore(&mut self) {
1105 if let Some(save) = self.saves.pop() {
1106 self.transform_stack.truncate(save.transform_depth);
1107 self.clip_stack.truncate(save.clip_depth);
1108 }
1109 }
1110
1111 pub fn transform(&mut self, transform: Transform2D) {
1112 self.transform_stack
1113 .push(transform.then(&self.current_transform()));
1114 }
1115
1116 pub fn clip(&mut self, path: Path) {
1117 let transform = self.current_transform();
1118 let Some(current_node) = self.current_node.as_mut() else {
1119 log::warn!("clip called without an active node");
1120 return;
1121 };
1122 if let Some(scissor) = axis_aligned_rect_scissor(&path, &transform) {
1123 self.clip_stack.push(ClipReference::Scissor(scissor));
1124 return;
1125 }
1126 let geometry = tessellate_clip_geometry(&path, self.tolerance);
1127 let geometry_signature = hash_clip_geometry(&geometry);
1128 let clip_index = current_node.owned_clip_keys.len() as u32;
1129 let Some((clip_key, clip_id)) = self.clip_arena.sync_clip(
1130 current_node.id,
1131 clip_index,
1132 geometry_signature,
1133 geometry,
1134 transform,
1135 ) else {
1136 return;
1137 };
1138 current_node.owned_clip_keys.push(clip_key);
1139 self.clip_stack.push(ClipReference::Stencil { clip_id });
1140 }
1141
1142 pub fn clip_alpha(&mut self, paints: Vec<AlphaMaskPaint>, feather: f32) {
1144 use crate::render_backend::alpha_mask::{Draw, Paint};
1145 let Some(node) = &self.current_node else {
1146 return;
1147 };
1148 let owner = node.id;
1149 let parent = alpha_for_clip_stack(&self.clip_stack);
1150 let mut hash = DefaultHasher::new();
1151 self.render_backend
1152 .alpha_masks
1153 .signature(parent)
1154 .hash(&mut hash);
1155 let transform = self.current_transform();
1156 let mut draws = Vec::new();
1157 for source in paints {
1158 let mut paint = Paint::default();
1159 paint.params[1] = source.opacity;
1160 match source.fill {
1161 Fill::Solid(color) => paint.params[0] = color.rgba[3],
1162 Fill::Gradient {
1163 gradient_type,
1164 pos,
1165 main_axis,
1166 off_axis,
1167 stops,
1168 } => {
1169 paint.axis = [pos.x, pos.y, main_axis.x, main_axis.y];
1170 paint.off_axis = [off_axis.x, off_axis.y, 0.0, 0.0];
1171 paint.params[2] = stops.len().min(8) as f32;
1172 paint.params[3] = if matches!(gradient_type, GradientType::Radial) {
1173 1.0
1174 } else {
1175 0.0
1176 };
1177 for (slot, stop) in paint.stops.iter_mut().zip(stops.iter()) {
1178 *slot = [stop.stop, stop.color.rgba[3], 0.0, 0.0];
1179 }
1180 }
1181 }
1182 bytemuck::bytes_of(&paint).hash(&mut hash);
1183 let mut geometry: VertexBuffers<[f32; 2], u32> = VertexBuffers::new();
1184 let mapping = source.transform.then(&transform);
1185 let mut builder = BuffersBuilder::new(&mut geometry, |v: FillVertex| {
1186 mapping
1187 .transform_point(point(v.position().x, v.position().y))
1188 .to_array()
1189 });
1190 if let Err(error) = FillTessellator::new().tessellate_path(
1191 &source.path,
1192 &FillOptions::default()
1193 .with_tolerance(self.tolerance)
1194 .with_fill_rule(FillRule::NonZero),
1195 &mut builder,
1196 ) {
1197 log::error!("alpha mask tessellation failed: {error:?}");
1198 }
1199 bytemuck::cast_slice::<_, u8>(&geometry.vertices).hash(&mut hash);
1200 geometry.indices.hash(&mut hash);
1201 draws.push(Draw {
1202 vertices: geometry.vertices,
1203 indices: geometry.indices,
1204 paint,
1205 });
1206 }
1207 feather.to_bits().hash(&mut hash);
1208 for value in self
1209 .render_backend
1210 .globals
1211 .resolution
1212 .into_iter()
1213 .chain(self.render_backend.globals.dpr)
1214 {
1215 value.to_bits().hash(&mut hash);
1216 }
1217 self.render_backend
1218 .render_alpha_mask(owner, hash.finish(), &draws, feather, parent);
1219 self.clip_stack.push(ClipReference::Alpha { owner });
1220 self.scene_dirty = true;
1221 }
1222
1223 pub fn resize(&mut self, width: f32, height: f32) {
1224 self.scene_dirty = true;
1225 self.render_backend.resize(width as u32, height as u32);
1226 }
1227
1228 pub fn resize_surface(&mut self, width: f32, height: f32) {
1229 self.scene_dirty = true;
1230 self.render_backend
1231 .resize_surface(width.round() as u32, height.round() as u32);
1232 }
1233
1234 pub fn set_viewport(&mut self, width: f32, height: f32, dpr: [f32; 2]) {
1235 self.scene_dirty = true;
1236 self.render_backend.set_viewport(width, height, dpr);
1237 }
1238
1239 pub fn max_surface_dimension(&self) -> u32 {
1240 self.render_backend.max_surface_dimension()
1241 }
1242
1243 pub fn scene_len(&self) -> usize {
1244 self.scene.len()
1245 }
1246
1247 pub fn visible_node_count(&self) -> usize {
1248 let viewport_bounds = self.viewport_bounds();
1251 self.scene
1252 .values()
1253 .filter(|node| node.intersects_bounds(&viewport_bounds))
1254 .count()
1255 }
1256
1257 pub fn size(&self) -> (f32, f32) {
1258 let res = &self.render_backend.globals.resolution;
1259 (res[0], res[1])
1260 }
1261
1262 pub fn request_screenshot_capture(&mut self, request_id: u32) {
1263 self.scene_dirty = true;
1264 self.render_backend.request_screenshot_capture(request_id);
1265 }
1266
1267 pub fn take_screenshot_capture(&mut self, request_id: u32) -> Option<CapturedFrame> {
1268 self.render_backend.take_screenshot_capture(request_id)
1269 }
1270
1271 pub fn begin_node(&mut self, node_id: u32, z_index: i32, light_mask: u32) -> bool {
1272 self.current_node = Some(PendingNode {
1273 id: node_id,
1274 z_index,
1275 light_mask,
1276 owned_clip_keys: Vec::new(),
1277 kind: PendingNodeKind::Empty,
1278 });
1279 true
1280 }
1281
1282 pub fn end_node(&mut self, node_id: u32) -> bool {
1283 let Some(pending_node) = self.current_node.take() else {
1284 return true;
1285 };
1286 if pending_node.id != node_id {
1287 return false;
1288 }
1289
1290 let pending_z_index = pending_node.z_index;
1291 let pending_owned_clip_keys = pending_node.owned_clip_keys.clone();
1292 let pending_clip_count = pending_owned_clip_keys.len();
1293 let previous_owned_clip_keys = self
1294 .clip_owner_keys
1295 .get(&node_id)
1296 .cloned()
1297 .unwrap_or_default();
1298
1299 let previous = self.scene.remove(&node_id);
1300 let prev_z = previous.as_ref().map(|node| node.z_index());
1301
1302 let updated = match pending_node.kind {
1303 PendingNodeKind::Empty => {
1304 if let Some(RetainedNode::Vector(existing)) = previous.as_ref() {
1305 self.transform_arena.release_keys(&existing.transform_keys);
1306 }
1307 None
1308 }
1309 PendingNodeKind::Vector(buffers) => {
1310 let geometry_signatures = buffers
1311 .ops
1312 .iter()
1313 .map(|op| op.geometry_signature)
1314 .collect::<Vec<_>>();
1315 let fill_signature = hash_vector_fills(&buffers.ops);
1316 let primitive_signature = hash_vector_primitives(&buffers.ops);
1317 let transform_signature = hash_vector_transforms(&buffers.ops);
1318 let local_transforms = build_local_transform_table(&buffers.ops);
1319 let (transform_keys, transform_ids) =
1320 self.transform_arena.sync_node(node_id, &local_transforms);
1321 let transform_layout_signature = hash_transform_ids(&transform_ids);
1322 let resource_key = VectorResourceKey {
1323 tolerance_bits: self.tolerance.to_bits(),
1324 geometry_signatures: geometry_signatures.clone(),
1325 fill_signature,
1326 primitive_signature,
1327 transform_layout_signature,
1328 };
1329
1330 match previous {
1331 Some(RetainedNode::Vector(mut existing)) => {
1332 if existing.transform_keys.len() > transform_keys.len() {
1333 self.transform_arena
1334 .release_keys(&existing.transform_keys[transform_keys.len()..]);
1335 }
1336 let reuse_geometry = existing.geometry_signatures == geometry_signatures;
1337 let geometry_changed = !reuse_geometry;
1338 let fill_changed = existing.fill_signature != fill_signature;
1339 let primitive_changed = existing.primitive_signature != primitive_signature;
1340 let transform_changed = existing.transform_signature != transform_signature;
1341 let transform_layout_changed =
1342 existing.transform_layout_signature != transform_layout_signature;
1343
1344 if geometry_changed
1345 || fill_changed
1346 || primitive_changed
1347 || transform_changed
1348 {
1349 rebuild_vector_buffers(
1350 self.tolerance,
1351 &buffers.ops,
1352 &mut existing.buffers,
1353 reuse_geometry,
1354 !fill_changed,
1355 &self.vector_geometry_cache,
1356 &mut self.resource_churn_stats,
1357 );
1358 }
1359
1360 if geometry_changed
1361 || fill_changed
1362 || primitive_changed
1363 || transform_layout_changed
1364 {
1365 existing.retained_primitives = build_retained_primitives(
1366 &existing.buffers.primitives,
1367 &transform_ids,
1368 );
1369 }
1370
1371 existing.transform_keys = transform_keys;
1372 existing.clip_stack = buffers.clip_stack;
1373 existing.z_index = pending_z_index;
1374 existing.resource_dirty.geometry |= geometry_changed;
1375 existing.resource_dirty.primitives |= geometry_changed
1376 || fill_changed
1377 || primitive_changed
1378 || transform_layout_changed;
1379 existing.resource_dirty.transforms = false;
1380 existing.resource_dirty.fill |= fill_changed;
1381 existing.geometry_signatures = geometry_signatures;
1382 existing.fill_signature = fill_signature;
1383 existing.primitive_signature = primitive_signature;
1384 existing.transform_signature = transform_signature;
1385 existing.transform_layout_signature = transform_layout_signature;
1386 existing.resource_key = resource_key;
1387 existing.bounds = compute_vector_node_bounds(&buffers.ops);
1388 Some(RetainedNode::Vector(existing))
1389 }
1390 Some(RetainedNode::Image(_existing)) => {
1391 let mut buffers_out = new_cpu_buffers();
1392 rebuild_vector_buffers(
1393 self.tolerance,
1394 &buffers.ops,
1395 &mut buffers_out,
1396 false,
1397 false,
1398 &self.vector_geometry_cache,
1399 &mut self.resource_churn_stats,
1400 );
1401 let retained_primitives =
1402 build_retained_primitives(&buffers_out.primitives, &transform_ids);
1403 Some(RetainedNode::Vector(RetainedVectorNode {
1404 resource: None,
1405 buffers: buffers_out,
1406 retained_primitives,
1407 transform_keys,
1408 clip_stack: buffers.clip_stack,
1409 z_index: pending_z_index,
1410 bounds: compute_vector_node_bounds(&buffers.ops),
1411 resource_dirty: VectorResourceDirty {
1412 geometry: true,
1413 primitives: true,
1414 transforms: false,
1415 fill: true,
1416 },
1417 geometry_signatures,
1418 fill_signature,
1419 primitive_signature,
1420 transform_signature,
1421 transform_layout_signature,
1422 resource_key,
1423 active_resource_key: None,
1424 }))
1425 }
1426 None => {
1427 let mut buffers_out = new_cpu_buffers();
1428 rebuild_vector_buffers(
1429 self.tolerance,
1430 &buffers.ops,
1431 &mut buffers_out,
1432 false,
1433 false,
1434 &self.vector_geometry_cache,
1435 &mut self.resource_churn_stats,
1436 );
1437 let retained_primitives =
1438 build_retained_primitives(&buffers_out.primitives, &transform_ids);
1439 Some(RetainedNode::Vector(RetainedVectorNode {
1440 resource: None,
1441 buffers: buffers_out,
1442 retained_primitives,
1443 transform_keys,
1444 clip_stack: buffers.clip_stack,
1445 z_index: pending_z_index,
1446 bounds: compute_vector_node_bounds(&buffers.ops),
1447 resource_dirty: VectorResourceDirty {
1448 geometry: true,
1449 primitives: true,
1450 transforms: false,
1451 fill: true,
1452 },
1453 geometry_signatures,
1454 fill_signature,
1455 primitive_signature,
1456 transform_signature,
1457 transform_layout_signature,
1458 resource_key,
1459 active_resource_key: None,
1460 }))
1461 }
1462 }
1463 }
1464 PendingNodeKind::Image(image_node) => {
1465 if let Some(RetainedNode::Vector(existing)) = previous.as_ref() {
1466 self.transform_arena.release_keys(&existing.transform_keys);
1467 }
1468 Some(RetainedNode::Image(RetainedImageNode {
1469 draw: image_node.draw,
1470 clip_stack: image_node.clip_stack,
1471 z_index: pending_z_index,
1472 bounds: image_node.bounds,
1473 }))
1474 }
1475 };
1476
1477 if previous_owned_clip_keys.len() > pending_clip_count {
1478 self.clip_arena
1479 .release_keys(&previous_owned_clip_keys[pending_clip_count..]);
1480 }
1481 if pending_owned_clip_keys.is_empty() {
1482 self.clip_owner_keys.remove(&node_id);
1483 } else {
1484 self.clip_owner_keys
1485 .insert(node_id, pending_owned_clip_keys.clone());
1486 }
1487
1488 match updated {
1489 Some(node) => {
1490 self.scene.insert(node_id, node);
1491 if prev_z != Some(pending_z_index) {
1492 self.order_dirty = true;
1493 }
1494 self.scene_dirty = true;
1495 }
1496 None => {
1497 if prev_z.is_some() {
1498 self.order_dirty = true;
1499 self.scene_dirty = true;
1500 }
1501 }
1502 }
1503 true
1504 }
1505
1506 pub fn remove_node(&mut self, node_id: u32) -> bool {
1507 let mut removed = false;
1508 if let Some(node) = self.scene.remove(&node_id) {
1509 if let RetainedNode::Vector(node) = &node {
1510 self.transform_arena.release_keys(&node.transform_keys);
1511 }
1512 removed = true;
1513 }
1514 if let Some(owned_clip_keys) = self.clip_owner_keys.remove(&node_id) {
1515 self.clip_arena.release_keys(&owned_clip_keys);
1516 removed = true;
1517 }
1518 if removed {
1519 self.order_dirty = true;
1520 self.scene_dirty = true;
1521 }
1522 removed
1523 }
1524
1525 fn ensure_vector_node(&mut self) -> Option<&mut PendingNode> {
1526 let current_node = self.current_node.as_mut()?;
1527 if matches!(current_node.kind, PendingNodeKind::Empty) {
1528 current_node.kind = PendingNodeKind::Vector(PendingVectorNode {
1529 ops: Vec::new(),
1530 clip_stack: self.clip_stack.clone(),
1531 });
1532 }
1533 Some(current_node)
1534 }
1535
1536 fn should_use_vector_scene_batch(&self) -> bool {
1537 self.scene.len() >= 64
1538 && self
1539 .scene
1540 .values()
1541 .all(|node| matches!(node, RetainedNode::Vector(_)))
1542 }
1543
1544 fn ensure_vector_resources_for_immediate_scene(&mut self) {
1545 let viewport_bounds = self.viewport_bounds();
1548 let node_ids: Vec<u32> = self
1549 .sorted_nodes
1550 .iter()
1551 .map(|(_, node_id)| *node_id)
1552 .collect();
1553 for node_id in node_ids {
1554 let Some(retained_node) = self.scene.remove(&node_id) else {
1555 continue;
1556 };
1557 let RetainedNode::Vector(mut node) = retained_node else {
1558 self.scene.insert(node_id, retained_node);
1559 continue;
1560 };
1561 if !boxes_intersect(&node.bounds, &viewport_bounds) {
1562 self.scene.insert(node_id, RetainedNode::Vector(node));
1563 continue;
1564 }
1565 if !node.resource_dirty.any() && node.resource.is_some() {
1566 self.scene.insert(node_id, RetainedNode::Vector(node));
1567 continue;
1568 }
1569
1570 self.ensure_vector_resource_for_node(&mut node);
1571 self.scene.insert(node_id, RetainedNode::Vector(node));
1572 }
1573 }
1574
1575 fn ensure_vector_resource_for_node(&mut self, node: &mut RetainedVectorNode) {
1576 let resource_key = node.resource_key.clone();
1577 if node.resource.is_some() && node.active_resource_key.as_ref() == Some(&resource_key) {
1578 node.resource_dirty = VectorResourceDirty::default();
1579 return;
1580 }
1581
1582 if let (Some(old_key), Some(resource)) =
1583 (node.active_resource_key.clone(), node.resource.as_mut())
1584 {
1585 let removed_cache_entry = if Rc::strong_count(resource.shared()) == 2 {
1586 self.vector_resource_cache
1587 .borrow_mut()
1588 .remove_if_same(&old_key, resource.shared())
1589 } else {
1590 None
1591 };
1592 if let Some(removed_cache_entry) = removed_cache_entry {
1593 self.resource_churn_stats.vector_resource_updates += 1;
1594 if let Some(upload_bytes) = self.render_backend.update_vector_resource(
1595 resource,
1596 &mut node.buffers,
1597 &node.retained_primitives,
1598 node.resource_dirty,
1599 ) {
1600 self.resource_churn_stats.vector_resource_update_bytes += upload_bytes;
1601 let cache_stats = self.vector_resource_cache.borrow_mut().insert(
1602 resource_key.clone(),
1603 Rc::clone(resource.shared()),
1604 removed_cache_entry.bytes as u64,
1605 );
1606 self.merge_vector_resource_cache_stats(cache_stats);
1607 node.active_resource_key = Some(resource_key);
1608 node.resource_dirty = VectorResourceDirty::default();
1609 return;
1610 }
1611 }
1612 self.resource_churn_stats.vector_resource_recreates += 1;
1613 }
1614
1615 let (cached_resource, cache_bytes) = {
1616 let mut cache = self.vector_resource_cache.borrow_mut();
1617 let cached_resource = cache.get(&resource_key);
1618 (cached_resource, cache.current_bytes())
1619 };
1620 if let Some(shared_resource) = cached_resource {
1621 self.resource_churn_stats.vector_resource_cache_hits += 1;
1622 self.resource_churn_stats.vector_resource_cache_bytes = self
1623 .resource_churn_stats
1624 .vector_resource_cache_bytes
1625 .max(cache_bytes as u64);
1626 node.resource = Some(
1627 self.render_backend
1628 .create_vector_resource_from_shared(shared_resource),
1629 );
1630 node.active_resource_key = Some(resource_key);
1631 node.resource_dirty = VectorResourceDirty::default();
1632 return;
1633 }
1634
1635 self.resource_churn_stats.vector_resource_cache_misses += 1;
1636 self.resource_churn_stats.vector_resource_creates += 1;
1637 let (shared_resource, upload_bytes) = self
1638 .render_backend
1639 .create_shared_vector_resource(&mut node.buffers, &node.retained_primitives);
1640 self.resource_churn_stats.vector_resource_create_bytes += upload_bytes;
1641 let cache_stats = self.vector_resource_cache.borrow_mut().insert(
1642 resource_key.clone(),
1643 Rc::clone(&shared_resource),
1644 upload_bytes,
1645 );
1646 self.merge_vector_resource_cache_stats(cache_stats);
1647 node.resource = Some(
1648 self.render_backend
1649 .create_vector_resource_from_shared(shared_resource),
1650 );
1651 node.active_resource_key = Some(resource_key);
1652 node.resource_dirty = VectorResourceDirty::default();
1653 }
1654
1655 fn merge_vector_resource_cache_stats(&mut self, stats: VectorResourceCacheStats) {
1656 self.resource_churn_stats.vector_resource_cache_evictions += stats.evicted_entries;
1657 self.resource_churn_stats.vector_resource_cache_bytes = self
1658 .resource_churn_stats
1659 .vector_resource_cache_bytes
1660 .max(stats.current_bytes as u64);
1661 }
1662
1663 fn flush_vector_scene_batch(&mut self, defer_submit: bool) {
1664 self.transform_arena.flush_to_gpu(&self.render_backend);
1665 self.clip_arena.flush_to_gpu(&self.render_backend);
1666 let viewport_bounds = self.viewport_bounds();
1667 let mut current_clip_stack: Vec<u32> = Vec::new();
1668 let mut current_batch_clip_stack: Option<Vec<ClipReference>> = None;
1669 let mut current_buffers = new_cpu_buffers();
1670 let mut current_segment_start: Option<usize> = None;
1671 let mut has_geometry = false;
1672 let mut segments = Vec::new();
1673 let mut batches = Vec::new();
1674 for (_, node_id) in &self.sorted_nodes {
1675 let Some(RetainedNode::Vector(node)) = self.scene.get(node_id) else {
1676 continue;
1677 };
1678 if !boxes_intersect(&node.bounds, &viewport_bounds) {
1679 continue;
1680 }
1681
1682 if let Some(batch_clip_stack) = current_batch_clip_stack.as_ref() {
1683 if !clip_stacks_match(batch_clip_stack, &node.clip_stack) {
1684 if has_geometry {
1685 push_primitive_segment(
1686 &mut segments,
1687 &mut current_clip_stack,
1688 batch_clip_stack,
1689 &self.clip_arena,
1690 current_segment_start.take().unwrap_or(0),
1691 current_buffers.geometry.indices.len(),
1692 );
1693 has_geometry = false;
1694 }
1695 current_batch_clip_stack = Some(node.clip_stack.clone());
1696 }
1697 } else {
1698 current_batch_clip_stack = Some(node.clip_stack.clone());
1699 }
1700
1701 if has_geometry && would_exceed_batch_capacity(¤t_buffers, &node.buffers) {
1702 if let Some(batch_clip_stack) = current_batch_clip_stack.as_ref() {
1703 push_primitive_segment(
1704 &mut segments,
1705 &mut current_clip_stack,
1706 batch_clip_stack,
1707 &self.clip_arena,
1708 current_segment_start.take().unwrap_or(0),
1709 current_buffers.geometry.indices.len(),
1710 );
1711 push_primitive_batch(
1712 &mut batches,
1713 std::mem::replace(&mut current_buffers, new_cpu_buffers()),
1714 std::mem::take(&mut segments),
1715 );
1716 }
1717 current_batch_clip_stack = Some(node.clip_stack.clone());
1718 }
1719
1720 if current_segment_start.is_none() {
1721 current_segment_start = Some(current_buffers.geometry.indices.len());
1722 }
1723 append_cpu_buffers(&mut current_buffers, &node.buffers);
1724 has_geometry = true;
1725 }
1726
1727 if has_geometry {
1728 if let Some(batch_clip_stack) = current_batch_clip_stack.as_ref() {
1729 push_primitive_segment(
1730 &mut segments,
1731 &mut current_clip_stack,
1732 batch_clip_stack,
1733 &self.clip_arena,
1734 current_segment_start.take().unwrap_or(0),
1735 current_buffers.geometry.indices.len(),
1736 );
1737 }
1738 }
1739 push_primitive_batch(&mut batches, current_buffers, segments);
1740 if batches.is_empty() {
1741 self.render_backend.ensure_frame_cleared();
1742 } else {
1743 self.render_backend.render_primitive_batches(&mut batches);
1744 }
1745
1746 let (active_clip_signatures, active_clip_ids) =
1747 collect_active_clip_resources(&self.scene, &self.clip_arena);
1748 self.render_backend
1749 .retain_stencil_resources(&active_clip_signatures, &active_clip_ids);
1750 self.finish_backend_frame(defer_submit);
1751 }
1752
1753 fn finish_backend_frame(&mut self, defer_submit: bool) {
1754 if defer_submit {
1755 self.render_backend.finish_frame();
1756 } else {
1757 self.render_backend.present();
1758 }
1759 }
1760
1761 pub fn take_pending_command_buffers(&mut self) -> Vec<wgpu::CommandBuffer> {
1762 self.render_backend.take_pending_command_buffers()
1763 }
1764
1765 pub fn submit_command_buffers(&self, command_buffers: Vec<wgpu::CommandBuffer>) {
1766 self.render_backend.submit_command_buffers(command_buffers);
1767 }
1768
1769 pub fn complete_submitted_work(&mut self) {
1770 self.render_backend.complete_submitted_work();
1771 }
1772
1773 pub fn has_pending_submission_cleanup(&self) -> bool {
1774 self.render_backend.has_pending_submission_cleanup()
1775 }
1776
1777 pub fn present_deferred_frame(&mut self) {
1778 self.render_backend.present_queued_frame();
1779 }
1780
1781 fn viewport_bounds(&self) -> Box2D {
1782 let (width, height) = self.size();
1783 Box2D {
1784 min: point(0.0, 0.0),
1785 max: point(width.max(0.0), height.max(0.0)),
1786 }
1787 }
1788}
1789
1790struct SceneStateSave {
1791 transform_depth: usize,
1792 clip_depth: usize,
1793}
1794
1795struct PendingNode {
1796 id: u32,
1797 z_index: i32,
1798 light_mask: u32,
1799 owned_clip_keys: Vec<ClipArenaKey>,
1800 kind: PendingNodeKind,
1801}
1802
1803enum PendingNodeKind {
1804 Empty,
1805 Vector(PendingVectorNode),
1806 Image(PendingImageNode),
1807}
1808
1809struct PendingVectorNode {
1810 ops: Vec<PendingVectorOp>,
1811 clip_stack: Vec<ClipReference>,
1812}
1813
1814struct PendingVectorOp {
1815 path: Path,
1816 fill: Fill,
1817 material: Material,
1818 light_mask: u32,
1819 transform: Transform2D,
1820 opacity: f32,
1821 kind: PendingVectorOpKind,
1822 geometry_signature: u64,
1823 draw_range: DrawRange,
1824 smoothing: PathSmoothing,
1825}
1826
1827#[derive(Clone, Copy)]
1828enum PendingVectorOpKind {
1829 Fill,
1830 Stroke(f32, StrokeCap, StrokeJoin),
1831}
1832
1833struct PendingImageNode {
1834 draw: RetainedImageDraw,
1835 clip_stack: Vec<ClipReference>,
1836 bounds: Box2D,
1837}
1838
1839enum RetainedNode {
1840 Vector(RetainedVectorNode),
1841 Image(RetainedImageNode),
1842}
1843
1844impl RetainedNode {
1845 fn z_index(&self) -> i32 {
1846 match self {
1847 RetainedNode::Vector(node) => node.z_index,
1848 RetainedNode::Image(node) => node.z_index,
1849 }
1850 }
1851
1852 fn clip_stack(&self) -> &[ClipReference] {
1853 match self {
1854 RetainedNode::Vector(node) => &node.clip_stack,
1855 RetainedNode::Image(node) => &node.clip_stack,
1856 }
1857 }
1858
1859 fn intersects_bounds(&self, viewport_bounds: &Box2D) -> bool {
1860 match self {
1861 RetainedNode::Vector(node) => boxes_intersect(&node.bounds, viewport_bounds),
1862 RetainedNode::Image(node) => boxes_intersect(&node.bounds, viewport_bounds),
1863 }
1864 }
1865}
1866
1867struct RetainedVectorNode {
1868 resource: Option<RetainedVectorResource>,
1869 buffers: CpuBuffers,
1870 retained_primitives: Vec<GpuPrimitive>,
1871 transform_keys: Vec<TransformArenaKey>,
1872 clip_stack: Vec<ClipReference>,
1873 z_index: i32,
1874 bounds: Box2D,
1875 resource_dirty: VectorResourceDirty,
1876 geometry_signatures: Vec<u64>,
1877 fill_signature: u64,
1878 primitive_signature: u64,
1879 transform_signature: u64,
1880 transform_layout_signature: u64,
1881 resource_key: VectorResourceKey,
1882 active_resource_key: Option<VectorResourceKey>,
1883}
1884
1885struct RetainedImageNode {
1886 draw: RetainedImageDraw,
1887 clip_stack: Vec<ClipReference>,
1888 z_index: i32,
1889 bounds: Box2D,
1890}
1891
1892fn new_cpu_buffers() -> CpuBuffers {
1893 CpuBuffers {
1894 geometry: VertexBuffers::new(),
1895 primitives: Vec::new(),
1896 colors: Vec::new(),
1897 gradients: Vec::new(),
1898 materials: Vec::new(),
1899 transforms: vec![GpuTransform::default()],
1900 }
1901}
1902
1903fn build_retained_primitives(
1904 local_primitives: &[GpuPrimitive],
1905 transform_ids: &[u32],
1906) -> Vec<GpuPrimitive> {
1907 local_primitives
1908 .iter()
1909 .map(|primitive| GpuPrimitive {
1910 transform_id: transform_ids
1911 .get(primitive.transform_id as usize)
1912 .copied()
1913 .unwrap_or(0),
1914 ..*primitive
1915 })
1916 .collect()
1917}
1918
1919fn record_retained_batch_stats(stats: &mut ResourceChurnStats, draws: &[RetainedDraw<'_>]) {
1920 if draws.is_empty() {
1921 return;
1922 }
1923 stats.retained_draw_batches += 1;
1924 stats.retained_draws += draws.len() as u64;
1925 for draw in draws {
1926 match draw {
1927 RetainedDraw::Vector(resource) if resource.is_drawable() => {
1928 stats.retained_vector_draws += 1
1929 }
1930 RetainedDraw::Vector(_) => {}
1931 RetainedDraw::Image { .. } => stats.retained_image_draws += 1,
1932 }
1933 }
1934}
1935
1936fn build_local_transform_table(ops: &[PendingVectorOp]) -> Vec<GpuTransform> {
1937 let mut transforms = vec![GpuTransform::default()];
1938 for op in ops {
1939 let transform = GpuTransform {
1940 transform: op.transform.to_arrays(),
1941 opacity: op.opacity,
1942 ..GpuTransform::default()
1943 };
1944 if transform.transform == GpuTransform::default().transform
1945 && (transform.opacity - GpuTransform::default().opacity).abs() <= f32::EPSILON
1946 {
1947 continue;
1948 }
1949 if transforms.iter().any(|existing| {
1950 existing.transform == transform.transform
1951 && (existing.opacity - transform.opacity).abs() <= f32::EPSILON
1952 }) {
1953 continue;
1954 }
1955 transforms.push(transform);
1956 }
1957 transforms
1958}
1959
1960fn rebuild_vector_buffers(
1961 tolerance: f32,
1962 ops: &[PendingVectorOp],
1963 buffers: &mut CpuBuffers,
1964 reuse_geometry: bool,
1965 reuse_fill: bool,
1966 geometry_cache: &SharedVectorGeometryCache,
1967 stats: &mut ResourceChurnStats,
1968) {
1969 stats.vector_buffer_rebuilds += 1;
1970 if !reuse_geometry {
1971 stats.vector_geometry_rebuilds += 1;
1972 buffers.geometry.vertices.clear();
1973 buffers.geometry.indices.clear();
1974 }
1975 buffers.primitives.clear();
1976 if !reuse_fill {
1977 buffers.colors.clear();
1978 buffers.gradients.clear();
1979 buffers.materials.clear();
1980 }
1981 buffers.transforms.truncate(1);
1982
1983 let mut next_color_id: u16 = 0;
1984 let mut next_gradient_id: u16 = 0;
1985 let mut next_material_id: u32 = 0;
1986
1987 for op in ops {
1988 let transform_id = push_transform_with_opacity(buffers, op.transform, op.opacity);
1989 let prim_id = if reuse_fill {
1990 push_primitive_with_existing_fill(
1991 buffers,
1992 &op.fill,
1993 transform_id,
1994 &mut next_color_id,
1995 &mut next_gradient_id,
1996 &mut next_material_id,
1997 op.draw_range,
1998 op.light_mask,
1999 )
2000 } else {
2001 push_primitive_def(
2002 buffers,
2003 op.fill.clone(),
2004 op.material,
2005 transform_id,
2006 op.draw_range,
2007 op.light_mask,
2008 )
2009 };
2010
2011 if reuse_geometry {
2012 continue;
2013 }
2014
2015 let cache_key = VectorGeometryCacheKey {
2016 geometry_signature: op.geometry_signature,
2017 tolerance_bits: tolerance.to_bits(),
2018 };
2019 let mut cache_hit = false;
2020 {
2021 let mut cache = geometry_cache.borrow_mut();
2022 if let Some(geometry) = cache.get(&cache_key) {
2023 stats.vector_geometry_cache_hits += 1;
2024 stats.cached_vertices_reused += geometry.vertices.len() as u64;
2025 stats.cached_indices_reused += geometry.indices.len() as u64;
2026 append_cached_geometry(
2027 buffers,
2028 &geometry.vertices,
2029 &geometry.indices,
2030 prim_id,
2031 op.geometry_signature,
2032 );
2033 cache_hit = true;
2034 }
2035 }
2036 if cache_hit {
2037 continue;
2038 }
2039
2040 stats.vector_geometry_cache_misses += 1;
2041 let geometry = tessellate_vector_geometry(tolerance, op);
2042 stats.tessellated_vertices += geometry.vertices.len() as u64;
2043 stats.tessellated_indices += geometry.indices.len() as u64;
2044 append_cached_geometry(
2045 buffers,
2046 &geometry.vertices,
2047 &geometry.indices,
2048 prim_id,
2049 op.geometry_signature,
2050 );
2051 let insert_stats =
2052 geometry_cache
2053 .borrow_mut()
2054 .insert(cache_key, geometry.vertices, geometry.indices);
2055 stats.vector_geometry_cache_evictions += insert_stats.evicted_entries;
2056 stats.vector_geometry_cache_bytes = insert_stats.current_bytes as u64;
2057 }
2058}
2059
2060#[derive(Default)]
2061struct StrokeProgressMap {
2062 endpoint_corrections: Vec<f32>,
2063}
2064
2065impl StrokeProgressMap {
2066 fn correction_for_source(&self, source: VertexSource) -> f32 {
2067 let endpoint = match source {
2068 VertexSource::Endpoint { id } => id,
2069 VertexSource::Edge { from, .. } => from,
2070 };
2071 self.correction_for_endpoint(endpoint)
2072 }
2073
2074 fn correction_for_endpoint(&self, endpoint: EndpointId) -> f32 {
2075 self.endpoint_corrections
2076 .get(endpoint.0 as usize)
2077 .copied()
2078 .unwrap_or(0.0)
2079 }
2080}
2081
2082fn stroke_progress_map(path: &Path, tolerance: f32) -> StrokeProgressMap {
2083 let mut endpoint_corrections = Vec::new();
2084 let mut global_cursor = 0.0f32;
2085 let mut lyon_open_cursor = 0.0f32;
2086 let mut contour_correction = 0.0f32;
2087 let mut contour_length = 0.0f32;
2088
2089 for event in path.iter() {
2090 match event {
2091 PathEvent::Begin { at: _ } => {
2092 contour_correction = global_cursor - lyon_open_cursor;
2093 contour_length = 0.0;
2094 endpoint_corrections.push(contour_correction);
2095 }
2096 PathEvent::Line { from, to } => {
2097 contour_length += line_length(from, to);
2098 endpoint_corrections.push(contour_correction);
2099 }
2100 PathEvent::Quadratic { from, ctrl, to } => {
2101 contour_length += QuadraticBezierSegment { from, ctrl, to }
2102 .approximate_length(tolerance)
2103 .max(0.0);
2104 endpoint_corrections.push(contour_correction);
2105 }
2106 PathEvent::Cubic {
2107 from,
2108 ctrl1,
2109 ctrl2,
2110 to,
2111 } => {
2112 contour_length += CubicBezierSegment {
2113 from,
2114 ctrl1,
2115 ctrl2,
2116 to,
2117 }
2118 .approximate_length(tolerance)
2119 .max(0.0);
2120 endpoint_corrections.push(contour_correction);
2121 }
2122 PathEvent::End { first, last, close } => {
2123 let closed = close;
2124 if closed {
2125 contour_length += line_length(last, first);
2126 }
2127 global_cursor += contour_length;
2128 if !closed {
2129 lyon_open_cursor += contour_length;
2130 }
2131 }
2132 }
2133 }
2134
2135 StrokeProgressMap {
2136 endpoint_corrections,
2137 }
2138}
2139
2140#[derive(Clone, Copy)]
2141struct SmoothingParams {
2142 angle_cut_degrees: f32,
2143 passes: usize,
2144}
2145
2146fn smooth_lyon_path(path: &Path, smoothing: PathSmoothing) -> Path {
2147 let Some(params) = smoothing_params(smoothing) else {
2148 return path.clone();
2149 };
2150
2151 let mut builder = Path::builder();
2152 let mut run = Vec::new();
2153
2154 for event in path.iter() {
2155 match event {
2156 PathEvent::Begin { at } => {
2157 flush_smoothing_run(&mut builder, &mut run, params);
2158 builder.begin(at);
2159 run.clear();
2160 run.push(at);
2161 }
2162 PathEvent::Line { to, .. } => {
2163 if run.is_empty() {
2164 builder.line_to(to);
2165 } else {
2166 push_distinct_smoothing_point(&mut run, to);
2167 }
2168 }
2169 PathEvent::Quadratic { ctrl, to, .. } => {
2170 flush_smoothing_run(&mut builder, &mut run, params);
2171 builder.quadratic_bezier_to(ctrl, to);
2172 run.clear();
2173 run.push(to);
2174 }
2175 PathEvent::Cubic {
2176 ctrl1, ctrl2, to, ..
2177 } => {
2178 flush_smoothing_run(&mut builder, &mut run, params);
2179 builder.cubic_bezier_to(ctrl1, ctrl2, to);
2180 run.clear();
2181 run.push(to);
2182 }
2183 PathEvent::End { first, last, close } => {
2184 if close && smoothing_distance(last, first) > f32::EPSILON {
2185 push_distinct_smoothing_point(&mut run, first);
2186 }
2187 flush_smoothing_run(&mut builder, &mut run, params);
2188 builder.end(close);
2189 run.clear();
2190 }
2191 }
2192 }
2193
2194 flush_smoothing_run(&mut builder, &mut run, params);
2195 builder.build()
2196}
2197
2198fn smoothing_params(smoothing: PathSmoothing) -> Option<SmoothingParams> {
2199 match smoothing {
2200 PathSmoothing::None => None,
2201 PathSmoothing::Light => Some(SmoothingParams {
2202 angle_cut_degrees: 130.0,
2203 passes: 1,
2204 }),
2205 PathSmoothing::Strong => Some(SmoothingParams {
2206 angle_cut_degrees: 95.0,
2207 passes: 2,
2208 }),
2209 }
2210}
2211
2212fn flush_smoothing_run(
2213 builder: &mut lyon::path::path::Builder,
2214 run: &mut Vec<LyonPoint>,
2215 params: SmoothingParams,
2216) {
2217 let clean = clean_smoothing_points(run);
2218 if clean.len() < 2 {
2219 run.clear();
2220 return;
2221 }
2222
2223 let mut cuts = vec![0usize];
2224 for index in 1..clean.len() - 1 {
2225 if smoothing_angle_degrees(clean[index - 1], clean[index], clean[index + 1])
2226 <= params.angle_cut_degrees
2227 {
2228 cuts.push(index);
2229 }
2230 }
2231 cuts.push(clean.len() - 1);
2232
2233 for pair in cuts.windows(2) {
2234 let start = pair[0];
2235 let end = pair[1];
2236 if end <= start {
2237 continue;
2238 }
2239 let subrun = smooth_smoothing_points(&clean[start..=end], params.passes);
2240 emit_smoothing_subrun(builder, &subrun);
2241 }
2242
2243 run.clear();
2244}
2245
2246fn emit_smoothing_subrun(builder: &mut lyon::path::path::Builder, points: &[LyonPoint]) {
2247 if points.len() < 2 {
2248 return;
2249 }
2250 if points.len() == 2 {
2251 builder.line_to(points[1]);
2252 return;
2253 }
2254
2255 for index in 0..points.len() - 1 {
2256 let p0 = if index == 0 {
2257 points[index]
2258 } else {
2259 points[index - 1]
2260 };
2261 let p1 = points[index];
2262 let p2 = points[index + 1];
2263 let p3 = if index + 2 < points.len() {
2264 points[index + 2]
2265 } else {
2266 points[index + 1]
2267 };
2268
2269 let control_1 = p1 + (p2 - p0) * (1.0 / 6.0);
2270 let control_2 = p2 - (p3 - p1) * (1.0 / 6.0);
2271 builder.cubic_bezier_to(control_1, control_2, p2);
2272 }
2273}
2274
2275fn smooth_smoothing_points(points: &[LyonPoint], passes: usize) -> Vec<LyonPoint> {
2276 if points.len() < 4 || passes == 0 {
2277 return points.to_vec();
2278 }
2279
2280 let mut output = points.to_vec();
2281 for _ in 0..passes {
2282 let mut next = Vec::with_capacity(output.len());
2283 next.push(output[0]);
2284 for index in 1..output.len() - 1 {
2285 next.push(LyonPoint::new(
2286 output[index - 1].x * 0.25 + output[index].x * 0.5 + output[index + 1].x * 0.25,
2287 output[index - 1].y * 0.25 + output[index].y * 0.5 + output[index + 1].y * 0.25,
2288 ));
2289 }
2290 next.push(output[output.len() - 1]);
2291 output = next;
2292 }
2293 output
2294}
2295
2296fn clean_smoothing_points(points: &[LyonPoint]) -> Vec<LyonPoint> {
2297 let mut clean = Vec::with_capacity(points.len());
2298 for point in points {
2299 push_distinct_smoothing_point(&mut clean, *point);
2300 }
2301 clean
2302}
2303
2304fn push_distinct_smoothing_point(points: &mut Vec<LyonPoint>, point: LyonPoint) {
2305 if points
2306 .last()
2307 .map(|last| smoothing_distance(*last, point) > f32::EPSILON)
2308 .unwrap_or(true)
2309 {
2310 points.push(point);
2311 }
2312}
2313
2314fn smoothing_angle_degrees(previous: LyonPoint, current: LyonPoint, next: LyonPoint) -> f32 {
2315 let vector_1 = previous - current;
2316 let vector_2 = next - current;
2317 let length_1 = vector_1.length();
2318 let length_2 = vector_2.length();
2319 if length_1 < f32::EPSILON || length_2 < f32::EPSILON {
2320 return 180.0;
2321 }
2322 let dot = ((vector_1.x * vector_2.x) + (vector_1.y * vector_2.y)) / (length_1 * length_2);
2323 dot.clamp(-1.0, 1.0).acos().to_degrees()
2324}
2325
2326fn smoothing_distance(a: LyonPoint, b: LyonPoint) -> f32 {
2327 (a - b).length()
2328}
2329
2330fn line_length(from: Point2D, to: Point2D) -> f32 {
2331 (to - from).length().max(0.0)
2332}
2333
2334fn tessellate_vector_geometry(
2335 tolerance: f32,
2336 op: &PendingVectorOp,
2337) -> VertexBuffers<GpuVertex, u16> {
2338 let mut geometry = VertexBuffers::new();
2339 let smoothed_path;
2340 let path = if op.smoothing == PathSmoothing::None {
2341 &op.path
2342 } else {
2343 smoothed_path = smooth_lyon_path(&op.path, op.smoothing);
2344 &smoothed_path
2345 };
2346 match op.kind {
2347 PendingVectorOpKind::Fill => {
2348 let options = FillOptions::tolerance(tolerance);
2349 let mut geometry_builder =
2350 BuffersBuilder::new(&mut geometry, |vertex: FillVertex| GpuVertex {
2351 position: vertex.position().to_array(),
2352 normal: [0.0; 2],
2353 prim_id: 0,
2354 path_progress: 0.0,
2355 });
2356 if let Err(err) =
2357 FillTessellator::new().tessellate_path(path, &options, &mut geometry_builder)
2358 {
2359 log::warn!("{:?}", err);
2360 }
2361 }
2362 PendingVectorOpKind::Stroke(stroke_width, stroke_cap, stroke_join) => {
2363 let options = StrokeOptions::tolerance(tolerance)
2364 .with_line_width(stroke_width)
2365 .with_line_cap(match stroke_cap {
2366 StrokeCap::Butt => lyon::tessellation::LineCap::Butt,
2367 StrokeCap::Round => lyon::tessellation::LineCap::Round,
2368 StrokeCap::Square => lyon::tessellation::LineCap::Square,
2369 })
2370 .with_line_join(match stroke_join {
2371 StrokeJoin::Miter => lyon::tessellation::LineJoin::Miter,
2372 StrokeJoin::Round => lyon::tessellation::LineJoin::Round,
2373 StrokeJoin::Bevel => lyon::tessellation::LineJoin::Bevel,
2374 });
2375 let progress_map = stroke_progress_map(path, tolerance);
2376 let max_advancement = std::cell::Cell::new(0.0f32);
2377 {
2378 let mut geometry_builder =
2379 BuffersBuilder::new(&mut geometry, |vertex: StrokeVertex| {
2380 let advancement = vertex.advancement()
2381 + progress_map.correction_for_source(vertex.source());
2382 let path_progress = if advancement.is_finite() {
2383 max_advancement.set(max_advancement.get().max(advancement));
2384 advancement.max(0.0)
2385 } else {
2386 0.0
2387 };
2388 GpuVertex {
2389 position: vertex.position().to_array(),
2390 normal: vertex.normal().to_array(),
2391 prim_id: 0,
2392 path_progress,
2393 }
2394 });
2395 if let Err(err) =
2396 StrokeTessellator::new().tessellate_path(path, &options, &mut geometry_builder)
2397 {
2398 log::warn!("{:?}", err);
2399 }
2400 }
2401 let total_advancement = max_advancement.get();
2402 if total_advancement > f32::EPSILON {
2403 for vertex in &mut geometry.vertices {
2404 vertex.path_progress =
2405 (vertex.path_progress / total_advancement).clamp(0.0, 1.0);
2406 }
2407 }
2408 }
2409 }
2410 geometry
2411}
2412
2413fn append_cached_geometry(
2414 buffers: &mut CpuBuffers,
2415 vertices: &[GpuVertex],
2416 indices: &[u16],
2417 prim_id: u32,
2418 geometry_signature: u64,
2419) {
2420 let vertex_offset = buffers.geometry.vertices.len();
2421 let Some(vertex_offset) = u16::try_from(vertex_offset).ok() else {
2422 log::warn!(
2423 "vector geometry cache append exceeded u16 vertex offset for signature {}",
2424 geometry_signature
2425 );
2426 return;
2427 };
2428 if indices
2429 .iter()
2430 .any(|index| (*index as usize) + vertex_offset as usize > u16::MAX as usize)
2431 {
2432 log::warn!(
2433 "vector geometry cache append exceeded u16 index capacity for signature {}",
2434 geometry_signature
2435 );
2436 return;
2437 }
2438
2439 buffers
2440 .geometry
2441 .vertices
2442 .extend(vertices.iter().map(|vertex| {
2443 let mut vertex = *vertex;
2444 vertex.prim_id = prim_id;
2445 vertex
2446 }));
2447 buffers
2448 .geometry
2449 .indices
2450 .extend(indices.iter().map(|index| *index + vertex_offset));
2451}
2452
2453fn vector_geometry_bytes(vertices: usize, indices: usize) -> usize {
2454 vertices * std::mem::size_of::<GpuVertex>() + indices * std::mem::size_of::<u16>()
2455}
2456
2457fn append_cpu_buffers(dst: &mut CpuBuffers, src: &CpuBuffers) {
2458 let vertex_offset = dst.geometry.vertices.len() as u16;
2459 let primitive_offset = dst.primitives.len() as u32;
2460 let src_uses_only_identity_transform = src.transforms.len() == 1
2461 && src.transforms[0].transform == GpuTransform::default().transform
2462 && (src.transforms[0].opacity - GpuTransform::default().opacity).abs() <= f32::EPSILON;
2463 let transform_offset = if src_uses_only_identity_transform {
2464 0
2465 } else {
2466 dst.transforms.len() as u32 - 1
2467 };
2468 let color_offset = dst.colors.len() as u16;
2469 let gradient_offset = dst.gradients.len() as u16;
2470 let material_offset = dst.materials.len() as u32;
2471
2472 dst.geometry
2473 .vertices
2474 .extend(src.geometry.vertices.iter().map(|vertex| {
2475 let mut vertex = *vertex;
2476 vertex.prim_id += primitive_offset;
2477 vertex
2478 }));
2479 dst.geometry.indices.extend(
2480 src.geometry
2481 .indices
2482 .iter()
2483 .map(|index| index.saturating_add(vertex_offset)),
2484 );
2485 dst.primitives
2486 .extend(src.primitives.iter().map(|primitive| {
2487 let mut primitive = *primitive;
2488 if primitive.transform_id != 0 {
2489 primitive.transform_id += transform_offset;
2490 }
2491 if primitive.fill_type_flag == 0 {
2492 primitive.fill_id = primitive.fill_id.saturating_add(color_offset);
2493 } else {
2494 primitive.fill_id = primitive.fill_id.saturating_add(gradient_offset);
2495 }
2496 primitive.material_id = primitive.material_id.saturating_add(material_offset);
2497 primitive
2498 }));
2499 if !src_uses_only_identity_transform {
2500 dst.transforms
2501 .extend(src.transforms.iter().skip(1).copied());
2502 }
2503 dst.colors.extend(src.colors.iter().copied());
2504 dst.gradients.extend(src.gradients.iter().copied());
2505 dst.materials.extend(src.materials.iter().copied());
2506}
2507
2508fn push_transform_with_opacity(
2509 buffers: &mut CpuBuffers,
2510 transform: Transform2D,
2511 opacity: f32,
2512) -> u32 {
2513 if transform == Transform2D::identity() && (opacity - 1.0).abs() <= f32::EPSILON {
2514 0
2515 } else {
2516 let transform_arrays = transform.to_arrays();
2517 if let Some(transform_id) = buffers.transforms.iter().position(|existing| {
2518 existing.transform == transform_arrays
2519 && (existing.opacity - opacity).abs() <= f32::EPSILON
2520 }) {
2521 return transform_id as u32;
2522 }
2523
2524 let transform_id = buffers.transforms.len() as u32;
2525 buffers.transforms.push(GpuTransform {
2526 transform: transform_arrays,
2527 opacity,
2528 ..GpuTransform::default()
2529 });
2530 transform_id
2531 }
2532}
2533
2534fn would_exceed_batch_capacity(dst: &CpuBuffers, src: &CpuBuffers) -> bool {
2535 let extra_transforms = src.transforms.len().saturating_sub(1);
2536
2537 dst.geometry.vertices.len() + src.geometry.vertices.len() > u16::MAX as usize
2538 || dst.primitives.len() + src.primitives.len() > MAX_BATCH_PRIMITIVES
2539 || dst.colors.len() + src.colors.len() > MAX_BATCH_COLORS
2540 || dst.gradients.len() + src.gradients.len() > MAX_BATCH_GRADIENTS
2541 || dst.materials.len() + src.materials.len() > MAX_BATCH_MATERIALS
2542 || dst.transforms.len() + extra_transforms > MAX_BATCH_TRANSFORMS
2543}
2544
2545fn hash_vector_fills(ops: &[PendingVectorOp]) -> u64 {
2546 let mut hasher = DefaultHasher::new();
2547 ops.len().hash(&mut hasher);
2548 for op in ops {
2549 hash_fill_bits(&op.fill, &mut hasher);
2550 hash_material_bits(&op.material, &mut hasher);
2551 }
2552 hasher.finish()
2553}
2554
2555fn hash_vector_primitives(ops: &[PendingVectorOp]) -> u64 {
2556 let mut hasher = DefaultHasher::new();
2557 ops.len().hash(&mut hasher);
2558 for op in ops {
2559 op.light_mask.hash(&mut hasher);
2560 for value in op.draw_range.as_gpu_range() {
2561 value.to_bits().hash(&mut hasher);
2562 }
2563 }
2564 hasher.finish()
2565}
2566
2567fn hash_vector_transforms(ops: &[PendingVectorOp]) -> u64 {
2568 let mut hasher = DefaultHasher::new();
2569 ops.len().hash(&mut hasher);
2570 for op in ops {
2571 hash_transform_bits(&op.transform, op.opacity, &mut hasher);
2572 }
2573 hasher.finish()
2574}
2575
2576fn hash_transform_ids(transform_ids: &[u32]) -> u64 {
2577 let mut hasher = DefaultHasher::new();
2578 transform_ids.len().hash(&mut hasher);
2579 for transform_id in transform_ids {
2580 transform_id.hash(&mut hasher);
2581 }
2582 hasher.finish()
2583}
2584
2585fn compute_vector_node_bounds(ops: &[PendingVectorOp]) -> Box2D {
2586 let mut bounds: Option<Box2D> = None;
2587 for op in ops {
2588 let Some(path_bounds) = path_control_bounds(&op.path) else {
2589 continue;
2590 };
2591 let mut op_bounds = transform_box(path_bounds, &op.transform);
2592 if let PendingVectorOpKind::Stroke(width, _, _) = op.kind {
2593 op_bounds = expand_box(op_bounds, width * 0.5);
2594 }
2595 bounds = Some(match bounds {
2596 Some(existing) => union_boxes(existing, op_bounds),
2597 None => op_bounds,
2598 });
2599 }
2600 bounds.unwrap_or_else(empty_box)
2601}
2602
2603fn path_control_bounds(path: &Path) -> Option<Box2D> {
2604 let mut min_x = f32::INFINITY;
2605 let mut min_y = f32::INFINITY;
2606 let mut max_x = f32::NEG_INFINITY;
2607 let mut max_y = f32::NEG_INFINITY;
2608
2609 let mut visit = |point: Point2D| {
2610 min_x = min_x.min(point.x);
2611 min_y = min_y.min(point.y);
2612 max_x = max_x.max(point.x);
2613 max_y = max_y.max(point.y);
2614 };
2615
2616 for event in path.iter() {
2617 match event {
2618 PathEvent::Begin { at } => visit(at),
2619 PathEvent::Line { from, to } => {
2620 visit(from);
2621 visit(to);
2622 }
2623 PathEvent::Quadratic { from, ctrl, to } => {
2624 visit(from);
2625 visit(ctrl);
2626 visit(to);
2627 }
2628 PathEvent::Cubic {
2629 from,
2630 ctrl1,
2631 ctrl2,
2632 to,
2633 } => {
2634 visit(from);
2635 visit(ctrl1);
2636 visit(ctrl2);
2637 visit(to);
2638 }
2639 PathEvent::End { first, last, .. } => {
2640 visit(first);
2641 visit(last);
2642 }
2643 }
2644 }
2645
2646 if min_x.is_finite() && min_y.is_finite() && max_x.is_finite() && max_y.is_finite() {
2647 Some(Box2D {
2648 min: point(min_x, min_y),
2649 max: point(max_x, max_y),
2650 })
2651 } else {
2652 None
2653 }
2654}
2655
2656fn transform_box(bounds: Box2D, transform: &Transform2D) -> Box2D {
2657 let corners = [
2658 bounds.min,
2659 point(bounds.max.x, bounds.min.y),
2660 point(bounds.min.x, bounds.max.y),
2661 bounds.max,
2662 ];
2663 let mut min_x = f32::INFINITY;
2664 let mut min_y = f32::INFINITY;
2665 let mut max_x = f32::NEG_INFINITY;
2666 let mut max_y = f32::NEG_INFINITY;
2667 for corner in corners {
2668 let transformed = transform.transform_point(corner);
2669 min_x = min_x.min(transformed.x);
2670 min_y = min_y.min(transformed.y);
2671 max_x = max_x.max(transformed.x);
2672 max_y = max_y.max(transformed.y);
2673 }
2674 Box2D {
2675 min: point(min_x, min_y),
2676 max: point(max_x, max_y),
2677 }
2678}
2679
2680fn expand_box(bounds: Box2D, inset: f32) -> Box2D {
2681 Box2D {
2682 min: point(bounds.min.x - inset, bounds.min.y - inset),
2683 max: point(bounds.max.x + inset, bounds.max.y + inset),
2684 }
2685}
2686
2687fn union_boxes(left: Box2D, right: Box2D) -> Box2D {
2688 Box2D {
2689 min: point(left.min.x.min(right.min.x), left.min.y.min(right.min.y)),
2690 max: point(left.max.x.max(right.max.x), left.max.y.max(right.max.y)),
2691 }
2692}
2693
2694fn empty_box() -> Box2D {
2695 Box2D {
2696 min: point(0.0, 0.0),
2697 max: point(0.0, 0.0),
2698 }
2699}
2700
2701fn boxes_intersect(left: &Box2D, right: &Box2D) -> bool {
2702 left.min.x <= right.max.x
2703 && left.max.x >= right.min.x
2704 && left.min.y <= right.max.y
2705 && left.max.y >= right.min.y
2706}
2707
2708fn axis_aligned_rect_scissor(path: &Path, transform: &Transform2D) -> Option<ScissorRect> {
2709 const EPSILON: f32 = 0.001;
2710 let mut points = Vec::new();
2711 for event in path.iter() {
2712 match event {
2713 PathEvent::Begin { at } => {
2714 push_unique_point(&mut points, transform.transform_point(at))
2715 }
2716 PathEvent::Line { to, .. } => {
2717 push_unique_point(&mut points, transform.transform_point(to));
2718 }
2719 PathEvent::End { close, .. } => {
2720 if !close {
2721 return None;
2722 }
2723 }
2724 PathEvent::Quadratic { .. } | PathEvent::Cubic { .. } => return None,
2725 }
2726 }
2727
2728 if points.len() > 1 && points_close(points[0], *points.last().unwrap(), EPSILON) {
2729 points.pop();
2730 }
2731 if points.len() != 4 {
2732 return None;
2733 }
2734
2735 let mut xs = Vec::new();
2736 let mut ys = Vec::new();
2737 for point in &points {
2738 push_unique_scalar(&mut xs, point.x, EPSILON);
2739 push_unique_scalar(&mut ys, point.y, EPSILON);
2740 }
2741 if xs.len() != 2 || ys.len() != 2 {
2742 return None;
2743 }
2744 xs.sort_by(|left, right| left.partial_cmp(right).unwrap_or(std::cmp::Ordering::Equal));
2745 ys.sort_by(|left, right| left.partial_cmp(right).unwrap_or(std::cmp::Ordering::Equal));
2746 let rect = ScissorRect {
2747 min_x: xs[0],
2748 min_y: ys[0],
2749 max_x: xs[1],
2750 max_y: ys[1],
2751 };
2752 if rect.max_x - rect.min_x <= EPSILON || rect.max_y - rect.min_y <= EPSILON {
2753 return None;
2754 }
2755 let has_all_corners = [
2756 (rect.min_x, rect.min_y),
2757 (rect.max_x, rect.min_y),
2758 (rect.max_x, rect.max_y),
2759 (rect.min_x, rect.max_y),
2760 ]
2761 .iter()
2762 .all(|(x, y)| {
2763 points
2764 .iter()
2765 .any(|point| (point.x - *x).abs() <= EPSILON && (point.y - *y).abs() <= EPSILON)
2766 });
2767 has_all_corners.then_some(rect)
2768}
2769
2770fn push_unique_point(points: &mut Vec<Point2D>, point: Point2D) {
2771 const EPSILON: f32 = 0.001;
2772 if points
2773 .last()
2774 .is_some_and(|previous| points_close(*previous, point, EPSILON))
2775 {
2776 return;
2777 }
2778 points.push(point);
2779}
2780
2781fn points_close(left: Point2D, right: Point2D, epsilon: f32) -> bool {
2782 (left.x - right.x).abs() <= epsilon && (left.y - right.y).abs() <= epsilon
2783}
2784
2785fn push_unique_scalar(values: &mut Vec<f32>, value: f32, epsilon: f32) {
2786 if !values
2787 .iter()
2788 .any(|existing| (*existing - value).abs() <= epsilon)
2789 {
2790 values.push(value);
2791 }
2792}
2793
2794fn hash_transform_bits<H: Hasher>(transform: &Transform2D, opacity: f32, state: &mut H) {
2795 for row in transform.to_arrays() {
2796 for value in row {
2797 value.to_bits().hash(state);
2798 }
2799 }
2800 opacity.to_bits().hash(state);
2801}
2802
2803fn hash_fill_bits<H: Hasher>(fill: &Fill, state: &mut H) {
2804 match fill {
2805 Fill::Solid(color) => {
2806 0u8.hash(state);
2807 hash_color_bits(color, state);
2808 }
2809 Fill::Gradient {
2810 gradient_type,
2811 pos,
2812 main_axis,
2813 off_axis,
2814 stops,
2815 } => {
2816 1u8.hash(state);
2817 match gradient_type {
2818 GradientType::Linear => 0u8.hash(state),
2819 GradientType::Radial => 1u8.hash(state),
2820 }
2821 pos.x.to_bits().hash(state);
2822 pos.y.to_bits().hash(state);
2823 main_axis.x.to_bits().hash(state);
2824 main_axis.y.to_bits().hash(state);
2825 off_axis.x.to_bits().hash(state);
2826 off_axis.y.to_bits().hash(state);
2827 stops.len().hash(state);
2828 for stop in stops {
2829 hash_color_bits(&stop.color, state);
2830 stop.stop.to_bits().hash(state);
2831 }
2832 }
2833 }
2834}
2835
2836fn hash_material_bits<H: Hasher>(material: &Material, state: &mut H) {
2837 material.unlit.hash(state);
2838 for value in material.coefficients {
2839 value.to_bits().hash(state);
2840 }
2841 for value in material.emissive {
2842 value.to_bits().hash(state);
2843 }
2844}
2845
2846fn hash_color_bits<H: Hasher>(color: &Color, state: &mut H) {
2847 for value in color.rgba {
2848 value.to_bits().hash(state);
2849 }
2850}
2851
2852fn to_gpu_material(material: Material) -> GpuMaterial {
2853 if material.unlit {
2854 return GpuMaterial {
2855 coefficients: [-1.0, 0.0, 0.0, 0.0],
2856 emissive: [0.0; 4],
2857 };
2858 }
2859
2860 GpuMaterial {
2861 coefficients: material.coefficients,
2862 emissive: material.emissive,
2863 }
2864}
2865
2866fn to_gpu_scene_lighting(lighting: &SceneLighting) -> GpuSceneLighting {
2867 let mut out = GpuSceneLighting {
2868 ambient: [
2869 lighting.ambient_color.rgba[0],
2870 lighting.ambient_color.rgba[1],
2871 lighting.ambient_color.rgba[2],
2872 lighting.ambient_intensity.max(0.0),
2873 ],
2874 meta: [
2875 lighting.active as u32,
2876 0,
2877 lighting.ambient_is_authored as u32,
2878 0,
2879 ],
2880 ..GpuSceneLighting::default()
2881 };
2882
2883 let count = lighting.lights.len().min(MAX_SCENE_LIGHTS);
2884 out.meta[1] = count as u32;
2885 for (index, light) in lighting.lights.iter().take(count).enumerate() {
2886 out.lights[index] = GpuSceneLight {
2887 position: [light.position[0], light.position[1], light.position[2], 0.0],
2888 direction: [
2889 light.direction[0],
2890 light.direction[1],
2891 light.direction[2],
2892 0.0,
2893 ],
2894 color: [
2895 light.color.rgba[0],
2896 light.color.rgba[1],
2897 light.color.rgba[2],
2898 light.intensity.max(0.0),
2899 ],
2900 params: [
2901 match light.shape {
2902 LightShape::Point => 0.0,
2903 LightShape::Directional => 1.0,
2904 },
2905 light.radius.max(0.0),
2906 0.0,
2907 0.0,
2908 ],
2909 };
2910 }
2911
2912 out
2913}
2914
2915fn push_primitive_def(
2916 buffers: &mut CpuBuffers,
2917 fill: Fill,
2918 material: Material,
2919 transform_id: u32,
2920 draw_range: DrawRange,
2921 light_mask: u32,
2922) -> u32 {
2923 let fill_id;
2924 let fill_type_flag;
2925 match fill {
2926 Fill::Solid(color) => {
2927 fill_id = buffers.colors.len() as u16;
2928 fill_type_flag = 0;
2929 buffers.colors.push(GpuColor { color: color.rgba });
2930 }
2931 Fill::Gradient {
2932 gradient_type,
2933 pos,
2934 main_axis,
2935 off_axis,
2936 stops,
2937 } => {
2938 fill_id = buffers.gradients.len() as u16;
2939 fill_type_flag = 1;
2940 if stops.len() > 8 {
2941 log::warn!("can't draw graidents with more than 8 stops. truncating.");
2942 }
2943 let len = stops.len().min(8);
2944 let mut colors_buff = [[0.0; 4]; 8];
2945 let mut stops_buff = [0.0; 8];
2946 for i in 0..len {
2947 colors_buff[i] = stops[i].color.rgba;
2948 stops_buff[i] = stops[i].stop;
2949 }
2950 buffers.gradients.push(GpuGradient {
2951 type_id: match gradient_type {
2952 GradientType::Linear => 0,
2953 GradientType::Radial => 1,
2954 },
2955 position: pos.to_array(),
2956 main_axis: main_axis.to_array(),
2957 off_axis: off_axis.to_array(),
2958 stop_count: len as u32,
2959 colors: colors_buff,
2960 stops: stops_buff,
2961 _padding: [0; 16],
2962 });
2963 }
2964 }
2965 let material_id = buffers.materials.len() as u32;
2966 buffers.materials.push(to_gpu_material(material));
2967 let primitive = GpuPrimitive {
2968 fill_id,
2969 fill_type_flag,
2970 material_id,
2971 transform_id,
2972 z_index: 0,
2973 draw_range: draw_range.as_gpu_range(),
2974 light_mask,
2975 _padding: [0; 3],
2976 };
2977 let prim_id = buffers.primitives.len() as u32;
2978 buffers.primitives.push(primitive);
2979 prim_id
2980}
2981
2982fn push_primitive_with_existing_fill(
2983 buffers: &mut CpuBuffers,
2984 fill: &Fill,
2985 transform_id: u32,
2986 next_color_id: &mut u16,
2987 next_gradient_id: &mut u16,
2988 next_material_id: &mut u32,
2989 draw_range: DrawRange,
2990 light_mask: u32,
2991) -> u32 {
2992 let (fill_id, fill_type_flag) = match fill {
2993 Fill::Solid(_) => {
2994 let fill_id = *next_color_id;
2995 *next_color_id = next_color_id.saturating_add(1);
2996 (fill_id, 0)
2997 }
2998 Fill::Gradient { .. } => {
2999 let fill_id = *next_gradient_id;
3000 *next_gradient_id = next_gradient_id.saturating_add(1);
3001 (fill_id, 1)
3002 }
3003 };
3004 let material_id = *next_material_id;
3005 *next_material_id = next_material_id.saturating_add(1);
3006 let prim_id = buffers.primitives.len() as u32;
3007 buffers.primitives.push(GpuPrimitive {
3008 fill_id,
3009 fill_type_flag,
3010 material_id,
3011 transform_id,
3012 z_index: 0,
3013 draw_range: draw_range.as_gpu_range(),
3014 light_mask,
3015 _padding: [0; 3],
3016 });
3017 prim_id
3018}
3019
3020fn hash_vector_path(path: &Path, kind: PendingVectorOpKind, smoothing: PathSmoothing) -> u64 {
3021 let mut hasher = std::collections::hash_map::DefaultHasher::new();
3022 smoothing.hash(&mut hasher);
3023 match kind {
3024 PendingVectorOpKind::Fill => 0u8.hash(&mut hasher),
3025 PendingVectorOpKind::Stroke(width, cap, join) => {
3026 1u8.hash(&mut hasher);
3027 width.to_bits().hash(&mut hasher);
3028 cap.hash(&mut hasher);
3029 join.hash(&mut hasher);
3030 }
3031 }
3032 for event in path.iter() {
3033 match event {
3034 PathEvent::Begin { at } => {
3035 0u8.hash(&mut hasher);
3036 at.x.to_bits().hash(&mut hasher);
3037 at.y.to_bits().hash(&mut hasher);
3038 }
3039 PathEvent::Line { from, to } => {
3040 1u8.hash(&mut hasher);
3041 from.x.to_bits().hash(&mut hasher);
3042 from.y.to_bits().hash(&mut hasher);
3043 to.x.to_bits().hash(&mut hasher);
3044 to.y.to_bits().hash(&mut hasher);
3045 }
3046 PathEvent::Quadratic { from, ctrl, to } => {
3047 2u8.hash(&mut hasher);
3048 from.x.to_bits().hash(&mut hasher);
3049 from.y.to_bits().hash(&mut hasher);
3050 ctrl.x.to_bits().hash(&mut hasher);
3051 ctrl.y.to_bits().hash(&mut hasher);
3052 to.x.to_bits().hash(&mut hasher);
3053 to.y.to_bits().hash(&mut hasher);
3054 }
3055 PathEvent::Cubic {
3056 from,
3057 ctrl1,
3058 ctrl2,
3059 to,
3060 } => {
3061 3u8.hash(&mut hasher);
3062 from.x.to_bits().hash(&mut hasher);
3063 from.y.to_bits().hash(&mut hasher);
3064 ctrl1.x.to_bits().hash(&mut hasher);
3065 ctrl1.y.to_bits().hash(&mut hasher);
3066 ctrl2.x.to_bits().hash(&mut hasher);
3067 ctrl2.y.to_bits().hash(&mut hasher);
3068 to.x.to_bits().hash(&mut hasher);
3069 to.y.to_bits().hash(&mut hasher);
3070 }
3071 PathEvent::End { last, first, close } => {
3072 4u8.hash(&mut hasher);
3073 last.x.to_bits().hash(&mut hasher);
3074 last.y.to_bits().hash(&mut hasher);
3075 first.x.to_bits().hash(&mut hasher);
3076 first.y.to_bits().hash(&mut hasher);
3077 close.hash(&mut hasher);
3078 }
3079 }
3080 }
3081 hasher.finish()
3082}
3083
3084fn hash_clip_geometry(geometry: &VertexBuffers<stencil::Vertex, u16>) -> u64 {
3085 let mut hasher = std::collections::hash_map::DefaultHasher::new();
3086 geometry.indices.hash(&mut hasher);
3087 for vertex in &geometry.vertices {
3088 vertex.position[0].to_bits().hash(&mut hasher);
3089 vertex.position[1].to_bits().hash(&mut hasher);
3090 }
3091 hasher.finish()
3092}
3093
3094fn tessellate_clip_geometry(path: &Path, tolerance: f32) -> VertexBuffers<stencil::Vertex, u16> {
3095 let options = FillOptions::tolerance(tolerance).with_fill_rule(FillRule::NonZero);
3099 let mut geometry = VertexBuffers::new();
3100 let mut geometry_builder =
3101 BuffersBuilder::new(&mut geometry, |vertex: FillVertex| stencil::Vertex {
3102 position: vertex.position().to_array(),
3103 });
3104 if let Err(err) = FillTessellator::new().tessellate_path(path, &options, &mut geometry_builder)
3105 {
3106 log::warn!("{:?}", err);
3107 }
3108 geometry
3109}
3110
3111fn clip_stack_sync<'a>(
3112 current_clip_stack: &[u32],
3113 desired_clip_stack: &[ClipReference],
3114 clip_arena: &'a ClipArena,
3115) -> (usize, Vec<stencil::ClipDraw<'a>>) {
3116 let desired_stencil_clip_ids: Vec<u32> = desired_clip_stack
3117 .iter()
3118 .filter_map(|clip| match clip {
3119 ClipReference::Stencil { clip_id } => Some(*clip_id),
3120 ClipReference::Scissor(_) | ClipReference::Alpha { .. } => None,
3121 })
3122 .collect();
3123 let shared_prefix = current_clip_stack
3124 .iter()
3125 .zip(desired_stencil_clip_ids.iter())
3126 .take_while(|(left, right)| left == right)
3127 .count();
3128 let mut clip_draws = Vec::new();
3129 for clip_id in desired_stencil_clip_ids.iter().skip(shared_prefix) {
3130 let Some(entry) = clip_arena.get(*clip_id) else {
3131 log::error!("missing clip arena entry for clip {}", clip_id);
3132 continue;
3133 };
3134 clip_draws.push(stencil::ClipDraw {
3135 clip_id: *clip_id,
3136 geometry_signature: entry.geometry_signature,
3137 geometry: &entry.geometry,
3138 });
3139 }
3140 (shared_prefix, clip_draws)
3141}
3142
3143fn update_current_clip_stack(
3144 current_clip_stack: &mut Vec<u32>,
3145 shared_prefix: usize,
3146 clip_draws: &[stencil::ClipDraw<'_>],
3147) {
3148 current_clip_stack.truncate(shared_prefix);
3149 current_clip_stack.extend(clip_draws.iter().map(|clip| clip.clip_id));
3150}
3151
3152fn push_primitive_segment<'a>(
3153 segments: &mut Vec<PrimitiveBatchSegment<'a>>,
3154 current_clip_stack: &mut Vec<u32>,
3155 desired_clip_stack: &[ClipReference],
3156 clip_arena: &'a ClipArena,
3157 index_start: usize,
3158 index_end: usize,
3159) {
3160 if index_end <= index_start {
3161 return;
3162 }
3163 let (shared_prefix, clip_draws) =
3164 clip_stack_sync(current_clip_stack, desired_clip_stack, clip_arena);
3165 update_current_clip_stack(current_clip_stack, shared_prefix, &clip_draws);
3166 segments.push(PrimitiveBatchSegment {
3167 alpha_mask: alpha_for_clip_stack(desired_clip_stack),
3168 stencil_depth: shared_prefix as u32,
3169 clips: clip_draws,
3170 scissor: scissor_for_clip_stack(desired_clip_stack),
3171 index_start: index_start as u32,
3172 index_count: (index_end - index_start) as u32,
3173 });
3174}
3175
3176fn push_primitive_batch<'a>(
3177 batches: &mut Vec<PrimitiveBatch<'a>>,
3178 buffers: CpuBuffers,
3179 segments: Vec<PrimitiveBatchSegment<'a>>,
3180) {
3181 if segments.is_empty() {
3182 return;
3183 }
3184 batches.push(PrimitiveBatch { buffers, segments });
3185}
3186
3187trait RetainedClipBackend {
3189 fn draw_runs(&mut self, runs: &[RetainedBatchRun<'_>]);
3190 fn sync_stencil(&mut self, depth: u32, clips: &[stencil::ClipDraw<'_>]);
3191}
3192
3193impl RetainedClipBackend for RenderBackend<'_> {
3194 fn draw_runs(&mut self, runs: &[RetainedBatchRun<'_>]) {
3195 self.draw_retained_batch_runs(runs);
3196 }
3197
3198 fn sync_stencil(&mut self, depth: u32, clips: &[stencil::ClipDraw<'_>]) {
3199 self.sync_stencil_stack(depth, clips);
3200 }
3201}
3202
3203fn sync_retained_clip_stack(
3204 render_backend: &mut impl RetainedClipBackend,
3205 retained_runs: &mut Vec<RetainedBatchRun<'_>>,
3206 current_clip_stack: &mut Vec<u32>,
3207 desired_clip_stack: &[ClipReference],
3208 clip_arena: &ClipArena,
3209) {
3210 let (shared_prefix, clip_draws) =
3211 clip_stack_sync(current_clip_stack, desired_clip_stack, clip_arena);
3212 if shared_prefix != current_clip_stack.len() || !clip_draws.is_empty() {
3216 render_backend.draw_runs(retained_runs);
3217 retained_runs.clear();
3218 }
3219 render_backend.sync_stencil(shared_prefix as u32, &clip_draws);
3220 update_current_clip_stack(current_clip_stack, shared_prefix, &clip_draws);
3221}
3222
3223fn scissor_for_clip_stack(clip_stack: &[ClipReference]) -> Option<ScissorRect> {
3224 let mut scissor = None;
3225 for clip in clip_stack {
3226 let ClipReference::Scissor(rect) = clip else {
3227 continue;
3228 };
3229 scissor = Some(match scissor {
3230 Some(existing) => intersect_scissor_rects(existing, *rect),
3231 None => *rect,
3232 });
3233 }
3234 scissor
3235}
3236
3237fn alpha_for_clip_stack(stack: &[ClipReference]) -> Option<u32> {
3238 stack.iter().rev().find_map(|clip| match clip {
3239 ClipReference::Alpha { owner } => Some(*owner),
3240 _ => None,
3241 })
3242}
3243
3244fn intersect_scissor_rects(left: ScissorRect, right: ScissorRect) -> ScissorRect {
3245 ScissorRect {
3246 min_x: left.min_x.max(right.min_x),
3247 min_y: left.min_y.max(right.min_y),
3248 max_x: left.max_x.min(right.max_x),
3249 max_y: left.max_y.min(right.max_y),
3250 }
3251}
3252
3253fn collect_active_clip_resources(
3254 scene: &HashMap<u32, RetainedNode>,
3255 clip_arena: &ClipArena,
3256) -> (HashSet<u64>, HashSet<u32>) {
3257 let mut signatures = HashSet::new();
3258 let mut clip_ids = HashSet::new();
3259 for node in scene.values() {
3260 for clip in node.clip_stack() {
3261 let ClipReference::Stencil { clip_id } = clip else {
3262 continue;
3263 };
3264 clip_ids.insert(*clip_id);
3265 if let Some(entry) = clip_arena.get(*clip_id) {
3266 signatures.insert(entry.geometry_signature);
3267 }
3268 }
3269 }
3270 (signatures, clip_ids)
3271}
3272
3273fn clip_stacks_match(left: &[ClipReference], right: &[ClipReference]) -> bool {
3274 left == right
3275}
3276
3277#[derive(Debug, Clone, Copy)]
3278pub struct GradientStop {
3280 pub color: Color,
3281 pub stop: f32,
3282}
3283
3284#[derive(Debug, Clone, Copy)]
3285pub enum GradientType {
3287 Linear,
3288 Radial,
3289}
3290
3291#[derive(Debug, Clone, Copy, PartialEq)]
3292pub struct Color {
3294 rgba: [f32; 4],
3295}
3296
3297impl Color {
3298 pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
3300 Self { rgba: [r, g, b, a] }
3301 }
3302
3303 pub fn hsva(h: f32, s: f32, v: f32, a: f32) -> Self {
3305 let i = (h * 6.0).floor() as i32;
3306 let f = h * 6.0 - i as f32;
3307 let p = v * (1.0 - s);
3308 let q = v * (1.0 - f * s);
3309 let t = v * (1.0 - (1.0 - f) * s);
3310
3311 let (r, g, b) = match i % 6 {
3312 0 => (v, t, p),
3313 1 => (q, v, p),
3314 2 => (p, v, t),
3315 3 => (p, q, v),
3316 4 => (t, p, v),
3317 _ => (v, p, q),
3318 };
3319 Self { rgba: [r, g, b, a] }
3320 }
3321
3322 pub fn hlca(h: f32, l: f32, c: f32, a: f32) -> Self {
3325 fn f_inv(t: f32) -> f32 {
3328 let d = 6. / 29.;
3329 if t > d {
3330 t.powi(3)
3331 } else {
3332 3. * d * d * (t - 4. / 29.)
3333 }
3334 }
3335 let th = h * (std::f32::consts::PI / 180.);
3336 let a_2 = c * th.cos();
3337 let b = c * th.sin();
3338 let ll = (l + 16.) * (1. / 116.);
3339 let x = f_inv(ll + a_2 * (1. / 500.));
3341 let y = f_inv(ll);
3342 let z = f_inv(ll - b * (1. / 200.));
3343 let r_lin = 3.02172918 * x - 1.61692294 * y - 0.40480625 * z;
3357 let g_lin = -0.94339358 * x + 1.91584267 * y + 0.02755094 * z;
3358 let b_lin = 0.06945666 * x - 0.22903204 * y + 1.15957526 * z;
3359 fn gamma(u: f32) -> f32 {
3360 if u <= 0.0031308 {
3361 12.92 * u
3362 } else {
3363 1.055 * u.powf(1. / 2.4) - 0.055
3364 }
3365 }
3366 Self {
3367 rgba: [
3368 gamma(r_lin).clamp(0.0, 1.0),
3369 gamma(g_lin).clamp(0.0, 1.0),
3370 gamma(b_lin).clamp(0.0, 1.0),
3371 a,
3372 ],
3373 }
3374 }
3375}
3376
3377#[derive(Debug, Clone)]
3378pub enum Fill {
3380 Solid(Color),
3381 Gradient {
3382 gradient_type: GradientType,
3383 pos: Point2D,
3384 main_axis: Vector2D,
3385 off_axis: Vector2D,
3386 stops: Vec<GradientStop>,
3387 },
3388}
3389
3390#[derive(Debug, Clone, Copy, PartialEq)]
3391pub struct Material {
3393 pub coefficients: [f32; 4],
3394 pub emissive: [f32; 4],
3395 pub unlit: bool,
3396}
3397
3398impl Material {
3399 pub fn matte() -> Self {
3400 Self::default()
3401 }
3402
3403 pub fn custom(
3404 ambient: f32,
3405 diffuse: f32,
3406 specular: f32,
3407 roughness: f32,
3408 emissive: Color,
3409 emissive_intensity: f32,
3410 ) -> Self {
3411 Self {
3412 coefficients: [
3413 ambient.clamp(0.0, 8.0),
3414 diffuse.clamp(0.0, 8.0),
3415 specular.clamp(0.0, 8.0),
3416 roughness.clamp(0.0, 1.0),
3417 ],
3418 emissive: [
3419 emissive.rgba[0],
3420 emissive.rgba[1],
3421 emissive.rgba[2],
3422 emissive_intensity.max(0.0),
3423 ],
3424 unlit: false,
3425 }
3426 }
3427
3428 pub fn unlit() -> Self {
3429 Self {
3430 coefficients: [0.0; 4],
3431 emissive: [0.0; 4],
3432 unlit: true,
3433 }
3434 }
3435}
3436
3437impl Default for Material {
3438 fn default() -> Self {
3439 Self {
3440 coefficients: [1.0, 0.82, 0.08, 0.78],
3441 emissive: [0.0; 4],
3442 unlit: false,
3443 }
3444 }
3445}
3446
3447#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3448pub enum LightShape {
3450 Point,
3451 Directional,
3452}
3453
3454#[derive(Debug, Clone, Copy, PartialEq)]
3455pub struct SceneLight {
3457 pub shape: LightShape,
3458 pub position: [f32; 3],
3459 pub direction: [f32; 3],
3460 pub color: Color,
3461 pub intensity: f32,
3462 pub radius: f32,
3463}
3464
3465#[derive(Debug, Clone, PartialEq)]
3466pub struct SceneLighting {
3468 pub active: bool,
3469 pub ambient_is_authored: bool,
3470 pub ambient_color: Color,
3471 pub ambient_intensity: f32,
3472 pub lights: Vec<SceneLight>,
3473}
3474
3475impl Default for SceneLighting {
3476 fn default() -> Self {
3477 Self {
3478 active: false,
3479 ambient_is_authored: false,
3480 ambient_color: Color::rgba(1.0, 1.0, 1.0, 1.0),
3481 ambient_intensity: 1.0,
3482 lights: vec![],
3483 }
3484 }
3485}
3486
3487#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3488pub enum StrokeCap {
3490 Butt,
3491 Round,
3492 Square,
3493}
3494
3495#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3496pub enum StrokeJoin {
3498 Miter,
3499 Round,
3500 Bevel,
3501}
3502
3503#[derive(Debug, Clone)]
3504pub struct Stroke {
3506 pub fill: Fill,
3507 pub weight: f32,
3508 pub cap: StrokeCap,
3509 pub join: StrokeJoin,
3510}
3511
3512#[derive(Debug, Clone, Copy)]
3513pub struct DrawRange {
3515 pub start: f32,
3516 pub end: f32,
3517 pub enabled: bool,
3518}
3519
3520impl DrawRange {
3521 pub fn disabled() -> Self {
3522 Self {
3523 start: 0.0,
3524 end: 1.0,
3525 enabled: false,
3526 }
3527 }
3528
3529 pub fn enabled(start: f32, end: f32) -> Self {
3530 Self {
3531 start,
3532 end,
3533 enabled: true,
3534 }
3535 }
3536
3537 fn as_gpu_range(&self) -> [f32; 4] {
3538 [
3539 self.start.clamp(0.0, 1.0),
3540 self.end.clamp(0.0, 1.0),
3541 if self.enabled { 1.0 } else { 0.0 },
3542 0.0,
3543 ]
3544 }
3545}
3546
3547#[cfg(test)]
3548mod tests {
3549 use super::*;
3550
3551 fn rect_path(width: f32, height: f32) -> Path {
3552 let mut builder = Path::builder();
3553 builder.begin(point(0.0, 0.0));
3554 builder.line_to(point(width, 0.0));
3555 builder.line_to(point(width, height));
3556 builder.line_to(point(0.0, height));
3557 builder.end(true);
3558 builder.build()
3559 }
3560
3561 fn two_closed_rects_path(width: f32, height: f32, gap: f32) -> Path {
3562 let mut builder = Path::builder();
3563 builder.begin(point(0.0, 0.0));
3564 builder.line_to(point(width, 0.0));
3565 builder.line_to(point(width, height));
3566 builder.line_to(point(0.0, height));
3567 builder.end(true);
3568
3569 let x = width + gap;
3570 builder.begin(point(x, 0.0));
3571 builder.line_to(point(x + width, 0.0));
3572 builder.line_to(point(x + width, height));
3573 builder.line_to(point(x, height));
3574 builder.end(true);
3575 builder.build()
3576 }
3577
3578 fn two_overlapping_rects_path(width: f32, height: f32, overlap: f32) -> Path {
3579 two_closed_rects_path(width, height, -overlap)
3580 }
3581
3582 fn tessellated_area(geometry: &VertexBuffers<stencil::Vertex, u16>) -> f32 {
3583 geometry
3584 .indices
3585 .chunks_exact(3)
3586 .map(|triangle| {
3587 let a = geometry.vertices[triangle[0] as usize].position;
3588 let b = geometry.vertices[triangle[1] as usize].position;
3589 let c = geometry.vertices[triangle[2] as usize].position;
3590 ((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])).abs() * 0.5
3591 })
3592 .sum()
3593 }
3594
3595 #[test]
3596 fn axis_aligned_rect_clip_becomes_scissor() {
3597 let transform = Transform2D::from_array([2.0, 0.0, 0.0, 3.0, 5.0, 7.0]);
3598 let scissor = axis_aligned_rect_scissor(&rect_path(10.0, 5.0), &transform).unwrap();
3599 assert_eq!(scissor.min_x, 5.0);
3600 assert_eq!(scissor.min_y, 7.0);
3601 assert_eq!(scissor.max_x, 25.0);
3602 assert_eq!(scissor.max_y, 22.0);
3603 }
3604
3605 #[test]
3606 fn sheared_rect_clip_stays_on_stencil_path() {
3607 let transform = Transform2D::from_array([1.0, 0.5, 0.0, 1.0, 0.0, 0.0]);
3608 assert!(axis_aligned_rect_scissor(&rect_path(10.0, 5.0), &transform).is_none());
3609 }
3610
3611 #[derive(Default)]
3612 struct RecordingClipBackend {
3613 stencil: Vec<u32>,
3614 draws: Vec<(u32, Vec<u32>)>,
3615 }
3616
3617 impl RetainedClipBackend for RecordingClipBackend {
3618 fn draw_runs(&mut self, runs: &[RetainedBatchRun<'_>]) {
3619 for run in runs {
3620 self.draws
3621 .push((run.alpha_mask.unwrap(), self.stencil.clone()));
3622 }
3623 }
3624
3625 fn sync_stencil(&mut self, depth: u32, clips: &[stencil::ClipDraw<'_>]) {
3626 self.stencil.truncate(depth as usize);
3627 self.stencil.extend(clips.iter().map(|clip| clip.clip_id));
3628 }
3629 }
3630
3631 fn labeled_run(label: u32) -> RetainedBatchRun<'static> {
3632 RetainedBatchRun {
3633 alpha_mask: Some(label),
3634 stencil_index: 0,
3635 scissor: None,
3636 draws: Vec::new(),
3637 }
3638 }
3639
3640 fn test_clip_arena() -> ClipArena {
3641 let mut arena = ClipArena::new();
3642 for node_id in 0..3 {
3643 let geometry = tessellate_clip_geometry(&rect_path(10.0, 10.0), 0.1);
3644 arena
3645 .sync_clip(
3646 node_id,
3647 0,
3648 hash_clip_geometry(&geometry),
3649 geometry,
3650 Transform2D::translation(node_id as f32 * 10.0, 0.0),
3651 )
3652 .unwrap();
3653 }
3654 arena
3655 }
3656
3657 #[test]
3658 fn retained_siblings_draw_before_their_stencil_is_replaced_or_popped() {
3659 let arena = test_clip_arena();
3660 let mut backend = RecordingClipBackend::default();
3661 let mut current = Vec::new();
3662 let mut runs = Vec::new();
3663 let root = ClipReference::Stencil { clip_id: 0 };
3664 for (label, clips) in [
3665 (1, vec![root, ClipReference::Stencil { clip_id: 1 }]),
3666 (2, vec![root, ClipReference::Stencil { clip_id: 2 }]),
3667 (3, vec![root]),
3668 (4, vec![]),
3669 ] {
3670 sync_retained_clip_stack(&mut backend, &mut runs, &mut current, &clips, &arena);
3671 runs.push(labeled_run(label));
3672 }
3673 backend.draw_runs(&runs);
3674 assert_eq!(
3675 backend.draws,
3676 vec![(1, vec![0, 1]), (2, vec![0, 2]), (3, vec![0]), (4, vec![]),]
3677 );
3678 }
3679
3680 #[test]
3681 fn retained_scissor_and_alpha_changes_preserve_stencil_batching() {
3682 let arena = test_clip_arena();
3683 let mut backend = RecordingClipBackend::default();
3684 let mut current = Vec::new();
3685 let mut runs = Vec::new();
3686 let root = ClipReference::Stencil { clip_id: 0 };
3687 sync_retained_clip_stack(&mut backend, &mut runs, &mut current, &[root], &arena);
3688 runs.push(labeled_run(1));
3689 for clip in [
3690 ClipReference::Scissor(ScissorRect {
3691 min_x: 1.0,
3692 min_y: 2.0,
3693 max_x: 3.0,
3694 max_y: 4.0,
3695 }),
3696 ClipReference::Alpha { owner: 5 },
3697 ] {
3698 sync_retained_clip_stack(&mut backend, &mut runs, &mut current, &[root, clip], &arena);
3699 }
3700 assert!(backend.draws.is_empty());
3701 assert_eq!(runs.len(), 1);
3702 assert_eq!(current, vec![0]);
3703 }
3704
3705 #[test]
3706 fn overlapping_clip_contours_form_a_union() {
3707 let geometry = tessellate_clip_geometry(
3708 &two_overlapping_rects_path(10.0, 10.0, 5.0),
3709 DEFAULT_TESSELLATION_TOLERANCE,
3710 );
3711 assert!((tessellated_area(&geometry) - 150.0).abs() < 0.01);
3712 }
3713
3714 #[test]
3715 fn vector_geometry_cache_reuses_tessellation_and_remaps_primitive_ids() {
3716 let path = rect_path(10.0, 5.0);
3717 let op = PendingVectorOp {
3718 path: path.clone(),
3719 fill: Fill::Solid(Color::rgba(1.0, 0.0, 0.0, 1.0)),
3720 material: Material::default(),
3721 light_mask: 0,
3722 transform: Transform2D::identity(),
3723 opacity: 1.0,
3724 kind: PendingVectorOpKind::Fill,
3725 geometry_signature: hash_vector_path(
3726 &path,
3727 PendingVectorOpKind::Fill,
3728 PathSmoothing::None,
3729 ),
3730 draw_range: DrawRange::disabled(),
3731 smoothing: PathSmoothing::None,
3732 };
3733 let ops = vec![
3734 PendingVectorOp {
3735 path,
3736 fill: Fill::Solid(Color::rgba(0.0, 1.0, 0.0, 1.0)),
3737 material: Material::default(),
3738 light_mask: 0,
3739 transform: Transform2D::identity(),
3740 opacity: 1.0,
3741 kind: PendingVectorOpKind::Fill,
3742 geometry_signature: op.geometry_signature,
3743 draw_range: DrawRange::disabled(),
3744 smoothing: PathSmoothing::None,
3745 },
3746 op,
3747 ];
3748 let cache = Rc::new(RefCell::new(VectorGeometryCache::default()));
3749 let mut stats = ResourceChurnStats::default();
3750 let mut buffers = new_cpu_buffers();
3751
3752 rebuild_vector_buffers(
3753 DEFAULT_TESSELLATION_TOLERANCE,
3754 &ops,
3755 &mut buffers,
3756 false,
3757 false,
3758 &cache,
3759 &mut stats,
3760 );
3761
3762 assert_eq!(stats.vector_geometry_cache_misses, 1);
3763 assert_eq!(stats.vector_geometry_cache_hits, 1);
3764 assert!(buffers
3765 .geometry
3766 .vertices
3767 .iter()
3768 .any(|vertex| vertex.prim_id == 0));
3769 assert!(buffers
3770 .geometry
3771 .vertices
3772 .iter()
3773 .any(|vertex| vertex.prim_id == 1));
3774
3775 let mut second_stats = ResourceChurnStats::default();
3776 let mut second_buffers = new_cpu_buffers();
3777 rebuild_vector_buffers(
3778 DEFAULT_TESSELLATION_TOLERANCE,
3779 &ops,
3780 &mut second_buffers,
3781 false,
3782 false,
3783 &cache,
3784 &mut second_stats,
3785 );
3786
3787 assert_eq!(second_stats.vector_geometry_cache_misses, 0);
3788 assert_eq!(second_stats.vector_geometry_cache_hits, 2);
3789 assert_eq!(second_stats.tessellated_vertices, 0);
3790 assert_eq!(
3791 buffers.geometry.vertices.len(),
3792 second_buffers.geometry.vertices.len()
3793 );
3794 for (left, right) in buffers
3795 .geometry
3796 .vertices
3797 .iter()
3798 .zip(second_buffers.geometry.vertices.iter())
3799 {
3800 assert_eq!(left.position, right.position);
3801 assert_eq!(left.prim_id, right.prim_id);
3802 }
3803 assert_eq!(buffers.geometry.indices, second_buffers.geometry.indices);
3804 }
3805
3806 #[test]
3807 fn draw_range_changes_do_not_rebuild_stroke_geometry() {
3808 let path = rect_path(10.0, 5.0);
3809 let kind = PendingVectorOpKind::Stroke(2.0, StrokeCap::Butt, StrokeJoin::Miter);
3810 let geometry_signature = hash_vector_path(&path, kind, PathSmoothing::None);
3811 let ops = vec![
3812 PendingVectorOp {
3813 path: path.clone(),
3814 fill: Fill::Solid(Color::rgba(1.0, 0.0, 0.0, 1.0)),
3815 material: Material::default(),
3816 light_mask: 0,
3817 transform: Transform2D::identity(),
3818 opacity: 1.0,
3819 kind,
3820 geometry_signature,
3821 draw_range: DrawRange::enabled(0.0, 0.5),
3822 smoothing: PathSmoothing::None,
3823 },
3824 PendingVectorOp {
3825 path,
3826 fill: Fill::Solid(Color::rgba(0.0, 1.0, 0.0, 1.0)),
3827 material: Material::default(),
3828 light_mask: 0,
3829 transform: Transform2D::identity(),
3830 opacity: 1.0,
3831 kind,
3832 geometry_signature,
3833 draw_range: DrawRange::enabled(0.25, 0.75),
3834 smoothing: PathSmoothing::None,
3835 },
3836 ];
3837 let cache = Rc::new(RefCell::new(VectorGeometryCache::default()));
3838 let mut stats = ResourceChurnStats::default();
3839 let mut buffers = new_cpu_buffers();
3840
3841 rebuild_vector_buffers(
3842 DEFAULT_TESSELLATION_TOLERANCE,
3843 &ops,
3844 &mut buffers,
3845 false,
3846 false,
3847 &cache,
3848 &mut stats,
3849 );
3850
3851 assert_eq!(stats.vector_geometry_cache_misses, 1);
3852 assert_eq!(stats.vector_geometry_cache_hits, 1);
3853 assert_eq!(buffers.primitives[0].draw_range, [0.0, 0.5, 1.0, 0.0]);
3854 assert_eq!(buffers.primitives[1].draw_range, [0.25, 0.75, 1.0, 0.0]);
3855 assert!(buffers
3856 .geometry
3857 .vertices
3858 .iter()
3859 .any(|vertex| vertex.path_progress > 0.0 && vertex.path_progress < 1.0));
3860 }
3861
3862 #[test]
3863 fn closed_subpaths_keep_compound_path_progress() {
3864 let path = two_closed_rects_path(10.0, 5.0, 20.0);
3865 let kind = PendingVectorOpKind::Stroke(1.0, StrokeCap::Butt, StrokeJoin::Miter);
3866 let op = PendingVectorOp {
3867 path: path.clone(),
3868 fill: Fill::Solid(Color::rgba(1.0, 0.0, 0.0, 1.0)),
3869 material: Material::default(),
3870 light_mask: 0,
3871 transform: Transform2D::identity(),
3872 opacity: 1.0,
3873 kind,
3874 geometry_signature: hash_vector_path(&path, kind, PathSmoothing::None),
3875 draw_range: DrawRange::enabled(0.0, 1.0),
3876 smoothing: PathSmoothing::None,
3877 };
3878
3879 let geometry = tessellate_vector_geometry(DEFAULT_TESSELLATION_TOLERANCE, &op);
3880 let first_rect_progress = geometry
3881 .vertices
3882 .iter()
3883 .filter(|vertex| vertex.position[0] < 20.0)
3884 .map(|vertex| vertex.path_progress)
3885 .collect::<Vec<_>>();
3886 let second_rect_progress = geometry
3887 .vertices
3888 .iter()
3889 .filter(|vertex| vertex.position[0] > 20.0)
3890 .map(|vertex| vertex.path_progress)
3891 .collect::<Vec<_>>();
3892
3893 let first_max = first_rect_progress
3894 .iter()
3895 .copied()
3896 .fold(f32::NEG_INFINITY, f32::max);
3897 let second_min = second_rect_progress
3898 .iter()
3899 .copied()
3900 .fold(f32::INFINITY, f32::min);
3901 let second_max = second_rect_progress
3902 .iter()
3903 .copied()
3904 .fold(f32::NEG_INFINITY, f32::max);
3905
3906 assert!(!first_rect_progress.is_empty());
3907 assert!(!second_rect_progress.is_empty());
3908 assert!(first_max <= 0.6, "first closed contour reached {first_max}");
3909 assert!(
3910 second_min >= 0.4,
3911 "second closed contour restarted at {second_min}"
3912 );
3913 assert!(second_max > 0.9, "compound path did not reach the end");
3914 }
3915
3916 #[test]
3917 fn draw_range_changes_refresh_primitives_without_rebuilding_geometry() {
3918 let path = rect_path(10.0, 5.0);
3919 let kind = PendingVectorOpKind::Stroke(2.0, StrokeCap::Butt, StrokeJoin::Miter);
3920 let geometry_signature = hash_vector_path(&path, kind, PathSmoothing::None);
3921 let make_ops = |draw_range| {
3922 vec![PendingVectorOp {
3923 path: path.clone(),
3924 fill: Fill::Solid(Color::rgba(1.0, 0.0, 0.0, 1.0)),
3925 material: Material::default(),
3926 light_mask: 0,
3927 transform: Transform2D::identity(),
3928 opacity: 1.0,
3929 kind,
3930 geometry_signature,
3931 draw_range,
3932 smoothing: PathSmoothing::None,
3933 }]
3934 };
3935 let cache = Rc::new(RefCell::new(VectorGeometryCache::default()));
3936 let mut stats = ResourceChurnStats::default();
3937 let mut buffers = new_cpu_buffers();
3938
3939 rebuild_vector_buffers(
3940 DEFAULT_TESSELLATION_TOLERANCE,
3941 &make_ops(DrawRange::enabled(0.0, 0.5)),
3942 &mut buffers,
3943 false,
3944 false,
3945 &cache,
3946 &mut stats,
3947 );
3948 let vertices = buffers.geometry.vertices.clone();
3949 let indices = buffers.geometry.indices.clone();
3950
3951 let mut update_stats = ResourceChurnStats::default();
3952 rebuild_vector_buffers(
3953 DEFAULT_TESSELLATION_TOLERANCE,
3954 &make_ops(DrawRange::enabled(0.25, 0.75)),
3955 &mut buffers,
3956 true,
3957 true,
3958 &cache,
3959 &mut update_stats,
3960 );
3961
3962 assert_eq!(update_stats.vector_geometry_rebuilds, 0);
3963 assert_eq!(buffers.geometry.vertices.len(), vertices.len());
3964 for (left, right) in buffers.geometry.vertices.iter().zip(vertices.iter()) {
3965 assert_eq!(left.position, right.position);
3966 assert_eq!(left.normal, right.normal);
3967 assert_eq!(left.prim_id, right.prim_id);
3968 assert_eq!(left.path_progress, right.path_progress);
3969 }
3970 assert_eq!(buffers.geometry.indices, indices);
3971 assert_eq!(buffers.primitives[0].draw_range, [0.25, 0.75, 1.0, 0.0]);
3972 }
3973
3974 #[test]
3975 fn draw_range_changes_are_part_of_primitive_signature() {
3976 let path = rect_path(10.0, 5.0);
3977 let kind = PendingVectorOpKind::Stroke(2.0, StrokeCap::Butt, StrokeJoin::Miter);
3978 let geometry_signature = hash_vector_path(&path, kind, PathSmoothing::None);
3979 let make_ops = |draw_range| {
3980 vec![PendingVectorOp {
3981 path: path.clone(),
3982 fill: Fill::Solid(Color::rgba(1.0, 0.0, 0.0, 1.0)),
3983 material: Material::default(),
3984 light_mask: 0,
3985 transform: Transform2D::identity(),
3986 opacity: 1.0,
3987 kind,
3988 geometry_signature,
3989 draw_range,
3990 smoothing: PathSmoothing::None,
3991 }]
3992 };
3993
3994 assert_ne!(
3995 hash_vector_primitives(&make_ops(DrawRange::enabled(0.0, 0.5))),
3996 hash_vector_primitives(&make_ops(DrawRange::enabled(0.25, 0.75)))
3997 );
3998 }
3999
4000 #[test]
4001 fn smoothing_changes_geometry_signature_not_primitive_signature() {
4002 let path = rect_path(10.0, 5.0);
4003 let kind = PendingVectorOpKind::Stroke(2.0, StrokeCap::Butt, StrokeJoin::Round);
4004 let unsmoothed = hash_vector_path(&path, kind, PathSmoothing::None);
4005 let smoothed = hash_vector_path(&path, kind, PathSmoothing::Strong);
4006 let make_op = |smoothing, geometry_signature| PendingVectorOp {
4007 path: path.clone(),
4008 fill: Fill::Solid(Color::rgba(1.0, 0.0, 0.0, 1.0)),
4009 material: Material::default(),
4010 light_mask: 0,
4011 transform: Transform2D::identity(),
4012 opacity: 1.0,
4013 kind,
4014 geometry_signature,
4015 draw_range: DrawRange::enabled(0.0, 0.5),
4016 smoothing,
4017 };
4018
4019 assert_ne!(unsmoothed, smoothed);
4020 assert_eq!(
4021 hash_vector_primitives(&[make_op(PathSmoothing::None, unsmoothed)]),
4022 hash_vector_primitives(&[make_op(PathSmoothing::Strong, smoothed)])
4023 );
4024 }
4025}