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