1use super::layer_surface::{
2 replay_batches_by_directional_priority, surface_intersects_coverage_bounds, LayerSurfaceEntry,
3 LayerSurfaceLayout, LayoutChangeKind, ReplayPriorityEntry, SurfaceReplayCoordinator,
4};
5#[cfg(debug_assertions)]
6use super::layer_surface::{visible_surface_escape, VisibleSurfaceEscape};
7use crate::render_instrumentation::{count, Counter};
8use kurbo::{BezPath, PathEl, Rect, Shape};
9use pax_gpu::render_backend::SharedGpuContext;
10use pax_gpu::{
11 point, Box2D, DrawRange as PixelDrawRange, Image, LightShape as PixelLightShape,
12 Material as PixelMaterial, Path, ResourceChurnStats, SceneLight as PixelSceneLight,
13 SceneLighting as PixelSceneLighting, Stroke as PixelStroke, StrokeCap as PixelStrokeCap,
14 StrokeJoin as PixelStrokeJoin, Transform2D, WgpuRenderer,
15};
16use pax_runtime_api::{
17 Axis, LayerSurfaceScreenshotData, PathSmoothing, RenderContext, ReplayCanvasLayerUpdate,
18 ResolvedMaterial, ResolvedStroke, SceneLighting, ScreenshotData, StrokeCap, StrokeJoin,
19};
20use std::{
21 cell::{Cell, RefCell},
22 collections::{HashMap, HashSet, VecDeque},
23 future::Future,
24 pin::Pin,
25 rc::Rc,
26};
27
28#[cfg(not(target_arch = "wasm32"))]
29use pollster;
30
31pub struct LayerRenderer {
33 key: String,
34 host_signature: String,
35 renderer: WgpuRenderer<'static>,
36 origin_x: f32,
37 origin_y: f32,
38 logical_width: f32,
39 logical_height: f32,
40 surface_width: u32,
41 surface_height: u32,
42 dpr: [f32; 2],
43 needs_initial_replay: bool,
44}
45
46pub struct LayerTarget {
48 renderers: Vec<LayerRenderer>,
49 active: bool,
50 needs_replay: bool,
51 failed_layout: Option<LayerSurfaceLayout>,
52}
53
54#[cfg(debug_assertions)]
55#[derive(Clone, Copy, Default)]
56struct TileCullStats {
57 nodes_considered: u64,
58 selected_surfaces: u64,
59 skipped_surfaces: u64,
60 stale_surface_removal_attempts: u64,
61 origin_only_retargets: u64,
62 targeted_replay_batches: u64,
63 targeted_replay_surfaces: u64,
64 targeted_replay_visible_batches: u64,
65 targeted_replay_warm_batches: u64,
66 targeted_replay_visible_surfaces: u64,
67 targeted_replay_warm_surfaces: u64,
68 full_layer_replays: u64,
69 resize_resets: u64,
70}
71
72#[cfg(debug_assertions)]
73fn replay_priority_stats(
74 entries: &[ReplayPriorityEntry],
75 batches: &[Vec<usize>],
76) -> (usize, usize, usize, usize) {
77 let visible_surfaces = entries.iter().filter(|entry| entry.priority <= 0).count();
78 let warm_surfaces = entries.len().saturating_sub(visible_surfaces);
79 let priority_by_index: HashMap<usize, i32> = entries
80 .iter()
81 .map(|entry| (entry.index, entry.priority))
82 .collect();
83 let visible_batches = batches
84 .iter()
85 .filter(|batch| {
86 batch.iter().any(|index| {
87 priority_by_index
88 .get(index)
89 .map_or(false, |priority| *priority <= 0)
90 })
91 })
92 .count();
93 let warm_batches = batches.len().saturating_sub(visible_batches);
94
95 (
96 visible_batches,
97 warm_batches,
98 visible_surfaces,
99 warm_surfaces,
100 )
101}
102
103#[cfg(debug_assertions)]
104fn log_visible_surface_escape(layer: usize, escape: VisibleSurfaceEscape) {
105 log::warn!(
106 "[pax-tile-window-escape] backend=wgpu layer={} visible_surfaces={} escaped_visible_surfaces={} max_gap_x={:.1} max_gap_y={:.1} previous_bounds={} visible_bounds={}",
107 layer,
108 escape.visible_surfaces,
109 escape.escaped_visible_surfaces,
110 escape.max_gap_x,
111 escape.max_gap_y,
112 format_rect(escape.previous_bounds),
113 format_rect(escape.visible_bounds),
114 );
115}
116
117#[cfg(debug_assertions)]
118fn format_rect(bounds: Rect) -> String {
119 format!(
120 "{:.1},{:.1}-{:.1},{:.1}",
121 bounds.x0, bounds.y0, bounds.x1, bounds.y1
122 )
123}
124
125#[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
126fn should_print_gpu_profile_logs() -> bool {
127 static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
128 *ENABLED.get_or_init(|| {
129 std::env::var("PAX_GPU_PROFILE")
130 .map(|value| {
131 let value = value.trim().to_ascii_lowercase();
132 !(value.is_empty() || value == "0" || value == "false" || value == "off")
133 })
134 .unwrap_or(false)
135 })
136}
137
138type LayerDef = (LayerTarget, Pin<Box<dyn Fn() -> LayerSurfaceLayout>>);
139type LayerFactory =
140 dyn Fn(usize, LayerCreationRequest) -> Pin<Box<dyn Future<Output = Option<LayerDef>>>>;
141
142pub struct LayerCreationRequest {
145 retained: Vec<(String, String)>,
146 pub shared_context: Option<SharedGpuContext>,
148 pub max_surface_dimension: u32,
150}
151
152impl Default for LayerCreationRequest {
153 fn default() -> Self {
154 Self {
155 retained: Vec::new(),
156 shared_context: None,
157 max_surface_dimension: u32::MAX,
158 }
159 }
160}
161
162impl LayerCreationRequest {
163 pub fn needs_surface(&self, key: &str, host_signature: &str) -> bool {
165 !self
166 .retained
167 .iter()
168 .any(|(k, h)| k == key && h == host_signature)
169 }
170
171 fn for_target(target: &LayerTarget) -> Self {
172 Self {
173 retained: target
174 .renderers
175 .iter()
176 .map(|r| (r.key.clone(), r.host_signature.clone()))
177 .collect(),
178 shared_context: target
179 .renderers
180 .first()
181 .map(|r| r.renderer.shared_gpu_context()),
182 max_surface_dimension: target
183 .renderers
184 .first()
185 .map_or(u32::MAX, |r| r.renderer.max_surface_dimension()),
186 }
187 }
188}
189
190impl LayerRenderer {
191 pub fn new(
193 key: String,
194 host_signature: String,
195 renderer: WgpuRenderer<'static>,
196 origin_x: f32,
197 origin_y: f32,
198 logical_width: f32,
199 logical_height: f32,
200 surface_width: u32,
201 surface_height: u32,
202 dpr: [f32; 2],
203 ) -> Self {
204 count(Counter::TileCreated, 1);
205 Self {
206 key,
207 host_signature,
208 renderer,
209 origin_x,
210 origin_y,
211 logical_width,
212 logical_height,
213 surface_width,
214 surface_height,
215 dpr,
216 needs_initial_replay: true,
217 }
218 }
219
220 pub fn renderer_mut(&mut self) -> &mut WgpuRenderer<'static> {
222 &mut self.renderer
223 }
224
225 pub fn matches_surface(&self, surface: &LayerSurfaceEntry) -> bool {
227 self.key == surface.key && self.host_signature == surface.host_signature
228 }
229
230 fn intersects_coverage_bounds(&self, bounds: &Rect) -> bool {
231 surface_intersects_coverage_bounds(
232 bounds,
233 self.origin_x as f64,
234 self.origin_y as f64,
235 self.logical_width as f64,
236 self.logical_height as f64,
237 )
238 }
239
240 fn coverage_bounds(&self) -> Rect {
241 Rect::new(
242 self.origin_x as f64,
243 self.origin_y as f64,
244 self.origin_x as f64 + self.logical_width as f64,
245 self.origin_y as f64 + self.logical_height as f64,
246 )
247 }
248
249 fn update_layout(&mut self, surface: &LayerSurfaceEntry) -> LayoutChangeKind {
250 let origin_changed = (self.origin_x - surface.origin_x).abs() > f32::EPSILON
251 || (self.origin_y - surface.origin_y).abs() > f32::EPSILON;
252 let size_changed = self.logical_width != surface.surface.logical_width
253 || self.logical_height != surface.surface.logical_height
254 || self.surface_width != surface.surface.surface_width
255 || self.surface_height != surface.surface.surface_height
256 || self.dpr != surface.surface.dpr;
257
258 self.origin_x = surface.origin_x;
259 self.origin_y = surface.origin_y;
260 self.logical_width = surface.surface.logical_width;
261 self.logical_height = surface.surface.logical_height;
262 self.surface_width = surface.surface.surface_width;
263 self.surface_height = surface.surface.surface_height;
264 self.dpr = surface.surface.dpr;
265
266 if size_changed {
267 LayoutChangeKind::Resized
268 } else if origin_changed {
269 LayoutChangeKind::OriginOnly
270 } else {
271 LayoutChangeKind::Unchanged
272 }
273 }
274
275 pub fn sync_layout_metadata(&mut self, surface: &LayerSurfaceEntry) {
278 self.update_layout(surface);
279 }
280}
281
282impl LayerTarget {
283 pub fn new(mut renderers: Vec<LayerRenderer>, active: bool) -> Self {
285 if let Some((first, rest)) = renderers.split_first_mut() {
288 for renderer in rest {
289 renderer.renderer.share_vector_caches_from(&first.renderer);
290 }
291 }
292 Self {
293 renderers,
294 active,
295 needs_replay: false,
296 failed_layout: None,
297 }
298 }
299
300 pub fn renderers_mut(&mut self) -> &mut [LayerRenderer] {
302 &mut self.renderers
303 }
304
305 fn activate(&mut self) {
306 if !self.active {
307 self.active = true;
308 self.needs_replay = true;
309 }
310 }
311
312 fn deactivate(&mut self) {
313 self.active = false;
314 }
315
316 fn prepare_for_render(&mut self) {
317 if !self.needs_replay {
318 return;
319 }
320 for renderer in &mut self.renderers {
321 count(Counter::SceneReset, 1);
322 renderer.renderer.reset_retained_scene();
323 }
324 self.needs_replay = false;
325 }
326}
327
328fn layer_layout_matches_target(target: &LayerTarget, layout: &LayerSurfaceLayout) -> bool {
329 layout.surfaces.len() == target.renderers.len()
333 && layout
334 .surfaces
335 .iter()
336 .zip(target.renderers.iter())
337 .all(|(surface, renderer)| {
338 surface.key == renderer.key && surface.host_signature == renderer.host_signature
339 })
340}
341
342fn layer_layout_matches_bootstrapped_target(
343 target: &LayerTarget,
344 layout: &LayerSurfaceLayout,
345) -> bool {
346 target.active == layout.active
350 && layer_layout_matches_target(target, layout)
351 && layout
352 .surfaces
353 .iter()
354 .zip(target.renderers.iter())
355 .all(|(surface, renderer)| {
356 (surface.origin_x - renderer.origin_x).abs() <= f32::EPSILON
357 && (surface.origin_y - renderer.origin_y).abs() <= f32::EPSILON
358 && (surface.surface.logical_width - renderer.logical_width).abs()
359 <= f32::EPSILON
360 && (surface.surface.logical_height - renderer.logical_height).abs()
361 <= f32::EPSILON
362 && surface.surface.surface_width == renderer.surface_width
363 && surface.surface.surface_height == renderer.surface_height
364 && (surface.surface.dpr[0] - renderer.dpr[0]).abs() <= f32::EPSILON
365 && (surface.surface.dpr[1] - renderer.dpr[1]).abs() <= f32::EPSILON
366 })
367}
368
369fn retained_surface_order(
372 existing: &[(&str, &str)],
373 desired: &[(&str, &str)],
374) -> Option<Vec<usize>> {
375 let mut seen = HashSet::new();
376 let mut order = Vec::new();
377 for identity in desired {
378 if !seen.insert(*identity) {
379 return None;
380 }
381 if let Some(index) = existing.iter().position(|candidate| candidate == identity) {
382 order.push(index);
383 }
384 }
385 Some(order)
386}
387
388fn retain_surfaces<T>(surfaces: &mut Vec<T>, order: Vec<usize>) {
389 let mut previous: Vec<_> = std::mem::take(surfaces).into_iter().map(Some).collect();
390 *surfaces = order
391 .into_iter()
392 .map(|index| previous[index].take().unwrap())
393 .collect();
394}
395
396const MAX_CONCURRENT_LAYER_INITIALIZATIONS: usize = 4;
397
398struct LayerInitialization {
401 index: usize,
402 cancelled: Cell<bool>,
403 scheduled: Cell<bool>,
404}
405
406impl LayerInitialization {
407 fn new(index: usize) -> Self {
408 Self {
409 index,
410 cancelled: Cell::new(false),
411 scheduled: Cell::new(false),
412 }
413 }
414
415 fn complete(
416 &self,
417 backend: Option<LayerDef>,
418 requested_layout: Option<LayerSurfaceLayout>,
419 backends: &RefCell<Vec<RenderLayerState>>,
420 ready_layers: &RefCell<Vec<usize>>,
421 changed_layers: &RefCell<Vec<usize>>,
422 ) -> bool {
423 if self.cancelled.get() {
424 return false;
425 }
426 let mut states = backends.borrow_mut();
427 let Some(state) = states.get_mut(self.index) else {
428 return false;
429 };
430 match backend {
431 Some((mut additions, provider)) => {
432 let layout = provider();
433 if let RenderLayerState::Ready((target, old_provider)) = state {
434 additions
438 .renderers
439 .retain(|r| layout.surfaces.iter().any(|s| r.matches_surface(s)));
440 for mut addition in additions.renderers {
441 if target.renderers.iter().any(|r| {
442 r.key == addition.key && r.host_signature == addition.host_signature
443 }) {
444 continue;
445 }
446 if let Some(first) = target.renderers.first() {
447 addition.renderer.share_vector_caches_from(&first.renderer);
448 }
449 target.renderers.push(addition);
450 }
451 target.failed_layout = None;
452 *old_provider = provider;
453 changed_layers.borrow_mut().push(self.index);
454 false
455 } else if layer_layout_matches_bootstrapped_target(&additions, &layout) {
456 *state = RenderLayerState::Ready((additions, provider));
457 ready_layers.borrow_mut().push(self.index);
458 false
459 } else {
460 *state = RenderLayerState::Pending;
463 true
464 }
465 }
466 None => {
467 if let RenderLayerState::Ready((target, _)) = state {
470 target.failed_layout = requested_layout;
471 } else {
472 *state = RenderLayerState::Failed;
473 }
474 log::warn!(
475 "failed to initialize render surfaces for layer {}",
476 self.index
477 );
478 false
479 }
480 }
481 }
482}
483
484fn pop_next_layer_initialization(
485 queue: &RefCell<VecDeque<Rc<LayerInitialization>>>,
486 priority: &dyn Fn(usize) -> i32,
487) -> Option<Rc<LayerInitialization>> {
488 let mut queue = queue.borrow_mut();
489 queue.retain(|entry| !entry.cancelled.get());
490 let index = queue
493 .iter()
494 .enumerate()
495 .min_by_key(|(index, entry)| (priority(entry.index), *index))
496 .map(|(index, _)| index)?;
497 queue.remove(index)
498}
499
500fn pump_layer_initialization_queue(
501 factory: Rc<LayerFactory>,
502 backends: Rc<RefCell<Vec<RenderLayerState>>>,
503 queue: Rc<RefCell<VecDeque<Rc<LayerInitialization>>>>,
504 in_flight: Rc<Cell<usize>>,
505 ready_layers: Rc<RefCell<Vec<usize>>>,
506 changed_layers: Rc<RefCell<Vec<usize>>>,
507 priority: Rc<dyn Fn(usize) -> i32>,
508) {
509 while in_flight.get() < MAX_CONCURRENT_LAYER_INITIALIZATIONS {
510 let next_layer = pop_next_layer_initialization(&queue, priority.as_ref());
511 let Some(initialization) = next_layer else {
512 break;
513 };
514 if initialization.cancelled.get() {
515 continue;
516 }
517
518 let (request, requested_layout) = match backends.borrow().get(initialization.index) {
519 Some(RenderLayerState::Ready((target, provider))) => {
520 (LayerCreationRequest::for_target(target), Some(provider()))
521 }
522 _ => (LayerCreationRequest::default(), None),
523 };
524 in_flight.set(in_flight.get() + 1);
525 let factory = Rc::clone(&factory);
526 let backends = Rc::clone(&backends);
527 let queue = Rc::clone(&queue);
528 let in_flight_count = Rc::clone(&in_flight);
529 let ready_layers = Rc::clone(&ready_layers);
530 let changed_layers = Rc::clone(&changed_layers);
531 let priority = Rc::clone(&priority);
532
533 let task = async move {
534 let should_requeue = if initialization.cancelled.get() {
535 false
536 } else {
537 let _init_timing = crate::render_instrumentation::Span::new(
538 crate::render_instrumentation::Phase::LayerInit,
539 );
540 let backend = (factory)(initialization.index, request).await;
541 initialization.complete(
542 backend,
543 requested_layout,
544 &backends,
545 &ready_layers,
546 &changed_layers,
547 )
548 };
549 initialization.scheduled.set(false);
550 if should_requeue {
551 initialization.scheduled.set(true);
552 queue.borrow_mut().push_back(initialization);
553 }
554 in_flight_count.set(in_flight_count.get().saturating_sub(1));
555 pump_layer_initialization_queue(
556 factory,
557 backends,
558 queue,
559 in_flight_count,
560 ready_layers,
561 changed_layers,
562 priority,
563 );
564 };
565 #[cfg(target_arch = "wasm32")]
566 wasm_bindgen_futures::spawn_local(task);
567 #[cfg(not(target_arch = "wasm32"))]
568 pollster::block_on(task);
569 }
570}
571pub struct PaxGpuRenderer {
573 backends: Rc<RefCell<Vec<RenderLayerState>>>,
574 #[cfg(test)]
575 flushed_surfaces: RefCell<Vec<(usize, usize, ResourceChurnStats)>>,
576 layer_factory: Rc<LayerFactory>,
577 layer_initialization_priority: Rc<dyn Fn(usize) -> i32>,
578 changed_layers: Rc<RefCell<Vec<usize>>>,
579 image_map: HashMap<String, Image>,
580 image_versions: HashMap<String, u64>,
581 failed_context_gets: RefCell<Vec<bool>>,
582 ready_layers: Rc<RefCell<Vec<usize>>>,
583 replay_layers: Rc<RefCell<Vec<usize>>>,
584 pending_layer_initializations: Rc<RefCell<VecDeque<Rc<LayerInitialization>>>>,
585 layer_initializations: Vec<Rc<LayerInitialization>>,
586 layer_initializations_in_flight: Rc<Cell<usize>>,
587 active_render_scopes: RefCell<Vec<Vec<Vec<usize>>>>,
588 dirty_render_surfaces: RefCell<Vec<HashSet<usize>>>,
589 last_scene_lighting: RefCell<Vec<Option<PixelSceneLighting>>>,
590 surface_replay: RefCell<SurfaceReplayCoordinator>,
591 clean_skipped_canvas_nodes: RefCell<HashSet<(usize, u32)>>,
592 alpha_source_scopes: Vec<(usize, usize)>,
593 #[cfg(feature = "shaders")]
594 shader_effect_scopes: Vec<(usize, usize)>,
595 #[cfg(debug_assertions)]
596 tile_cull_stats: RefCell<Vec<TileCullStats>>,
597}
598
599pub enum RenderLayerState {
601 Pending,
602 Failed,
603 Ready(LayerDef),
604}
605
606impl PaxGpuRenderer {
607 fn is_capturing_source(&self) -> bool {
608 #[cfg(feature = "shaders")]
609 if !self.shader_effect_scopes.is_empty() {
610 return true;
611 }
612 !self.alpha_source_scopes.is_empty()
613 }
614
615 pub fn new(
617 layer_factory: impl Fn(usize, LayerCreationRequest) -> Pin<Box<dyn Future<Output = Option<LayerDef>>>>
618 + 'static,
619 ) -> Self {
620 Self {
621 backends: Default::default(),
622 #[cfg(test)]
623 flushed_surfaces: Default::default(),
624 layer_factory: Rc::new(layer_factory),
625 layer_initialization_priority: Rc::new(|_| 0),
626 changed_layers: Default::default(),
627 image_map: Default::default(),
628 image_versions: Default::default(),
629 failed_context_gets: RefCell::new(vec![]),
630 ready_layers: Default::default(),
631 replay_layers: Default::default(),
632 pending_layer_initializations: Rc::new(RefCell::new(VecDeque::new())),
633 layer_initializations: Vec::new(),
634 layer_initializations_in_flight: Rc::new(Cell::new(0)),
635 active_render_scopes: Default::default(),
636 dirty_render_surfaces: Default::default(),
637 last_scene_lighting: Default::default(),
638 surface_replay: Default::default(),
639 clean_skipped_canvas_nodes: Default::default(),
640 alpha_source_scopes: Vec::new(),
641 #[cfg(feature = "shaders")]
642 shader_effect_scopes: Vec::new(),
643 #[cfg(debug_assertions)]
644 tile_cull_stats: Default::default(),
645 }
646 }
647}
648
649impl PaxGpuRenderer {
650 pub fn with_layer_initialization_priority(
654 mut self,
655 priority: impl Fn(usize) -> i32 + 'static,
656 ) -> Self {
657 self.layer_initialization_priority = Rc::new(priority);
658 self
659 }
660
661 fn queue_layer_initialization(&self, layer_index: usize) {
662 let initialization = &self.layer_initializations[layer_index];
663 if !initialization.scheduled.replace(true) {
664 self.pending_layer_initializations
665 .borrow_mut()
666 .push_back(Rc::clone(initialization));
667 }
668 }
669
670 fn enqueue_layer_initialization(&self, layer_index: usize) {
671 self.queue_layer_initialization(layer_index);
672 self.schedule_layer_initialization();
673 }
674
675 fn with_layer_context(&self, layer: usize, mut f: impl FnMut(&mut WgpuRenderer)) {
676 let mut backends = self.backends.borrow_mut();
677 match backends.get_mut(layer) {
678 Some(layer_state) => match layer_state {
679 RenderLayerState::Pending => {
680 let mut failed_context_gets = self.failed_context_gets.borrow_mut();
681 if failed_context_gets.len() <= layer {
682 failed_context_gets.resize(layer + 1, false);
683 }
684 failed_context_gets[layer] = true;
685 }
686 RenderLayerState::Failed => {}
687 RenderLayerState::Ready((target, _)) => {
688 if !target.active {
689 return;
690 }
691 target.prepare_for_render();
692 let scoped_indices = self.active_render_scope(layer).or_else(|| {
693 self.targeted_replay_scope(layer)
696 });
697 if let Some(indices) = scoped_indices {
698 for index in indices {
699 if let Some(renderer) = target.renderers.get_mut(index) {
700 f(&mut renderer.renderer);
701 }
702 }
703 } else {
704 for renderer in &mut target.renderers {
705 f(&mut renderer.renderer);
706 }
707 }
708 }
709 },
710 None => log::warn!(
711 "tried to retrieve layer {} context for non-existent layer",
712 layer
713 ),
714 }
715 }
716
717 fn active_render_scope(&self, layer: usize) -> Option<Vec<usize>> {
718 self.active_render_scopes
719 .borrow()
720 .get(layer)
721 .and_then(|scopes| scopes.last().cloned())
722 }
723
724 fn push_render_scope(&self, layer: usize, renderer_indices: Vec<usize>) {
725 let mut scopes = self.active_render_scopes.borrow_mut();
726 if scopes.len() <= layer {
727 scopes.resize_with(layer + 1, Vec::new);
728 }
729 scopes[layer].push(renderer_indices);
730 }
731
732 fn pop_render_scope(&self, layer: usize) {
733 if let Some(scopes) = self.active_render_scopes.borrow_mut().get_mut(layer) {
734 scopes.pop();
735 }
736 }
737
738 fn mark_render_surfaces_dirty(
739 &self,
740 layer: usize,
741 renderer_indices: impl IntoIterator<Item = usize>,
742 ) {
743 let mut dirty_surfaces = self.dirty_render_surfaces.borrow_mut();
744 if dirty_surfaces.len() <= layer {
745 dirty_surfaces.resize_with(layer + 1, HashSet::new);
746 }
747 dirty_surfaces[layer].extend(renderer_indices);
748 }
749
750 fn dirty_render_surface_scope(&self, layer: usize) -> Option<Vec<usize>> {
751 let mut indices: Vec<_> = self
752 .dirty_render_surfaces
753 .borrow()
754 .get(layer)?
755 .iter()
756 .copied()
757 .collect();
758 if indices.is_empty() {
759 return None;
760 }
761 indices.sort_unstable();
762 Some(indices)
763 }
764
765 fn clear_dirty_render_surfaces(&self, layer: usize, renderer_indices: &[usize]) {
766 if let Some(dirty_surfaces) = self.dirty_render_surfaces.borrow_mut().get_mut(layer) {
767 for index in renderer_indices {
768 dirty_surfaces.remove(index);
769 }
770 }
771 }
772
773 fn update_scene_lighting_cache(&self, layer: usize, lighting: &PixelSceneLighting) -> bool {
774 let mut last_scene_lighting = self.last_scene_lighting.borrow_mut();
775 if last_scene_lighting.len() <= layer {
776 last_scene_lighting.resize_with(layer + 1, || None);
777 }
778 let changed = last_scene_lighting[layer].as_ref() != Some(lighting);
779 if changed {
780 last_scene_lighting[layer] = Some(lighting.clone());
781 }
782 changed
783 }
784
785 fn targeted_replay_scope(&self, layer: usize) -> Option<Vec<usize>> {
786 self.surface_replay.borrow().targeted_replay_scope(layer)
787 }
788
789 fn set_targeted_replay_batches(
790 &self,
791 layer: usize,
792 batches: Vec<Vec<usize>>,
793 bounds_by_surface: HashMap<usize, Vec<Rect>>,
794 ) {
795 self.surface_replay
796 .borrow_mut()
797 .set_targeted_replay_batches(layer, batches, bounds_by_surface);
798 }
799
800 fn advance_targeted_replay_queue(&self, layer: usize) -> bool {
801 self.surface_replay
802 .borrow_mut()
803 .advance_targeted_replay_queue(layer)
804 }
805
806 fn flush_targeted_or_dirty(&self, layer: usize, target: &mut LayerTarget) -> bool {
807 #[cfg(test)]
808 self.flushed_surfaces
809 .borrow_mut()
810 .retain(|(old_layer, _, _)| *old_layer != layer);
811 use crate::render_instrumentation::{Phase, Span};
812 let targeted_indices = self.targeted_replay_scope(layer);
813 let dirty_indices = self.dirty_render_surface_scope(layer);
814 let used_targeted_replay = targeted_indices.is_some();
815 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
816 let targeted_surface_count = targeted_indices.as_ref().map(Vec::len).unwrap_or(0);
817 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
818 let dirty_surface_count = dirty_indices.as_ref().map(Vec::len).unwrap_or(0);
819 let mut indices = targeted_indices.unwrap_or_default();
820 if let Some(dirty_indices) = dirty_indices {
821 indices.extend(dirty_indices);
822 }
823 indices.sort_unstable();
824 indices.dedup();
825
826 if !indices.is_empty() {
827 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
828 let flush_start = std::time::Instant::now();
829 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
830 let mut encode_us = 0u128;
831 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
832 let mut submit_us = 0u128;
833 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
834 let mut cleanup_us = 0u128;
835 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
836 let mut present_us = 0u128;
837 let mut resource_stats = ResourceChurnStats::default();
838 let mut command_buffers = Vec::new();
839 let mut submitter_index = None;
840 let mut cleanup_indices = Vec::new();
841 let mut present_indices = Vec::new();
842 for index in &indices {
843 if let Some(renderer) = target.renderers.get_mut(*index) {
844 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
845 let encode_start = std::time::Instant::now();
846 let encode_timing = Span::new(Phase::Encode);
847 renderer.renderer.flush_deferred();
848 drop(encode_timing);
849 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
850 {
851 encode_us += encode_start.elapsed().as_micros();
852 }
853 let mut renderer_command_buffers =
854 renderer.renderer.take_pending_command_buffers();
855 if !renderer_command_buffers.is_empty() {
856 if submitter_index.is_none() {
857 submitter_index = Some(*index);
858 }
859 command_buffers.append(&mut renderer_command_buffers);
860 }
861 if renderer.renderer.has_pending_submission_cleanup() {
862 cleanup_indices.push(*index);
863 }
864 present_indices.push(*index);
865 let stats = renderer.renderer.take_resource_churn_stats();
866 #[cfg(test)]
867 self.flushed_surfaces
868 .borrow_mut()
869 .push((layer, *index, stats));
870 count(
871 Counter::ImageTextureUploaded,
872 stats.texture_creates as usize,
873 );
874 count(
875 Counter::ImageTextureUploadBytes,
876 stats.texture_upload_bytes as usize,
877 );
878 count(
879 Counter::ImageTextureReused,
880 stats.texture_cache_hits as usize,
881 );
882 resource_stats.merge(stats);
883 }
884 }
885 let command_buffer_count = command_buffers.len();
886 if command_buffer_count != 0 {
887 if let Some(index) = submitter_index {
888 if let Some(renderer) = target.renderers.get(index) {
889 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
890 let submit_start = std::time::Instant::now();
891 let submit_timing = Span::new(Phase::Submit);
892 renderer.renderer.submit_command_buffers(command_buffers);
893 drop(submit_timing);
894 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
895 {
896 submit_us += submit_start.elapsed().as_micros();
897 }
898 log::trace!(
899 "[pax-gpu-submit] layer={} surfaces={} command_buffers={} shared_submit=1",
900 layer,
901 indices.len(),
902 command_buffer_count
903 );
904 }
905 }
906 }
907 for index in &cleanup_indices {
908 if let Some(renderer) = target.renderers.get_mut(*index) {
909 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
910 let cleanup_start = std::time::Instant::now();
911 let cleanup_timing = Span::new(Phase::Cleanup);
912 renderer.renderer.complete_submitted_work();
913 drop(cleanup_timing);
914 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
915 {
916 cleanup_us += cleanup_start.elapsed().as_micros();
917 }
918 }
919 }
920 for index in &present_indices {
921 if let Some(renderer) = target.renderers.get_mut(*index) {
922 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
923 let present_start = std::time::Instant::now();
924 let present_timing = Span::new(Phase::Present);
925 renderer.renderer.present_deferred_frame();
926 drop(present_timing);
927 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
928 {
929 present_us += present_start.elapsed().as_micros();
930 }
931 }
932 }
933 #[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
934 {
935 let total_us = flush_start.elapsed().as_micros();
936 let line = format!(
937 "[pax-gpu-flush] layer={} surfaces={} targeted={} targeted_surfaces={} dirty_surfaces={} command_buffers={} cleanup_surfaces={} present_surfaces={} total_us={} encode_us={} submit_us={} cleanup_us={} present_us={} flushes={} retained_nodes={}/{} retained_batches={} retained_draws={} vector_draws={} image_draws={} batch_flushes={} geometry_rebuilds={} geometry_cache_hits={} geometry_cache_misses={} geometry_cache_evictions={} geometry_cache_bytes={} tessellated_vertices={} tessellated_indices={} cached_vertices_reused={} cached_indices_reused={} resource_creates={} resource_updates={} resource_recreates={} create_bytes={} update_bytes={} cache_hits={} cache_misses={} cache_evictions={} cache_bytes={} texture_creates={} texture_bytes={} texture_cache_hits={}",
938 layer,
939 indices.len(),
940 used_targeted_replay,
941 targeted_surface_count,
942 dirty_surface_count,
943 command_buffer_count,
944 cleanup_indices.len(),
945 present_indices.len(),
946 total_us,
947 encode_us,
948 submit_us,
949 cleanup_us,
950 present_us,
951 resource_stats.flushes,
952 resource_stats.retained_nodes_visible,
953 resource_stats.retained_nodes_considered,
954 resource_stats.retained_draw_batches,
955 resource_stats.retained_draws,
956 resource_stats.retained_vector_draws,
957 resource_stats.retained_image_draws,
958 resource_stats.vector_batch_flushes,
959 resource_stats.vector_geometry_rebuilds,
960 resource_stats.vector_geometry_cache_hits,
961 resource_stats.vector_geometry_cache_misses,
962 resource_stats.vector_geometry_cache_evictions,
963 resource_stats.vector_geometry_cache_bytes,
964 resource_stats.tessellated_vertices,
965 resource_stats.tessellated_indices,
966 resource_stats.cached_vertices_reused,
967 resource_stats.cached_indices_reused,
968 resource_stats.vector_resource_creates,
969 resource_stats.vector_resource_updates,
970 resource_stats.vector_resource_recreates,
971 resource_stats.vector_resource_create_bytes,
972 resource_stats.vector_resource_update_bytes,
973 resource_stats.vector_resource_cache_hits,
974 resource_stats.vector_resource_cache_misses,
975 resource_stats.vector_resource_cache_evictions,
976 resource_stats.vector_resource_cache_bytes,
977 resource_stats.texture_creates,
978 resource_stats.texture_upload_bytes,
979 resource_stats.texture_cache_hits,
980 );
981 log::trace!("{}", line);
982 #[cfg(not(target_arch = "wasm32"))]
983 if should_print_gpu_profile_logs()
984 && (used_targeted_replay
985 || total_us >= 8_000
986 || resource_stats.has_resource_churn())
987 {
988 println!("{}", line);
989 }
990 }
991 if resource_stats.has_resource_churn() {
992 log::trace!(
993 "[pax-gpu-resources] layer={} surfaces={} targeted={} {:?}",
994 layer,
995 indices.len(),
996 used_targeted_replay,
997 resource_stats
998 );
999 }
1000 self.clear_dirty_render_surfaces(layer, &indices);
1001 }
1002
1003 used_targeted_replay
1004 }
1005
1006 fn clear_targeted_replay_scope(&self, layer: usize) {
1007 self.surface_replay
1008 .borrow_mut()
1009 .clear_targeted_replay_scope(layer);
1010 }
1011
1012 fn targeted_or_all_indices(&self, layer: usize, renderer_count: usize) -> Vec<usize> {
1013 self.surface_replay
1014 .borrow()
1015 .targeted_or_all_indices(layer, renderer_count)
1016 }
1017
1018 fn mark_clean_skipped_node(&self, layer: usize, node_id: u32) {
1019 self.clean_skipped_canvas_nodes
1020 .borrow_mut()
1021 .insert((layer, node_id));
1022 }
1023
1024 fn targeted_replay_surface_bounds(
1025 &self,
1026 layer: usize,
1027 target: &LayerTarget,
1028 ) -> Option<Vec<Rect>> {
1029 self.surface_replay
1030 .borrow()
1031 .targeted_replay_surface_bounds(layer, |index| {
1032 let renderer = target.renderers.get(index)?;
1033 Some(Rect::new(
1034 renderer.origin_x as f64,
1035 renderer.origin_y as f64,
1036 renderer.origin_x as f64 + renderer.logical_width as f64,
1037 renderer.origin_y as f64 + renderer.logical_height as f64,
1038 ))
1039 })
1040 }
1041
1042 fn replay_region_bounds(&self, layer: usize) -> Option<Vec<kurbo::Rect>> {
1043 let backends = self.backends.borrow();
1044 let Some(RenderLayerState::Ready((target, _))) = backends.get(layer) else {
1045 return None;
1046 };
1047 self.targeted_replay_surface_bounds(layer, target)
1048 }
1049
1050 fn take_replay_layer_ids(&mut self) -> Vec<usize> {
1051 let mut replay_layers = self.replay_layers.borrow_mut();
1052 let mut replay = std::mem::take(&mut *replay_layers);
1053 replay.sort_unstable();
1054 replay.dedup();
1055 replay
1056 }
1057
1058 #[cfg(debug_assertions)]
1059 fn update_tile_cull_stats(&self, layer: usize, update: impl FnOnce(&mut TileCullStats)) {
1060 let mut stats = self.tile_cull_stats.borrow_mut();
1061 if stats.len() <= layer {
1062 stats.resize_with(layer + 1, TileCullStats::default);
1063 }
1064 update(&mut stats[layer]);
1065 }
1066
1067 #[cfg(debug_assertions)]
1068 fn emit_tile_cull_stats(&self, layer: usize) {
1069 let stats = self
1070 .tile_cull_stats
1071 .borrow_mut()
1072 .get_mut(layer)
1073 .map(std::mem::take);
1074 if let Some(stats) = stats {
1075 if stats.origin_only_retargets > 0
1076 || stats.targeted_replay_batches > 0
1077 || stats.full_layer_replays > 0
1078 || stats.resize_resets > 0
1079 {
1080 let line = format!(
1081 "[pax-tile-cull] layer={} nodes={} selected_surfaces={} skipped_surfaces={} stale_removal_attempts={} origin_only_retargets={} targeted_replay_batches={} targeted_replay_surfaces={} visible_batches={} warm_batches={} visible_surfaces={} warm_surfaces={} full_layer_replays={} resize_resets={}",
1082 layer,
1083 stats.nodes_considered,
1084 stats.selected_surfaces,
1085 stats.skipped_surfaces,
1086 stats.stale_surface_removal_attempts,
1087 stats.origin_only_retargets,
1088 stats.targeted_replay_batches,
1089 stats.targeted_replay_surfaces,
1090 stats.targeted_replay_visible_batches,
1091 stats.targeted_replay_warm_batches,
1092 stats.targeted_replay_visible_surfaces,
1093 stats.targeted_replay_warm_surfaces,
1094 stats.full_layer_replays,
1095 stats.resize_resets
1096 );
1097 log::trace!("{}", line);
1098 #[cfg(not(target_arch = "wasm32"))]
1099 if should_print_gpu_profile_logs() {
1100 println!("{}", line);
1101 }
1102 }
1103 }
1104 }
1105
1106 fn schedule_layer_initialization(&self) {
1107 pump_layer_initialization_queue(
1108 Rc::clone(&self.layer_factory),
1109 Rc::clone(&self.backends),
1110 Rc::clone(&self.pending_layer_initializations),
1111 Rc::clone(&self.layer_initializations_in_flight),
1112 Rc::clone(&self.ready_layers),
1113 Rc::clone(&self.changed_layers),
1114 Rc::clone(&self.layer_initialization_priority),
1115 );
1116 }
1117
1118 fn refresh_layer_layouts<I>(&mut self, layer_indices: I)
1119 where
1120 I: IntoIterator<Item = usize>,
1121 {
1122 let mut needs_reinitialization = false;
1123 let mut backends = self.backends.borrow_mut();
1124 for layer_index in layer_indices {
1125 let Some(backend) = backends.get_mut(layer_index) else {
1126 continue;
1127 };
1128 match backend {
1129 RenderLayerState::Pending => {}
1130 RenderLayerState::Failed => {}
1131 RenderLayerState::Ready((target, layout_provider)) => {
1132 let layout = (layout_provider)();
1133 let layout_matches = layer_layout_matches_target(target, &layout);
1134
1135 if layout.active {
1136 target.activate();
1137 } else {
1138 target.deactivate();
1139 self.clear_targeted_replay_scope(layer_index);
1140 }
1141
1142 if !layout_matches {
1143 let existing: Vec<_> = target
1144 .renderers
1145 .iter()
1146 .map(|r| (r.key.as_str(), r.host_signature.as_str()))
1147 .collect();
1148 let desired: Vec<_> = layout
1149 .surfaces
1150 .iter()
1151 .map(|s| (s.key.as_str(), s.host_signature.as_str()))
1152 .collect();
1153 let Some(order) = retained_surface_order(&existing, &desired) else {
1154 log::error!("duplicate surface identity in layer {}", layer_index);
1155 continue;
1156 };
1157 let reordered = order.len() != target.renderers.len()
1158 || order.iter().enumerate().any(|(new, old)| new != *old);
1159 if reordered {
1160 count(Counter::TileSetChanged, 1);
1161 count(Counter::TileReused, order.len());
1162 count(
1163 Counter::MatchingTileDiscarded,
1164 existing
1165 .iter()
1166 .enumerate()
1167 .filter(|(index, identity)| {
1168 desired.contains(identity) && !order.contains(index)
1169 })
1170 .count(),
1171 );
1172 self.surface_replay
1173 .borrow_mut()
1174 .remap_surfaces(layer_index, &order);
1175 if let Some(dirty) =
1176 self.dirty_render_surfaces.borrow_mut().get_mut(layer_index)
1177 {
1178 *dirty = order
1179 .iter()
1180 .enumerate()
1181 .filter_map(|(new, old)| dirty.contains(old).then_some(new))
1182 .collect();
1183 }
1184 retain_surfaces(&mut target.renderers, order);
1185 }
1186 if target.renderers.len() < layout.surfaces.len()
1187 && target.failed_layout.as_ref() != Some(&layout)
1188 {
1189 self.queue_layer_initialization(layer_index);
1190 needs_reinitialization = true;
1191 }
1192 }
1193
1194 let mut targeted_replay_entries = Vec::new();
1195 let mut targeted_replay_bounds = HashMap::new();
1196 for (index, bounds) in self
1199 .surface_replay
1200 .borrow()
1201 .pending_replay_surfaces(layer_index)
1202 {
1203 let Some(renderer) = target.renderers.get(index) else {
1204 continue;
1205 };
1206 let Some(surface) = layout
1207 .surfaces
1208 .iter()
1209 .find(|surface| renderer.matches_surface(surface))
1210 else {
1211 continue;
1212 };
1213 let current = renderer.coverage_bounds();
1214 targeted_replay_entries.push(ReplayPriorityEntry::new(
1215 index,
1216 surface.replay_priority,
1217 current,
1218 current,
1219 ));
1220 targeted_replay_bounds.insert(index, bounds);
1221 }
1222 let needs_full_layer_replay = target.needs_replay;
1223 #[cfg(debug_assertions)]
1224 {
1225 let previous_surface_bounds: Vec<_> = target
1226 .renderers
1227 .iter()
1228 .map(LayerRenderer::coverage_bounds)
1229 .collect();
1230 if let Some(escape) =
1231 visible_surface_escape(&previous_surface_bounds, &layout.surfaces)
1232 {
1233 log_visible_surface_escape(layer_index, escape);
1234 }
1235 }
1236 for (index, renderer) in target.renderers.iter_mut().enumerate() {
1237 let Some(surface) = layout.surfaces.iter().find(|surface| {
1238 surface.key == renderer.key
1239 && surface.host_signature == renderer.host_signature
1240 }) else {
1241 continue;
1242 };
1243 if std::mem::take(&mut renderer.needs_initial_replay) {
1244 let bounds = Rect::new(
1245 surface.origin_x as f64,
1246 surface.origin_y as f64,
1247 (surface.origin_x + surface.surface.logical_width) as f64,
1248 (surface.origin_y + surface.surface.logical_height) as f64,
1249 );
1250 targeted_replay_entries.push(ReplayPriorityEntry::new(
1251 index,
1252 surface.replay_priority,
1253 bounds,
1254 bounds,
1255 ));
1256 targeted_replay_bounds.insert(index, vec![bounds]);
1257 if let Some(Some(lighting)) =
1259 self.last_scene_lighting.borrow().get(layer_index)
1260 {
1261 renderer.renderer.set_scene_lighting(
1262 translate_scene_lighting_for_surface(
1263 lighting,
1264 surface.origin_x,
1265 surface.origin_y,
1266 ),
1267 );
1268 }
1269 }
1270 let previous_bounds = renderer.coverage_bounds();
1271 let layout_change = renderer.update_layout(surface);
1272 let current_bounds = renderer.coverage_bounds();
1273 match layout_change {
1274 LayoutChangeKind::Unchanged => {}
1275 LayoutChangeKind::OriginOnly => {
1276 count(Counter::OriginRetargeted, 1);
1277 count(Counter::SceneReset, 1);
1278 targeted_replay_entries.push(ReplayPriorityEntry::new(
1279 index,
1280 surface.replay_priority,
1281 previous_bounds,
1282 current_bounds,
1283 ));
1284 targeted_replay_bounds
1288 .insert(index, vec![previous_bounds, current_bounds]);
1289 renderer.renderer.reset_retained_scene();
1290 renderer
1291 .renderer
1292 .set_surface_transform(Transform2D::from_array([
1293 1.0,
1294 0.0,
1295 0.0,
1296 1.0,
1297 -surface.origin_x,
1298 -surface.origin_y,
1299 ]));
1300 self.replay_layers.borrow_mut().push(layer_index);
1301 }
1302 LayoutChangeKind::Resized => {
1303 count(Counter::TileResized, 1);
1304 count(Counter::SceneReset, 1);
1305 #[cfg(debug_assertions)]
1306 self.update_tile_cull_stats(layer_index, |stats| {
1307 stats.resize_resets += 1;
1308 });
1309 renderer.renderer.reset_retained_scene();
1310 renderer
1311 .renderer
1312 .set_surface_transform(Transform2D::from_array([
1313 1.0,
1314 0.0,
1315 0.0,
1316 1.0,
1317 -surface.origin_x,
1318 -surface.origin_y,
1319 ]));
1320 renderer.renderer.resize_surface(
1321 surface.surface.surface_width as f32,
1322 surface.surface.surface_height as f32,
1323 );
1324 renderer.renderer.set_viewport(
1325 surface.surface.logical_width,
1326 surface.surface.logical_height,
1327 surface.surface.dpr,
1328 );
1329 targeted_replay_entries.push(ReplayPriorityEntry::new(
1330 index,
1331 surface.replay_priority,
1332 previous_bounds,
1333 current_bounds,
1334 ));
1335 targeted_replay_bounds
1336 .insert(index, vec![previous_bounds, current_bounds]);
1337 }
1338 }
1339 }
1340 if needs_full_layer_replay {
1341 self.clear_targeted_replay_scope(layer_index);
1342 #[cfg(debug_assertions)]
1343 self.update_tile_cull_stats(layer_index, |stats| {
1344 stats.full_layer_replays += 1;
1345 });
1346 self.mark_render_surfaces_dirty(layer_index, 0..target.renderers.len());
1347 self.replay_layers.borrow_mut().push(layer_index);
1348 } else if !targeted_replay_entries.is_empty() {
1349 targeted_replay_entries.sort_by_key(|entry| entry.index);
1350 targeted_replay_entries.dedup_by_key(|entry| entry.index);
1351 let replay_batches =
1352 replay_batches_by_directional_priority(&targeted_replay_entries);
1353 #[cfg(debug_assertions)]
1354 let replay_priority_stats =
1355 replay_priority_stats(&targeted_replay_entries, &replay_batches);
1356 #[cfg(debug_assertions)]
1357 self.update_tile_cull_stats(layer_index, |stats| {
1358 stats.origin_only_retargets +=
1359 replay_batches.iter().map(Vec::len).sum::<usize>() as u64;
1360 stats.targeted_replay_batches += replay_batches.len() as u64;
1361 stats.targeted_replay_surfaces +=
1362 replay_batches.iter().map(Vec::len).sum::<usize>() as u64;
1363 stats.targeted_replay_visible_batches += replay_priority_stats.0 as u64;
1364 stats.targeted_replay_warm_batches += replay_priority_stats.1 as u64;
1365 stats.targeted_replay_visible_surfaces +=
1366 replay_priority_stats.2 as u64;
1367 stats.targeted_replay_warm_surfaces += replay_priority_stats.3 as u64;
1368 });
1369 self.set_targeted_replay_batches(
1370 layer_index,
1371 replay_batches,
1372 targeted_replay_bounds,
1373 );
1374 self.replay_layers.borrow_mut().push(layer_index);
1375 }
1376 }
1377 }
1378 }
1379 drop(backends);
1380 if needs_reinitialization {
1381 self.schedule_layer_initialization();
1382 }
1383 }
1384}
1385
1386impl RenderContext for PaxGpuRenderer {
1387 fn fill_with_opacity(
1388 &mut self,
1389 layer: usize,
1390 path: kurbo::BezPath,
1391 fill: &pax_runtime_api::Paint,
1392 opacity: f64,
1393 ) {
1394 self.with_layer_context(layer, |context| {
1395 let bounds = path.bounding_box();
1396 let path = convert_kurbo_to_lyon_path(&path);
1397 let fill = to_pax_gpu_fill(fill, bounds, context.current_transform());
1398 context.fill_path_with_opacity(path, fill, opacity as f32);
1399 });
1400 }
1401
1402 fn fill_with_material_and_opacity(
1403 &mut self,
1404 layer: usize,
1405 path: kurbo::BezPath,
1406 fill: &pax_runtime_api::Paint,
1407 material: &ResolvedMaterial,
1408 opacity: f64,
1409 ) {
1410 self.with_layer_context(layer, |context| {
1411 let bounds = path.bounding_box();
1412 let path = convert_kurbo_to_lyon_path(&path);
1413 let fill = to_pax_gpu_fill(fill, bounds, context.current_transform());
1414 let material = to_pax_gpu_material(material);
1415 context.fill_path_with_material_and_opacity(path, fill, material, opacity as f32);
1416 });
1417 }
1418
1419 fn fill_with_material_and_opacity_and_smoothing(
1420 &mut self,
1421 layer: usize,
1422 path: kurbo::BezPath,
1423 fill: &pax_runtime_api::Paint,
1424 material: &ResolvedMaterial,
1425 opacity: f64,
1426 smoothing: PathSmoothing,
1427 ) {
1428 self.with_layer_context(layer, |context| {
1429 let bounds =
1430 pax_runtime_api::drawing::path_smoothing::smooth_bez_path(&path, smoothing)
1431 .bounding_box();
1432 let path = convert_kurbo_to_lyon_path(&path);
1433 let fill = to_pax_gpu_fill(fill, bounds, context.current_transform());
1434 let material = to_pax_gpu_material(material);
1435 context.fill_path_with_material_and_opacity_and_smoothing(
1436 path,
1437 fill,
1438 material,
1439 opacity as f32,
1440 smoothing,
1441 );
1442 });
1443 }
1444
1445 fn stroke_with_opacity(
1446 &mut self,
1447 layer: usize,
1448 path: kurbo::BezPath,
1449 stroke: &ResolvedStroke,
1450 opacity: f64,
1451 ) {
1452 self.with_layer_context(layer, |context| {
1453 let bounds = path.bounding_box();
1454 context.stroke_path_with_opacity(
1455 convert_kurbo_to_lyon_path(&path),
1456 PixelStroke {
1457 fill: to_pax_gpu_fill(&stroke.paint, bounds, context.current_transform()),
1458 weight: stroke.width_pixels() as f32,
1459 cap: match stroke.cap {
1460 StrokeCap::Butt => PixelStrokeCap::Butt,
1461 StrokeCap::Round => PixelStrokeCap::Round,
1462 StrokeCap::Square => PixelStrokeCap::Square,
1463 },
1464 join: match stroke.join {
1465 StrokeJoin::Miter => PixelStrokeJoin::Miter,
1466 StrokeJoin::Round => PixelStrokeJoin::Round,
1467 StrokeJoin::Bevel => PixelStrokeJoin::Bevel,
1468 },
1469 },
1470 opacity as f32,
1471 );
1472 });
1473 }
1474
1475 fn stroke_with_material_and_opacity(
1476 &mut self,
1477 layer: usize,
1478 path: kurbo::BezPath,
1479 stroke: &ResolvedStroke,
1480 material: &ResolvedMaterial,
1481 opacity: f64,
1482 ) {
1483 self.with_layer_context(layer, |context| {
1484 let bounds = path.bounding_box();
1485 context.stroke_path_with_material_and_opacity(
1486 convert_kurbo_to_lyon_path(&path),
1487 PixelStroke {
1488 fill: to_pax_gpu_fill(&stroke.paint, bounds, context.current_transform()),
1489 weight: stroke.width_pixels() as f32,
1490 cap: match stroke.cap {
1491 StrokeCap::Butt => PixelStrokeCap::Butt,
1492 StrokeCap::Round => PixelStrokeCap::Round,
1493 StrokeCap::Square => PixelStrokeCap::Square,
1494 },
1495 join: match stroke.join {
1496 StrokeJoin::Miter => PixelStrokeJoin::Miter,
1497 StrokeJoin::Round => PixelStrokeJoin::Round,
1498 StrokeJoin::Bevel => PixelStrokeJoin::Bevel,
1499 },
1500 },
1501 to_pax_gpu_material(material),
1502 opacity as f32,
1503 );
1504 });
1505 }
1506
1507 fn stroke_with_material_and_opacity_and_smoothing(
1508 &mut self,
1509 layer: usize,
1510 path: kurbo::BezPath,
1511 stroke: &ResolvedStroke,
1512 material: &ResolvedMaterial,
1513 opacity: f64,
1514 smoothing: PathSmoothing,
1515 ) {
1516 self.with_layer_context(layer, |context| {
1517 let bounds =
1518 pax_runtime_api::drawing::path_smoothing::smooth_bez_path(&path, smoothing)
1519 .bounding_box();
1520 context.stroke_path_with_material_and_opacity_and_smoothing(
1521 convert_kurbo_to_lyon_path(&path),
1522 PixelStroke {
1523 fill: to_pax_gpu_fill(&stroke.paint, bounds, context.current_transform()),
1524 weight: stroke.width_pixels() as f32,
1525 cap: match stroke.cap {
1526 StrokeCap::Butt => PixelStrokeCap::Butt,
1527 StrokeCap::Round => PixelStrokeCap::Round,
1528 StrokeCap::Square => PixelStrokeCap::Square,
1529 },
1530 join: match stroke.join {
1531 StrokeJoin::Miter => PixelStrokeJoin::Miter,
1532 StrokeJoin::Round => PixelStrokeJoin::Round,
1533 StrokeJoin::Bevel => PixelStrokeJoin::Bevel,
1534 },
1535 },
1536 to_pax_gpu_material(material),
1537 opacity as f32,
1538 smoothing,
1539 );
1540 });
1541 }
1542
1543 fn stroke_with_draw_range_and_material_and_opacity(
1544 &mut self,
1545 layer: usize,
1546 path: kurbo::BezPath,
1547 stroke: &ResolvedStroke,
1548 material: &ResolvedMaterial,
1549 opacity: f64,
1550 draw_start: f64,
1551 draw_end: f64,
1552 ) {
1553 self.with_layer_context(layer, |context| {
1554 let bounds = path.bounding_box();
1555 context.stroke_path_with_draw_range_and_material_and_opacity(
1556 convert_kurbo_to_lyon_path(&path),
1557 PixelStroke {
1558 fill: to_pax_gpu_fill(&stroke.paint, bounds, context.current_transform()),
1559 weight: stroke.width_pixels() as f32,
1560 cap: match stroke.cap {
1561 StrokeCap::Butt => PixelStrokeCap::Butt,
1562 StrokeCap::Round => PixelStrokeCap::Round,
1563 StrokeCap::Square => PixelStrokeCap::Square,
1564 },
1565 join: match stroke.join {
1566 StrokeJoin::Miter => PixelStrokeJoin::Miter,
1567 StrokeJoin::Round => PixelStrokeJoin::Round,
1568 StrokeJoin::Bevel => PixelStrokeJoin::Bevel,
1569 },
1570 },
1571 to_pax_gpu_material(material),
1572 opacity as f32,
1573 PixelDrawRange::enabled(draw_start as f32, draw_end as f32),
1574 );
1575 });
1576 }
1577
1578 fn stroke_with_draw_range_and_material_and_opacity_and_smoothing(
1579 &mut self,
1580 layer: usize,
1581 path: kurbo::BezPath,
1582 stroke: &ResolvedStroke,
1583 material: &ResolvedMaterial,
1584 opacity: f64,
1585 draw_start: f64,
1586 draw_end: f64,
1587 smoothing: PathSmoothing,
1588 ) {
1589 self.with_layer_context(layer, |context| {
1590 let bounds =
1591 pax_runtime_api::drawing::path_smoothing::smooth_bez_path(&path, smoothing)
1592 .bounding_box();
1593 context.stroke_path_with_draw_range_and_material_and_opacity_and_smoothing(
1594 convert_kurbo_to_lyon_path(&path),
1595 PixelStroke {
1596 fill: to_pax_gpu_fill(&stroke.paint, bounds, context.current_transform()),
1597 weight: stroke.width_pixels() as f32,
1598 cap: match stroke.cap {
1599 StrokeCap::Butt => PixelStrokeCap::Butt,
1600 StrokeCap::Round => PixelStrokeCap::Round,
1601 StrokeCap::Square => PixelStrokeCap::Square,
1602 },
1603 join: match stroke.join {
1604 StrokeJoin::Miter => PixelStrokeJoin::Miter,
1605 StrokeJoin::Round => PixelStrokeJoin::Round,
1606 StrokeJoin::Bevel => PixelStrokeJoin::Bevel,
1607 },
1608 },
1609 to_pax_gpu_material(material),
1610 opacity as f32,
1611 PixelDrawRange::enabled(draw_start as f32, draw_end as f32),
1612 smoothing,
1613 );
1614 });
1615 }
1616
1617 fn save(&mut self, layer: usize) {
1618 self.with_layer_context(layer, |context| {
1619 context.save();
1620 });
1621 }
1622 fn restore(&mut self, layer: usize) {
1623 self.with_layer_context(layer, |context| {
1624 context.restore();
1625 });
1626 }
1627 fn clip(&mut self, layer: usize, path: kurbo::BezPath) {
1628 self.with_layer_context(layer, |context| {
1629 let path = convert_kurbo_to_lyon_path(&path);
1630 context.clip(path);
1631 });
1632 }
1633 fn clip_alpha(
1634 &mut self,
1635 layer: usize,
1636 paints: &[pax_runtime_api::AlphaMaskPaint],
1637 feather: f64,
1638 ) {
1639 self.with_layer_context(layer, |context| {
1640 let paints = paints
1641 .iter()
1642 .map(|paint| {
1643 let transform =
1644 Transform2D::from_array(paint.transform.as_coeffs().map(|v| v as f32));
1645 pax_gpu::AlphaMaskPaint {
1646 path: convert_kurbo_to_lyon_path(&paint.path),
1647 fill: to_pax_gpu_alpha_fill(
1648 &paint.fill,
1649 paint.paint_bounds,
1650 transform.then(&context.current_transform()),
1651 ),
1652 transform,
1653 opacity: paint.opacity.clamp(0.0, 1.0) as f32,
1654 composition: paint.composition,
1655 }
1656 })
1657 .collect();
1658 context.clip_alpha(paints, feather.max(0.0) as f32);
1659 });
1660 }
1661
1662 fn transform(&mut self, layer: usize, affine: kurbo::Affine) {
1663 self.with_layer_context(layer, |context| {
1664 context.transform(Transform2D::from_array(
1665 affine.as_coeffs().map(|v| v as f32),
1666 ))
1667 });
1668 }
1669
1670 fn load_image(&mut self, identifier: &str, image: &[u8], width: usize, height: usize) {
1671 self.image_map.insert(
1672 identifier.to_string(),
1673 Image {
1674 rgba: image.into(),
1675 pixel_width: width as u32,
1676 pixel_height: height as u32,
1677 },
1678 );
1679 *self
1680 .image_versions
1681 .entry(identifier.to_string())
1682 .or_insert(0) += 1;
1683 }
1684
1685 fn draw_image_with_opacity(
1686 &mut self,
1687 layer: usize,
1688 image_path: &str,
1689 rect: kurbo::Rect,
1690 opacity: f64,
1691 ) {
1692 self.with_layer_context(layer, |context| {
1693 if let Some(image) = self.image_map.get(image_path) {
1694 let version = *self.image_versions.get(image_path).unwrap_or(&0);
1695 context.draw_image_with_opacity(
1696 image_path,
1697 version,
1698 image,
1699 Box2D {
1700 min: point(rect.x0 as f32, rect.y0 as f32),
1701 max: point(rect.x1 as f32, rect.y1 as f32),
1702 },
1703 opacity as f32,
1704 );
1705 }
1706 });
1707 }
1708
1709 fn get_image_size(&mut self, image_path: &str) -> Option<(usize, usize)> {
1710 self.image_map
1711 .get(image_path)
1712 .map(|img| (img.pixel_width as usize, img.pixel_height as usize))
1713 }
1714
1715 fn image_loaded(&self, image_path: &str) -> bool {
1716 self.image_map.contains_key(image_path)
1717 }
1718
1719 fn layers(&self) -> usize {
1720 self.backends.borrow().len()
1721 }
1722
1723 fn resize_layers_to(&mut self, layer_count: usize, _dirty_canvases: Rc<RefCell<Vec<bool>>>) {
1724 let current_len = self.backends.borrow().len();
1725 match layer_count.cmp(¤t_len) {
1726 std::cmp::Ordering::Less => {
1727 for initialization in self.layer_initializations.drain(layer_count..) {
1728 initialization.cancelled.set(true);
1729 }
1730 self.pending_layer_initializations
1731 .borrow_mut()
1732 .retain(|initialization| !initialization.cancelled.get());
1733 self.backends.borrow_mut().truncate(layer_count);
1734 self.ready_layers
1735 .borrow_mut()
1736 .retain(|layer| *layer < layer_count);
1737 self.changed_layers
1738 .borrow_mut()
1739 .retain(|layer| *layer < layer_count);
1740 self.replay_layers
1741 .borrow_mut()
1742 .retain(|layer| *layer < layer_count);
1743 self.failed_context_gets.borrow_mut().truncate(layer_count);
1744 self.active_render_scopes.borrow_mut().truncate(layer_count);
1745 self.dirty_render_surfaces
1746 .borrow_mut()
1747 .truncate(layer_count);
1748 self.last_scene_lighting.borrow_mut().truncate(layer_count);
1749 self.surface_replay
1750 .borrow_mut()
1751 .truncate_layers(layer_count);
1752 self.clean_skipped_canvas_nodes
1753 .borrow_mut()
1754 .retain(|(layer, _)| *layer < layer_count);
1755 #[cfg(debug_assertions)]
1756 self.tile_cull_stats.borrow_mut().truncate(layer_count);
1757 }
1758 std::cmp::Ordering::Equal => return,
1759 std::cmp::Ordering::Greater => {
1760 for i in current_len..layer_count {
1761 self.backends.borrow_mut().push(RenderLayerState::Pending);
1762 self.layer_initializations
1763 .push(Rc::new(LayerInitialization::new(i)));
1764 self.enqueue_layer_initialization(i);
1765 }
1766 }
1767 }
1768 }
1769
1770 fn clear(&mut self, layer: usize) {
1771 let mut backends = self.backends.borrow_mut();
1772 match backends.get_mut(layer) {
1773 Some(RenderLayerState::Pending) => {
1774 let mut failed_context_gets = self.failed_context_gets.borrow_mut();
1775 if failed_context_gets.len() <= layer {
1776 failed_context_gets.resize(layer + 1, false);
1777 }
1778 failed_context_gets[layer] = true;
1779 }
1780 Some(RenderLayerState::Failed) => {}
1781 Some(RenderLayerState::Ready((target, _))) => {
1782 if !target.active {
1783 return;
1784 }
1785 target.prepare_for_render();
1786 let indices = self.targeted_or_all_indices(layer, target.renderers.len());
1787 for index in &indices {
1788 if let Some(renderer) = target.renderers.get_mut(*index) {
1789 renderer.renderer.clear();
1790 }
1791 }
1792 self.mark_render_surfaces_dirty(layer, indices);
1793 }
1794 None => log::warn!(
1795 "tried to clear layer {} context for non-existent layer",
1796 layer
1797 ),
1798 }
1799 }
1800
1801 fn flush(&mut self, layer: usize, dirty_canvases: Rc<RefCell<Vec<bool>>>) {
1802 #[cfg(debug_assertions)]
1803 self.emit_tile_cull_stats(layer);
1804
1805 let mut flushed_targeted_batch = false;
1806 let mut backends = self.backends.borrow_mut();
1807 match backends.get_mut(layer) {
1808 Some(RenderLayerState::Pending) => {}
1809 Some(RenderLayerState::Failed) => {}
1810 Some(RenderLayerState::Ready((target, _))) => {
1811 if !target.active {
1812 return;
1813 }
1814 if let Some(failed) = self.failed_context_gets.borrow_mut().get_mut(layer) {
1815 if *failed {
1816 if let Some(dirty_bit) = dirty_canvases.borrow_mut().get_mut(layer) {
1817 *dirty_bit = true;
1820 }
1821 *failed = false;
1822 }
1823 }
1824 flushed_targeted_batch = self.flush_targeted_or_dirty(layer, target);
1825 }
1826 None => log::warn!(
1827 "tried to flush layer {} context for non-existent layer",
1828 layer
1829 ),
1830 }
1831 drop(backends);
1832
1833 if flushed_targeted_batch && self.advance_targeted_replay_queue(layer) {
1834 self.replay_layers.borrow_mut().push(layer);
1835 }
1836 }
1837
1838 fn resize(&mut self, _width: usize, _height: usize) {
1839 let layer_count = self.backends.borrow().len();
1840 self.refresh_layer_layouts(0..layer_count);
1841 }
1842
1843 fn refresh_layers(&mut self, layers: &[usize]) {
1844 self.refresh_layer_layouts(layers.iter().copied());
1845 }
1846
1847 fn set_scene_lighting(&mut self, layer: usize, lighting: &SceneLighting) {
1848 let lighting = to_pax_gpu_scene_lighting(lighting);
1849 self.update_scene_lighting_cache(layer, &lighting);
1850 let mut backends = self.backends.borrow_mut();
1851 match backends.get_mut(layer) {
1852 Some(RenderLayerState::Pending) => {}
1853 Some(RenderLayerState::Failed) => {}
1854 Some(RenderLayerState::Ready((target, _))) => {
1855 if !target.active {
1856 return;
1857 }
1858 for (index, renderer) in target.renderers.iter_mut().enumerate() {
1859 if renderer
1860 .renderer
1861 .set_scene_lighting(translate_scene_lighting_for_surface(
1862 &lighting,
1863 renderer.origin_x,
1864 renderer.origin_y,
1865 ))
1866 {
1867 self.mark_render_surfaces_dirty(layer, [index]);
1868 }
1869 }
1870 }
1871 None => log::warn!(
1872 "tried to set lighting for layer {} context for non-existent layer",
1873 layer
1874 ),
1875 }
1876 }
1877
1878 fn clear_targeted_replay(&mut self, layer: usize) {
1879 self.clear_targeted_replay_scope(layer);
1880 }
1881
1882 fn take_ready_canvas_layers(&mut self) -> Vec<usize> {
1883 let mut ready = Vec::new();
1884 for _ in 0..MAX_CONCURRENT_LAYER_INITIALIZATIONS {
1887 let newly_ready = std::mem::take(&mut *self.ready_layers.borrow_mut());
1888 let mut changed = std::mem::take(&mut *self.changed_layers.borrow_mut());
1889 changed.extend(newly_ready.iter().copied());
1890 ready.extend(newly_ready);
1891 changed.sort_unstable();
1892 changed.dedup();
1893 if changed.is_empty() {
1894 break;
1895 }
1896 self.refresh_layer_layouts(changed);
1897 }
1898 ready.sort_unstable();
1899 ready.dedup();
1900 for layer in &ready {
1901 self.clear_targeted_replay_scope(*layer);
1902 }
1903 ready
1904 }
1905
1906 fn take_replay_canvas_layers(&mut self) -> Vec<usize> {
1907 self.take_replay_layer_ids()
1908 }
1909
1910 fn take_replay_canvas_layer_updates(&mut self) -> Vec<ReplayCanvasLayerUpdate> {
1911 self.take_replay_layer_ids()
1912 .into_iter()
1913 .map(|layer| ReplayCanvasLayerUpdate {
1914 layer,
1915 regions: self.replay_region_bounds(layer),
1916 })
1917 .collect()
1918 }
1919
1920 fn request_layer_screenshot(&mut self, layer: usize, request_id: u32) {
1921 self.with_layer_context(layer, |context| {
1922 context.request_screenshot_capture(request_id);
1923 });
1924 self.replay_layers.borrow_mut().push(layer);
1925 }
1926
1927 fn take_layer_screenshot(&mut self, layer: usize, request_id: u32) -> Option<ScreenshotData> {
1928 let mut screenshot = None;
1929 self.with_layer_context(layer, |context| {
1930 screenshot =
1931 context
1932 .take_screenshot_capture(request_id)
1933 .map(|capture| ScreenshotData {
1934 id: request_id,
1935 data: capture.rgba,
1936 width: capture.width as usize,
1937 height: capture.height as usize,
1938 });
1939 });
1940 screenshot
1941 }
1942
1943 fn take_layer_surface_screenshots(
1944 &mut self,
1945 layer: usize,
1946 request_id: u32,
1947 ) -> Vec<LayerSurfaceScreenshotData> {
1948 let mut screenshots = Vec::new();
1949 let mut backends = self.backends.borrow_mut();
1950 let Some(RenderLayerState::Ready((target, _))) = backends.get_mut(layer) else {
1951 return screenshots;
1952 };
1953 if !target.active {
1954 return screenshots;
1955 }
1956
1957 for renderer in target.renderers_mut() {
1958 let Some(capture) = renderer.renderer_mut().take_screenshot_capture(request_id) else {
1959 continue;
1960 };
1961 screenshots.push(LayerSurfaceScreenshotData {
1962 id: request_id,
1963 key: renderer.key.clone(),
1964 data: capture.rgba,
1965 width: capture.width as usize,
1966 height: capture.height as usize,
1967 origin_x: renderer.origin_x,
1968 origin_y: renderer.origin_y,
1969 logical_width: renderer.logical_width,
1970 logical_height: renderer.logical_height,
1971 });
1972 }
1973 screenshots
1974 }
1975
1976 #[cfg(feature = "shaders")]
1977 fn begin_shader_effect(
1978 &mut self,
1979 layer: usize,
1980 id: u32,
1981 z: i32,
1982 effect: &pax_runtime_api::ShaderEffect,
1983 transform: kurbo::Affine,
1984 bounds: (f64, f64),
1985 scopes: &[pax_runtime_api::OpacityScope],
1986 children: &[u32],
1987 ) -> bool {
1988 let inherited = self
1989 .shader_effect_scopes
1990 .last()
1991 .map_or(0, |s| s.1)
1992 .min(scopes.len());
1993 let mut replay = false;
1994 let mut selected = Vec::new();
1995 {
1996 let mut backends = self.backends.borrow_mut();
1997 if let Some(RenderLayerState::Ready((target, _))) = backends.get_mut(layer) {
1998 if target.active {
1999 target.prepare_for_render();
2000 selected = self.active_render_scope(layer).unwrap_or_else(|| {
2001 self.targeted_or_all_indices(layer, target.renderers.len())
2002 });
2003 for index in &selected {
2004 replay |= target.renderers[*index].renderer.begin_shader_effect(
2005 id,
2006 z,
2007 effect,
2008 Transform2D::from_array(transform.as_coeffs().map(|v| v as f32)),
2009 bounds,
2010 &scopes[inherited..],
2011 children,
2012 );
2013 }
2014 }
2015 }
2016 }
2017 self.mark_render_surfaces_dirty(layer, selected.iter().copied());
2018 self.push_render_scope(layer, selected);
2019 self.shader_effect_scopes.push((layer, scopes.len()));
2020 replay
2021 }
2022
2023 #[cfg(feature = "shaders")]
2024 fn end_shader_effect(&mut self, layer: usize, _id: u32) {
2025 self.with_layer_context(layer, |context| context.end_shader_effect());
2026 assert_eq!(self.shader_effect_scopes.pop().map(|s| s.0), Some(layer));
2027 self.pop_render_scope(layer);
2028 }
2029
2030 fn supports_alpha_source_capture(&self) -> bool {
2031 true
2032 }
2033
2034 fn alpha_source_layer(&self) -> Option<usize> {
2035 self.alpha_source_scopes.last().map(|&(layer, _)| layer)
2036 }
2037
2038 fn begin_alpha_source(
2039 &mut self,
2040 layer: usize,
2041 mapping: kurbo::Affine,
2042 inherited_scopes: usize,
2043 feather: f64,
2044 ) {
2045 self.with_layer_context(layer, |context| {
2046 context.begin_alpha_source(
2047 Transform2D::from_array(mapping.as_coeffs().map(|v| v as f32)),
2048 feather as f32,
2049 )
2050 });
2051 self.alpha_source_scopes.push((layer, inherited_scopes));
2052 }
2053
2054 fn end_alpha_source(&mut self, layer: usize) {
2055 self.with_layer_context(layer, |context| context.end_alpha_source());
2056 assert_eq!(self.alpha_source_scopes.pop().map(|s| s.0), Some(layer));
2057 }
2058
2059 fn supports_subtree_opacity(&self) -> bool {
2060 true
2061 }
2062
2063 fn set_node_opacity_scopes(
2064 &mut self,
2065 layer: usize,
2066 node_id: u32,
2067 scopes: &[pax_runtime_api::OpacityScope],
2068 ) {
2069 let inherited = self.alpha_source_scopes.last();
2070 #[cfg(feature = "shaders")]
2071 let inherited = inherited.or_else(|| self.shader_effect_scopes.last());
2072 let scopes = &scopes[inherited.map_or(0, |s| s.1).min(scopes.len())..];
2073 self.with_layer_context(layer, |context| {
2074 context.set_node_opacity_scopes(node_id, scopes)
2075 });
2076 }
2077
2078 fn begin_node(&mut self, layer: usize, node_id: u32, z_index: i32, light_mask: u32) -> bool {
2079 if self.is_capturing_source() {
2080 let mut began = false;
2081 self.with_layer_context(layer, |context| {
2082 began |= context.begin_node(node_id, z_index, light_mask);
2083 });
2084 if began {
2085 let indices = self
2086 .active_render_scope(layer)
2087 .expect("source has an owning mask scope");
2088 self.push_render_scope(layer, indices);
2089 }
2090 return began;
2091 }
2092 let mut backends = self.backends.borrow_mut();
2093 match backends.get_mut(layer) {
2094 Some(RenderLayerState::Pending) => {
2095 let mut failed_context_gets = self.failed_context_gets.borrow_mut();
2096 if failed_context_gets.len() <= layer {
2097 failed_context_gets.resize(layer + 1, false);
2098 }
2099 failed_context_gets[layer] = true;
2100 false
2101 }
2102 Some(RenderLayerState::Failed) => false,
2103 Some(RenderLayerState::Ready((target, _))) => {
2104 if !target.active {
2105 return false;
2106 }
2107 target.prepare_for_render();
2108 let candidate_indices = self.targeted_or_all_indices(layer, target.renderers.len());
2109 let mut selected = Vec::new();
2110 for index in candidate_indices {
2111 let Some(renderer) = target.renderers.get_mut(index) else {
2112 continue;
2113 };
2114 if renderer.renderer.begin_node(node_id, z_index, light_mask) {
2115 selected.push(index);
2116 }
2117 }
2118 let began = !selected.is_empty();
2119 if began {
2120 self.mark_render_surfaces_dirty(layer, selected.iter().copied());
2121 self.push_render_scope(layer, selected);
2122 }
2123 began
2124 }
2125 None => {
2126 log::warn!(
2127 "tried to retrieve layer {} context for non-existent layer",
2128 layer
2129 );
2130 false
2131 }
2132 }
2133 }
2134
2135 fn begin_node_with_bounds(
2136 &mut self,
2137 layer: usize,
2138 node_id: u32,
2139 z_index: i32,
2140 coverage_bounds: Rect,
2141 light_mask: u32,
2142 ) -> bool {
2143 if self.is_capturing_source() {
2144 return self.begin_node(layer, node_id, z_index, light_mask);
2146 }
2147 let mut backends = self.backends.borrow_mut();
2148 match backends.get_mut(layer) {
2149 Some(RenderLayerState::Pending) => {
2150 let mut failed_context_gets = self.failed_context_gets.borrow_mut();
2151 if failed_context_gets.len() <= layer {
2152 failed_context_gets.resize(layer + 1, false);
2153 }
2154 failed_context_gets[layer] = true;
2155 false
2156 }
2157 Some(RenderLayerState::Failed) => false,
2158 Some(RenderLayerState::Ready((target, _))) => {
2159 if !target.active {
2160 return false;
2161 }
2162 target.prepare_for_render();
2163 let renderer_count = target.renderers.len();
2164 let candidate_indices = self.targeted_or_all_indices(layer, renderer_count);
2165 let candidate_indices: HashSet<_> = candidate_indices.into_iter().collect();
2166 #[cfg(debug_assertions)]
2167 self.update_tile_cull_stats(layer, |stats| {
2168 stats.nodes_considered += 1;
2169 });
2170 let mut selected = Vec::new();
2171 let mut removed = Vec::new();
2172 #[cfg(debug_assertions)]
2173 let mut skipped_surfaces = 0u64;
2174 #[cfg(debug_assertions)]
2175 let mut stale_surface_removal_attempts = 0u64;
2176 for (index, renderer) in target.renderers.iter_mut().enumerate() {
2177 let intersects = renderer.intersects_coverage_bounds(&coverage_bounds);
2178 if !intersects {
2179 #[cfg(debug_assertions)]
2180 {
2181 skipped_surfaces += 1;
2182 stale_surface_removal_attempts += 1;
2183 }
2184 if renderer.renderer.remove_node(node_id) {
2187 removed.push(index);
2188 }
2189 continue;
2190 }
2191 if !candidate_indices.contains(&index) {
2192 #[cfg(debug_assertions)]
2193 {
2194 skipped_surfaces += 1;
2195 }
2196 continue;
2197 }
2198 if renderer.renderer.begin_node(node_id, z_index, light_mask) {
2199 selected.push(index);
2200 }
2201 }
2202 let began = !selected.is_empty();
2203 self.mark_render_surfaces_dirty(
2204 layer,
2205 selected.iter().chain(removed.iter()).copied(),
2206 );
2207 #[cfg(debug_assertions)]
2208 self.update_tile_cull_stats(layer, |stats| {
2209 let selected_count = selected.len() as u64;
2210 stats.selected_surfaces += selected_count;
2211 stats.skipped_surfaces += skipped_surfaces;
2212 stats.stale_surface_removal_attempts += stale_surface_removal_attempts;
2213 });
2214 if began {
2215 self.push_render_scope(layer, selected);
2216 } else {
2217 self.mark_clean_skipped_node(layer, node_id);
2218 }
2219 began
2220 }
2221 None => {
2222 log::warn!(
2223 "tried to retrieve layer {} context for non-existent layer",
2224 layer
2225 );
2226 false
2227 }
2228 }
2229 }
2230
2231 fn take_clean_skipped_node(&mut self, layer: usize, node_id: u32) -> bool {
2232 self.clean_skipped_canvas_nodes
2233 .borrow_mut()
2234 .remove(&(layer, node_id))
2235 }
2236
2237 fn end_node(&mut self, layer: usize, node_id: u32) -> bool {
2238 let mut ended = false;
2239 self.with_layer_context(layer, |context| {
2240 ended = context.end_node(node_id);
2241 });
2242 self.pop_render_scope(layer);
2243 ended
2244 }
2245
2246 fn remove_node(&mut self, layer: usize, node_id: u32) -> bool {
2247 let mut backends = self.backends.borrow_mut();
2248 match backends.get_mut(layer) {
2249 Some(RenderLayerState::Pending) => {
2250 let mut failed_context_gets = self.failed_context_gets.borrow_mut();
2251 if failed_context_gets.len() <= layer {
2252 failed_context_gets.resize(layer + 1, false);
2253 }
2254 failed_context_gets[layer] = true;
2255 false
2256 }
2257 Some(RenderLayerState::Failed) => false,
2258 Some(RenderLayerState::Ready((target, _))) => {
2259 if !target.active {
2260 return false;
2261 }
2262 target.prepare_for_render();
2263 let candidate_indices = 0..target.renderers.len();
2264 let mut removed_indices = Vec::new();
2265 for index in candidate_indices {
2266 let Some(renderer) = target.renderers.get_mut(index) else {
2267 continue;
2268 };
2269 if renderer.renderer.remove_node(node_id) {
2270 removed_indices.push(index);
2271 }
2272 }
2273 let removed = !removed_indices.is_empty();
2274 if removed {
2275 self.mark_render_surfaces_dirty(layer, removed_indices);
2276 }
2277 true
2278 }
2279 None => {
2280 log::warn!(
2281 "tried to remove node from layer {} context for non-existent layer",
2282 layer
2283 );
2284 false
2285 }
2286 }
2287 }
2288}
2289
2290fn to_pax_gpu_fill(
2291 fill: &pax_runtime_api::Paint,
2292 rect: kurbo::Rect,
2293 transform: pax_gpu::Transform2D,
2294) -> pax_gpu::Fill {
2295 let Some(rect) = pax_runtime_api::drawing::paint_bounds(rect) else {
2296 return pax_gpu::Fill::Solid(pax_gpu::Color::rgba(0.0, 0.0, 0.0, 0.0));
2297 };
2298 let bounds = (rect.width(), rect.height());
2299 let orig = rect.origin();
2300 match fill {
2301 pax_runtime_api::Paint::MeshGradient(mesh) => {
2302 if let Err(error) = mesh.validate() {
2303 thread_local! { static REPORTED: std::cell::RefCell<std::collections::HashSet<String>> = std::cell::RefCell::new(std::collections::HashSet::new()); }
2304 REPORTED.with(|reported| {
2305 if reported.borrow_mut().insert(error.clone()) {
2306 log::warn!("{error}; mesh paints no field");
2307 }
2308 });
2309 return pax_gpu::Fill::Solid(pax_gpu::Color::rgba(0.0, 0.0, 0.0, 0.0));
2310 }
2311 let pos = transform.transform_point(point(orig.x as f32, orig.y as f32));
2312 let main_axis =
2313 transform.transform_vector(pax_gpu::Vector2D::new(bounds.0 as f32, 0.0));
2314 let off_axis = transform.transform_vector(pax_gpu::Vector2D::new(0.0, bounds.1 as f32));
2315 let determinant = main_axis.cross(off_axis);
2316 if ![
2317 pos.x,
2318 pos.y,
2319 main_axis.x,
2320 main_axis.y,
2321 off_axis.x,
2322 off_axis.y,
2323 determinant,
2324 ]
2325 .iter()
2326 .all(|v| v.is_finite())
2327 || determinant.abs() < 1e-6
2328 {
2329 return pax_gpu::Fill::Solid(pax_gpu::Color::rgba(0.0, 0.0, 0.0, 0.0));
2330 }
2331 pax_gpu::Fill::Mesh {
2332 rows: mesh
2333 .rows
2334 .iter()
2335 .map(|row| {
2336 row.iter()
2337 .map(|p| {
2338 (
2339 [
2340 (p.position.0.evaluate(bounds, Axis::X) / bounds.0) as f32,
2341 (p.position.1.evaluate(bounds, Axis::Y) / bounds.1) as f32,
2342 ],
2343 to_pax_gpu_color(&p.color),
2344 )
2345 })
2346 .collect()
2347 })
2348 .collect(),
2349 pos,
2350 main_axis,
2351 off_axis,
2352 }
2353 }
2354 pax_runtime_api::Paint::Blend(terms) => pax_gpu::Fill::Blend(
2355 terms
2356 .iter()
2357 .map(|(fill, weight)| (to_pax_gpu_fill(fill, rect, transform), *weight as f32))
2358 .collect(),
2359 ),
2360 pax_runtime_api::Paint::Solid(color) => pax_gpu::Fill::Solid(to_pax_gpu_color(color)),
2361 pax_runtime_api::Paint::LinearGradient(gradient) => {
2362 let start_x = gradient.start.0.evaluate(bounds, Axis::X);
2363 let start_y = gradient.start.1.evaluate(bounds, Axis::Y);
2364 let end_x = gradient.end.0.evaluate(bounds, Axis::X);
2365 let end_y = gradient.end.1.evaluate(bounds, Axis::Y);
2366 let local_pos =
2367 pax_gpu::Point2D::new((orig.x + start_x) as f32, (orig.y + start_y) as f32);
2368 let local_end = pax_gpu::Point2D::new((orig.x + end_x) as f32, (orig.y + end_y) as f32);
2369 let world_pos = transform.transform_point(local_pos);
2370 let world_end = transform.transform_point(local_end);
2371 let main_axis = world_end - world_pos;
2372 let local_axis = local_end - local_pos;
2373 let local_length = local_axis.length();
2374 let off_axis =
2375 transform.transform_vector(pax_gpu::Vector2D::new(-local_axis.y, local_axis.x));
2376 if local_length <= f32::EPSILON
2377 || !local_length.is_finite()
2378 || !main_axis.length().is_finite()
2379 {
2380 return pax_gpu::Fill::Solid(pax_gpu::Color::rgba(0.0, 0.0, 0.0, 0.0));
2381 }
2382 pax_gpu::Fill::Gradient {
2383 stops: gradient
2384 .stops
2385 .iter()
2386 .map(|g| pax_gpu::GradientStop {
2387 color: to_pax_gpu_color(&g.color),
2388 stop: g.position.evaluate((local_length as f64, 0.0), Axis::X) as f32
2389 / local_length
2390 * main_axis.length(),
2391 })
2392 .collect(),
2393 gradient_type: pax_gpu::GradientType::Linear,
2394 pos: world_pos,
2395 main_axis,
2396 off_axis,
2397 }
2398 }
2399 pax_runtime_api::Paint::RadialGradient(gradient) => {
2400 let Some(g) = gradient.resolve_geometry(rect) else {
2401 return pax_gpu::Fill::Solid(pax_gpu::Color::rgba(0.0, 0.0, 0.0, 0.0));
2402 };
2403 let center = point(g.center.x as f32, g.center.y as f32);
2404 let pos = transform.transform_point(center);
2405 let main_axis =
2406 transform.transform_vector(pax_gpu::Vector2D::new(g.radius as f32, 0.0));
2407 let off_axis = transform.transform_vector(pax_gpu::Vector2D::new(0.0, g.radius as f32));
2408 let focal_point = point(
2409 ((g.focal_point.x - g.center.x) / g.radius) as f32,
2410 ((g.focal_point.y - g.center.y) / g.radius) as f32,
2411 );
2412 if ![
2414 pos.x,
2415 pos.y,
2416 main_axis.x,
2417 main_axis.y,
2418 off_axis.x,
2419 off_axis.y,
2420 focal_point.x,
2421 focal_point.y,
2422 ]
2423 .into_iter()
2424 .all(f32::is_finite)
2425 {
2426 return pax_gpu::Fill::Solid(pax_gpu::Color::rgba(0.0, 0.0, 0.0, 0.0));
2427 }
2428 pax_gpu::Fill::Gradient {
2429 gradient_type: pax_gpu::GradientType::Radial { focal_point },
2430 pos,
2431 main_axis,
2432 off_axis,
2433 stops: gradient
2434 .stops
2435 .iter()
2436 .map(|s| pax_gpu::GradientStop {
2437 color: to_pax_gpu_color(&s.color),
2438 stop: (s.position.evaluate((g.radius, 0.0), Axis::X) / g.radius) as f32,
2439 })
2440 .collect(),
2441 }
2442 }
2443 }
2444}
2445
2446fn to_pax_gpu_alpha_fill(
2447 fill: &pax_runtime_api::Paint,
2448 rect: Rect,
2449 transform: Transform2D,
2450) -> pax_gpu::Fill {
2451 to_pax_gpu_fill(fill, rect, transform)
2452}
2453
2454fn to_pax_gpu_material(material: &pax_runtime_api::ResolvedMaterial) -> PixelMaterial {
2455 match material {
2456 pax_runtime_api::ResolvedMaterial::Unlit => PixelMaterial::unlit(),
2457 pax_runtime_api::ResolvedMaterial::Lit(params) => {
2458 let metallic = params.metallic.clamp(0.0, 1.0) as f32;
2459 let diffuse = params.diffuse.max(0.0) as f32 * (1.0 - metallic * 0.55);
2460 let specular = params.specular.max(0.0) as f32 + metallic * 0.45;
2461 let roughness =
2462 (params.roughness.clamp(0.0, 1.0) as f32 * (1.0 - metallic * 0.35)).clamp(0.0, 1.0);
2463 PixelMaterial::custom(
2464 params.ambient.max(0.0) as f32,
2465 diffuse,
2466 specular,
2467 roughness,
2468 to_pax_gpu_color(¶ms.emissive.clone()),
2469 params.emissive_intensity.max(0.0) as f32,
2470 )
2471 }
2472 }
2473}
2474
2475fn to_pax_gpu_scene_lighting(lighting: &SceneLighting) -> PixelSceneLighting {
2476 PixelSceneLighting {
2477 active: lighting.active,
2478 ambient_is_authored: lighting.ambient_is_authored,
2479 ambient_color: to_pax_gpu_color(&lighting.ambient.color),
2480 ambient_intensity: lighting.ambient.intensity.max(0.0) as f32,
2481 lights: lighting
2482 .lights
2483 .iter()
2484 .map(|light| PixelSceneLight {
2485 shape: match light.shape {
2486 pax_runtime_api::LightShape::Point => PixelLightShape::Point,
2487 pax_runtime_api::LightShape::Directional => PixelLightShape::Directional,
2488 },
2489 position: [
2490 light.position.x as f32,
2491 light.position.y as f32,
2492 light.position.z as f32,
2493 ],
2494 direction: [
2495 light.direction.x as f32,
2496 light.direction.y as f32,
2497 light.direction.z as f32,
2498 ],
2499 color: to_pax_gpu_color(&light.color),
2500 intensity: light.intensity.max(0.0) as f32,
2501 radius: light.radius.max(0.0) as f32,
2502 })
2503 .collect(),
2504 }
2505}
2506
2507fn translate_scene_lighting_for_surface(
2508 lighting: &PixelSceneLighting,
2509 origin_x: f32,
2510 origin_y: f32,
2511) -> PixelSceneLighting {
2512 let mut lighting = lighting.clone();
2513 for light in &mut lighting.lights {
2514 if matches!(light.shape, PixelLightShape::Point) {
2515 light.position[0] -= origin_x;
2516 light.position[1] -= origin_y;
2517 }
2518 }
2519 lighting
2520}
2521
2522pub fn to_pax_gpu_color(color: &pax_runtime_api::Color) -> pax_gpu::Color {
2524 let [r, g, b, a] = color.to_rgba_0_1();
2525 pax_gpu::Color::rgba(r as f32, g as f32, b as f32, a as f32)
2526}
2527
2528pub fn convert_kurbo_to_lyon_path(kurbo_path: &BezPath) -> Path {
2530 let mut builder = Path::builder();
2531 let mut has_open_subpath = false;
2532 for el in kurbo_path.elements() {
2533 match el {
2534 PathEl::MoveTo(p) => {
2535 if has_open_subpath {
2536 builder.end(false);
2537 }
2538 builder.begin(point(p.x as f32, p.y as f32));
2539 has_open_subpath = true;
2540 }
2541 PathEl::LineTo(p) => {
2542 if has_open_subpath {
2543 builder.line_to(point(p.x as f32, p.y as f32));
2544 }
2545 }
2546 PathEl::QuadTo(p1, p2) => {
2547 if has_open_subpath {
2548 builder.quadratic_bezier_to(
2549 point(p1.x as f32, p1.y as f32),
2550 point(p2.x as f32, p2.y as f32),
2551 );
2552 }
2553 }
2554 PathEl::CurveTo(p1, p2, p3) => {
2555 if has_open_subpath {
2556 builder.cubic_bezier_to(
2557 point(p1.x as f32, p1.y as f32),
2558 point(p2.x as f32, p2.y as f32),
2559 point(p3.x as f32, p3.y as f32),
2560 );
2561 }
2562 }
2563 PathEl::ClosePath => {
2564 if has_open_subpath {
2565 builder.end(true);
2566 has_open_subpath = false;
2567 }
2568 }
2569 }
2570 }
2571 if has_open_subpath {
2572 builder.end(false);
2573 }
2574
2575 builder.build()
2576}
2577
2578#[cfg(test)]
2579mod tests {
2580 use super::*;
2581
2582 #[test]
2583 fn surface_eviction_keeps_survivors_in_host_order() {
2584 let existing = [("a", "row"), ("b", "row"), ("c", "row")];
2585 assert_eq!(
2586 retained_surface_order(&existing, &[("c", "row"), ("a", "row")]),
2587 Some(vec![2, 0])
2588 );
2589 assert_eq!(retained_surface_order(&existing, &[]), Some(vec![]));
2590 assert_eq!(
2591 retained_surface_order(&existing, &existing),
2592 Some(vec![0, 1, 2])
2593 );
2594 }
2595
2596 #[test]
2597 fn new_or_rebound_surface_cannot_reuse_an_old_backend() {
2598 let existing = [("a", "row"), ("b", "row")];
2599 assert_eq!(
2600 retained_surface_order(&existing, &[("a", "row"), ("c", "row")]),
2601 Some(vec![0])
2602 );
2603 assert_eq!(
2604 retained_surface_order(&existing, &[("a", "new-host")]),
2605 Some(vec![])
2606 );
2607 assert_eq!(
2608 retained_surface_order(&existing, &[("a", "row"), ("a", "row")]),
2609 None
2610 );
2611 }
2612
2613 #[test]
2614 fn surface_eviction_releases_only_removed_resources() {
2615 struct Resource(Rc<Cell<usize>>);
2616 impl Drop for Resource {
2617 fn drop(&mut self) {
2618 self.0.set(self.0.get() + 1);
2619 }
2620 }
2621 let drops = Rc::new(Cell::new(0));
2622 let mut surfaces: Vec<_> = (0..3)
2623 .map(|_| Box::new(Resource(Rc::clone(&drops))))
2624 .collect();
2625 let first = surfaces[0].as_ref() as *const Resource;
2626 let last = surfaces[2].as_ref() as *const Resource;
2627 let order = retained_surface_order(
2628 &[("a", "row"), ("b", "row"), ("c", "row")],
2629 &[("c", "row"), ("a", "row")],
2630 )
2631 .unwrap();
2632 retain_surfaces(&mut surfaces, order);
2633 assert_eq!(drops.get(), 1);
2634 assert_eq!(surfaces[0].as_ref() as *const Resource, last);
2635 assert_eq!(surfaces[1].as_ref() as *const Resource, first);
2636 retain_surfaces(&mut surfaces, vec![]);
2637 assert_eq!(drops.get(), 3);
2638 assert!(surfaces.is_empty());
2639 }
2640
2641 #[test]
2642 fn stale_initialization_cannot_publish_into_reused_layer_slot() {
2643 let states = Rc::new(RefCell::new(vec![RenderLayerState::Pending]));
2644 let first = Rc::new(LayerInitialization::new(0));
2645 let replacement = Rc::new(LayerInitialization::new(0));
2646 first.scheduled.set(true);
2647 replacement.scheduled.set(true);
2648 let queue = Rc::new(RefCell::new(VecDeque::from([Rc::clone(&first)])));
2649 let ready = Rc::new(RefCell::new(Vec::new()));
2650 let changed = Rc::new(RefCell::new(Vec::new()));
2651 let calls = Rc::new(Cell::new(0));
2652 let factory: Rc<LayerFactory> = Rc::new({
2653 let first = Rc::clone(&first);
2654 let replacement = Rc::clone(&replacement);
2655 let queue = Rc::clone(&queue);
2656 let calls = Rc::clone(&calls);
2657 let states = Rc::clone(&states);
2658 let ready = Rc::clone(&ready);
2659 move |_, _| {
2660 let first = Rc::clone(&first);
2661 let replacement = Rc::clone(&replacement);
2662 let queue = Rc::clone(&queue);
2663 let calls = Rc::clone(&calls);
2664 let states = Rc::clone(&states);
2665 let ready = Rc::clone(&ready);
2666 Box::pin(async move {
2667 calls.set(calls.get() + 1);
2668 if calls.get() == 1 {
2669 first.cancelled.set(true);
2672 queue.borrow_mut().push_back(replacement);
2673 } else {
2674 assert!(matches!(states.borrow()[0], RenderLayerState::Pending));
2675 assert!(ready.borrow().is_empty(), "stale result was published");
2676 }
2677 Some(empty_layer(true, true))
2678 })
2679 }
2680 });
2681 let in_flight = Rc::new(Cell::new(0));
2682 pump_layer_initialization_queue(
2683 factory,
2684 Rc::clone(&states),
2685 Rc::clone(&queue),
2686 Rc::clone(&in_flight),
2687 Rc::clone(&ready),
2688 changed,
2689 Rc::new(|_| 0),
2690 );
2691 assert_eq!(calls.get(), 2);
2692 assert_eq!(*ready.borrow(), vec![0]);
2693 assert!(matches!(&states.borrow()[0], RenderLayerState::Ready(_)));
2694 assert!(!first.scheduled.get());
2695 assert!(!replacement.scheduled.get());
2696 assert!(queue.borrow().is_empty());
2697 assert_eq!(in_flight.get(), 0);
2698 }
2699
2700 #[test]
2701 fn initialization_priority_tracks_visibility_and_keeps_fifo_ties() {
2702 let queue = RefCell::new(VecDeque::from_iter(
2703 (0..5).map(|index| Rc::new(LayerInitialization::new(index))),
2704 ));
2705 queue.borrow()[2].cancelled.set(true);
2706 let visible = Cell::new(3);
2707 let priority = |layer| if layer == visible.get() { 0 } else { 1 };
2708 assert_eq!(
2709 pop_next_layer_initialization(&queue, &priority)
2710 .unwrap()
2711 .index,
2712 3
2713 );
2714 visible.set(4);
2715 assert_eq!(
2716 pop_next_layer_initialization(&queue, &priority)
2717 .unwrap()
2718 .index,
2719 4
2720 );
2721 assert_eq!(
2722 pop_next_layer_initialization(&queue, &priority)
2723 .unwrap()
2724 .index,
2725 0
2726 );
2727 assert_eq!(
2728 pop_next_layer_initialization(&queue, &priority)
2729 .unwrap()
2730 .index,
2731 1
2732 );
2733 assert!(pop_next_layer_initialization(&queue, &priority).is_none());
2734 }
2735
2736 #[test]
2737 fn failed_addition_preserves_ready_layer_and_records_retry_layout() {
2738 let mut target = LayerTarget::new(vec![], true);
2739 target.needs_replay = true;
2740 let layout = LayerSurfaceLayout {
2741 surfaces: vec![],
2742 active: true,
2743 };
2744 let states = Rc::new(RefCell::new(vec![RenderLayerState::Ready((
2745 target,
2746 Box::pin(|| LayerSurfaceLayout {
2747 surfaces: vec![],
2748 active: true,
2749 }),
2750 ))]));
2751 let ready = Rc::new(RefCell::new(Vec::new()));
2752 pump_layer_initialization_queue(
2753 Rc::new(|_, _| Box::pin(async { None })),
2754 Rc::clone(&states),
2755 Rc::new(RefCell::new(VecDeque::from([Rc::new(
2756 LayerInitialization::new(0),
2757 )]))),
2758 Rc::new(Cell::new(0)),
2759 Rc::clone(&ready),
2760 Rc::new(RefCell::new(Vec::new())),
2761 Rc::new(|_| 0),
2762 );
2763 let states = states.borrow();
2764 let RenderLayerState::Ready((target, _)) = &states[0] else {
2765 panic!("survivor lost");
2766 };
2767 assert!(target.needs_replay);
2768 assert_eq!(target.failed_layout, Some(layout));
2769 assert!(ready.borrow().is_empty());
2770 }
2771
2772 #[test]
2773 fn mesh_paint_resolves_full_bounds_origin_combined_units_and_affine_axes() {
2774 use pax_runtime_api::{Color, MeshGradient, MeshPoint, Paint, Size};
2775 let paint = Paint::MeshGradient(MeshGradient {
2776 rows: vec![
2777 vec![
2778 MeshPoint {
2779 position: (
2780 Size::Combined(5.into(), 50.into()),
2781 Size::Percent(100.into())
2782 ),
2783 color: Color::CYAN,
2784 };
2785 2
2786 ];
2787 2
2788 ],
2789 });
2790 let transform = pax_gpu::Transform2D::new(2.0, 0.5, 1.0, 3.0, 4.0, 7.0);
2791 let rect = kurbo::Rect::new(10.0, 20.0, 110.0, 70.0);
2792 let pax_gpu::Fill::Mesh {
2793 rows,
2794 pos,
2795 main_axis,
2796 off_axis,
2797 } = to_pax_gpu_fill(&paint, rect, transform)
2798 else {
2799 panic!("mesh")
2800 };
2801 assert_eq!(pos, point(44.0, 72.0));
2802 assert_eq!(main_axis, pax_gpu::Vector2D::new(200.0, 50.0));
2803 assert_eq!(off_axis, pax_gpu::Vector2D::new(50.0, 150.0));
2804 assert_eq!(rows[0][0].0, [0.55, 1.0]);
2805 let pax_gpu::Fill::Mesh {
2806 rows, main_axis, ..
2807 } = to_pax_gpu_fill(
2808 &paint,
2809 kurbo::Rect::new(10.0, 20.0, 10.0, 70.0),
2810 pax_gpu::Transform2D::identity(),
2811 )
2812 else {
2813 panic!("canonical zero axis")
2814 };
2815 assert_eq!(main_axis.x, 1.0);
2816 assert_eq!(rows[0][0].0[0], 5.5);
2817 assert!(matches!(
2818 to_pax_gpu_fill(&paint, rect, pax_gpu::Transform2D::scale(0.0, 1.0)),
2819 pax_gpu::Fill::Solid(_)
2820 ));
2821 }
2822
2823 fn empty_layer(active: bool, current_active: bool) -> LayerDef {
2824 (
2825 LayerTarget::new(Vec::new(), active),
2826 Box::pin(move || LayerSurfaceLayout {
2827 active: current_active,
2828 surfaces: Vec::new(),
2829 }),
2830 )
2831 }
2832
2833 #[test]
2834 fn removed_layer_initializations_cannot_publish_into_reused_indices() {
2835 let mut renderer = PaxGpuRenderer::new(|_, _| panic!("initialization stays queued"));
2836 renderer
2837 .layer_initializations_in_flight
2838 .set(MAX_CONCURRENT_LAYER_INITIALIZATIONS);
2839 let dirty = Rc::new(RefCell::new(Vec::new()));
2840 renderer.resize_layers_to(2, Rc::clone(&dirty));
2841 let removed = Rc::clone(&renderer.layer_initializations[1]);
2842 renderer.ready_layers.borrow_mut().push(1);
2843 renderer.changed_layers.borrow_mut().push(1);
2844 renderer.replay_layers.borrow_mut().push(1);
2845 renderer.resize_layers_to(1, Rc::clone(&dirty));
2846 assert!(removed.cancelled.get());
2847 assert_eq!(renderer.pending_layer_initializations.borrow().len(), 1);
2848 assert!(renderer.ready_layers.borrow().is_empty());
2849 assert!(renderer.replay_layers.borrow().is_empty());
2850 assert!(renderer.changed_layers.borrow().is_empty());
2851 assert!(!removed.complete(
2852 Some(empty_layer(true, true)),
2853 None,
2854 &renderer.backends,
2855 &renderer.ready_layers,
2856 &renderer.changed_layers
2857 ));
2858
2859 renderer.resize_layers_to(2, dirty);
2860 let replacement = &renderer.layer_initializations[1];
2861 assert!(!Rc::ptr_eq(&removed, replacement));
2862 for stale_result in [Some(empty_layer(true, true)), None] {
2863 assert!(!removed.complete(
2864 stale_result,
2865 None,
2866 &renderer.backends,
2867 &renderer.ready_layers,
2868 &renderer.changed_layers
2869 ));
2870 assert!(matches!(
2871 renderer.backends.borrow()[1],
2872 RenderLayerState::Pending
2873 ));
2874 assert!(replacement.scheduled.get());
2875 assert!(renderer.ready_layers.borrow().is_empty());
2876 }
2877 assert!(!replacement.complete(
2878 Some(empty_layer(true, true)),
2879 None,
2880 &renderer.backends,
2881 &renderer.ready_layers,
2882 &renderer.changed_layers
2883 ));
2884 assert!(matches!(
2885 renderer.backends.borrow()[1],
2886 RenderLayerState::Ready(_)
2887 ));
2888 assert_eq!(*renderer.ready_layers.borrow(), vec![1]);
2889 }
2890
2891 #[test]
2892 fn live_layer_initialization_retries_changed_hosts_and_reports_failure() {
2893 let initialization = LayerInitialization::new(0);
2894 let backends = RefCell::new(vec![RenderLayerState::Pending]);
2895 let ready = RefCell::new(Vec::new());
2896 assert!(initialization.complete(
2897 Some(empty_layer(true, false)),
2898 None,
2899 &backends,
2900 &ready,
2901 &RefCell::new(Vec::new())
2902 ));
2903 assert!(matches!(backends.borrow()[0], RenderLayerState::Pending));
2904 assert!(ready.borrow().is_empty());
2905 assert!(!initialization.complete(None, None, &backends, &ready, &RefCell::new(Vec::new())));
2906 assert!(matches!(backends.borrow()[0], RenderLayerState::Failed));
2907 }
2908
2909 #[test]
2910 fn alpha_radial_gradient_retains_radius_with_coincident_endpoints() {
2911 use pax_runtime_api::{Paint, RadialGradient, Size};
2912 let fill = Paint::RadialGradient(RadialGradient {
2913 start: (Size::Percent(50.into()), Size::Percent(50.into())),
2914 end: (Size::Percent(50.into()), Size::Percent(50.into())),
2915 radius: 25.0,
2916 stops: vec![],
2917 });
2918 let pax_gpu::Fill::Gradient {
2919 pos,
2920 main_axis,
2921 off_axis,
2922 ..
2923 } = to_pax_gpu_alpha_fill(
2924 &fill,
2925 Rect::new(0.0, 0.0, 100.0, 100.0),
2926 Transform2D::new(2.0, 0.0, 1.0, 3.0, 0.0, 0.0),
2927 )
2928 else {
2929 panic!("expected a radial gradient")
2930 };
2931 assert_eq!(pos, point(150.0, 150.0));
2932 assert_eq!(main_axis, pax_gpu::Vector2D::new(50.0, 0.0));
2933 assert_eq!(off_axis, pax_gpu::Vector2D::new(25.0, 75.0));
2934 }
2935
2936 #[test]
2937 fn radial_color_and_mask_share_focal_geometry_and_local_pixel_stops() {
2938 use pax_runtime_api::{Color, GradientStop, Paint, RadialGradient, Size};
2939 let fill = Paint::RadialGradient(RadialGradient {
2940 start: (Size::Percent(75.into()), Size::Percent(25.into())),
2941 end: (Size::Percent(50.into()), Size::Percent(50.into())),
2942 radius: 50.0,
2943 stops: vec![
2944 GradientStop::get(Color::BLACK, Size::Pixels(25.into())),
2945 GradientStop::get(Color::WHITE, Size::Percent(100.into())),
2946 ],
2947 });
2948 let rect = Rect::new(10.0, 20.0, 210.0, 120.0);
2949 let transform = Transform2D::new(-2.0, 0.0, 1.0, 3.0, 5.0, 7.0);
2950 for paint in [
2951 to_pax_gpu_fill(&fill, rect, transform),
2952 to_pax_gpu_alpha_fill(&fill, rect, transform),
2953 ] {
2954 let pax_gpu::Fill::Gradient {
2955 gradient_type,
2956 pos,
2957 main_axis,
2958 off_axis,
2959 stops,
2960 } = paint
2961 else {
2962 panic!("expected radial paint")
2963 };
2964 let pax_gpu::GradientType::Radial { focal_point } = gradient_type else {
2965 panic!("radial")
2966 };
2967 assert_eq!(focal_point, point(1.0, -0.5));
2968 assert_eq!(pos, point(-145.0, 217.0));
2969 assert_eq!(main_axis, pax_gpu::Vector2D::new(-100.0, 0.0));
2970 assert_eq!(off_axis, pax_gpu::Vector2D::new(50.0, 150.0));
2971 assert_eq!(
2972 stops.iter().map(|s| s.stop).collect::<Vec<_>>(),
2973 vec![0.5, 1.0]
2974 );
2975 }
2976 }
2977
2978 #[test]
2979 fn alpha_linear_gradient_retains_sheared_basis() {
2980 use pax_runtime_api::{LinearGradient, Paint, Size};
2981 let fill = Paint::LinearGradient(LinearGradient {
2982 start: (Size::Percent(0.into()), Size::Percent(0.into())),
2983 end: (Size::Percent(100.into()), Size::Percent(0.into())),
2984 stops: vec![],
2985 });
2986 let pax_gpu::Fill::Gradient {
2987 main_axis,
2988 off_axis,
2989 ..
2990 } = to_pax_gpu_alpha_fill(
2991 &fill,
2992 Rect::new(0.0, 0.0, 100.0, 100.0),
2993 Transform2D::new(1.0, 0.0, 0.5, 1.0, 0.0, 0.0),
2994 )
2995 else {
2996 panic!("expected a linear gradient")
2997 };
2998 assert_eq!(main_axis, pax_gpu::Vector2D::new(100.0, 0.0));
2999 assert_eq!(off_axis, pax_gpu::Vector2D::new(50.0, 100.0));
3000 }
3001
3002 #[test]
3003 fn surface_intersection_includes_edges() {
3004 let bounds = Rect::new(100.0, 100.0, 200.0, 200.0);
3005
3006 assert!(surface_intersects_coverage_bounds(
3007 &bounds, 200.0, 100.0, 100.0, 100.0
3008 ));
3009 assert!(surface_intersects_coverage_bounds(
3010 &bounds, 0.0, 0.0, 100.0, 100.0
3011 ));
3012 }
3013
3014 #[test]
3015 fn surface_intersection_rejects_disjoint_surfaces() {
3016 let bounds = Rect::new(100.0, 100.0, 200.0, 200.0);
3017
3018 assert!(!surface_intersects_coverage_bounds(
3019 &bounds, 201.0, 100.0, 100.0, 100.0
3020 ));
3021 assert!(!surface_intersects_coverage_bounds(
3022 &bounds, 100.0, 201.0, 100.0, 100.0
3023 ));
3024 }
3025
3026 #[test]
3027 fn surface_intersection_keeps_non_finite_bounds_conservative() {
3028 let bounds = Rect::new(f64::NAN, 0.0, 100.0, 100.0);
3029
3030 assert!(surface_intersects_coverage_bounds(
3031 &bounds, 500.0, 500.0, 100.0, 100.0
3032 ));
3033 }
3034
3035 #[test]
3036 fn converts_multiple_kurbo_subpaths_to_lyon_path() {
3037 let mut path = BezPath::new();
3038 path.move_to((0.0, 0.0));
3039 path.line_to((10.0, 0.0));
3040 path.move_to((20.0, 0.0));
3041 path.line_to((30.0, 0.0));
3042 path.close_path();
3043
3044 let lyon_path = convert_kurbo_to_lyon_path(&path);
3045
3046 assert_eq!(lyon_path.iter().count(), 6);
3047 }
3048}
3049
3050#[cfg(all(test, target_os = "macos"))]
3051#[path = "pax_gpu_tile_tests.rs"]
3052mod tile_tests;