Skip to main content

cranpose_render_wgpu/
render.rs

1use std::{
2    borrow::Cow,
3    cell::Cell,
4    hash::{Hash, Hasher},
5    rc::Rc,
6    sync::{Arc, mpsc},
7    time::Duration,
8};
9
10use bytemuck::{Pod, Zeroable};
11use cranpose_core::{
12    NodeId,
13    collections::{bounded_lru::BoundedLruCache, map::HashMap},
14    hash::default as default_hash,
15};
16use cranpose_render_common::{
17    geometry::blur_reach,
18    graph::{DrawCommandId, quad_bounds},
19    software_text_raster::{
20        SoftwareGlyphAtlasGlyph, SoftwareGlyphAtlasKey, SoftwareGlyphAtlasPlacement,
21        SoftwareGlyphAtlasRunGlyph, SoftwareGlyphRasterCache, SoftwareTextFontSet,
22        collect_solid_text_atlas_run, rasterize_annotated_text_region,
23    },
24    text_mask_gamma::TextLuminance,
25};
26use cranpose_ui_graphics::{
27    BlendMode, Color, ColorFilter, FRAGMENT_KIND_ARC, FRAGMENT_KIND_FILL, FRAGMENT_KIND_LINE,
28    FRAGMENT_KIND_STROKE, FRAGMENT_KIND_TRAPEZOID, FxHasher, ImageBitmap, ImageSampling, Point,
29    RecordLane, RecordSegment, Rect, RenderHash, TileMode,
30};
31use smallvec::SmallVec;
32use web_time::Instant;
33
34use crate::{
35    DebugCpuAllocationStats,
36    ablation::{Ablation, ShapeAblation},
37    arc_trig_fill::ArcTrigFill,
38    collect::LayerScene,
39    debug_toggles::DebugToggle,
40    draw_pass::{
41        PassSegment, PassTarget, ResolvedComposite, ResolvedCompositeKind, SourceContent,
42        TrackedPass,
43    },
44    effect_renderer::{CompositeSampleMode, EffectRenderer, RoundedCompositeMask},
45    fixed_pipeline::FixedPipeline,
46    frame::{AdmissionGate, FrameExecutor, StageSideScratch},
47    frame_graph::{
48        BufferUpload, FrameCommandRecorder, FrameCommandStats, FrameTextureDescriptor,
49        FrameUploadAllocators, UniformUpload, UploadAllocatorId, UploadAllocatorSpec, UploadMode,
50        WgpuFrameGraph, WgpuFrameGraphExecutor,
51    },
52    frame_packet::{CancelReason, FramePacket, FrameSceneStorage, PresentOutcome, RenderReturns},
53    geometry::{
54        DevicePixelBounds, SegmentTransform, anchored_device_rect, axis_aligned_quad_rect,
55        canonicalize_device_coordinate, canonicalized_scaled_quad, offscreen_byte_size,
56        scaled_quad, snap_delta_for_anchor, snapped_anchor_device_origin, translate_quad,
57        translation_stable_anchored_device_pixel_bounds,
58    },
59    glyph_run::{RunGlyphScratch, RunGlyphs},
60    glyph_run_arena::{GlyphRunArena, GlyphRunSpan},
61    gpu_stats::{self, gpu_stats_enabled},
62    idle_pool::FrameScratch,
63    layer_cache::LayerCache,
64    lazy_resource::Pending,
65    offscreen::{COMPOSITION_BYTES_PER_PIXEL, OffscreenTarget},
66    output_conversion::OutputConverter,
67    pipeline_compiler::{CompileLane, CompilerSend, PipelineCompilation, PipelineCompiler},
68    record_columns::record_vertex_layouts,
69    rect_to_quad,
70    rrect_shadow::{SHADOW_QUAD_CORNERS, ShadowInstance, create_rrect_shadow_pipeline},
71    run_store::{ArenaBinding, PlacementData, RunBufferMode, RunDrawCall, RunStore, StoredTables},
72    scene::{
73        CompositorScene, DrawOp, DrawOpKind, DrawnText, ImageDraw, RunDraw, ShadowDraw, SnapAnchor,
74        TextDraw,
75    },
76    shaders,
77    shape_pipelines::{PipelineDemand, ShapePipelineFactory, ShapePipelines},
78    shared_shader::{ShaderCode, SharedShader},
79};
80const MAX_SHADOW_SURFACE_CACHE_ITEMS: usize = 512;
81const MAX_TRANSPARENT_SOURCES: usize = 16;
82const MAX_SHADOW_SURFACE_CACHE_BYTES: u64 = 384 * 1024 * 1024;
83
84static SKIP_SHADOWS: DebugToggle = DebugToggle::new("CRANPOSE_SKIP_SHADOWS");
85
86fn skip_shadow_draws() -> bool {
87    SKIP_SHADOWS.flag()
88}
89const MAX_TEXT_IMAGE_CACHE_ITEMS: usize = 1024;
90const MAX_TEXT_GLYPH_MASK_CACHE_ITEMS: usize = 8192;
91const MAX_TEXT_GLYPH_ATLAS_ITEMS: usize = 8192;
92/// The fewest text runs the run cache holds. It holds twice the runs the
93/// last frame drew when that is more, so a frame never evicts runs the next
94/// one draws again; idle runs leave it after `TEXT_GLYPH_RUN_IDLE_FRAMES`.
95const MIN_TEXT_GLYPH_RUN_CACHE_ITEMS: usize = 1024;
96const MAX_TEXT_GLYPH_GPU_RUN_CACHE_ITEMS: usize = 1024;
97/// Frames a text run may go undrawn before its glyphs and quads are freed,
98/// on the CPU and in retained GPU buffers alike. Shorter than the shape
99/// store's span: a list scrolling at speed leaves several screens of text
100/// behind each second, which at 120 frames held 12 to 19 MB of GPU quads on
101/// a scrolling feed, and the CPU runs, bounded only by their count, held
102/// another 4.6 MB of placements and quads.
103const TEXT_GLYPH_RUN_IDLE_FRAMES: u64 = 30;
104/// Frames a retained run's quads stay on the GPU undrawn. Writing a run's
105/// quads again costs far less than laying its glyphs out, which the CPU run
106/// keeps for [`TEXT_GLYPH_RUN_IDLE_FRAMES`]. On the desktop gauntlet at tier
107/// 16, 270 retained runs drew each frame while 30 frames held 650 of them;
108/// 8 frames hold 400 and write 25 runs a frame again instead of 16.
109const TEXT_GLYPH_GPU_RUN_IDLE_FRAMES: u64 = 8;
110/// Text runs with at least this many glyphs keep their quads in retained GPU
111/// buffers and draw on their own; shorter ones are written into the frame's
112/// shared quads each frame, where consecutive runs share a draw.
113const RETAINED_TEXT_GLYPH_RUN_MIN_QUADS: usize = 64;
114
115const TEXT_GLYPH_ATLAS_MIN_SIZE: u32 = 512;
116const TEXT_GLYPH_ATLAS_MAX_SIZE: u32 = 4096;
117const _: () = assert!(TEXT_GLYPH_ATLAS_MAX_SIZE <= u16::MAX as u32);
118const TEXT_GLYPH_ATLAS_PADDING: u32 = 1;
119const MAX_TEXT_LINE_INDEX_CACHE_ITEMS: usize = 512;
120const MIN_MULTILINE_TEXT_LINES_FOR_CLIPPED_RASTER: usize = 2;
121
122const CACHE_MISS_WARMUP_FRAMES: u8 = 1;
123pub(crate) const CLEAR_COLOR: wgpu::Color = wgpu::Color {
124    r: cranpose_render_common::FRAME_CLEAR_COLOR[0] as f64,
125    g: cranpose_render_common::FRAME_CLEAR_COLOR[1] as f64,
126    b: cranpose_render_common::FRAME_CLEAR_COLOR[2] as f64,
127    a: cranpose_render_common::FRAME_CLEAR_COLOR[3] as f64,
128};
129const MAX_TEXTURE_CACHE_ITEMS: usize = 256;
130const MAX_IMAGE_TEXTURE_CACHE_BYTES: usize = 256 * 1024 * 1024;
131
132const DEFAULT_WGPU_RENDER_STAGE_TELEMETRY_THRESHOLD_MS: f64 = 4.0;
133
134fn wgpu_render_stage_telemetry_threshold_ms() -> Option<f64> {
135    static THRESHOLD_MS: std::sync::OnceLock<Option<f64>> = std::sync::OnceLock::new();
136    *THRESHOLD_MS.get_or_init(|| {
137        let explicit =
138            crate::debug_toggles::debug_toggle("CRANPOSE_WGPU_RENDER_STAGE_TELEMETRY_MS")
139                .and_then(|value| value.parse::<f64>().ok())
140                .filter(|value| value.is_finite() && *value >= 0.0);
141        explicit.or_else(|| {
142            std::env::var_os("CRANPOSE_WGPU_RENDER_STAGE_TELEMETRY")
143                .is_some()
144                .then_some(DEFAULT_WGPU_RENDER_STAGE_TELEMETRY_THRESHOLD_MS)
145        })
146    })
147}
148
149pub(crate) fn instant_ms(start: Instant, end: Instant) -> f64 {
150    end.duration_since(start).as_secs_f64() * 1000.0
151}
152
153pub(crate) fn should_log_wgpu_render_stage(start: Instant, end: Instant) -> Option<f64> {
154    let threshold_ms = wgpu_render_stage_telemetry_threshold_ms()?;
155    let total_ms = instant_ms(start, end);
156    (total_ms >= threshold_ms).then_some(total_ms)
157}
158
159pub static PRESENTED_FRAMES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
160
161pub fn frames_presented() -> u64 {
162    PRESENTED_FRAMES.load(std::sync::atomic::Ordering::Relaxed)
163}
164
165fn text_atlas_fallback_diag_enabled() -> bool {
166    cranpose_core::env_flag!("CRANPOSE_TEXT_ATLAS_FALLBACK_DIAG")
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
170struct ShadowSurfaceCacheKey {
171    content_hash: u64,
172    pixel_size: [u32; 2],
173    root_scale_bits: u32,
174    blur_radius_bits: u32,
175}
176
177struct CachedShadowSurface {
178    target: Rc<OffscreenTarget>,
179    byte_size: u64,
180}
181
182type DeviceRect4 = (f32, f32, f32, f32);
183/// A rect of whole target pixels: x, y, width, height.
184pub(crate) type TargetRect = (u32, u32, u32, u32);
185
186/// The target pixels a clipped shape variant keeps of `placement`'s records
187/// under `viewport`, as a scissor that stands in for its clip test: every
188/// pixel whose centre, moved by the viewport's offset, lies inside the
189/// device clip, inclusive of its edges, as the shader compares them. `None`
190/// without a clip or under a turn, where the variant tests the clip itself.
191/// An empty clip keeps an empty scissor.
192pub(crate) fn clip_scissor(
193    placement: &crate::scene::Placement,
194    root_scale: f32,
195    viewport: ViewportUniformParams,
196) -> Option<TargetRect> {
197    if !viewport.transform.is_identity() {
198        return None;
199    }
200    let [x, y, width, height] = crate::run_store::device_clip(placement, root_scale)?;
201    let columns = kept_pixels(x, x + width, viewport.offset[0], viewport.width);
202    let rows = kept_pixels(y, y + height, viewport.offset[1], viewport.height);
203    Some((
204        columns.start,
205        rows.start,
206        columns.end - columns.start,
207        rows.end - rows.start,
208    ))
209}
210
211/// The pixels along one axis whose centre, moved by `offset`, lies within
212/// `low..=high`, within `0..extent`: stepped with the shader's own float
213/// sums so the edges fall where its comparisons put them.
214fn kept_pixels(low: f32, high: f32, offset: f32, extent: u32) -> std::ops::Range<u32> {
215    let centre = |pixel: u32| pixel as f32 + 0.5 + offset;
216    let guess = |edge: f32| (edge - offset - 0.5).ceil().clamp(0.0, extent as f32) as u32;
217    let mut start = guess(low);
218    while start > 0 && centre(start - 1) >= low {
219        start -= 1;
220    }
221    while start < extent && centre(start) < low {
222        start += 1;
223    }
224    let mut end = guess(high).max(start);
225    while end > start && centre(end - 1) > high {
226        end -= 1;
227    }
228    while end < extent && centre(end) <= high {
229        end += 1;
230    }
231    start..end
232}
233
234/// The pixels both scissors keep, `None` when they share none.
235pub(crate) fn intersect_target_rects(a: TargetRect, b: TargetRect) -> Option<TargetRect> {
236    let left = a.0.max(b.0);
237    let top = a.1.max(b.1);
238    let right = (a.0 + a.2).min(b.0 + b.2);
239    let bottom = (a.1 + a.3).min(b.1 + b.3);
240    (right > left && bottom > top).then(|| (left, top, right - left, bottom - top))
241}
242
243/// A draw's scissor cut down to the pixels its pass segment may touch;
244/// `None` when nothing of it remains.
245pub(crate) fn bounded_scissor(
246    scissor: (u32, u32, u32, u32),
247    bound: Option<(u32, u32, u32, u32)>,
248) -> Option<(u32, u32, u32, u32)> {
249    let Some((bx, by, bw, bh)) = bound else {
250        return Some(scissor);
251    };
252    let (x, y, width, height) = scissor;
253    let left = x.max(bx);
254    let top = y.max(by);
255    let right = (x + width).min(bx + bw);
256    let bottom = (y + height).min(by + bh);
257    (right > left && bottom > top).then(|| (left, top, right - left, bottom - top))
258}
259
260#[derive(Clone, Copy, Debug, PartialEq)]
261pub(crate) struct GlyphExtent {
262    left: f32,
263    top: f32,
264    right: f32,
265    bottom: f32,
266}
267
268impl GlyphExtent {
269    pub(crate) const EMPTY: Self = Self {
270        left: f32::INFINITY,
271        top: f32::INFINITY,
272        right: f32::NEG_INFINITY,
273        bottom: f32::NEG_INFINITY,
274    };
275
276    pub(crate) fn add(&mut self, rect: [f32; 4]) {
277        self.left = self.left.min(rect[0]);
278        self.top = self.top.min(rect[1]);
279        self.right = self.right.max(rect[2]);
280        self.bottom = self.bottom.max(rect[3]);
281    }
282}
283
284/// The scissor an unturned shared glyph run needs and the target pixels it
285/// touches. A run whose quads all lie inside `scissor` needs none of its own,
286/// which lets it share a draw with its neighbours. A turned run never needs
287/// one: its quads were cut to its clip before the turn.
288fn shared_glyph_clip(
289    extent: GlyphExtent,
290    scissor: TargetRect,
291    viewport: ViewportUniformParams,
292) -> (Option<TargetRect>, TargetRect) {
293    let GlyphExtent {
294        left,
295        top,
296        right,
297        bottom,
298    } = extent;
299    if left > right {
300        return (Some(scissor), scissor);
301    }
302    let left = (left - viewport.offset[0]).floor().max(0.0);
303    let top = (top - viewport.offset[1]).floor().max(0.0);
304    let right = (right - viewport.offset[0]).ceil();
305    let bottom = (bottom - viewport.offset[1]).ceil();
306    let (x, y, width, height) = scissor;
307    let inside = left >= x as f32
308        && top >= y as f32
309        && right <= (x + width) as f32
310        && bottom <= (y + height) as f32;
311    if !inside || right <= left || bottom <= top {
312        return (Some(scissor), scissor);
313    }
314    let bounds = (
315        left as u32,
316        top as u32,
317        (right - left) as u32,
318        (bottom - top) as u32,
319    );
320    (None, bounds)
321}
322
323fn intersect_device_rects(a: DeviceRect4, b: DeviceRect4) -> Option<DeviceRect4> {
324    let left = a.0.max(b.0);
325    let top = a.1.max(b.1);
326    let right = (a.0 + a.2).min(b.0 + b.2);
327    let bottom = (a.1 + a.3).min(b.1 + b.3);
328    (right > left && bottom > top).then_some((left, top, right - left, bottom - top))
329}
330
331pub(crate) fn anchored_rect_to_device(
332    rect: Rect,
333    snap_anchor: Option<SnapAnchor>,
334    root_scale: f32,
335) -> DeviceRect4 {
336    let device = anchored_device_rect(rect, snap_anchor, root_scale);
337    (device.x, device.y, device.width, device.height)
338}
339
340fn mask_rect(rect: Rect) -> [f32; 4] {
341    [rect.x, rect.y, rect.width, rect.height]
342}
343
344#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
345struct TextImageCacheKey(u64);
346
347struct CachedTextImage {
348    image: ImageBitmap,
349}
350
351#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
352struct TextGlyphRunCacheKey(u64);
353
354#[derive(Clone, Copy)]
355struct CachedTextGlyphQuad {
356    x: i32,
357    y: i32,
358    width: usize,
359    height: usize,
360    color: (f32, f32, f32, f32),
361    uv: ImageUvRect,
362}
363
364struct CachedTextGlyphRun {
365    /// The run's glyphs that draw: zero-sized and transparent ones are left
366    /// out when the run is collected.
367    glyphs: RunGlyphs,
368    bounds: GlyphRunBounds,
369    /// Where each of `glyphs` sits in the atlas at `atlas_generation`. A
370    /// glyph's quad is derived from the two as it is drawn, so no glyph is
371    /// held twice.
372    atlas_entries: Option<Rc<[GlyphAtlasEntry]>>,
373    atlas_generation: u64,
374    /// The frame the run last drew in; see [`TEXT_GLYPH_RUN_IDLE_FRAMES`].
375    last_frame: Cell<u64>,
376}
377
378/// A cached run drawn as quads: its glyphs and their atlas entries, each
379/// quad derived as it is read.
380#[derive(Clone, Copy)]
381struct GlyphRunQuads<'a> {
382    glyphs: &'a RunGlyphs,
383    entries: &'a [GlyphAtlasEntry],
384    /// One atlas texel's share of the atlas side: see
385    /// [`TextGlyphAtlas::texel`].
386    texel: f32,
387    bounds: GlyphRunBounds,
388}
389
390/// The pixel box a run's drawing glyphs cover, from its raster origin.
391#[derive(Clone, Copy, Debug, PartialEq)]
392struct GlyphRunBounds {
393    min: [i32; 2],
394    max: [i32; 2],
395}
396
397impl GlyphRunBounds {
398    fn of(glyphs: impl IntoIterator<Item = SoftwareGlyphAtlasPlacement>) -> Self {
399        let mut bounds = Self {
400            min: [i32::MAX; 2],
401            max: [i32::MIN; 2],
402        };
403        for glyph in glyphs {
404            let width = i32::try_from(glyph.width).unwrap_or(i32::MAX);
405            let height = i32::try_from(glyph.height).unwrap_or(i32::MAX);
406            bounds.min = [bounds.min[0].min(glyph.x), bounds.min[1].min(glyph.y)];
407            bounds.max = [
408                bounds.max[0].max(glyph.x.saturating_add(width)),
409                bounds.max[1].max(glyph.y.saturating_add(height)),
410            ];
411        }
412        bounds
413    }
414
415    /// Whether every glyph of the run at `source_raster_rect` lies inside
416    /// what `viewport` shows of `clip`, so none needs its own test.
417    fn within_viewport(
418        self,
419        source_raster_rect: Rect,
420        clip: Option<Rect>,
421        viewport: ViewportUniformParams,
422        root_scale: f32,
423    ) -> bool {
424        if self.min[0] > self.max[0] || !root_scale.is_finite() || root_scale <= 0.0 {
425            return false;
426        }
427        let left = (source_raster_rect.x + self.min[0] as f32) / root_scale;
428        let top = (source_raster_rect.y + self.min[1] as f32) / root_scale;
429        let right = (source_raster_rect.x + self.max[0] as f32) / root_scale;
430        let bottom = (source_raster_rect.y + self.max[1] as f32) / root_scale;
431        let viewport_rect = viewport.scene_rect(root_scale);
432        let visible = match clip {
433            Some(clip) => clip.intersect(viewport_rect),
434            None => Some(viewport_rect),
435        };
436        visible
437            .is_some_and(|visible| visible.contains(left, top) && visible.contains(right, bottom))
438    }
439}
440
441/// A text's glyph run as the frame found it.
442struct TextGlyphRunLookup {
443    glyphs: RunGlyphs,
444    bounds: GlyphRunBounds,
445    /// Whether the run came from the cache rather than from this frame's
446    /// collection.
447    cached: bool,
448    /// The glyphs' atlas entries, while the atlas they were taken from holds.
449    entries: Option<Rc<[GlyphAtlasEntry]>>,
450}
451
452/// A text's glyph run and where it draws.
453struct TextGlyphRunDraw<'a> {
454    text_draw: &'a TextDraw,
455    raster_rect: Rect,
456    run_key: TextGlyphRunCacheKey,
457    run: TextGlyphRunLookup,
458}
459
460/// Logs why a text fell back from the glyph atlas, when
461/// `CRANPOSE_TEXT_ATLAS_FALLBACK_DIAG` asks.
462fn log_text_atlas_fallback(text_draw: &TextDraw) {
463    if !text_atlas_fallback_diag_enabled() {
464        return;
465    }
466    let preview: String = text_draw.text.string().text().chars().take(96).collect();
467    log::warn!(
468        "[text-atlas-fallback] node={:?} spans={} links={} text_len={} preview={:?} span_style={:?} paragraph_style={:?}",
469        text_draw.node_id,
470        text_draw.text.string().span_styles().len(),
471        text_draw.text.string().links().len(),
472        text_draw.text.string().text().len(),
473        preview,
474        text_draw.text_style.span_style,
475        text_draw.text_style.paragraph_style,
476    );
477}
478
479impl GlyphRunQuads<'_> {
480    fn len(&self) -> usize {
481        self.entries.len()
482    }
483
484    fn iter(&self) -> impl Iterator<Item = CachedTextGlyphQuad> + '_ {
485        self.glyphs
486            .iter()
487            .zip(self.entries)
488            .map(|(glyph, entry)| cached_text_glyph_quad(&glyph, *entry, self.texel))
489    }
490}
491
492struct CachedGpuTextGlyphRun {
493    span: GlyphRunSpan,
494    atlas_generation: u64,
495    /// The frame the run last drew in; a run idle for
496    /// [`TEXT_GLYPH_GPU_RUN_IDLE_FRAMES`] gives its quads back.
497    last_frame: Cell<u64>,
498}
499
500#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
501struct TextLineIndexCacheKey(usize);
502
503struct CachedTextLineIndex {
504    text: std::sync::Weak<cranpose_ui::text::RenderString>,
505    len: usize,
506    starts: Rc<[usize]>,
507}
508
509struct TextLineIndexCache {
510    entries: BoundedLruCache<TextLineIndexCacheKey, CachedTextLineIndex>,
511}
512
513impl TextLineIndexCache {
514    fn new(capacity: usize) -> Self {
515        Self {
516            entries: BoundedLruCache::with_capacity_at_least_one(capacity),
517        }
518    }
519
520    fn line_starts(&mut self, text: &Arc<cranpose_ui::text::RenderString>) -> Rc<[usize]> {
521        let key = TextLineIndexCacheKey(Arc::as_ptr(text) as usize);
522        if let Some(cached) = self.entries.get(&key)
523            && cached.len == text.text().len()
524            && cached
525                .text
526                .upgrade()
527                .is_some_and(|cached_text| Arc::ptr_eq(&cached_text, text))
528        {
529            return cached.starts.clone();
530        }
531
532        let starts = Rc::<[usize]>::from(line_start_offsets(text.text()));
533        self.entries.put(
534            key,
535            CachedTextLineIndex {
536                text: Arc::downgrade(text),
537                len: text.text().len(),
538                starts: starts.clone(),
539            },
540        );
541        starts
542    }
543}
544
545#[derive(Default)]
546struct DeviceErrorSentry {
547    errors: std::sync::atomic::AtomicU64,
548    poisoned: std::sync::atomic::AtomicBool,
549}
550
551impl DeviceErrorSentry {
552    fn record(&self, error: &wgpu::Error) {
553        use std::sync::atomic::Ordering;
554        self.poisoned.store(true, Ordering::Release);
555        let count = self.errors.fetch_add(1, Ordering::Relaxed) + 1;
556        if count.is_power_of_two() {
557            log::error!("[gpu-device] uncaptured wgpu error #{count}: {error}");
558        }
559    }
560
561    fn take_poison(&self) -> bool {
562        self.poisoned
563            .swap(false, std::sync::atomic::Ordering::AcqRel)
564    }
565
566    fn error_count(&self) -> u64 {
567        self.errors.load(std::sync::atomic::Ordering::Relaxed)
568    }
569}
570
571fn is_blend_mode_supported(mode: BlendMode) -> bool {
572    matches!(
573        mode,
574        BlendMode::Src | BlendMode::SrcOver | BlendMode::DstOut
575    )
576}
577
578pub(crate) fn blend_state_for_mode(mode: BlendMode) -> wgpu::BlendState {
579    match mode {
580        BlendMode::Src => wgpu::BlendState::REPLACE,
581        BlendMode::DstOut => wgpu::BlendState {
582            color: wgpu::BlendComponent {
583                src_factor: wgpu::BlendFactor::Zero,
584                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
585                operation: wgpu::BlendOperation::Add,
586            },
587            alpha: wgpu::BlendComponent {
588                src_factor: wgpu::BlendFactor::Zero,
589                dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
590                operation: wgpu::BlendOperation::Add,
591            },
592        },
593        _ => wgpu::BlendState::ALPHA_BLENDING,
594    }
595}
596
597pub(crate) fn supported_blend_mode(mode: BlendMode) -> BlendMode {
598    if is_blend_mode_supported(mode) {
599        return mode;
600    }
601
602    BlendMode::SrcOver
603}
604
605pub(crate) fn hash_f32_for_cache<H: Hasher>(value: f32, state: &mut H) {
606    value.to_bits().hash(state);
607}
608
609fn hash_text_raster_geometry_for_cache<H: Hasher>(
610    rect: Rect,
611    static_text_motion: bool,
612    state: &mut H,
613) {
614    hash_f32_for_cache(rect.width, state);
615    hash_f32_for_cache(rect.height, state);
616    static_text_motion.hash(state);
617    if !static_text_motion {
618        hash_f32_for_cache(rect.x.fract(), state);
619        hash_f32_for_cache(rect.y.fract(), state);
620    }
621}
622
623/// Hashes what a text draws apart from where and how large its rect is.
624fn hash_text_draw_for_cache<H: Hasher>(
625    text_draw: &TextDraw,
626    raster_rect: Rect,
627    text_scale: f32,
628    state: &mut H,
629) {
630    text_draw.text.render_hash().hash(state);
631    text_draw.style_hash.hash(state);
632    text_draw.color.render_hash().hash(state);
633    hash_text_gradient_phase_for_cache(text_draw, raster_rect, state);
634    text_draw.font_size.to_bits().hash(state);
635    text_scale.to_bits().hash(state);
636    text_draw.layout_options.hash(state);
637}
638
639pub(crate) fn hash_text_gradient_phase_for_cache<H: Hasher>(
640    text_draw: &TextDraw,
641    raster_rect: Rect,
642    state: &mut H,
643) {
644    if text_draw.text.gradient() {
645        for coordinate in [raster_rect.x, raster_rect.y] {
646            let phase = if coordinate >= 0.0 {
647                coordinate.rem_euclid(4.0)
648            } else {
649                coordinate
650            };
651            phase.to_bits().hash(state);
652        }
653    }
654}
655
656/// Where a text draws in the scene, snapped, and the scale its glyphs
657/// raster at; `None` for a text that draws nothing.
658pub(crate) fn text_logical_geometry_for_draw(
659    text_draw: &TextDraw,
660    root_scale: f32,
661) -> Option<(Rect, f32)> {
662    if text_draw.text.is_empty()
663        || text_draw.rect.width <= 0.0
664        || text_draw.rect.height <= 0.0
665        || !root_scale.is_finite()
666        || root_scale <= 0.0
667    {
668        return None;
669    }
670
671    let text_scale = text_draw.scale * root_scale;
672    if !text_scale.is_finite() || text_scale <= 0.0 {
673        return None;
674    }
675
676    let snap_delta = text_draw
677        .snap_anchor
678        .map(|anchor| snap_delta_for_anchor(anchor, root_scale))
679        .unwrap_or_default();
680    let logical_rect = text_draw.rect.translate(snap_delta.x, snap_delta.y);
681    Some((logical_rect, text_scale))
682}
683
684pub(crate) fn text_raster_geometry_for_draw(
685    text_draw: &TextDraw,
686    root_scale: f32,
687) -> Option<(Rect, Rect, Option<Rect>, f32, bool)> {
688    text_logical_geometry_for_draw(text_draw, root_scale)
689        .map(|logical| text_raster_geometry_at(text_draw, logical, root_scale))
690}
691
692/// The raster geometry of a text whose logical geometry is
693/// `(logical_rect, text_scale)`: see [`text_raster_geometry_for_draw`].
694fn text_raster_geometry_at(
695    text_draw: &TextDraw,
696    (logical_rect, text_scale): (Rect, f32),
697    root_scale: f32,
698) -> (Rect, Rect, Option<Rect>, f32, bool) {
699    let static_text_motion = text_draw.text.static_motion();
700    let clip = text_draw.clip;
701    let mut raster_rect = Rect {
702        x: logical_rect.x * root_scale,
703        y: logical_rect.y * root_scale,
704        width: logical_rect.width * root_scale,
705        height: logical_rect.height * root_scale,
706    };
707    if text_draw.snap_anchor.is_some() {
708        raster_rect.x = canonicalize_device_coordinate(raster_rect.x);
709        raster_rect.y = canonicalize_device_coordinate(raster_rect.y);
710    }
711    if static_text_motion {
712        raster_rect.x = raster_rect.x.round();
713        raster_rect.y = raster_rect.y.round();
714    }
715    raster_rect.width = raster_rect.width.ceil().max(1.0);
716    raster_rect.height = raster_rect.height.ceil().max(1.0);
717    (
718        logical_rect,
719        raster_rect,
720        clip,
721        text_scale,
722        static_text_motion,
723    )
724}
725
726fn text_draw_is_visible_in_viewport(
727    logical_rect: Rect,
728    clip: Option<Rect>,
729    viewport: ViewportUniformParams,
730    root_scale: f32,
731) -> bool {
732    draw_rect_is_visible_in_viewport(logical_rect, clip, viewport, root_scale)
733}
734
735fn expand_rect(rect: Rect, margin_x: f32, margin_y: f32) -> Rect {
736    Rect {
737        x: rect.x - margin_x,
738        y: rect.y - margin_y,
739        width: rect.width + margin_x * 2.0,
740        height: rect.height + margin_y * 2.0,
741    }
742}
743
744fn draw_rect_is_visible_in_viewport(
745    rect: Rect,
746    clip: Option<Rect>,
747    viewport: ViewportUniformParams,
748    root_scale: f32,
749) -> bool {
750    if !root_scale.is_finite() || root_scale <= 0.0 {
751        return false;
752    }
753    rect_is_visible_in_rect(rect, clip, viewport.scene_rect(root_scale))
754}
755
756fn rect_is_visible_in_rect(rect: Rect, clip: Option<Rect>, viewport_rect: Rect) -> bool {
757    let visible_rect = match clip {
758        Some(clip) => clip.intersect(viewport_rect),
759        None => Some(viewport_rect),
760    };
761    visible_rect.is_some_and(|visible| rect.intersect(visible).is_some())
762}
763
764fn snapped_quad_bounds(quad: [[f32; 2]; 4], anchor: Option<SnapAnchor>, root_scale: f32) -> Rect {
765    let snap_delta = anchor
766        .map(|anchor| snap_delta_for_anchor(anchor, root_scale))
767        .unwrap_or_default();
768    quad_bounds(translate_quad(quad, snap_delta))
769}
770
771/// The logical rect a draw may touch: its snapped bounds within its clip,
772/// `None` when the clip leaves nothing.
773fn clipped_bounds(rect: Rect, clip: Option<Rect>) -> Option<Rect> {
774    match clip {
775        Some(clip) => rect.intersect(clip),
776        None => Some(rect),
777    }
778}
779
780pub(crate) fn text_draw_bounds(text: &TextDraw, root_scale: f32) -> Option<Rect> {
781    text_logical_geometry_for_draw(text, root_scale)
782        .and_then(|(logical_rect, _)| clipped_bounds(logical_rect, text.clip))
783}
784
785pub(crate) fn image_draw_bounds(image: &ImageDraw, root_scale: f32) -> Option<Rect> {
786    clipped_bounds(
787        snapped_quad_bounds(image.quad, image.snap_anchor, root_scale),
788        image.clip,
789    )
790}
791
792pub(crate) fn run_draw_bounds(run: &RunDraw, root_scale: f32) -> Option<Rect> {
793    let snap_delta = run
794        .placement
795        .snap_anchor
796        .map(|anchor| snap_delta_for_anchor(anchor, root_scale))
797        .unwrap_or_default();
798    clipped_bounds(
799        run.bounds.translate(snap_delta.x, snap_delta.y),
800        run.placement.clip,
801    )
802}
803
804/// The logical geometry of `text` (see [`text_logical_geometry_for_draw`])
805/// when it draws any pixel inside `viewport_rect`.
806pub(crate) fn text_draw_geometry_in_rect(
807    text: &TextDraw,
808    viewport_rect: Rect,
809    root_scale: f32,
810) -> Option<(Rect, f32)> {
811    text_logical_geometry_for_draw(text, root_scale).filter(|(logical_rect, _)| {
812        clipped_bounds(*logical_rect, text.clip)
813            .is_some_and(|bounds| bounds.intersect(viewport_rect).is_some())
814    })
815}
816
817fn cached_text_glyph_quad(
818    glyph: &SoftwareGlyphAtlasPlacement,
819    entry: GlyphAtlasEntry,
820    texel: f32,
821) -> CachedTextGlyphQuad {
822    CachedTextGlyphQuad {
823        x: glyph.x,
824        y: glyph.y,
825        width: glyph.width,
826        height: glyph.height,
827        color: (
828            glyph.color.0.clamp(0.0, 1.0),
829            glyph.color.1.clamp(0.0, 1.0),
830            glyph.color.2.clamp(0.0, 1.0),
831            glyph.color.3.clamp(0.0, 1.0),
832        ),
833        uv: glyph_atlas_uv_rect(entry, texel),
834    }
835}
836
837/// `quad` at `source_raster_rect`'s origin. `None` for a quad that draws
838/// nothing.
839fn cached_text_glyph_instance(
840    source_raster_rect: Rect,
841    quad: &CachedTextGlyphQuad,
842) -> Option<GlyphInstance> {
843    if quad.width == 0 || quad.height == 0 || quad.color.3 <= 0.0 {
844        return None;
845    }
846    let x0 = source_raster_rect.x + quad.x as f32;
847    let y0 = source_raster_rect.y + quad.y as f32;
848    Some(GlyphInstance {
849        rect: [x0, y0, x0 + quad.width as f32, y0 + quad.height as f32],
850        uv: [
851            quad.uv.min[0],
852            quad.uv.min[1],
853            quad.uv.max[0],
854            quad.uv.max[1],
855        ],
856        uv_bounds: quad.uv.sample_bounds,
857        color: [quad.color.0, quad.color.1, quad.color.2, quad.color.3],
858    })
859}
860
861fn cached_text_glyph_quad_logical_rect(
862    source_raster_rect: Rect,
863    quad: &CachedTextGlyphQuad,
864    root_scale: f32,
865) -> Option<Rect> {
866    if !root_scale.is_finite() || root_scale <= 0.0 {
867        return None;
868    }
869    Some(Rect {
870        x: (source_raster_rect.x + quad.x as f32) / root_scale,
871        y: (source_raster_rect.y + quad.y as f32) / root_scale,
872        width: quad.width as f32 / root_scale,
873        height: quad.height as f32 / root_scale,
874    })
875}
876
877fn cached_text_glyph_quad_is_visible_in_viewport(
878    source_raster_rect: Rect,
879    quad: &CachedTextGlyphQuad,
880    clip: Option<Rect>,
881    viewport: ViewportUniformParams,
882    root_scale: f32,
883) -> bool {
884    cached_text_glyph_quad_logical_rect(source_raster_rect, quad, root_scale)
885        .is_some_and(|rect| draw_rect_is_visible_in_viewport(rect, clip, viewport, root_scale))
886}
887
888pub(crate) fn hash_device_offset<H: Hasher>(
889    value: f32,
890    origin: f32,
891    root_scale: f32,
892    state: &mut H,
893) {
894    hash_f32_for_cache((value - origin) * root_scale, state);
895}
896
897pub(crate) fn hash_device_rect<H: Hasher>(
898    rect: Rect,
899    origin_x: f32,
900    origin_y: f32,
901    root_scale: f32,
902    state: &mut H,
903) {
904    hash_device_offset(rect.x, origin_x, root_scale, state);
905    hash_device_offset(rect.y, origin_y, root_scale, state);
906    hash_device_offset(rect.width, 0.0, root_scale, state);
907    hash_device_offset(rect.height, 0.0, root_scale, state);
908}
909
910/// Hashes the rect `placement` is clipped to, `clip`, and its rounding, in
911/// device pixels from `origin`.
912fn hash_placement_clips<H: Hasher>(
913    placement: &crate::scene::Placement,
914    clip: Option<Rect>,
915    (origin_x, origin_y): (f32, f32),
916    root_scale: f32,
917    state: &mut H,
918) {
919    let rounding = placement.rounding;
920    for (rect, radius) in [
921        (clip, 0.0),
922        (
923            rounding.map(|rounding| rounding.rect),
924            rounding.map_or(0.0, |rounding| rounding.radius),
925        ),
926    ] {
927        rect.is_some().hash(state);
928        if let Some(rect) = rect {
929            let [x, y, width, height] =
930                crate::run_store::placement_device_rect(placement, rect, root_scale);
931            for value in [
932                x - origin_x,
933                y - origin_y,
934                width,
935                height,
936                radius * root_scale,
937            ] {
938                hash_f32_for_cache(value, state);
939            }
940        }
941    }
942}
943
944pub(crate) fn hash_run_item_with_clip<H: Hasher>(
945    run: &RunDraw,
946    clip: Option<Rect>,
947    origin_x: f32,
948    origin_y: f32,
949    root_scale: f32,
950    state: &mut H,
951) {
952    let tables = run.tables();
953    tables.fingerprint().hash(state);
954    run.segments.start.hash(state);
955    run.segments.end.hash(state);
956    let placement = &run.placement;
957    let snap = placement
958        .snap_anchor
959        .map(|anchor| snap_delta_for_anchor(anchor, root_scale))
960        .unwrap_or_default();
961    let offset = Point::new(placement.offset.x + snap.x, placement.offset.y + snap.y);
962    let canonicalize = placement.snap_anchor.is_some();
963    canonicalize.hash(state);
964    for segment in &tables.segments[run.segments.start as usize..run.segments.end as usize] {
965        if segment.lane != RecordLane::Shapes {
966            continue;
967        }
968        let exact_placement =
969            !canonicalize || segment.kinds & ((1u8 << FRAGMENT_KIND_ARC) | LINE_KINDS) != 0;
970        exact_placement.hash(state);
971        if exact_placement {
972            for value in [offset.x, offset.y, origin_x, origin_y, root_scale] {
973                hash_f32_for_cache(value, state);
974            }
975            continue;
976        }
977        for index in segment.start..segment.start + segment.count {
978            let record = tables
979                .shapes
980                .get(index as usize)
981                .expect("shape segment record");
982            let half_width = if record.is_stroked() {
983                record.stroke_width * 0.5
984            } else {
985                0.0
986            };
987            let [left, top, right, bottom] = crate::run_geometry::device_shape_edges(
988                record.rect,
989                half_width,
990                offset,
991                root_scale,
992                canonicalize,
993            );
994            for value in [left - origin_x, top - origin_y, right - left, bottom - top] {
995                hash_f32_for_cache(value, state);
996            }
997        }
998    }
999    hash_placement_clips(placement, clip, (origin_x, origin_y), root_scale, state);
1000    if tables.segments[run.segments.start as usize..run.segments.end as usize]
1001        .iter()
1002        .any(|segment| segment.gradient || segment.vertex_gradient)
1003    {
1004        let dither = placement
1005            .snap_anchor
1006            .map(|anchor| snapped_anchor_device_origin(anchor, root_scale))
1007            .unwrap_or_default();
1008        hash_f32_for_cache(dither.x - origin_x, state);
1009        hash_f32_for_cache(dither.y - origin_y, state);
1010    }
1011    hash_f32_for_cache(placement.alpha, state);
1012    match &placement.color_filter {
1013        Some(filter) => {
1014            1u8.hash(state);
1015            filter.render_hash().hash(state);
1016        }
1017        None => 0u8.hash(state),
1018    }
1019}
1020
1021pub(crate) fn shadow_content_hash(shadow: &ShadowDraw, root_scale: f32) -> u64 {
1022    let mut hasher = FxHasher::default();
1023    let origin = shape_shadow_bounds(shadow).unwrap_or(Rect {
1024        x: 0.0,
1025        y: 0.0,
1026        width: 0.0,
1027        height: 0.0,
1028    });
1029    let device = anchored_device_rect(
1030        origin,
1031        shadow
1032            .shapes
1033            .as_ref()
1034            .and_then(|run| run.placement.snap_anchor),
1035        root_scale,
1036    );
1037    for run in shadow.shapes.iter().chain(&shadow.post_blur_cutouts) {
1038        hash_run_item_with_clip(
1039            run,
1040            run.placement.clip,
1041            device.x.floor(),
1042            device.y.floor(),
1043            root_scale,
1044            &mut hasher,
1045        );
1046    }
1047    hasher.finish()
1048}
1049
1050fn shape_shadow_surface_cache_key(
1051    shadow: &ShadowDraw,
1052    device_bounds: DevicePixelBounds,
1053    pixel_radius: f32,
1054    root_scale: f32,
1055) -> Option<ShadowSurfaceCacheKey> {
1056    (root_scale.is_finite() && root_scale > 0.0).then(|| ShadowSurfaceCacheKey {
1057        content_hash: shadow_content_hash(shadow, root_scale),
1058        pixel_size: [device_bounds.width, device_bounds.height],
1059        root_scale_bits: root_scale.to_bits(),
1060        blur_radius_bits: pixel_radius.to_bits(),
1061    })
1062}
1063
1064fn shape_shadow_bounds(shadow: &ShadowDraw) -> Option<Rect> {
1065    shadow.shapes.as_ref().map(|run| run.bounds)
1066}
1067
1068pub(crate) fn shadow_draw_bounds(shadow: &ShadowDraw) -> Option<Rect> {
1069    shape_shadow_bounds(shadow)
1070        .into_iter()
1071        .chain(shadow.texts.iter().map(|text| text.rect))
1072        .reduce(|a, b| Rect {
1073            x: a.x.min(b.x),
1074            y: a.y.min(b.y),
1075            width: (a.x + a.width).max(b.x + b.width) - a.x.min(b.x),
1076            height: (a.y + a.height).max(b.y + b.height) - a.y.min(b.y),
1077        })
1078}
1079
1080/// The shape shader's source for `mode`, built when its module is parsed.
1081fn shape_shader_code(mode: RunBufferMode) -> ShaderCode {
1082    if mode.trig_fill {
1083        ShaderCode::wgsl(|| Cow::Owned(shaders::filling_shape_shader()))
1084    } else if mode.storage {
1085        ShaderCode::wgsl(|| Cow::Owned(shaders::storage_shape_shader()))
1086    } else {
1087        shaders::SHAPE
1088    }
1089}
1090
1091/// The vertex entry point of every fullscreen strip.
1092pub(crate) const FULLSCREEN_VS: &str = "fullscreen_vs";
1093
1094/// A pipeline that draws one full-screen triangle strip from `fullscreen_vs`
1095/// into a single color target, the shape every effect and composite pass
1096/// shares; `constants` fixes the shader's override constants.
1097#[expect(clippy::too_many_arguments)]
1098pub(crate) fn create_fullscreen_strip_pipeline(
1099    device: &wgpu::Device,
1100    cache: Option<&wgpu::PipelineCache>,
1101    log_label: &str,
1102    label: &'static str,
1103    layout: &wgpu::PipelineLayout,
1104    (vertex_module, fragment_module): (&wgpu::ShaderModule, &wgpu::ShaderModule),
1105    (vertex_entry, fragment_entry): (&'static str, &'static str),
1106    constants: &[(&str, f64)],
1107    target: wgpu::ColorTargetState,
1108) -> wgpu::RenderPipeline {
1109    create_render_pipeline_logged(
1110        device,
1111        cache,
1112        log_label,
1113        wgpu::RenderPipelineDescriptor {
1114            label: Some(label),
1115            layout: Some(layout),
1116            vertex: wgpu::VertexState {
1117                module: vertex_module,
1118                entry_point: Some(vertex_entry),
1119                buffers: &[],
1120                compilation_options: wgpu::PipelineCompilationOptions {
1121                    constants,
1122                    ..wgpu::PipelineCompilationOptions::default()
1123                },
1124            },
1125            fragment: Some(wgpu::FragmentState {
1126                module: fragment_module,
1127                entry_point: Some(fragment_entry),
1128                targets: &[Some(target)],
1129                compilation_options: wgpu::PipelineCompilationOptions {
1130                    constants,
1131                    ..wgpu::PipelineCompilationOptions::default()
1132                },
1133            }),
1134            primitive: wgpu::PrimitiveState {
1135                topology: wgpu::PrimitiveTopology::TriangleStrip,
1136                strip_index_format: None,
1137                front_face: wgpu::FrontFace::Ccw,
1138                cull_mode: None,
1139                ..Default::default()
1140            },
1141            depth_stencil: None,
1142            multisample: wgpu::MultisampleState::default(),
1143            multiview_mask: None,
1144            cache: None,
1145        },
1146    )
1147}
1148
1149pub(crate) fn create_render_pipeline_logged<'a>(
1150    device: &wgpu::Device,
1151    cache: Option<&'a wgpu::PipelineCache>,
1152    tag: &str,
1153    mut descriptor: wgpu::RenderPipelineDescriptor<'a>,
1154) -> wgpu::RenderPipeline {
1155    descriptor.cache = cache;
1156    build_pipeline_logged(tag, || device.create_render_pipeline(&descriptor))
1157}
1158
1159pub(crate) fn build_pipeline_logged<T>(tag: &str, build: impl FnOnce() -> T) -> T {
1160    let started = Instant::now();
1161    let pipeline = build();
1162    log::info!(
1163        "[pipeline-create] {tag} {:.1}ms on {}",
1164        instant_ms(started, Instant::now()),
1165        std::thread::current().name().unwrap_or("unnamed thread"),
1166    );
1167    if OFF_FRAME_BUILDS.with(Cell::get) {
1168        PIPELINES_CREATED_OFF_FRAME.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1169    } else {
1170        PIPELINES_CREATED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
1171        THREAD_BUILDS.with(|builds| builds.set(builds.get() + 1));
1172    }
1173    #[cfg(not(target_arch = "wasm32"))]
1174    crate::pipeline_disk_cache::note_change();
1175    pipeline
1176}
1177
1178/// Pipelines this process has built on a thread that draws.
1179///
1180/// A build runs the backend's shader compiler, and whoever asks for one while
1181/// drawing waits for it there. A count that grows across an interaction names
1182/// work a person waited on, whatever the driver's own caches made a compile
1183/// cost on this machine. Builds handed to [`crate::pipeline_compiler`] are
1184/// counted apart, by [`pipelines_created_off_frame`]: they cost a frame
1185/// nothing.
1186static PIPELINES_CREATED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
1187static PIPELINES_CREATED_OFF_FRAME: std::sync::atomic::AtomicU64 =
1188    std::sync::atomic::AtomicU64::new(0);
1189
1190thread_local! {
1191    static OFF_FRAME_BUILDS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
1192    /// The pipelines this drawing thread has built itself.
1193    static THREAD_BUILDS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
1194}
1195
1196/// The pipelines this thread has built while drawing: a frame that drew
1197/// with this unchanged built nothing itself.
1198#[cfg(not(target_arch = "wasm32"))]
1199fn thread_builds() -> u64 {
1200    THREAD_BUILDS.with(Cell::get)
1201}
1202
1203/// Declares that pipelines built on this thread are built away from any
1204/// frame. The compiler thread says so once, when it starts.
1205#[cfg(not(target_arch = "wasm32"))]
1206pub(crate) fn mark_thread_off_frame() {
1207    OFF_FRAME_BUILDS.with(|off_frame| off_frame.set(true));
1208}
1209
1210pub fn pipelines_created() -> u64 {
1211    PIPELINES_CREATED.load(std::sync::atomic::Ordering::Relaxed)
1212}
1213
1214/// Pipelines built away from every frame, on the compiler thread.
1215pub fn pipelines_created_off_frame() -> u64 {
1216    PIPELINES_CREATED_OFF_FRAME.load(std::sync::atomic::Ordering::Relaxed)
1217}
1218
1219#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1220#[repr(u8)]
1221pub(crate) enum RunTier {
1222    Store = 0,
1223    Arena = 1,
1224    Either = 2,
1225}
1226
1227#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1228pub(crate) struct ShapeVariant {
1229    kinds: u8,
1230    brush: Option<u8>,
1231    solid: bool,
1232    /// How its records meet their placements' clips. A rounded clip gives
1233    /// every fragment its coverage, on the general entry points.
1234    clip: SegmentClip,
1235    interior: bool,
1236    dither: bool,
1237    ablation: ShapeAblation,
1238}
1239
1240const FLAT_FILL_ENTRIES: [[(&str, &str); 2]; 2] = [
1241    [
1242        ("vs_record_plain_fill", "fs_plain_fill"),
1243        ("vs_record_dithered_fill", "fs_dithered_fill"),
1244    ],
1245    [
1246        ("vs_record_clipped_fill", "fs_clipped_fill"),
1247        ("vs_record_solid", "fs_solid"),
1248    ],
1249];
1250
1251/// The entry points of a variant that draws slices of path fills alone and
1252/// flat: a solid brush, and a gradient between two stops from the vertices.
1253const SLICE_ENTRIES: [(&str, &str); 2] = [
1254    ("vs_record_slice", "fs_slice"),
1255    ("vs_record_gradient_slice", "fs_gradient_slice"),
1256];
1257
1258const ALL_SHAPE_KINDS: u8 =
1259    (1 << FRAGMENT_KIND_FILL) | (1 << FRAGMENT_KIND_STROKE) | (1 << FRAGMENT_KIND_ARC) | LINE_KINDS;
1260/// The kinds the shader draws from line records: segments, and slices of
1261/// path fills. A variant compiles in the code of only those it draws.
1262const LINE_KINDS: u8 = (1 << FRAGMENT_KIND_LINE) | TRAPEZOID_KIND;
1263const TRAPEZOID_KIND: u8 = 1 << FRAGMENT_KIND_TRAPEZOID;
1264
1265fn variant_kinds(kinds: u8) -> u8 {
1266    if kinds.count_ones() <= 1 {
1267        kinds
1268    } else {
1269        (ALL_SHAPE_KINDS & !LINE_KINDS) | (kinds & LINE_KINDS)
1270    }
1271}
1272
1273impl ShapeVariant {
1274    /// Every kind but slices of path fills, which only a segment holding
1275    /// them compiles in.
1276    const GENERAL: Self = Self {
1277        kinds: ALL_SHAPE_KINDS & !TRAPEZOID_KIND,
1278        brush: None,
1279        solid: false,
1280        clip: SegmentClip::Tested,
1281        interior: true,
1282        dither: false,
1283        ablation: ShapeAblation {
1284            material: false,
1285            fill: false,
1286        },
1287    };
1288
1289    /// The variant `segment` draws with. `vertex_gradients` says a rect
1290    /// fill may take its gradient from its vertices here; a segment of
1291    /// slices of path fills may wherever it is drawn `flat`. Under a rounded
1292    /// clip the variant keeps the general entry points, which carry the
1293    /// clip's radius to the fragment, but compiles in only the segment's
1294    /// kinds and brush: the general program with every kind and brush is
1295    /// twice the code (Mali-G76: 108-115 ms to compile or link, the
1296    /// specialized ones 22-62).
1297    pub(crate) fn of_segment(
1298        segment: &RecordSegment,
1299        clip: SegmentClip,
1300        ablation: ShapeAblation,
1301        laid: bool,
1302        (flat, vertex_gradients): (bool, bool),
1303    ) -> Self {
1304        if !shape_variants_enabled() {
1305            return Self {
1306                kinds: Self::GENERAL.kinds | (segment.kinds & TRAPEZOID_KIND),
1307                ablation,
1308                ..Self::GENERAL
1309            }
1310            .clipped_by(clip);
1311        }
1312        let vertex_gradients = if segment.kinds == TRAPEZOID_KIND {
1313            flat
1314        } else {
1315            vertex_gradients
1316        };
1317        let gradient = segment.gradient || (segment.vertex_gradient && !vertex_gradients);
1318        Self {
1319            kinds: variant_kinds(segment.kinds),
1320            brush: gradient
1321                .then(|| segment.uniform_brush())
1322                .flatten()
1323                .map(|brush| brush as u8),
1324            solid: !gradient,
1325            clip,
1326            // A rounded clip's corners are not opaque: no interior path.
1327            interior: clip != SegmentClip::Rounded
1328                && gradient
1329                && segment.interiors
1330                && !(laid && segment.occluders && !segment.bare_interiors),
1331            dither: segment.vertex_gradient && vertex_gradients,
1332            ablation,
1333        }
1334    }
1335
1336    /// The one kind the variant draws, as the shader's packed kind names
1337    /// it: a slice of a path fill is a line record.
1338    fn kind(self) -> Option<u8> {
1339        (self.kinds.count_ones() == 1).then(|| match self.kinds.trailing_zeros() {
1340            FRAGMENT_KIND_TRAPEZOID => FRAGMENT_KIND_LINE as u8,
1341            kind => kind as u8,
1342        })
1343    }
1344
1345    /// The vertex and fragment entry points of this variant, for records
1346    /// drawn `flat` (none of them turned).
1347    fn entries(self, flat: bool) -> (&'static str, &'static str) {
1348        let fill = self.kind() == Some(FRAGMENT_KIND_FILL as u8);
1349        if self.rounded() {
1350            ("vs_record", "fs_main")
1351        } else if self.solid && flat && self.kinds == TRAPEZOID_KIND {
1352            SLICE_ENTRIES[usize::from(self.dither)]
1353        } else if self.solid && fill && flat {
1354            FLAT_FILL_ENTRIES[usize::from(self.clip == SegmentClip::Tested)]
1355                [usize::from(self.dither)]
1356        } else if self.solid {
1357            ("vs_record_solid", "fs_solid")
1358        } else if fill && !self.ablation.material {
1359            ("vs_record_gradient_fill", "fs_gradient_fill")
1360        } else {
1361            ("vs_record", "fs_main")
1362        }
1363    }
1364
1365    fn general(self) -> Self {
1366        Self {
1367            kinds: (ALL_SHAPE_KINDS & !LINE_KINDS) | (self.kinds & LINE_KINDS),
1368            ablation: self.ablation,
1369            ..Self::GENERAL
1370        }
1371        .clipped_by(self.clip)
1372    }
1373
1374    /// The variant, keeping the rounded clip `clip` names: the general
1375    /// variant tests every rect clip, but no fallback may drop a rounding.
1376    fn clipped_by(self, clip: SegmentClip) -> Self {
1377        if clip == SegmentClip::Rounded {
1378            Self { clip, ..self }
1379        } else {
1380            self
1381        }
1382    }
1383
1384    fn rounded(self) -> bool {
1385        self.clip == SegmentClip::Rounded
1386    }
1387
1388    fn joined(self) -> Self {
1389        if self.solid {
1390            Self {
1391                dither: true,
1392                ..self
1393            }
1394        } else {
1395            Self {
1396                interior: true,
1397                ..self
1398            }
1399        }
1400    }
1401}
1402
1403/// How a segment's draw meets its placement's clip: a scissor or no clip
1404/// leaves the shader nothing to test, a rect clip off the scissor's grid is
1405/// tested per fragment, and a rounded clip gives every fragment coverage.
1406#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1407pub(crate) enum SegmentClip {
1408    Untested,
1409    Tested,
1410    Rounded,
1411}
1412
1413impl SegmentClip {
1414    /// The clip a draw under `placement` tests, given whether its rect clip
1415    /// became the draw's scissor.
1416    pub(crate) fn of(placement: &crate::scene::Placement, scissored: bool) -> Self {
1417        if placement.clip_rounded() {
1418            Self::Rounded
1419        } else if placement.clip.is_some() && !scissored {
1420            Self::Tested
1421        } else {
1422            Self::Untested
1423        }
1424    }
1425}
1426
1427static SHAPE_VARIANTS: DebugToggle = DebugToggle::new("CRANPOSE_SHAPE_VARIANTS");
1428
1429fn shape_variants_enabled() -> bool {
1430    !SHAPE_VARIANTS.equals("0")
1431}
1432
1433/// What every draw of one pass shares: its target's size and whether the
1434/// pass has a depth buffer its opaque interiors fill first.
1435#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1436pub(crate) struct PassFrame {
1437    pub(crate) size: (u32, u32),
1438    pub(crate) depth: bool,
1439}
1440
1441impl PassFrame {
1442    /// The scissor a draw bounded by `scissor`, or by nothing, sets.
1443    pub(crate) fn scissor(self, scissor: Option<(u32, u32, u32, u32)>) -> (u32, u32, u32, u32) {
1444        scissor.unwrap_or((0, 0, self.size.0, self.size.1))
1445    }
1446}
1447
1448/// Which of a pass's two stages a shape draw records: the front-to-back
1449/// opaque interiors, or the paint in draw order.
1450#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1451pub(crate) enum RunStage {
1452    Interiors,
1453    Paint,
1454}
1455
1456/// The depth buffer of a pass that lays opaque interiors down first.
1457pub(crate) const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float;
1458
1459/// How a pipeline meets its pass's depth buffer.
1460#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1461pub(crate) enum ShapeDepth {
1462    /// The pass has no depth buffer.
1463    Off,
1464    /// Paints what the opaque interiors of later records leave visible.
1465    Tested,
1466    /// Lays down opaque interiors, front to back, ahead of the paint.
1467    Interior,
1468}
1469
1470impl ShapeDepth {
1471    fn stencil_state(self) -> Option<wgpu::DepthStencilState> {
1472        let (depth_write_enabled, depth_compare) = match self {
1473            Self::Off => return None,
1474            Self::Tested => (false, wgpu::CompareFunction::Less),
1475            Self::Interior => (true, wgpu::CompareFunction::Less),
1476        };
1477        Some(wgpu::DepthStencilState {
1478            format: DEPTH_FORMAT,
1479            depth_write_enabled: Some(depth_write_enabled),
1480            depth_compare: Some(depth_compare),
1481            stencil: wgpu::StencilState::default(),
1482            bias: wgpu::DepthBiasState::default(),
1483        })
1484    }
1485}
1486
1487/// The depth state of a glyph or image pipeline: in a pass with a depth
1488/// buffer it paints what later opaque interiors leave visible, as shapes do.
1489pub(crate) fn overlay_depth_state(depth: bool) -> Option<wgpu::DepthStencilState> {
1490    if depth {
1491        ShapeDepth::Tested.stencil_state()
1492    } else {
1493        None
1494    }
1495}
1496
1497/// Which records a shape pipeline draws turned, as `SHAPE_TURNS` in the
1498/// shape shader: none, every one, or each as its placement says.
1499#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1500pub(crate) enum ShapeTurns {
1501    None,
1502    All,
1503    Mixed,
1504}
1505
1506impl ShapeTurns {
1507    /// The turns of records placed under `turn`, in a pass whose flat and
1508    /// turned records alternate often when `mixed`.
1509    pub(crate) fn of(turn: SegmentTransform, mixed: bool) -> Self {
1510        if mixed {
1511            Self::Mixed
1512        } else if turn.is_identity() {
1513            Self::None
1514        } else {
1515            Self::All
1516        }
1517    }
1518
1519    fn constant(self) -> f64 {
1520        match self {
1521            Self::None => 0.0,
1522            Self::All => 1.0,
1523            Self::Mixed => 2.0,
1524        }
1525    }
1526}
1527
1528#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1529pub(crate) struct ShapePipelineKey {
1530    pub(crate) blend_mode: BlendMode,
1531    pub(crate) tier: RunTier,
1532    pub(crate) variant: ShapeVariant,
1533    pub(crate) turns: ShapeTurns,
1534    pub(crate) depth: ShapeDepth,
1535}
1536
1537/// Packs a key's fields into a number, low bit first.
1538#[cfg(not(target_arch = "wasm32"))]
1539#[derive(Default)]
1540struct KeyWriter {
1541    bits: u64,
1542    width: u32,
1543}
1544
1545#[cfg(not(target_arch = "wasm32"))]
1546impl KeyWriter {
1547    fn put(&mut self, value: u64, width: u32) {
1548        self.bits |= value << self.width;
1549        self.width += width;
1550    }
1551
1552    fn put_byte(&mut self, value: Option<u8>) {
1553        self.put(u64::from(value.is_some()), 1);
1554        self.put(u64::from(value.unwrap_or_default()), 8);
1555    }
1556}
1557
1558/// Unpacks what [`KeyWriter`] packed, low bit first.
1559/// The bits a key's shape kinds take: one per fragment kind.
1560const KIND_BITS: u32 = FRAGMENT_KIND_TRAPEZOID + 1;
1561
1562struct KeyReader(u64);
1563
1564impl KeyReader {
1565    fn take(&mut self, width: u32) -> u64 {
1566        let value = self.0 & ((1 << width) - 1);
1567        self.0 >>= width;
1568        value
1569    }
1570
1571    fn take_flag(&mut self) -> bool {
1572        self.take(1) == 1
1573    }
1574
1575    fn take_byte(&mut self) -> Option<u8> {
1576        let present = self.take_flag();
1577        let value = u8::try_from(self.take(8)).ok()?;
1578        present.then_some(value)
1579    }
1580
1581    fn take_kinds(&mut self) -> Option<u8> {
1582        u8::try_from(self.take(KIND_BITS))
1583            .ok()
1584            .filter(|kinds| *kinds != 0)
1585    }
1586}
1587
1588impl ShapePipelineKey {
1589    #[cfg(not(target_arch = "wasm32"))]
1590    pub(crate) const DISK_LAYOUT: [u8; 8] = *b"CPKEY003";
1591
1592    /// The flat solid rect fill of a pass without a depth buffer, which
1593    /// every screen draws: on all 38 demo pages, and in a launch's first
1594    /// frames, which draw without the depth pre-pass.
1595    #[cfg(not(target_arch = "wasm32"))]
1596    const SOLID_RECT: Self = Self {
1597        blend_mode: BlendMode::SrcOver,
1598        tier: RunTier::Arena,
1599        variant: ShapeVariant {
1600            kinds: 1 << FRAGMENT_KIND_FILL,
1601            brush: None,
1602            solid: true,
1603            clip: SegmentClip::Untested,
1604            interior: false,
1605            dither: false,
1606            ablation: ShapeAblation {
1607                material: false,
1608                fill: false,
1609            },
1610        },
1611        turns: ShapeTurns::None,
1612        depth: ShapeDepth::Off,
1613    };
1614
1615    /// The key as a number a later launch reads back with
1616    /// [`Self::from_bits`], to build the pipeline ahead of its first frame.
1617    #[cfg(not(target_arch = "wasm32"))]
1618    pub(crate) fn to_bits(self) -> u64 {
1619        let mut bits = KeyWriter::default();
1620        bits.put(self.blend_mode as u64, 5);
1621        bits.put(self.tier as u64, 2);
1622        let variant = self.variant;
1623        bits.put(u64::from(variant.kinds), KIND_BITS);
1624        bits.put_byte(variant.brush);
1625        bits.put(u64::from(variant.solid), 1);
1626        bits.put(
1627            match variant.clip {
1628                SegmentClip::Untested => 0,
1629                SegmentClip::Tested => 1,
1630                SegmentClip::Rounded => 2,
1631            },
1632            2,
1633        );
1634        bits.put(u64::from(variant.interior), 1);
1635        bits.put(u64::from(variant.dither), 1);
1636        bits.put(u64::from(variant.ablation.material), 1);
1637        bits.put(u64::from(variant.ablation.fill), 1);
1638        bits.put(
1639            match self.turns {
1640                ShapeTurns::None => 0,
1641                ShapeTurns::All => 1,
1642                ShapeTurns::Mixed => 2,
1643            },
1644            2,
1645        );
1646        bits.put(
1647            match self.depth {
1648                ShapeDepth::Off => 0,
1649                ShapeDepth::Tested => 1,
1650                ShapeDepth::Interior => 2,
1651            },
1652            2,
1653        );
1654        bits.bits
1655    }
1656
1657    /// The key [`Self::to_bits`] packed, or `None` for a number no key
1658    /// packs to.
1659    pub(crate) fn from_bits(packed: u64) -> Option<Self> {
1660        let mut bits = KeyReader(packed);
1661        let blend_mode = *BlendMode::ALL.get(usize::try_from(bits.take(5)).ok()?)?;
1662        let tier = match bits.take(2) {
1663            0 => RunTier::Store,
1664            1 => RunTier::Arena,
1665            2 => RunTier::Either,
1666            _ => return None,
1667        };
1668        let kinds = bits.take_kinds()?;
1669        let brush = bits.take_byte();
1670        let solid = bits.take_flag();
1671        let clip = match bits.take(2) {
1672            0 => SegmentClip::Untested,
1673            1 => SegmentClip::Tested,
1674            2 => SegmentClip::Rounded,
1675            _ => return None,
1676        };
1677        let interior = bits.take_flag();
1678        let dither = bits.take_flag();
1679        let ablation = ShapeAblation {
1680            material: bits.take_flag(),
1681            fill: bits.take_flag(),
1682        };
1683        let turns = match bits.take(2) {
1684            0 => ShapeTurns::None,
1685            1 => ShapeTurns::All,
1686            2 => ShapeTurns::Mixed,
1687            _ => return None,
1688        };
1689        let depth = match bits.take(2) {
1690            0 => ShapeDepth::Off,
1691            1 => ShapeDepth::Tested,
1692            2 => ShapeDepth::Interior,
1693            _ => return None,
1694        };
1695        (bits.0 == 0).then_some(Self {
1696            blend_mode,
1697            tier,
1698            variant: ShapeVariant {
1699                kinds,
1700                brush,
1701                solid,
1702                clip,
1703                interior,
1704                dither,
1705                ablation,
1706            },
1707            turns,
1708            depth,
1709        })
1710    }
1711
1712    #[cfg(test)]
1713    pub(crate) fn general_for(blend_mode: BlendMode, tier: RunTier) -> Self {
1714        Self {
1715            blend_mode,
1716            tier,
1717            variant: ShapeVariant::GENERAL,
1718            turns: ShapeTurns::None,
1719            depth: ShapeDepth::Off,
1720        }
1721    }
1722
1723    /// The pipeline that lays down the opaque interiors of this key's
1724    /// draws, if they have any: plain source-over draws. A turned record's
1725    /// layer draws in place only under a rigid turn, which keeps the
1726    /// interior's half-pixel inset from the fill's edge exact.
1727    pub(crate) fn interior(self) -> Option<Self> {
1728        (self.depth == ShapeDepth::Tested
1729            && !self.variant.rounded()
1730            && self.blend_mode == BlendMode::SrcOver
1731            && !self.variant.ablation.material
1732            && !self.variant.ablation.fill)
1733            .then_some(Self {
1734                variant: self.variant.general(),
1735                depth: ShapeDepth::Interior,
1736                ..self
1737            })
1738    }
1739
1740    pub(crate) fn general(self) -> Self {
1741        Self {
1742            tier: RunTier::Either,
1743            variant: self.variant.general(),
1744            ..self
1745        }
1746    }
1747
1748    pub(crate) fn is_general(self) -> bool {
1749        self.variant == self.variant.general()
1750    }
1751
1752    pub(crate) fn joined(self) -> Self {
1753        Self {
1754            variant: self.variant.joined(),
1755            ..self
1756        }
1757    }
1758}
1759
1760/// The entry points and pipeline constants of the shape program a key names.
1761pub(crate) struct ShapeProgram {
1762    pub(crate) vertex_entry: &'static str,
1763    pub(crate) fragment_entry: &'static str,
1764    pub(crate) constants: [(&'static str, f64); 15],
1765}
1766
1767impl ShapePipelineKey {
1768    pub(crate) fn program(self, mode: RunBufferMode) -> ShapeProgram {
1769        let Self {
1770            tier,
1771            variant,
1772            turns,
1773            depth,
1774            ..
1775        } = self;
1776        let constants = [
1777            ("SHAPE_KIND_FIXED", variant.kind().map_or(-1.0, f64::from)),
1778            ("SHAPE_KINDS", f64::from(variant.kinds)),
1779            ("BRUSH_KIND_FIXED", variant.brush.map_or(-1.0, f64::from)),
1780            ("SHAPE_SOLID", f64::from(u8::from(variant.solid))),
1781            (
1782                "SHAPE_CLIPPED",
1783                f64::from(u8::from(variant.clip != SegmentClip::Untested)),
1784            ),
1785            ("SHAPE_ROUNDED_CLIP", f64::from(u8::from(variant.rounded()))),
1786            (
1787                "SHAPE_INTERIOR",
1788                f64::from(u8::from(variant.interior && !variant.rounded())),
1789            ),
1790            ("SHAPE_DITHER", f64::from(u8::from(variant.dither))),
1791            ("SHAPE_TURNS", turns.constant()),
1792            ("SHAPE_TIER", f64::from(tier as u8)),
1793            ("SHAPE_DEPTH", f64::from(u8::from(depth != ShapeDepth::Off))),
1794            ("SHAPE_BANDS", f64::from(u8::from(mode.storage))),
1795            ("SHAPE_TRIG_FILLED", f64::from(u8::from(mode.trig_fill))),
1796            ("SHAPE_FLAT", f64::from(u8::from(variant.ablation.material))),
1797            ("SHAPE_DISCARD", f64::from(u8::from(variant.ablation.fill))),
1798        ];
1799        let (vertex_entry, fragment_entry) = if depth == ShapeDepth::Interior {
1800            ("vs_record_interior", "fs_interior")
1801        } else {
1802            variant.entries(turns == ShapeTurns::None)
1803        };
1804        ShapeProgram {
1805            vertex_entry,
1806            fragment_entry,
1807            constants,
1808        }
1809    }
1810}
1811
1812pub(crate) fn create_shape_pipeline(
1813    device: &wgpu::Device,
1814    cache: Option<&wgpu::PipelineCache>,
1815    surface_format: wgpu::TextureFormat,
1816    shader: &SharedShader,
1817    key: ShapePipelineKey,
1818    mode: RunBufferMode,
1819) -> wgpu::RenderPipeline {
1820    let ShapePipelineKey {
1821        blend_mode,
1822        tier,
1823        variant,
1824        turns,
1825        depth,
1826    } = key;
1827    let ShapeProgram {
1828        vertex_entry,
1829        fragment_entry,
1830        constants,
1831    } = key.program(mode);
1832    let blend = (depth != ShapeDepth::Interior).then(|| blend_state_for_mode(blend_mode));
1833    let instance_layout = record_vertex_layouts().map(Some);
1834    let stages = shader.stages((vertex_entry, fragment_entry), &constants);
1835
1836    create_render_pipeline_logged(
1837        device,
1838        cache,
1839        &format!(
1840            "shape blend={blend_mode:?} tier={tier:?} variant={variant:?} turns={turns:?} depth={depth:?}"
1841        ),
1842        wgpu::RenderPipelineDescriptor {
1843            label: Some("Shape Pipeline"),
1844            layout: Some(shader.layout()),
1845            vertex: wgpu::VertexState {
1846                module: &stages.vertex,
1847                entry_point: Some(vertex_entry),
1848                compilation_options: wgpu::PipelineCompilationOptions {
1849                    constants: stages.constants,
1850                    ..wgpu::PipelineCompilationOptions::default()
1851                },
1852                buffers: &instance_layout,
1853            },
1854            fragment: Some(wgpu::FragmentState {
1855                module: &stages.fragment,
1856                entry_point: Some(fragment_entry),
1857                compilation_options: wgpu::PipelineCompilationOptions {
1858                    constants: stages.constants,
1859                    ..wgpu::PipelineCompilationOptions::default()
1860                },
1861                targets: &[Some(wgpu::ColorTargetState {
1862                    format: surface_format,
1863                    blend,
1864                    write_mask: wgpu::ColorWrites::ALL,
1865                })],
1866            }),
1867            primitive: wgpu::PrimitiveState {
1868                topology: wgpu::PrimitiveTopology::TriangleList,
1869                strip_index_format: None,
1870                front_face: wgpu::FrontFace::Ccw,
1871                cull_mode: None,
1872                unclipped_depth: false,
1873                polygon_mode: wgpu::PolygonMode::Fill,
1874                conservative: false,
1875            },
1876            depth_stencil: depth.stencil_state(),
1877            multisample: wgpu::MultisampleState::default(),
1878            multiview_mask: None,
1879            cache: None,
1880        },
1881    )
1882}
1883fn create_image_pipeline(
1884    device: &wgpu::Device,
1885    cache: Option<&wgpu::PipelineCache>,
1886    surface_format: wgpu::TextureFormat,
1887    shader: &SharedShader,
1888    blend_mode: BlendMode,
1889    variant: ImageVariant,
1890) -> wgpu::RenderPipeline {
1891    let module = shader.module();
1892    let ImageVariant {
1893        depth,
1894        alpha_mask,
1895        rounded,
1896    } = variant;
1897    let (vertex_entry, fragment_entry) = match (alpha_mask, rounded) {
1898        (false, false) => ("image_vs_main", "image_fs_main"),
1899        (true, false) => ("image_vs_main", "image_mask_fs_main"),
1900        (false, true) => ("image_rounded_vs_main", "image_rounded_fs_main"),
1901        (true, true) => ("image_rounded_vs_main", "image_mask_rounded_fs_main"),
1902    };
1903    // A rounded image reads its clip from an instance beside its vertices.
1904    let buffers = [Some(Vertex::desc()), Some(DeviceRoundedClip::desc())];
1905    create_render_pipeline_logged(
1906        device,
1907        cache,
1908        &format!(
1909            "image blend={blend_mode:?} depth={depth} alpha_mask={alpha_mask} rounded={rounded}"
1910        ),
1911        wgpu::RenderPipelineDescriptor {
1912            label: Some("Image Pipeline"),
1913            layout: Some(shader.layout()),
1914            vertex: wgpu::VertexState {
1915                module,
1916                entry_point: Some(vertex_entry),
1917                compilation_options: wgpu::PipelineCompilationOptions::default(),
1918                buffers: if rounded { &buffers } else { &buffers[..1] },
1919            },
1920            fragment: Some(wgpu::FragmentState {
1921                module,
1922                entry_point: Some(fragment_entry),
1923                compilation_options: wgpu::PipelineCompilationOptions::default(),
1924                targets: &[Some(wgpu::ColorTargetState {
1925                    format: surface_format,
1926                    blend: Some(blend_state_for_mode(blend_mode)),
1927                    write_mask: wgpu::ColorWrites::ALL,
1928                })],
1929            }),
1930            primitive: wgpu::PrimitiveState {
1931                topology: wgpu::PrimitiveTopology::TriangleList,
1932                strip_index_format: None,
1933                front_face: wgpu::FrontFace::Ccw,
1934                cull_mode: None,
1935                unclipped_depth: false,
1936                polygon_mode: wgpu::PolygonMode::Fill,
1937                conservative: false,
1938            },
1939            depth_stencil: overlay_depth_state(depth),
1940            multisample: wgpu::MultisampleState::default(),
1941            multiview_mask: None,
1942            cache: None,
1943        },
1944    )
1945}
1946
1947fn create_glyph_atlas_pipeline(
1948    device: &wgpu::Device,
1949    cache: Option<&wgpu::PipelineCache>,
1950    surface_format: wgpu::TextureFormat,
1951    shader: &SharedShader,
1952    (depth, quads): (bool, GlyphQuads),
1953) -> wgpu::RenderPipeline {
1954    let module = shader.module();
1955    create_render_pipeline_logged(
1956        device,
1957        cache,
1958        match (depth, quads) {
1959            (false, GlyphQuads::Plain) => "glyph-atlas",
1960            (true, GlyphQuads::Plain) => "glyph-atlas depth",
1961            (false, GlyphQuads::Aligned) => "glyph-atlas aligned",
1962            (true, GlyphQuads::Aligned) => "glyph-atlas aligned depth",
1963            (false, GlyphQuads::Turned) => "glyph-atlas turned",
1964            (true, GlyphQuads::Turned) => "glyph-atlas turned depth",
1965        },
1966        wgpu::RenderPipelineDescriptor {
1967            label: Some("Glyph Atlas Pipeline"),
1968            layout: Some(shader.layout()),
1969            vertex: wgpu::VertexState {
1970                module,
1971                entry_point: Some(match quads {
1972                    GlyphQuads::Plain => "glyph_atlas_vs_main",
1973                    GlyphQuads::Aligned => "glyph_atlas_aligned_vs_main",
1974                    GlyphQuads::Turned => "glyph_atlas_turned_vs_main",
1975                }),
1976                compilation_options: wgpu::PipelineCompilationOptions::default(),
1977                buffers: &[Some(match quads {
1978                    GlyphQuads::Turned => TurnedGlyph::desc(),
1979                    GlyphQuads::Plain | GlyphQuads::Aligned => GlyphInstance::desc(),
1980                })],
1981            },
1982            fragment: Some(wgpu::FragmentState {
1983                module,
1984                entry_point: Some(match quads {
1985                    GlyphQuads::Aligned => "glyph_atlas_aligned_fs_main",
1986                    GlyphQuads::Plain | GlyphQuads::Turned => "glyph_atlas_fs_main",
1987                }),
1988                compilation_options: wgpu::PipelineCompilationOptions::default(),
1989                targets: &[Some(wgpu::ColorTargetState {
1990                    format: surface_format,
1991                    blend: Some(blend_state_for_mode(BlendMode::SrcOver)),
1992                    write_mask: wgpu::ColorWrites::ALL,
1993                })],
1994            }),
1995            primitive: wgpu::PrimitiveState {
1996                topology: wgpu::PrimitiveTopology::TriangleStrip,
1997                strip_index_format: None,
1998                front_face: wgpu::FrontFace::Ccw,
1999                cull_mode: None,
2000                unclipped_depth: false,
2001                polygon_mode: wgpu::PolygonMode::Fill,
2002                conservative: false,
2003            },
2004            depth_stencil: overlay_depth_state(depth),
2005            multisample: wgpu::MultisampleState::default(),
2006            multiview_mask: None,
2007            cache: None,
2008        },
2009    )
2010}
2011
2012#[repr(C)]
2013#[derive(Copy, Clone, Debug, Pod, Zeroable)]
2014pub(crate) struct Vertex {
2015    position: [f32; 2],
2016    color: [f32; 4],
2017    uv: [f32; 2],
2018    uv_bounds: [f32; 4],
2019}
2020
2021impl Vertex {
2022    const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
2023        0 => Float32x2,
2024        1 => Float32x4,
2025        2 => Float32x2,
2026        3 => Float32x4,
2027    ];
2028
2029    fn desc() -> wgpu::VertexBufferLayout<'static> {
2030        wgpu::VertexBufferLayout {
2031            array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
2032            step_mode: wgpu::VertexStepMode::Vertex,
2033            attributes: &Self::ATTRIBS,
2034        }
2035    }
2036}
2037
2038/// The corners a glyph instance draws as a triangle strip: top-left,
2039/// top-right, bottom-left, bottom-right.
2040const GLYPH_QUAD_CORNERS: u32 = 4;
2041
2042/// One glyph quad as the glyph pipeline draws it: an instance whose four
2043/// corners the vertex stage picks from `rect` and `uv`.
2044#[repr(C)]
2045#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
2046pub(crate) struct GlyphInstance {
2047    rect: [f32; 4],
2048    uv: [f32; 4],
2049    uv_bounds: [f32; 4],
2050    color: [f32; 4],
2051}
2052
2053impl GlyphInstance {
2054    const ATTRIBS: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
2055        0 => Float32x4,
2056        1 => Float32x4,
2057        2 => Float32x4,
2058        3 => Float32x4
2059    ];
2060
2061    fn desc() -> wgpu::VertexBufferLayout<'static> {
2062        wgpu::VertexBufferLayout {
2063            array_stride: std::mem::size_of::<GlyphInstance>() as wgpu::BufferAddress,
2064            step_mode: wgpu::VertexStepMode::Instance,
2065            attributes: &Self::ATTRIBS,
2066        }
2067    }
2068
2069    /// The part of the quad inside `edges` (left, top, right, bottom, in the
2070    /// quad's own space), its atlas coordinates cut to match; `None` when
2071    /// none of it is inside.
2072    pub(crate) fn clipped_to(self, edges: [f32; 4]) -> Option<Self> {
2073        let [x0, y0, x1, y1] = self.rect;
2074        let rect = [
2075            x0.max(edges[0]),
2076            y0.max(edges[1]),
2077            x1.min(edges[2]),
2078            y1.min(edges[3]),
2079        ];
2080        if rect[2] <= rect[0] || rect[3] <= rect[1] {
2081            return None;
2082        }
2083        if rect == self.rect {
2084            return Some(self);
2085        }
2086        let [u0, v0, u1, v1] = self.uv;
2087        let u = |x: f32| u0 + (x - x0) / (x1 - x0) * (u1 - u0);
2088        let v = |y: f32| v0 + (y - y0) / (y1 - y0) * (v1 - v0);
2089        Some(Self {
2090            rect,
2091            uv: [u(rect[0]), v(rect[1]), u(rect[2]), v(rect[3])],
2092            ..self
2093        })
2094    }
2095}
2096
2097/// Hands `sink` each quad of a glyph run at `source_raster_rect` that the
2098/// viewport shows, as an instance cut to `cut` when the text is cut.
2099#[inline]
2100fn for_each_visible_glyph(
2101    source_raster_rect: Rect,
2102    quads: GlyphRunQuads<'_>,
2103    (clip, cut): (Option<Rect>, Option<[f32; 4]>),
2104    viewport: ViewportUniformParams,
2105    root_scale: f32,
2106    mut sink: impl FnMut(GlyphInstance),
2107) {
2108    let whole_run_visible =
2109        quads
2110            .bounds
2111            .within_viewport(source_raster_rect, clip, viewport, root_scale);
2112    for quad in quads.iter() {
2113        if !whole_run_visible
2114            && !cached_text_glyph_quad_is_visible_in_viewport(
2115                source_raster_rect,
2116                &quad,
2117                clip,
2118                viewport,
2119                root_scale,
2120            )
2121        {
2122            continue;
2123        }
2124        let Some(glyph) = cached_text_glyph_instance(source_raster_rect, &quad) else {
2125            continue;
2126        };
2127        let glyph = match cut {
2128            Some(edges) => glyph.clipped_to(edges),
2129            None => Some(glyph),
2130        };
2131        if let Some(glyph) = glyph {
2132            sink(glyph);
2133        }
2134    }
2135}
2136
2137/// A glyph quad of a layer drawn in place under a turn, with the turn that
2138/// places it: glyphs of layers turned differently then share a draw, and
2139/// every other glyph keeps [`GlyphInstance`]'s smaller layout.
2140#[repr(C)]
2141#[derive(Copy, Clone, Debug, PartialEq, Pod, Zeroable)]
2142pub(crate) struct TurnedGlyph {
2143    glyph: GlyphInstance,
2144    /// The turn, row-major.
2145    turn: [f32; 4],
2146    /// The turn's translation in `xy`.
2147    translation: [f32; 4],
2148}
2149
2150impl TurnedGlyph {
2151    const ATTRIBS: [wgpu::VertexAttribute; 6] = wgpu::vertex_attr_array![
2152        0 => Float32x4,
2153        1 => Float32x4,
2154        2 => Float32x4,
2155        3 => Float32x4,
2156        4 => Float32x4,
2157        5 => Float32x4
2158    ];
2159
2160    pub(crate) fn new(glyph: GlyphInstance, turn: SegmentTransform) -> Self {
2161        let (linear, [x, y], _) = turn.uniform_parts();
2162        Self {
2163            glyph,
2164            turn: linear,
2165            translation: [x, y, 0.0, 0.0],
2166        }
2167    }
2168
2169    fn desc() -> wgpu::VertexBufferLayout<'static> {
2170        wgpu::VertexBufferLayout {
2171            array_stride: std::mem::size_of::<TurnedGlyph>() as wgpu::BufferAddress,
2172            step_mode: wgpu::VertexStepMode::Instance,
2173            attributes: &Self::ATTRIBS,
2174        }
2175    }
2176}
2177
2178/// The glyph quads a pass draws from its shared buffers: the plain ones and
2179/// those of layers drawn in place under a turn.
2180#[derive(Default)]
2181pub(crate) struct GlyphInstances {
2182    pub(crate) plain: FrameScratch<GlyphInstance>,
2183    pub(crate) turned: FrameScratch<TurnedGlyph>,
2184}
2185
2186impl GlyphInstances {
2187    pub(crate) fn clear(&mut self) {
2188        self.plain.clear();
2189        self.turned.clear();
2190    }
2191
2192    /// Ends a frame: see [`FrameScratch::end_frame`]. Passes need the
2193    /// most glyphs at startup, when nothing is cached yet.
2194    pub(crate) fn end_frame(&mut self) {
2195        self.plain.end_frame();
2196        self.turned.end_frame();
2197    }
2198
2199    fn lens(&self) -> (usize, usize) {
2200        (self.plain.len(), self.turned.len())
2201    }
2202
2203    /// How many turned glyphs, or plain ones, the pass holds.
2204    fn len_of(&self, turned: bool) -> usize {
2205        if turned {
2206            self.turned.len()
2207        } else {
2208            self.plain.len()
2209        }
2210    }
2211
2212    fn truncate(&mut self, (plain, turned): (usize, usize)) {
2213        self.plain.truncate(plain);
2214        self.turned.truncate(turned);
2215    }
2216}
2217
2218/// A text clip's edges in device pixels of the space its glyph quads are
2219/// laid out in, before a viewport transform moves them.
2220fn glyph_clip_edges(clip: Rect, root_scale: f32) -> [f32; 4] {
2221    [
2222        canonicalize_device_coordinate(clip.x * root_scale),
2223        canonicalize_device_coordinate(clip.y * root_scale),
2224        canonicalize_device_coordinate((clip.x + clip.width) * root_scale),
2225        canonicalize_device_coordinate((clip.y + clip.height) * root_scale),
2226    ]
2227}
2228
2229/// Where the glyph quads of a text are cut, in the quads' own space. Under a
2230/// turned viewport the cut is the text's `clip` itself, before the turn,
2231/// which no scissor can follow; the text then draws without one. Otherwise
2232/// it is the pixel edges of the text's `scissor`, its draw rect within its
2233/// clip: cut there, the quads cover exactly the pixels the scissor would
2234/// pass, with the same texels, so the text needs no scissor of its own and
2235/// draws in one call with its neighbours, even where a last line's
2236/// descenders or a glyph's padding reach past its draw rect.
2237fn glyph_cut_edges(
2238    clip: Option<Rect>,
2239    scissor: TargetRect,
2240    viewport: ViewportUniformParams,
2241    root_scale: f32,
2242) -> Option<[f32; 4]> {
2243    if !viewport.transform.is_identity() {
2244        return clip.map(|clip| glyph_clip_edges(clip, root_scale));
2245    }
2246    let (x, y, width, height) = scissor;
2247    let [offset_x, offset_y] = viewport.offset;
2248    Some([
2249        x as f32 + offset_x,
2250        y as f32 + offset_y,
2251        (x + width) as f32 + offset_x,
2252        (y + height) as f32 + offset_y,
2253    ])
2254}
2255
2256#[repr(C)]
2257#[derive(Copy, Clone, Debug, Pod, Zeroable)]
2258struct Uniforms {
2259    viewport: [f32; 2],
2260    viewport_offset: [f32; 2],
2261    transform: [f32; 4],
2262    translation: [f32; 2],
2263    reserved: [f32; 2],
2264    inverse: [f32; 4],
2265    origin: [f32; 2],
2266    origin_reserved: [f32; 2],
2267    placement: PlacementData,
2268}
2269
2270impl Uniforms {
2271    fn of(params: ViewportUniformParams, placement: PlacementData, tier: RunTier) -> Self {
2272        let (transform, translation, inverse) = params.transform.uniform_parts();
2273        Self {
2274            viewport: [params.width as f32, params.height as f32],
2275            viewport_offset: params.offset,
2276            transform,
2277            translation,
2278            reserved: [params.depth_base, f32::from(tier as u8)],
2279            inverse,
2280            origin: params.origin,
2281            origin_reserved: [0.0; 2],
2282            placement,
2283        }
2284    }
2285}
2286
2287static SURVIVE_GPU_ERRORS: DebugToggle = DebugToggle::new("CRANPOSE_SURVIVE_GPU_ERRORS");
2288
2289fn survive_gpu_errors_enabled() -> bool {
2290    !SURVIVE_GPU_ERRORS.equals("0")
2291}
2292
2293struct CachedImageTexture {
2294    alpha_mask: bool,
2295    _texture: wgpu::Texture,
2296    _view: wgpu::TextureView,
2297    nearest_bind_group: wgpu::BindGroup,
2298    linear_bind_group: wgpu::BindGroup,
2299    bytes: usize,
2300}
2301
2302impl CachedImageTexture {
2303    fn binding(&self, sampling: ImageSampling) -> ImageBinding {
2304        ImageBinding {
2305            bind_group: match sampling {
2306                ImageSampling::Nearest => self.nearest_bind_group.clone(),
2307                ImageSampling::Linear => self.linear_bind_group.clone(),
2308            },
2309            alpha_mask: self.alpha_mask,
2310        }
2311    }
2312}
2313
2314/// What an image's draw binds: its texture's bind group for the draw's
2315/// sampling, which keeps the texture alive whatever the cache evicts before
2316/// the pass is drawn, and whether the texture is an alpha mask.
2317pub(crate) struct ImageBinding {
2318    bind_group: wgpu::BindGroup,
2319    alpha_mask: bool,
2320}
2321
2322#[derive(Clone, Copy)]
2323struct GlyphAtlasEntry {
2324    x: u16,
2325    y: u16,
2326    width: u16,
2327    height: u16,
2328}
2329
2330fn next_glyph_atlas_size(current: u32, max: u32) -> u32 {
2331    current.saturating_mul(2).clamp(1, max.max(1))
2332}
2333
2334/// The samplers the glyph atlas binds: nearest for glyphs on the pixel grid,
2335/// linear for glyphs a segment transform turns.
2336#[derive(Clone, Copy)]
2337struct GlyphSamplers<'a> {
2338    nearest: &'a wgpu::Sampler,
2339    linear: &'a wgpu::Sampler,
2340}
2341
2342/// A glyph's atlas slot: its mask corrected for the luminance class of the
2343/// text it is drawn in, so one glyph in light and dark text takes two slots.
2344#[derive(Clone, Copy, PartialEq, Eq, Hash)]
2345struct GlyphAtlasSlotKey {
2346    glyph: SoftwareGlyphAtlasKey,
2347    luminance: TextLuminance,
2348}
2349
2350impl GlyphAtlasSlotKey {
2351    fn new(glyph: SoftwareGlyphAtlasKey, color: Color) -> Self {
2352        Self {
2353            glyph,
2354            luminance: TextLuminance::of_color(color),
2355        }
2356    }
2357}
2358
2359/// Slots of [`RunAtlasEntries`], by glyph id.
2360const RUN_ATLAS_ENTRY_SLOTS: usize = 64;
2361
2362/// The atlas entries a run's preparation has looked up, by glyph id: a run
2363/// repeats its letters, and each atlas lookup hashes the slot key and moves
2364/// the entry up the atlas's recency order, where the first lookup already
2365/// put it for this frame.
2366struct RunAtlasEntries {
2367    slots: [Option<(GlyphAtlasSlotKey, GlyphAtlasEntry)>; RUN_ATLAS_ENTRY_SLOTS],
2368}
2369
2370impl RunAtlasEntries {
2371    fn new() -> Self {
2372        Self {
2373            slots: [None; RUN_ATLAS_ENTRY_SLOTS],
2374        }
2375    }
2376
2377    /// `glyph`'s entry from an earlier glyph of the run, else from
2378    /// `look_up`, remembered for the rest of it.
2379    fn entry(
2380        &mut self,
2381        renderer: &mut GpuRenderer,
2382        glyph: &SoftwareGlyphAtlasPlacement,
2383        look_up: impl FnOnce(&mut GpuRenderer) -> Result<GlyphAtlasEntry, String>,
2384    ) -> Result<GlyphAtlasEntry, String> {
2385        let key = GlyphAtlasSlotKey::new(glyph.key, glyph.color);
2386        let slot = &mut self.slots[glyph.key.glyph_id as usize % RUN_ATLAS_ENTRY_SLOTS];
2387        if let Some((seen, entry)) = *slot
2388            && seen == key
2389        {
2390            renderer.frame_stats.record_text_glyph_atlas_hits(1);
2391            return Ok(entry);
2392        }
2393        let entry = look_up(renderer)?;
2394        *slot = Some((key, entry));
2395        Ok(entry)
2396    }
2397}
2398
2399struct TextGlyphAtlas {
2400    texture: wgpu::Texture,
2401    _view: wgpu::TextureView,
2402    texel_bind_group: Rc<wgpu::BindGroup>,
2403    filtered_bind_group: Rc<wgpu::BindGroup>,
2404    entries: BoundedLruCache<GlyphAtlasSlotKey, GlyphAtlasEntry>,
2405    generation: u64,
2406    size: u32,
2407    max_size: u32,
2408    cursor_x: u32,
2409    cursor_y: u32,
2410    row_height: u32,
2411    upload_scratch: Vec<u8>,
2412}
2413
2414impl TextGlyphAtlas {
2415    fn new(
2416        device: &wgpu::Device,
2417        image_layout: &wgpu::BindGroupLayout,
2418        samplers: GlyphSamplers<'_>,
2419        size: u32,
2420    ) -> Self {
2421        // The side stays a power of two, whose texel is exact: see
2422        // [`TextGlyphAtlas::texel`].
2423        let device_max = device.limits().max_texture_dimension_2d.max(1);
2424        let max_size = TEXT_GLYPH_ATLAS_MAX_SIZE.min(1 << device_max.ilog2());
2425        let size = size
2426            .next_power_of_two()
2427            .clamp(TEXT_GLYPH_ATLAS_MIN_SIZE.min(max_size), max_size);
2428        let texture = Self::create_texture(device, size);
2429        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
2430        let bind = |label: &'static str, sampler: &wgpu::Sampler| {
2431            Rc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
2432                label: Some(label),
2433                layout: image_layout,
2434                entries: &[
2435                    wgpu::BindGroupEntry {
2436                        binding: 0,
2437                        resource: wgpu::BindingResource::TextureView(&view),
2438                    },
2439                    wgpu::BindGroupEntry {
2440                        binding: 1,
2441                        resource: wgpu::BindingResource::Sampler(sampler),
2442                    },
2443                ],
2444            }))
2445        };
2446        let texel_bind_group = bind("Text Glyph Atlas Bind Group", samplers.nearest);
2447        let filtered_bind_group = bind("Filtered Text Glyph Atlas Bind Group", samplers.linear);
2448        Self {
2449            texture,
2450            _view: view,
2451            texel_bind_group,
2452            filtered_bind_group,
2453            entries: BoundedLruCache::with_capacity_at_least_one(MAX_TEXT_GLYPH_ATLAS_ITEMS),
2454            generation: 0,
2455            size,
2456            max_size,
2457            cursor_x: TEXT_GLYPH_ATLAS_PADDING,
2458            cursor_y: TEXT_GLYPH_ATLAS_PADDING,
2459            row_height: 0,
2460            upload_scratch: Vec::new(),
2461        }
2462    }
2463
2464    fn create_texture(device: &wgpu::Device, size: u32) -> wgpu::Texture {
2465        device.create_texture(&wgpu::TextureDescriptor {
2466            label: Some("Text Glyph Atlas Texture"),
2467            size: wgpu::Extent3d {
2468                width: size,
2469                height: size,
2470                depth_or_array_layers: 1,
2471            },
2472            mip_level_count: 1,
2473            sample_count: 1,
2474            dimension: wgpu::TextureDimension::D2,
2475            format: wgpu::TextureFormat::R8Unorm,
2476            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2477            view_formats: &[],
2478        })
2479    }
2480
2481    fn reset(
2482        &mut self,
2483        device: &wgpu::Device,
2484        image_layout: &wgpu::BindGroupLayout,
2485        samplers: GlyphSamplers<'_>,
2486    ) {
2487        let generation = self.generation.wrapping_add(1);
2488        let grown = next_glyph_atlas_size(self.size, self.max_size);
2489        let mut next = Self::new(device, image_layout, samplers, grown);
2490        next.generation = generation;
2491        *self = next;
2492    }
2493
2494    /// The atlas bound for glyphs drawn under `transform`: texel for texel
2495    /// on the pixel grid, filtered once a transform turns the glyph quads
2496    /// off it.
2497    fn bind_group(&self, transform: SegmentTransform) -> Rc<wgpu::BindGroup> {
2498        Rc::clone(if transform.is_identity() {
2499            &self.texel_bind_group
2500        } else {
2501            &self.filtered_bind_group
2502        })
2503    }
2504
2505    fn generation(&self) -> u64 {
2506        self.generation
2507    }
2508
2509    fn size(&self) -> u32 {
2510        self.size
2511    }
2512
2513    /// One texel's share of the atlas side. The side is a power of two, so
2514    /// this reciprocal is exact and a coordinate times it is the coordinate
2515    /// divided by the side: a glyph's quad takes eight of them.
2516    fn texel(&self) -> f32 {
2517        (self.size() as f32).recip()
2518    }
2519
2520    fn entry(&mut self, key: &GlyphAtlasSlotKey) -> Option<GlyphAtlasEntry> {
2521        self.entries.get(key).copied()
2522    }
2523
2524    fn allocate(&mut self, width: u32, height: u32) -> Option<GlyphAtlasEntry> {
2525        let max_extent = self.size.saturating_sub(TEXT_GLYPH_ATLAS_PADDING * 2);
2526        if width == 0 || height == 0 || width > max_extent || height > max_extent {
2527            return None;
2528        }
2529
2530        if self.cursor_x + width + TEXT_GLYPH_ATLAS_PADDING > self.size {
2531            self.cursor_x = TEXT_GLYPH_ATLAS_PADDING;
2532            self.cursor_y = self
2533                .cursor_y
2534                .saturating_add(self.row_height)
2535                .saturating_add(TEXT_GLYPH_ATLAS_PADDING);
2536            self.row_height = 0;
2537        }
2538        if self.cursor_y > self.size - height - TEXT_GLYPH_ATLAS_PADDING {
2539            return None;
2540        }
2541
2542        let entry = GlyphAtlasEntry {
2543            x: self.cursor_x as u16,
2544            y: self.cursor_y as u16,
2545            width: width as u16,
2546            height: height as u16,
2547        };
2548        self.cursor_x = self
2549            .cursor_x
2550            .saturating_add(width)
2551            .saturating_add(TEXT_GLYPH_ATLAS_PADDING);
2552        self.row_height = self.row_height.max(height);
2553        Some(entry)
2554    }
2555
2556    fn upload_glyph(
2557        &mut self,
2558        glyph: &SoftwareGlyphAtlasGlyph,
2559        queue: &wgpu::Queue,
2560        executor: &mut WgpuFrameGraphExecutor,
2561        frame_stats: &mut gpu_stats::FrameStats,
2562    ) -> Option<GlyphAtlasEntry> {
2563        let key = GlyphAtlasSlotKey::new(glyph.key, glyph.color);
2564        if let Some(entry) = self.entry(&key) {
2565            frame_stats.record_text_glyph_atlas_hits(1);
2566            return Some(entry);
2567        }
2568
2569        let width = u32::try_from(glyph.mask.width).ok()?;
2570        let height = u32::try_from(glyph.mask.height).ok()?;
2571        let entry = self.allocate(width, height)?;
2572        self.upload_scratch.clear();
2573        let correction = key.luminance.correction();
2574        self.upload_scratch.extend(
2575            glyph
2576                .mask
2577                .alpha
2578                .iter()
2579                .map(|&coverage| correction.apply(coverage)),
2580        );
2581
2582        let upload_stats = executor.upload_texture(
2583            queue,
2584            wgpu::TexelCopyTextureInfo {
2585                texture: &self.texture,
2586                mip_level: 0,
2587                origin: wgpu::Origin3d {
2588                    x: u32::from(entry.x),
2589                    y: u32::from(entry.y),
2590                    z: 0,
2591                },
2592                aspect: wgpu::TextureAspect::All,
2593            },
2594            &self.upload_scratch,
2595            wgpu::TexelCopyBufferLayout {
2596                offset: 0,
2597                bytes_per_row: Some(width),
2598                rows_per_image: Some(height),
2599            },
2600            wgpu::Extent3d {
2601                width,
2602                height,
2603                depth_or_array_layers: 1,
2604            },
2605        );
2606        frame_stats.record_command_stats(upload_stats);
2607        frame_stats.record_text_glyph_atlas_miss(width, height);
2608        self.entries.put(key, entry);
2609        Some(entry)
2610    }
2611}
2612
2613/// The image pipeline a draw takes: in a pass with depth or not, for an
2614/// alpha mask or a color image, and taking a rounded clip in place or not.
2615#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2616pub(crate) struct ImageVariant {
2617    depth: bool,
2618    alpha_mask: bool,
2619    rounded: bool,
2620}
2621
2622pub(crate) struct ImageDrawCmd {
2623    index_start: u32,
2624    scissor: (u32, u32, u32, u32),
2625    image: ImageBinding,
2626    /// The rounded clip whose coverage the image takes in place: its index
2627    /// among the pass's clip instances.
2628    clip: Option<u32>,
2629}
2630
2631impl ImageVariant {
2632    /// The variant `cmd` draws with in a pass with a depth buffer or not.
2633    fn of(cmd: &ImageDrawCmd, depth: bool) -> Self {
2634        Self {
2635            depth,
2636            alpha_mask: cmd.image.alpha_mask,
2637            rounded: cmd.clip.is_some(),
2638        }
2639    }
2640}
2641
2642impl ImageDrawCmd {
2643    /// Target pixels the draw can touch: its scissor.
2644    pub(crate) fn bounds(&self) -> TargetRect {
2645        self.scissor
2646    }
2647}
2648
2649/// Which glyph pipeline draws a stretch of quads: plain quads, sampled
2650/// within their glyph's texel centers; plain quads on whole pixels at one
2651/// texel a pixel, whose samples land on those centers unclamped; or quads
2652/// of a layer drawn in place under a turn, which are [`TurnedGlyph`]s.
2653#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2654pub(crate) enum GlyphQuads {
2655    Plain,
2656    Aligned,
2657    Turned,
2658}
2659
2660impl GlyphQuads {
2661    fn turned(self) -> bool {
2662        self == Self::Turned
2663    }
2664
2665    /// The plain kind that fits quads whose every corner is `aligned`.
2666    fn plain(aligned: bool) -> Self {
2667        if aligned { Self::Aligned } else { Self::Plain }
2668    }
2669}
2670
2671/// Whether a plain glyph quad at `rect` lands on whole pixels of a target
2672/// whose origin sits at `offset`: its quads hold one texel a pixel.
2673fn glyph_rect_aligned(rect: [f32; 4], offset: [f32; 2]) -> bool {
2674    let whole = |value: f32| value.fract() == 0.0;
2675    whole(rect[0] - offset[0])
2676        && whole(rect[1] - offset[1])
2677        && whole(rect[2] - offset[0])
2678        && whole(rect[3] - offset[1])
2679}
2680
2681#[derive(Clone)]
2682enum GlyphDrawSource {
2683    Shared {
2684        instance_start: u32,
2685        instance_count: u32,
2686        quads: GlyphQuads,
2687    },
2688    Retained {
2689        run: Rc<CachedGpuTextGlyphRun>,
2690        uniform_slot: usize,
2691        aligned: bool,
2692    },
2693}
2694
2695#[derive(Clone)]
2696pub(crate) struct GlyphDrawCmd {
2697    atlas: Rc<wgpu::BindGroup>,
2698    source: GlyphDrawSource,
2699    /// The scissor the text's own clip needs, `None` when its quads lie
2700    /// inside that clip anyway and only the batch's bound applies.
2701    scissor: Option<(u32, u32, u32, u32)>,
2702    /// Target pixels the draw can touch, for ordering it against others.
2703    bounds: (u32, u32, u32, u32),
2704}
2705
2706/// One draw of a glyph batch: a stretch of shared quads, or a retained run.
2707struct GlyphDraw<'a> {
2708    atlas: &'a Rc<wgpu::BindGroup>,
2709    scissor: Option<(u32, u32, u32, u32)>,
2710    step: GlyphDrawStep<'a>,
2711}
2712
2713enum GlyphDrawStep<'a> {
2714    /// A stretch of the shared instances of one kind.
2715    Shared(std::ops::Range<u32>, GlyphQuads),
2716    Retained {
2717        run: &'a CachedGpuTextGlyphRun,
2718        uniform_slot: usize,
2719        aligned: bool,
2720    },
2721}
2722
2723/// The draws a batch's glyph commands take: consecutive shared commands with
2724/// one atlas and one scissor whose quads follow each other in the frame's
2725/// shared buffer draw as one.
2726struct GlyphDraws<'a> {
2727    cmds: &'a [GlyphDrawCmd],
2728}
2729
2730impl<'a> GlyphDraws<'a> {
2731    fn new(cmds: &'a [GlyphDrawCmd]) -> Self {
2732        Self { cmds }
2733    }
2734}
2735
2736impl<'a> Iterator for GlyphDraws<'a> {
2737    type Item = GlyphDraw<'a>;
2738
2739    fn next(&mut self) -> Option<Self::Item> {
2740        let (first, rest) = self.cmds.split_first()?;
2741        self.cmds = rest;
2742        let step = match &first.source {
2743            GlyphDrawSource::Retained {
2744                run,
2745                uniform_slot,
2746                aligned,
2747            } => GlyphDrawStep::Retained {
2748                run,
2749                uniform_slot: *uniform_slot,
2750                aligned: *aligned,
2751            },
2752            GlyphDrawSource::Shared {
2753                instance_start,
2754                instance_count,
2755                quads,
2756            } => {
2757                let mut end = instance_start + instance_count;
2758                while let Some((next, rest)) = self.cmds.split_first() {
2759                    match next.source {
2760                        GlyphDrawSource::Shared {
2761                            instance_start,
2762                            instance_count,
2763                            quads: next_quads,
2764                        } if instance_start == end
2765                            && next_quads == *quads
2766                            && next.scissor == first.scissor
2767                            && Rc::ptr_eq(&next.atlas, &first.atlas) =>
2768                        {
2769                            end += instance_count;
2770                            self.cmds = rest;
2771                        }
2772                        _ => break,
2773                    }
2774                }
2775                GlyphDrawStep::Shared(*instance_start..end, *quads)
2776            }
2777        };
2778        Some(GlyphDraw {
2779            atlas: &first.atlas,
2780            scissor: first.scissor,
2781            step,
2782        })
2783    }
2784}
2785
2786impl GlyphDrawCmd {
2787    /// The quads the command draws, which pick its pipeline.
2788    fn quads(&self) -> GlyphQuads {
2789        match &self.source {
2790            GlyphDrawSource::Shared { quads, .. } => *quads,
2791            GlyphDrawSource::Retained { aligned, .. } => GlyphQuads::plain(*aligned),
2792        }
2793    }
2794
2795    fn shared(
2796        (instances, quads): (std::ops::Range<usize>, GlyphQuads),
2797        scissor: Option<(u32, u32, u32, u32)>,
2798        bounds: (u32, u32, u32, u32),
2799        atlas: Rc<wgpu::BindGroup>,
2800    ) -> Self {
2801        let start = u32::try_from(instances.start).unwrap_or(u32::MAX);
2802        let end = u32::try_from(instances.end).unwrap_or(u32::MAX);
2803        Self {
2804            atlas,
2805            source: GlyphDrawSource::Shared {
2806                instance_start: start,
2807                instance_count: end.saturating_sub(start),
2808                quads,
2809            },
2810            scissor,
2811            bounds,
2812        }
2813    }
2814
2815    fn retained(
2816        (run, uniform_slot, aligned): (Rc<CachedGpuTextGlyphRun>, usize, bool),
2817        scissor: (u32, u32, u32, u32),
2818        atlas: Rc<wgpu::BindGroup>,
2819    ) -> Self {
2820        Self {
2821            atlas,
2822            source: GlyphDrawSource::Retained {
2823                run,
2824                uniform_slot,
2825                aligned,
2826            },
2827            scissor: Some(scissor),
2828            bounds: scissor,
2829        }
2830    }
2831
2832    /// Target pixels the draw can touch.
2833    pub(crate) fn bounds(&self) -> (u32, u32, u32, u32) {
2834        self.bounds
2835    }
2836
2837    /// Whether the draw's quads are [`TurnedGlyph`]s, or `None` for a
2838    /// retained run, which draws on its own.
2839    fn turned(&self) -> Option<bool> {
2840        match self.source {
2841            GlyphDrawSource::Shared { quads, .. } => Some(quads.turned()),
2842            GlyphDrawSource::Retained { .. } => None,
2843        }
2844    }
2845}
2846
2847/// The kind of glyph draw, turned (`true`) or plain, that can move after
2848/// every draw of the other kind in `draws` without moving past a draw it
2849/// shares a pixel with: glyphs blend, so only draws that share pixels must
2850/// keep their order. The fewer kind is tried first, which checks fewer
2851/// pairs. `None` when neither can move, a retained run is among them, or
2852/// the kinds do not change back and forth.
2853pub(crate) fn movable_glyph_kind<T>(
2854    draws: &[T],
2855    kind: impl Fn(&T) -> Option<bool>,
2856    bounds: impl Fn(&T) -> TargetRect,
2857) -> Option<bool> {
2858    let mut turned = 0;
2859    for draw in draws {
2860        turned += usize::from(kind(draw)?);
2861    }
2862    let changes = draws
2863        .windows(2)
2864        .filter(|pair| kind(&pair[0]) != kind(&pair[1]))
2865        .count();
2866    if changes < 2 {
2867        return None;
2868    }
2869    let fewer = turned * 2 < draws.len();
2870    [fewer, !fewer].into_iter().find(|&moved| {
2871        draws.iter().enumerate().all(|(index, draw)| {
2872            kind(draw) != Some(moved)
2873                || draws[index + 1..].iter().all(|later| {
2874                    kind(later) == Some(moved)
2875                        || !crate::draw_pass::target_rects_overlap(bounds(draw), bounds(later))
2876                })
2877        })
2878    })
2879}
2880
2881/// Moves a glyph batch's retained runs after its shared quads, each in its
2882/// order, when no run shares a pixel with a shared draw after it: the
2883/// shared quads around a run, which follow each other in the frame's
2884/// buffer, then draw at once. A card's body text, a retained run between
2885/// its title and its labels, split them into two draws. Returns how many
2886/// shared draws lead the batch, none when its runs keep their places;
2887/// `moved` is scratch.
2888pub(crate) fn defer_retained_glyph_runs(
2889    cmds: &mut [GlyphDrawCmd],
2890    moved: &mut Vec<GlyphDrawCmd>,
2891) -> usize {
2892    let retained = |cmd: &GlyphDrawCmd| cmd.turned().is_none();
2893    let shared = cmds.iter().filter(|cmd| !retained(cmd)).count();
2894    if cmds[..shared].iter().all(|cmd| !retained(cmd)) {
2895        return shared;
2896    }
2897    let deferrable = cmds.iter().enumerate().all(|(index, cmd)| {
2898        !retained(cmd)
2899            || cmds[index + 1..].iter().all(|later| {
2900                retained(later)
2901                    || !crate::draw_pass::target_rects_overlap(cmd.bounds(), later.bounds())
2902            })
2903    });
2904    if !deferrable {
2905        return 0;
2906    }
2907    moved.clear();
2908    let mut kept = 0;
2909    for read in 0..cmds.len() {
2910        if retained(&cmds[read]) {
2911            moved.push(cmds[read].clone());
2912        } else {
2913            cmds.swap(kept, read);
2914            kept += 1;
2915        }
2916    }
2917    for (slot, cmd) in cmds[kept..].iter_mut().zip(moved.drain(..)) {
2918        *slot = cmd;
2919    }
2920    shared
2921}
2922
2923/// Moves a glyph batch's draws of the kind [`movable_glyph_kind`] picks
2924/// after those of the other kind, each kind in its order, so each draws
2925/// once instead of once per change between them. `moved` is scratch.
2926pub(crate) fn group_glyph_kinds(cmds: &mut [GlyphDrawCmd], moved: &mut Vec<GlyphDrawCmd>) {
2927    let Some(kind) = movable_glyph_kind(cmds, GlyphDrawCmd::turned, GlyphDrawCmd::bounds) else {
2928        return;
2929    };
2930    moved.clear();
2931    let mut kept = 0;
2932    for read in 0..cmds.len() {
2933        if cmds[read].turned() == Some(kind) {
2934            moved.push(cmds[read].clone());
2935        } else {
2936            cmds.swap(kept, read);
2937            kept += 1;
2938        }
2939    }
2940    for (slot, cmd) in cmds[kept..].iter_mut().zip(moved.drain(..)) {
2941        *slot = cmd;
2942    }
2943}
2944
2945#[derive(Clone, Copy, Debug, PartialEq)]
2946struct ImageUvRect {
2947    min: [f32; 2],
2948    max: [f32; 2],
2949    sample_bounds: [f32; 4],
2950}
2951
2952/// A growable vertex buffer and index buffer pair.
2953/// One pass's image and shared glyph quads: the frame's vertex and index
2954/// uploads they were appended to.
2955pub(crate) struct ImageSlot {
2956    vertices: BufferUpload,
2957    indices: BufferUpload,
2958    /// The rounded clips the pass's images take in place, one instance each.
2959    clips: Option<BufferUpload>,
2960}
2961
2962fn image_vertex_spec() -> UploadAllocatorSpec {
2963    UploadAllocatorSpec::vertex("Image Vertex Buffer", std::mem::size_of::<Vertex>() as u64)
2964}
2965
2966fn image_index_spec() -> UploadAllocatorSpec {
2967    UploadAllocatorSpec::index("Image Index Buffer", std::mem::size_of::<u32>() as u64)
2968}
2969
2970fn glyph_instance_spec() -> UploadAllocatorSpec {
2971    UploadAllocatorSpec::vertex(
2972        "Glyph Instance Buffer",
2973        std::mem::size_of::<GlyphInstance>() as u64,
2974    )
2975}
2976
2977fn turned_glyph_spec() -> UploadAllocatorSpec {
2978    UploadAllocatorSpec::vertex(
2979        "Turned Glyph Instance Buffer",
2980        std::mem::size_of::<TurnedGlyph>() as u64,
2981    )
2982}
2983
2984#[derive(Default)]
2985struct ViewportUniforms {
2986    uploads: FrameUploadAllocators,
2987    slots: Vec<UniformUpload>,
2988}
2989
2990impl ViewportUniforms {
2991    fn begin_frame(&mut self) {
2992        self.slots.clear();
2993        self.uploads.reset();
2994    }
2995
2996    fn claim(
2997        &mut self,
2998        device: &wgpu::Device,
2999        layout: &wgpu::BindGroupLayout,
3000        uniforms: &Uniforms,
3001    ) -> usize {
3002        let slot = self.slots.len();
3003        self.slots.push(self.uploads.upload_uniform(
3004            UploadAllocatorId::Viewport,
3005            UploadAllocatorSpec::uniform(
3006                "Viewport Uniform Buffer",
3007                "Viewport Uniform Bind Group",
3008                std::mem::size_of::<Uniforms>() as u64,
3009            ),
3010            device,
3011            layout,
3012            bytemuck::bytes_of(uniforms),
3013        ));
3014        slot
3015    }
3016
3017    fn bind(&self, pass: &mut TrackedPass<'_, '_>, slot: usize) -> Result<(), String> {
3018        let uniform = self
3019            .slots
3020            .get(slot)
3021            .ok_or_else(|| "viewport uniform slot was never claimed this frame".to_string())?;
3022        pass.set_bind_group(0, &uniform.bind_group, &[uniform.offset]);
3023        Ok(())
3024    }
3025
3026    fn stage_pending(
3027        &mut self,
3028        device: &wgpu::Device,
3029        recorder: &mut impl FrameCommandRecorder,
3030    ) -> FrameCommandStats {
3031        self.uploads.stage_pending(|buffer, offset, bytes| {
3032            recorder.stage_frame_buffer_copy(device, buffer, offset, bytes)
3033        })
3034    }
3035}
3036
3037#[derive(Clone, Copy, Debug, PartialEq)]
3038pub(crate) struct ViewportUniformParams {
3039    pub(crate) width: u32,
3040    pub(crate) height: u32,
3041    pub(crate) offset: [f32; 2],
3042    pub(crate) transform: SegmentTransform,
3043    /// Where the drawn vertices' origin sits in the segment's device space,
3044    /// added before the transform: zero, except for a retained glyph run
3045    /// drawn under a transform, whose vertices sit at its raster origin. The
3046    /// sum is the one the shared path writes, so both draw the same pixels.
3047    pub(crate) origin: [f32; 2],
3048    /// The pass-order index of the batch's first shape record, from which
3049    /// each record's depth counts.
3050    pub(crate) depth_base: f32,
3051}
3052
3053impl ViewportUniformParams {
3054    /// The logical rect of the drawn scene that the target shows.
3055    pub(crate) fn scene_rect(self, root_scale: f32) -> Rect {
3056        segment_scene_rect(
3057            self.transform,
3058            Rect {
3059                x: self.offset[0],
3060                y: self.offset[1],
3061                width: self.width as f32,
3062                height: self.height as f32,
3063            },
3064            root_scale,
3065        )
3066    }
3067}
3068
3069/// The logical rect of a scene drawn under `transform` that the device rect
3070/// `device` of the target's scene space shows: the rect itself when the
3071/// scene is not transformed, else the bounds it maps back to.
3072pub(crate) fn segment_scene_rect(
3073    transform: SegmentTransform,
3074    device: Rect,
3075    root_scale: f32,
3076) -> Rect {
3077    let device = if transform.is_identity() {
3078        device
3079    } else {
3080        transform.segment_bounds(device)
3081    };
3082    Rect {
3083        x: device.x / root_scale,
3084        y: device.y / root_scale,
3085        width: device.width / root_scale,
3086        height: device.height / root_scale,
3087    }
3088}
3089
3090/// A stored run's draws for one pass: the tables current when it was
3091/// prepared, the uniform slot holding its placement, and the range of the
3092/// pass's run draws with the pipeline and vertex range of each segment's
3093/// quads and bands.
3094pub(crate) struct StoreRunBatch {
3095    pub(crate) tables: StoredTables,
3096    pub(crate) uniform_slot: usize,
3097    pub(crate) draws: std::ops::Range<usize>,
3098    /// The scissor an unturned run's clip puts on its paint: see
3099    /// [`clip_scissor`].
3100    pub(crate) clip: Option<TargetRect>,
3101}
3102
3103struct CompositionTarget {
3104    target: Rc<OffscreenTarget>,
3105    output_bind_group: wgpu::BindGroup,
3106}
3107
3108/// Where a frame renders: straight into the presentable image, or into the
3109/// reusable composition target that the output conversion then copies out.
3110enum FrameRoot {
3111    Surface(Rc<OffscreenTarget>),
3112    Composition(CompositionTarget),
3113}
3114
3115impl FrameRoot {
3116    fn target(&self) -> &Rc<OffscreenTarget> {
3117        match self {
3118            Self::Surface(target) => target,
3119            Self::Composition(composition) => &composition.target,
3120        }
3121    }
3122
3123    /// The output conversion's destination and source bind group; nothing
3124    /// when the frame already rendered into the presentable image.
3125    fn output<'a>(
3126        &'a self,
3127        output_view: Option<&'a wgpu::TextureView>,
3128        screenshot_bind_group: Option<&'a wgpu::BindGroup>,
3129    ) -> Option<(&'a wgpu::TextureView, &'a wgpu::BindGroup)> {
3130        match self {
3131            Self::Surface(_) => None,
3132            Self::Composition(composition) => output_view.map(|view| {
3133                (
3134                    view,
3135                    screenshot_bind_group.unwrap_or(&composition.output_bind_group),
3136                )
3137            }),
3138        }
3139    }
3140}
3141
3142/// What a presentable image must allow to be the frame's root target:
3143/// rendering, and the copies a capture and the opaque-prefix cache take of
3144/// the page and put back.
3145const DIRECT_SURFACE_ROOT_USAGES: wgpu::TextureUsages = wgpu::TextureUsages::RENDER_ATTACHMENT
3146    .union(wgpu::TextureUsages::COPY_SRC)
3147    .union(wgpu::TextureUsages::COPY_DST);
3148
3149/// The usages to ask of a presentable image: rendering, and whichever of
3150/// the copies and sampling the surface offers. Sampling lets a frame whose
3151/// backdrops read the page draw straight into the image too.
3152pub fn presentable_root_usages(supported: wgpu::TextureUsages) -> wgpu::TextureUsages {
3153    wgpu::TextureUsages::RENDER_ATTACHMENT
3154        | (supported & (DIRECT_SURFACE_ROOT_USAGES | wgpu::TextureUsages::TEXTURE_BINDING))
3155}
3156
3157/// Whether the presented image can be the frame's root target: its bytes
3158/// are the composition format (so the 8-bit output conversion would be an
3159/// identity), it is the viewport's size, it can be copied as the page is,
3160/// and it can be sampled when the frame `reads_page` through a backdrop.
3161/// Metal's images cannot be sampled, so only a frame without backdrops
3162/// draws straight into one there.
3163fn surface_is_direct_root(
3164    texture: &wgpu::Texture,
3165    composition_format: wgpu::TextureFormat,
3166    viewport: (u32, u32),
3167    reads_page: bool,
3168) -> bool {
3169    let usage = texture.usage();
3170    texture.format().remove_srgb_suffix() == composition_format
3171        && usage.contains(DIRECT_SURFACE_ROOT_USAGES)
3172        && (!reads_page || usage.contains(wgpu::TextureUsages::TEXTURE_BINDING))
3173        && (texture.width(), texture.height()) == viewport
3174}
3175
3176#[derive(Clone, Copy)]
3177enum OutputMode {
3178    Display,
3179    Screenshot,
3180}
3181
3182pub struct GpuRenderer {
3183    pub(crate) device: Arc<wgpu::Device>,
3184    pub(crate) queue: Arc<wgpu::Queue>,
3185    device_errors: Arc<DeviceErrorSentry>,
3186    renderer_epoch: u64,
3187    pub(crate) composition_format: wgpu::TextureFormat,
3188    #[cfg(not(target_arch = "wasm32"))]
3189    display_format: wgpu::TextureFormat,
3190    composition_target: Option<CompositionTarget>,
3191    output_converter: OutputConverter,
3192    screenshot_converter: OutputConverter,
3193    adapter_backend: wgpu::Backend,
3194    pipeline_cache: Option<wgpu::PipelineCache>,
3195    #[cfg(not(target_arch = "wasm32"))]
3196    _pipeline_cache_watcher: Option<crate::pipeline_disk_cache::PersistWatcher>,
3197    /// The pipelines recent launches of another build drew with, built once
3198    /// this renderer's first frame is drawn.
3199    #[cfg(not(target_arch = "wasm32"))]
3200    recent_after_update: Option<Arc<crate::pipeline_records::PipelineRecords>>,
3201    /// What this renderer draws with, for the next launches.
3202    recorder: crate::pipeline_recorder::PipelineRecorder,
3203    /// The background compiler, which builds a pass's missing pipelines side
3204    /// by side.
3205    pipeline_compiler: PipelineCompiler,
3206    /// Where the background compiler says a pipeline a placeholder waited
3207    /// for landed.
3208    #[cfg(not(target_arch = "wasm32"))]
3209    landing: Option<Arc<crate::pipeline_compiler::Landing>>,
3210    /// The pipelines the background threads had built when this frame
3211    /// started.
3212    #[cfg(not(target_arch = "wasm32"))]
3213    built_at_frame_start: u64,
3214    /// Whether this frame drew a placeholder in place of an effect whose
3215    /// pipelines were compiling.
3216    pub(crate) drew_placeholder: bool,
3217    shape_pipelines: ShapePipelines,
3218    /// Image and glyph pipelines for passes without and with a depth buffer.
3219    image_pipeline: [FixedPipeline; 8],
3220    image_pipeline_dst_out: [FixedPipeline; 8],
3221    /// Indexed by depth, then turned: see [`GpuRenderer::glyph_atlas_pipeline`].
3222    glyph_atlas_pipeline: [FixedPipeline; 6],
3223    /// Round rect shadow pipelines without and with a depth buffer.
3224    rrect_shadow_pipeline: [FixedPipeline; 2],
3225    image_shader: SharedShader,
3226    glyph_atlas_shader: SharedShader,
3227    rrect_shadow_shader: SharedShader,
3228    /// Transient depth buffers by target size, for passes that lay opaque
3229    /// interiors down first, dropped once no frame has used them for
3230    /// [`crate::idle_pool::IDLE_FRAMES`].
3231    depth_targets: crate::idle_pool::IdlePool<((u32, u32), wgpu::TextureView)>,
3232    pub(crate) depth_gate: crate::depth_gate::DepthGate,
3233    /// The size of the atlas the layer surfaces drawn again go into.
3234    pub(crate) surface_atlas_sizes: crate::frame::SurfaceAtlasSizes,
3235    uniform_bind_group_layout: wgpu::BindGroupLayout,
3236    image_bind_group_layout: wgpu::BindGroupLayout,
3237    image_nearest_sampler: wgpu::Sampler,
3238    image_linear_sampler: wgpu::Sampler,
3239    text_fonts: SoftwareTextFontSet,
3240    viewport_uniforms: ViewportUniforms,
3241    run_store: RunStore,
3242    image_texture_cache: BoundedLruCache<u64, CachedImageTexture>,
3243    image_texture_cache_bytes: usize,
3244    text_image_cache: BoundedLruCache<TextImageCacheKey, CachedTextImage>,
3245    text_glyph_atlas: TextGlyphAtlas,
3246    text_glyph_run_cache: BoundedLruCache<TextGlyphRunCacheKey, CachedTextGlyphRun>,
3247    text_glyph_gpu_run_cache: BoundedLruCache<TextGlyphRunCacheKey, Rc<CachedGpuTextGlyphRun>>,
3248    text_glyph_run_arena: GlyphRunArena,
3249    /// How run tables and retained glyph runs reach the GPU, and whether
3250    /// kept layer surfaces stay in their atlas: see [`UploadMode::copy_free`].
3251    pub(crate) copy_free: UploadMode,
3252    text_glyph_run_frame: u64,
3253    /// Text runs the frame has drawn so far, cached or not.
3254    text_glyph_runs_drawn: usize,
3255    text_glyph_mask_cache: SoftwareGlyphRasterCache,
3256    text_line_index_cache: TextLineIndexCache,
3257    pub(crate) scratch_image_vertices: Vec<Vertex>,
3258    pub(crate) scratch_image_indices: Vec<u32>,
3259    pub(crate) scratch_glyph_instances: GlyphInstances,
3260    pub(crate) scratch_image_cmds: Vec<ImageDrawCmd>,
3261    pub(crate) scratch_image_clips: Vec<DeviceRoundedClip>,
3262    pub(crate) scratch_glyph_cmds: Vec<GlyphDrawCmd>,
3263    pub(crate) scratch_glyph_moved: Vec<GlyphDrawCmd>,
3264    pub(crate) scratch_shadow_instances: Vec<ShadowInstance>,
3265    pub(crate) scratch_arena_draws: Vec<RunDrawCall>,
3266    pub(crate) scratch_pipelines: PipelineDemand,
3267    pub(crate) scratch_held: crate::draw_pass::HeldDraws,
3268    scratch_text_glyph_run: Vec<SoftwareGlyphAtlasRunGlyph>,
3269    scratch_text_glyph_entries: Vec<GlyphAtlasEntry>,
3270    run_glyph_scratch: RunGlyphScratch,
3271    frame_graph_executor: WgpuFrameGraphExecutor,
3272    pub(crate) stage_side_scratch: StageSideScratch,
3273    deferred_offscreen_releases: Vec<OffscreenTarget>,
3274    pub(crate) effect_renderer: EffectRenderer,
3275    pub(crate) layer_cache: LayerCache,
3276    pub(crate) ablation: Ablation,
3277    pub(crate) ablation_frames: u32,
3278    pub(crate) nesting_overflow_reported: bool,
3279    pub(crate) backdrop_gates: HashMap<NodeId, AdmissionGate>,
3280    pub(crate) fill_gates: HashMap<DrawCommandId, AdmissionGate>,
3281    pub(crate) effect_gates: HashMap<NodeId, AdmissionGate>,
3282    pub(crate) source_gates: HashMap<NodeId, AdmissionGate>,
3283    transparent_sources: HashMap<(u32, u32), Rc<OffscreenTarget>>,
3284    shadow_surface_cache: BoundedLruCache<ShadowSurfaceCacheKey, CachedShadowSurface>,
3285    shadow_surface_cache_bytes: u64,
3286    pub(crate) frame_stats: gpu_stats::FrameStats,
3287    last_frame_stats: Option<gpu_stats::FrameStatsSnapshot>,
3288    pending_frame_warmup_frames: u8,
3289    frame_count: u64,
3290    /// How many requested shader warm-ups this renderer has queued.
3291    shader_warm_ups_queued: usize,
3292}
3293
3294/// What a frame clears to before it draws: nothing for a transparent
3295/// window, the framework's background for every other surface.
3296pub fn frame_clear_color(transparent: bool) -> wgpu::Color {
3297    if transparent {
3298        wgpu::Color::TRANSPARENT
3299    } else {
3300        CLEAR_COLOR
3301    }
3302}
3303
3304fn image_sampler_descriptor(sampling: ImageSampling) -> wgpu::SamplerDescriptor<'static> {
3305    let filter = match sampling {
3306        ImageSampling::Nearest => wgpu::FilterMode::Nearest,
3307        ImageSampling::Linear => wgpu::FilterMode::Linear,
3308    };
3309    wgpu::SamplerDescriptor {
3310        label: Some(match sampling {
3311            ImageSampling::Nearest => "Nearest Image Sampler",
3312            ImageSampling::Linear => "Linear Image Sampler",
3313        }),
3314        address_mode_u: wgpu::AddressMode::ClampToEdge,
3315        address_mode_v: wgpu::AddressMode::ClampToEdge,
3316        address_mode_w: wgpu::AddressMode::ClampToEdge,
3317        mag_filter: filter,
3318        min_filter: filter,
3319        mipmap_filter: wgpu::MipmapFilterMode::Nearest,
3320        ..Default::default()
3321    }
3322}
3323
3324/// What a [`GpuRenderer`] is built from. It crosses to the present thread
3325/// whole, so it holds only owned, `Send` parts.
3326pub(crate) struct GpuRendererInit {
3327    pub(crate) device: Arc<wgpu::Device>,
3328    pub(crate) queue: Arc<wgpu::Queue>,
3329    pub(crate) surface_format: wgpu::TextureFormat,
3330    pub(crate) adapter_backend: wgpu::Backend,
3331    pub(crate) adapter_downlevel: wgpu::DownlevelFlags,
3332    pub(crate) text_fonts: SoftwareTextFontSet,
3333    pub(crate) renderer_epoch: u64,
3334    pub(crate) pipeline_compilation: PipelineCompilation,
3335}
3336
3337impl GpuRenderer {
3338    pub fn new(init: GpuRendererInit) -> Self {
3339        let GpuRendererInit {
3340            device,
3341            queue,
3342            surface_format,
3343            adapter_backend,
3344            adapter_downlevel,
3345            text_fonts,
3346            renderer_epoch,
3347            pipeline_compilation,
3348        } = init;
3349        let display_format = surface_format;
3350        let construction_started = Instant::now();
3351        let device_errors = Arc::new(DeviceErrorSentry::default());
3352        if survive_gpu_errors_enabled() {
3353            let sentry = Arc::clone(&device_errors);
3354            device.on_uncaptured_error(Arc::new(move |error| sentry.record(&error)));
3355        }
3356        let composition_format = crate::offscreen::composition_format(display_format);
3357        device.set_device_lost_callback(|reason, message| {
3358            log::error!("[gpu-device] device lost ({reason:?}): {message}");
3359        });
3360        let copy_free = UploadMode::copy_free(&device, adapter_backend);
3361        let mut run_store = RunStore::new(
3362            &device,
3363            RunBufferMode::for_device(&device, adapter_downlevel),
3364            copy_free,
3365        );
3366        let uniform_bind_group_layout =
3367            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3368                label: Some("Viewport Uniform Bind Group Layout"),
3369                entries: &[wgpu::BindGroupLayoutEntry {
3370                    binding: 0,
3371                    visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
3372                    ty: wgpu::BindingType::Buffer {
3373                        ty: wgpu::BufferBindingType::Uniform,
3374                        has_dynamic_offset: true,
3375                        min_binding_size: wgpu::BufferSize::new(
3376                            std::mem::size_of::<Uniforms>() as u64
3377                        ),
3378                    },
3379                    count: None,
3380                }],
3381            });
3382        let image_bind_group_layout =
3383            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3384                label: Some("Image Texture Bind Group Layout"),
3385                entries: &[
3386                    wgpu::BindGroupLayoutEntry {
3387                        binding: 0,
3388                        visibility: wgpu::ShaderStages::FRAGMENT,
3389                        ty: wgpu::BindingType::Texture {
3390                            multisampled: false,
3391                            view_dimension: wgpu::TextureViewDimension::D2,
3392                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
3393                        },
3394                        count: None,
3395                    },
3396                    wgpu::BindGroupLayoutEntry {
3397                        binding: 1,
3398                        visibility: wgpu::ShaderStages::FRAGMENT,
3399                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3400                        count: None,
3401                    },
3402                ],
3403            });
3404        let image_nearest_sampler =
3405            device.create_sampler(&image_sampler_descriptor(ImageSampling::Nearest));
3406        let image_linear_sampler =
3407            device.create_sampler(&image_sampler_descriptor(ImageSampling::Linear));
3408        let text_glyph_atlas = TextGlyphAtlas::new(
3409            &device,
3410            &image_bind_group_layout,
3411            GlyphSamplers {
3412                nearest: &image_nearest_sampler,
3413                linear: &image_linear_sampler,
3414            },
3415            TEXT_GLYPH_ATLAS_MIN_SIZE,
3416        );
3417        let viewport_uniforms = ViewportUniforms::default();
3418
3419        static GLASS_MATERIAL_FOLDS: DebugToggle =
3420            DebugToggle::new("CRANPOSE_GLASS_MATERIAL_FOLDS");
3421        if GLASS_MATERIAL_FOLDS.equals("1") {
3422            cranpose_ui_graphics::set_glass_material_folds(true);
3423        } else if GLASS_MATERIAL_FOLDS.equals("0") {
3424            cranpose_ui_graphics::set_glass_material_folds(false);
3425        }
3426        log::info!(
3427            "[gpu-init] liquid glass material folds {}",
3428            if cranpose_ui_graphics::glass_material_folds_enabled() {
3429                "on: a pipeline per material's feature set"
3430            } else {
3431                "off: one pipeline per blend mode"
3432            }
3433        );
3434
3435        #[cfg(not(target_arch = "wasm32"))]
3436        let crate::pipeline_disk_cache::Loaded {
3437            cache: pipeline_cache,
3438            records,
3439            updated,
3440            holds_recorded,
3441        } = crate::pipeline_disk_cache::load(&device);
3442        #[cfg(not(target_arch = "wasm32"))]
3443        let first_screen: Vec<u64> = records
3444            .shapes
3445            .iter()
3446            .filter(|shape| shape.first_screen)
3447            .map(|shape| shape.entry)
3448            .collect();
3449        #[cfg(target_arch = "wasm32")]
3450        let (pipeline_cache, first_screen): (Option<wgpu::PipelineCache>, Vec<u64>) =
3451            (None, Vec::new());
3452        let recorder = crate::pipeline_recorder::PipelineRecorder::default();
3453        #[cfg(not(target_arch = "wasm32"))]
3454        let pipeline_cache_watcher = crate::pipeline_disk_cache::spawn_persist_watcher(
3455            pipeline_cache.clone(),
3456            Arc::clone(&records),
3457            recorder.clone(),
3458        );
3459
3460        let effects_started = Instant::now();
3461        let pipeline_compiler = PipelineCompiler::for_compilation(pipeline_compilation);
3462        let depth_gate = crate::depth_gate::DepthGate::new(
3463            crate::draw_pass::lays_interiors_first(adapter_backend),
3464            pipeline_compiler.is_active(),
3465        );
3466        let effect_renderer = EffectRenderer::new(
3467            &device,
3468            pipeline_compiler.clone(),
3469            pipeline_cache.clone(),
3470            composition_format,
3471            adapter_backend,
3472            recorder.clone(),
3473        );
3474        let output_converter = OutputConverter::new(&device, adapter_backend, display_format);
3475        let screenshot_converter =
3476            OutputConverter::new(&device, adapter_backend, wgpu::TextureFormat::Rgba8Unorm);
3477        let effects_ms = instant_ms(effects_started, Instant::now());
3478        let mut frame_graph_executor = WgpuFrameGraphExecutor::new();
3479        frame_graph_executor.init_pass_timing(&device, &queue);
3480        #[cfg(target_arch = "wasm32")]
3481        frame_graph_executor.batch_queue_writes(adapter_backend == wgpu::Backend::BrowserWebGpu);
3482        let shape_shader = SharedShader::new(
3483            &device,
3484            adapter_backend,
3485            "Shape Shader",
3486            shape_shader_code(run_store.mode()),
3487            &[Some(&uniform_bind_group_layout), Some(run_store.layout())],
3488        );
3489        if run_store.mode().trig_fill {
3490            let fill = ArcTrigFill::new(
3491                &device,
3492                adapter_backend,
3493                pipeline_cache.clone(),
3494                shape_shader.clone(),
3495                recorder.clone(),
3496            );
3497            if first_screen.contains(&crate::arc_trig_fill::FIRST_SCREEN_KEY) {
3498                fill.warm(&pipeline_compiler);
3499            }
3500            run_store.attach_trig_fill(fill);
3501        }
3502        let shape_pipelines = ShapePipelines::new(
3503            ShapePipelineFactory {
3504                device: Arc::clone(&device),
3505                cache: pipeline_cache.clone(),
3506                format: composition_format,
3507                shader: shape_shader,
3508                mode: run_store.mode(),
3509            },
3510            adapter_backend,
3511            &pipeline_compiler,
3512            first_screen
3513                .into_iter()
3514                .filter(|&bits| bits != crate::arc_trig_fill::FIRST_SCREEN_KEY)
3515                .filter_map(ShapePipelineKey::from_bits),
3516            recorder.clone(),
3517        );
3518        let image_layouts = [
3519            Some(&uniform_bind_group_layout),
3520            Some(&image_bind_group_layout),
3521        ];
3522        let image_shader = SharedShader::new(
3523            &device,
3524            adapter_backend,
3525            "Image Shader",
3526            shaders::IMAGE,
3527            &image_layouts,
3528        );
3529        let glyph_atlas_shader = SharedShader::new(
3530            &device,
3531            adapter_backend,
3532            "Glyph Atlas Shader",
3533            shaders::GLYPH_ATLAS,
3534            &image_layouts,
3535        );
3536        let rrect_shadow_shader = SharedShader::new(
3537            &device,
3538            adapter_backend,
3539            "Round Rect Shadow Shader",
3540            shaders::RRECT_SHADOW,
3541            &[Some(&uniform_bind_group_layout)],
3542        );
3543
3544        let mut renderer = Self {
3545            device,
3546            queue,
3547            device_errors,
3548            renderer_epoch,
3549            composition_format,
3550            #[cfg(not(target_arch = "wasm32"))]
3551            display_format,
3552            composition_target: None,
3553            output_converter,
3554            screenshot_converter,
3555            adapter_backend,
3556            pipeline_cache,
3557            #[cfg(not(target_arch = "wasm32"))]
3558            _pipeline_cache_watcher: pipeline_cache_watcher,
3559            #[cfg(not(target_arch = "wasm32"))]
3560            recent_after_update: updated.then(|| Arc::clone(&records)),
3561            recorder,
3562            #[cfg(not(target_arch = "wasm32"))]
3563            landing: pipeline_compiler.landing(),
3564            pipeline_compiler,
3565            #[cfg(not(target_arch = "wasm32"))]
3566            built_at_frame_start: 0,
3567            drew_placeholder: false,
3568            shape_pipelines,
3569            image_pipeline: [
3570                FixedPipeline::new("image/src-over"),
3571                FixedPipeline::new("image/src-over/depth"),
3572                FixedPipeline::new("image/src-over/alpha-mask"),
3573                FixedPipeline::new("image/src-over/alpha-mask/depth"),
3574                FixedPipeline::new("image/src-over/rounded"),
3575                FixedPipeline::new("image/src-over/rounded/depth"),
3576                FixedPipeline::new("image/src-over/alpha-mask/rounded"),
3577                FixedPipeline::new("image/src-over/alpha-mask/rounded/depth"),
3578            ],
3579            image_pipeline_dst_out: [
3580                FixedPipeline::new("image/dst-out"),
3581                FixedPipeline::new("image/dst-out/depth"),
3582                FixedPipeline::new("image/dst-out/alpha-mask"),
3583                FixedPipeline::new("image/dst-out/alpha-mask/depth"),
3584                FixedPipeline::new("image/dst-out/rounded"),
3585                FixedPipeline::new("image/dst-out/rounded/depth"),
3586                FixedPipeline::new("image/dst-out/alpha-mask/rounded"),
3587                FixedPipeline::new("image/dst-out/alpha-mask/rounded/depth"),
3588            ],
3589            glyph_atlas_pipeline: [
3590                FixedPipeline::new("glyph/atlas"),
3591                FixedPipeline::new("glyph/atlas/depth"),
3592                FixedPipeline::new("glyph/atlas/aligned"),
3593                FixedPipeline::new("glyph/atlas/aligned/depth"),
3594                FixedPipeline::new("glyph/atlas/turned"),
3595                FixedPipeline::new("glyph/atlas/turned/depth"),
3596            ],
3597            rrect_shadow_pipeline: [
3598                FixedPipeline::new("shadow/rrect"),
3599                FixedPipeline::new("shadow/rrect/depth"),
3600            ],
3601            image_shader,
3602            glyph_atlas_shader,
3603            rrect_shadow_shader,
3604            depth_targets: crate::idle_pool::IdlePool::default(),
3605            depth_gate,
3606            surface_atlas_sizes: crate::frame::SurfaceAtlasSizes::for_backend(adapter_backend),
3607            uniform_bind_group_layout,
3608            image_bind_group_layout,
3609            image_nearest_sampler,
3610            image_linear_sampler,
3611            text_fonts,
3612            viewport_uniforms,
3613            run_store,
3614            image_texture_cache: BoundedLruCache::with_capacity_at_least_one(
3615                MAX_TEXTURE_CACHE_ITEMS,
3616            ),
3617            image_texture_cache_bytes: 0,
3618            text_image_cache: BoundedLruCache::with_capacity_at_least_one(
3619                MAX_TEXT_IMAGE_CACHE_ITEMS,
3620            ),
3621            text_glyph_atlas,
3622            text_glyph_run_cache: BoundedLruCache::with_capacity_at_least_one(
3623                MIN_TEXT_GLYPH_RUN_CACHE_ITEMS,
3624            ),
3625            text_glyph_runs_drawn: 0,
3626            text_glyph_gpu_run_cache: BoundedLruCache::with_capacity_at_least_one(
3627                MAX_TEXT_GLYPH_GPU_RUN_CACHE_ITEMS,
3628            ),
3629            text_glyph_run_arena: GlyphRunArena::new(copy_free),
3630            copy_free,
3631            text_glyph_run_frame: 0,
3632            text_glyph_mask_cache: SoftwareGlyphRasterCache::with_capacity_at_least_one(
3633                MAX_TEXT_GLYPH_MASK_CACHE_ITEMS,
3634            ),
3635            text_line_index_cache: TextLineIndexCache::new(MAX_TEXT_LINE_INDEX_CACHE_ITEMS),
3636            scratch_image_vertices: Vec::new(),
3637            scratch_image_indices: Vec::new(),
3638            scratch_glyph_instances: GlyphInstances::default(),
3639            scratch_image_cmds: Vec::new(),
3640            scratch_image_clips: Vec::new(),
3641            scratch_glyph_cmds: Vec::new(),
3642            scratch_glyph_moved: Vec::new(),
3643            scratch_shadow_instances: Vec::new(),
3644            scratch_arena_draws: Vec::new(),
3645            scratch_pipelines: PipelineDemand::default(),
3646            scratch_held: crate::draw_pass::HeldDraws::default(),
3647            scratch_text_glyph_run: Vec::new(),
3648            scratch_text_glyph_entries: Vec::new(),
3649            run_glyph_scratch: RunGlyphScratch::default(),
3650            frame_graph_executor,
3651            stage_side_scratch: StageSideScratch::default(),
3652            deferred_offscreen_releases: Vec::new(),
3653            effect_renderer,
3654            layer_cache: LayerCache::new(),
3655            ablation: Ablation::default(),
3656            ablation_frames: 0,
3657            nesting_overflow_reported: false,
3658            backdrop_gates: HashMap::default(),
3659            fill_gates: HashMap::default(),
3660            effect_gates: HashMap::default(),
3661            source_gates: HashMap::default(),
3662            transparent_sources: HashMap::default(),
3663            shadow_surface_cache: BoundedLruCache::with_capacity_at_least_one(
3664                MAX_SHADOW_SURFACE_CACHE_ITEMS,
3665            ),
3666            shadow_surface_cache_bytes: 0,
3667            frame_stats: gpu_stats::FrameStats::default(),
3668            last_frame_stats: None,
3669            pending_frame_warmup_frames: 0,
3670            frame_count: 0,
3671            shader_warm_ups_queued: 0,
3672        };
3673        #[cfg(not(target_arch = "wasm32"))]
3674        renderer.warm_first_screen(&records, holds_recorded);
3675        renderer.warm_requested_shaders();
3676        log::info!(
3677            "[gpu-init] {:?} renderer ready in {:.1} ms (effects {:.1} ms)",
3678            adapter_backend,
3679            instant_ms(construction_started, Instant::now()),
3680            effects_ms,
3681        );
3682        renderer
3683    }
3684
3685    /// Prepares the first screen the last launches recorded. The first frame
3686    /// waits for its shapes and fixed pipelines, but draws placeholders for
3687    /// runtime shaders that have not compiled: those queue behind, since a
3688    /// Mali driver compiles largely one pipeline at a time. A driver cache
3689    /// that `holds_recorded` pipelines builds them as cache hits, so those
3690    /// draw from it, and need no stand-in.
3691    #[cfg(not(target_arch = "wasm32"))]
3692    fn warm_first_screen(
3693        &mut self,
3694        records: &crate::pipeline_records::PipelineRecords,
3695        holds_recorded: bool,
3696    ) {
3697        self.warm_fixed_pipelines(&|label| {
3698            records
3699                .fixed
3700                .iter()
3701                .any(|fixed| fixed.first_screen && fixed.entry == label)
3702        });
3703        if holds_recorded {
3704            self.effect_renderer.trust_cached(records);
3705        }
3706        self.effect_renderer.warm_first_screen_shaders(
3707            records
3708                .shaders
3709                .iter()
3710                .filter(|shader| shader.first_screen)
3711                .map(|shader| &shader.entry),
3712        );
3713        if !records.shapes.iter().any(|shape| shape.first_screen) {
3714            self.warm_every_screen();
3715        }
3716    }
3717
3718    /// Queues the pipelines every screen draws with, for a launch with no
3719    /// record of its first screen: flat solid rects and text on whole
3720    /// pixels. They build while the first screen composes, when the
3721    /// compiler's threads have nothing else; on a Mate 20 X the gauntlet's
3722    /// first content showed 333-366 ms after the first frame with them
3723    /// against 383-399 ms without.
3724    #[cfg(not(target_arch = "wasm32"))]
3725    fn warm_every_screen(&mut self) {
3726        self.shape_pipelines.warm(ShapePipelineKey::SOLID_RECT);
3727        self.glyph_atlas_pipeline_resource(false, GlyphQuads::Aligned)
3728            .prefetch(
3729                CompileLane::WarmUp,
3730                &self.pipeline_compiler,
3731                self.adapter_backend,
3732                || self.glyph_atlas_pipeline_job(false, GlyphQuads::Aligned),
3733            );
3734    }
3735
3736    fn ensure_shape_pipeline(&mut self, key: ShapePipelineKey, vertices: u64) {
3737        self.shape_pipelines.ensure(key, vertices);
3738    }
3739
3740    /// Queues the shader warm-ups requested since the last call, each at the
3741    /// target it draws to (`cranpose_ui_graphics::request_shader_warm_ups`).
3742    fn warm_requested_shaders(&mut self) {
3743        let requested = cranpose_ui_graphics::shader_warm_ups_after(self.shader_warm_ups_queued);
3744        if requested.is_empty() {
3745            return;
3746        }
3747        self.shader_warm_ups_queued += requested.len();
3748        self.effect_renderer.warm_shaders(&requested);
3749    }
3750
3751    /// Queues every pipeline recent launches of another build drew with:
3752    /// after an update their compiled pipelines are gone, and each would
3753    /// otherwise compile inside the first frame that draws with it.
3754    #[cfg(not(target_arch = "wasm32"))]
3755    fn warm_recent(&mut self, records: &crate::pipeline_records::PipelineRecords) {
3756        self.warm_fixed_pipelines(&|label| records.fixed.iter().any(|fixed| fixed.entry == label));
3757        let shapes = records
3758            .shapes
3759            .iter()
3760            .map(|shape| shape.entry)
3761            .filter(|&bits| bits != crate::arc_trig_fill::FIRST_SCREEN_KEY)
3762            .filter_map(ShapePipelineKey::from_bits);
3763        for key in shapes {
3764            self.shape_pipelines.warm(key);
3765        }
3766        self.effect_renderer
3767            .warm_recorded_shaders(records.shaders.iter().map(|shader| &shader.entry));
3768    }
3769
3770    /// Queues the fixed pipelines whose labels `wanted` accepts.
3771    #[cfg(not(target_arch = "wasm32"))]
3772    fn warm_fixed_pipelines(&self, wanted: &dyn Fn(&str) -> bool) {
3773        self.effect_renderer.warm_fixed(&self.device, wanted);
3774        let warm =
3775            |pipeline: &FixedPipeline,
3776             job: &dyn Fn() -> Box<dyn FnOnce() -> wgpu::RenderPipeline + Send>| {
3777                if wanted(pipeline.label()) {
3778                    pipeline.queue(
3779                        self.effect_renderer.compiler(),
3780                        crate::pipeline_compiler::CompileLane::WarmUp,
3781                        self.adapter_backend,
3782                        job(),
3783                    );
3784                }
3785            };
3786        for depth in [false, true] {
3787            for (alpha_mask, rounded) in
3788                [(false, false), (true, false), (false, true), (true, true)]
3789            {
3790                let variant = ImageVariant {
3791                    depth,
3792                    alpha_mask,
3793                    rounded,
3794                };
3795                for blend_mode in [BlendMode::SrcOver, BlendMode::DstOut] {
3796                    warm(self.image_pipeline_resource(blend_mode, variant), &|| {
3797                        Box::new(self.image_pipeline_job(blend_mode, variant))
3798                    });
3799                }
3800            }
3801            for quads in [GlyphQuads::Plain, GlyphQuads::Aligned, GlyphQuads::Turned] {
3802                warm(self.glyph_atlas_pipeline_resource(depth, quads), &|| {
3803                    Box::new(self.glyph_atlas_pipeline_job(depth, quads))
3804                });
3805            }
3806            warm(&self.rrect_shadow_pipeline[usize::from(depth)], &|| {
3807                Box::new(self.rrect_shadow_pipeline_job(depth))
3808            });
3809        }
3810    }
3811
3812    fn image_pipeline_resource(
3813        &self,
3814        blend_mode: BlendMode,
3815        variant: ImageVariant,
3816    ) -> &FixedPipeline {
3817        let pipelines = match blend_mode {
3818            BlendMode::DstOut => &self.image_pipeline_dst_out,
3819            _ => &self.image_pipeline,
3820        };
3821        &pipelines[usize::from(variant.depth)
3822            + 2 * usize::from(variant.alpha_mask)
3823            + 4 * usize::from(variant.rounded)]
3824    }
3825
3826    fn image_pipeline_job(
3827        &self,
3828        blend_mode: BlendMode,
3829        variant: ImageVariant,
3830    ) -> impl FnOnce() -> wgpu::RenderPipeline + CompilerSend + 'static {
3831        let device = Arc::clone(&self.device);
3832        let cache = self.pipeline_cache.clone();
3833        let format = self.composition_format;
3834        let shader = self.image_shader.clone();
3835        move || {
3836            create_image_pipeline(
3837                &device,
3838                cache.as_ref(),
3839                format,
3840                &shader,
3841                blend_mode,
3842                variant,
3843            )
3844        }
3845    }
3846
3847    /// Queues the builds of the image pipelines `cmds` draw with that are
3848    /// missing on `lane` (see [`FixedPipeline::prefetch`]), handing
3849    /// `awaited` each still to land.
3850    pub(crate) fn prefetch_image_pipelines(
3851        &self,
3852        cmds: &[ImageDrawCmd],
3853        blend_mode: BlendMode,
3854        (depth, lane): (bool, CompileLane),
3855        awaited: &mut impl FnMut(Pending<wgpu::RenderPipeline>),
3856    ) {
3857        let mut last = None;
3858        for cmd in cmds {
3859            let variant = ImageVariant::of(cmd, depth);
3860            if last.replace(variant) == Some(variant) {
3861                continue;
3862            }
3863            if let Some(pending) = self.image_pipeline_resource(blend_mode, variant).prefetch(
3864                lane,
3865                &self.pipeline_compiler,
3866                self.adapter_backend,
3867                || self.image_pipeline_job(blend_mode, variant),
3868            ) {
3869                awaited(pending);
3870            }
3871        }
3872    }
3873
3874    /// Queues the builds of the glyph pipelines `cmds` draw with that are
3875    /// missing on `lane` (see [`FixedPipeline::prefetch`]), handing
3876    /// `awaited` each still to land.
3877    pub(crate) fn prefetch_glyph_pipelines(
3878        &self,
3879        cmds: &[GlyphDrawCmd],
3880        (depth, lane): (bool, CompileLane),
3881        awaited: &mut impl FnMut(Pending<wgpu::RenderPipeline>),
3882    ) {
3883        let mut last = None;
3884        for cmd in cmds {
3885            let quads = cmd.quads();
3886            if last.replace(quads) == Some(quads) {
3887                continue;
3888            }
3889            if let Some(pending) = self.glyph_atlas_pipeline_resource(depth, quads).prefetch(
3890                lane,
3891                &self.pipeline_compiler,
3892                self.adapter_backend,
3893                || self.glyph_atlas_pipeline_job(depth, quads),
3894            ) {
3895                awaited(pending);
3896            }
3897        }
3898    }
3899
3900    /// Queues the build of the shadow pipeline on `lane` when it is missing
3901    /// (see [`FixedPipeline::prefetch`]), handing `awaited` the pipeline
3902    /// while it is still to land.
3903    pub(crate) fn prefetch_shadow_pipeline(
3904        &self,
3905        (depth, lane): (bool, CompileLane),
3906        awaited: &mut impl FnMut(Pending<wgpu::RenderPipeline>),
3907    ) {
3908        if let Some(pending) = self.rrect_shadow_pipeline[usize::from(depth)].prefetch(
3909            lane,
3910            &self.pipeline_compiler,
3911            self.adapter_backend,
3912            || self.rrect_shadow_pipeline_job(depth),
3913        ) {
3914            awaited(pending);
3915        }
3916    }
3917
3918    pub(crate) fn image_pipeline(
3919        &self,
3920        blend_mode: BlendMode,
3921        variant: ImageVariant,
3922    ) -> &wgpu::RenderPipeline {
3923        self.image_pipeline_resource(blend_mode, variant).for_draw(
3924            &self.recorder,
3925            self.adapter_backend,
3926            || self.image_pipeline_job(blend_mode, variant)(),
3927        )
3928    }
3929
3930    fn glyph_atlas_pipeline_job(
3931        &self,
3932        depth: bool,
3933        quads: GlyphQuads,
3934    ) -> impl FnOnce() -> wgpu::RenderPipeline + CompilerSend + 'static {
3935        let device = Arc::clone(&self.device);
3936        let cache = self.pipeline_cache.clone();
3937        let format = self.composition_format;
3938        let shader = self.glyph_atlas_shader.clone();
3939        move || {
3940            create_glyph_atlas_pipeline(&device, cache.as_ref(), format, &shader, (depth, quads))
3941        }
3942    }
3943
3944    /// The glyph pipeline for a pass with a depth buffer or not, drawing
3945    /// `quads`.
3946    fn glyph_atlas_pipeline(&self, depth: bool, quads: GlyphQuads) -> &wgpu::RenderPipeline {
3947        self.glyph_atlas_pipeline_resource(depth, quads).for_draw(
3948            &self.recorder,
3949            self.adapter_backend,
3950            || self.glyph_atlas_pipeline_job(depth, quads)(),
3951        )
3952    }
3953
3954    fn rrect_shadow_pipeline_job(
3955        &self,
3956        depth: bool,
3957    ) -> impl FnOnce() -> wgpu::RenderPipeline + CompilerSend + 'static {
3958        let device = Arc::clone(&self.device);
3959        let cache = self.pipeline_cache.clone();
3960        let format = self.composition_format;
3961        let shader = self.rrect_shadow_shader.clone();
3962        move || create_rrect_shadow_pipeline(&device, cache.as_ref(), format, &shader, depth)
3963    }
3964
3965    /// Draws `instances` of the pass's uploaded shadow instances, bound with
3966    /// `uniform_slot` under `scissor`.
3967    pub(crate) fn draw_shadow_instances(
3968        &self,
3969        pass: &mut TrackedPass<'_, '_>,
3970        buffer: &BufferUpload,
3971        uniform_slot: usize,
3972        instances: std::ops::Range<u32>,
3973        scissor: Option<(u32, u32, u32, u32)>,
3974        frame: PassFrame,
3975    ) -> Result<(), String> {
3976        let depth = frame.depth;
3977        let pipeline = self.rrect_shadow_pipeline[usize::from(depth)].for_draw(
3978            &self.recorder,
3979            self.adapter_backend,
3980            || self.rrect_shadow_pipeline_job(depth)(),
3981        );
3982        let (x, y, width, height) = frame.scissor(scissor);
3983        pass.set_scissor_rect(x, y, width, height);
3984        pass.set_pipeline(pipeline);
3985        self.viewport_uniforms.bind(pass, uniform_slot)?;
3986        pass.set_vertex_buffer(0, buffer.slice());
3987        pass.draw(0..SHADOW_QUAD_CORNERS, instances);
3988        self.frame_stats.add_draw_calls(1);
3989        Ok(())
3990    }
3991
3992    pub(crate) fn upload_shadow_instances<C: FrameCommandRecorder>(
3993        &self,
3994        recorder: &mut C,
3995        instances: &[ShadowInstance],
3996    ) -> BufferUpload {
3997        recorder.upload_buffer(
3998            UploadAllocatorSpec::vertex(
3999                "Shadow Instance Buffer",
4000                std::mem::size_of::<ShadowInstance>() as u64,
4001            ),
4002            &self.device,
4003            bytemuck::cast_slice(instances),
4004        )
4005    }
4006
4007    fn glyph_atlas_pipeline_resource(&self, depth: bool, quads: GlyphQuads) -> &FixedPipeline {
4008        let kind = match quads {
4009            GlyphQuads::Plain => 0,
4010            GlyphQuads::Aligned => 1,
4011            GlyphQuads::Turned => 2,
4012        };
4013        &self.glyph_atlas_pipeline[usize::from(depth) + 2 * kind]
4014    }
4015
4016    /// The transient depth buffer for a target of `size`, created on first
4017    /// use; the few sizes recent frames used are kept.
4018    pub(crate) fn depth_target(&mut self, size: (u32, u32)) -> wgpu::TextureView {
4019        const KEPT_DEPTH_TARGETS: usize = 4;
4020        let view = match self.depth_targets.take(|(kept, _)| *kept == size) {
4021            Some((_, view)) => view,
4022            None => self.create_depth_target(size),
4023        };
4024        self.depth_targets
4025            .put((size, view.clone()), KEPT_DEPTH_TARGETS, u64::MAX, |_| 0);
4026        view
4027    }
4028
4029    fn create_depth_target(&self, size: (u32, u32)) -> wgpu::TextureView {
4030        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
4031            label: Some("Opaque interior depth"),
4032            size: wgpu::Extent3d {
4033                width: size.0,
4034                height: size.1,
4035                depth_or_array_layers: 1,
4036            },
4037            mip_level_count: 1,
4038            sample_count: 1,
4039            dimension: wgpu::TextureDimension::D2,
4040            format: DEPTH_FORMAT,
4041            usage: wgpu::TextureUsages::RENDER_ATTACHMENT
4042                | wgpu::TextureUsages::TRANSIENT_ATTACHMENT,
4043            view_formats: &[],
4044        });
4045        texture.create_view(&wgpu::TextureViewDescriptor::default())
4046    }
4047
4048    /// The binding a draw of `image` sampled by `sampling` takes, uploading
4049    /// the image the first time a draw needs it.
4050    fn image_binding(
4051        &mut self,
4052        image: &ImageBitmap,
4053        sampling: ImageSampling,
4054    ) -> Result<ImageBinding, String> {
4055        if let Some(cached) = self.image_texture_cache.get(&image.id()) {
4056            return Ok(cached.binding(sampling));
4057        }
4058
4059        let size = wgpu::Extent3d {
4060            width: image.width(),
4061            height: image.height(),
4062            depth_or_array_layers: 1,
4063        };
4064
4065        let alpha_mask = matches!(
4066            image.format(),
4067            cranpose_ui_graphics::ImagePixelFormat::Alpha8 { .. }
4068        );
4069        let (format, bytes_per_pixel) = if alpha_mask {
4070            (wgpu::TextureFormat::R8Unorm, 1)
4071        } else {
4072            (wgpu::TextureFormat::Rgba8Unorm, 4)
4073        };
4074
4075        let texture = self.device.create_texture(&wgpu::TextureDescriptor {
4076            label: Some("Image Texture"),
4077            size,
4078            mip_level_count: 1,
4079            sample_count: 1,
4080            dimension: wgpu::TextureDimension::D2,
4081            format,
4082            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4083            view_formats: &[],
4084        });
4085
4086        let upload_stats = self.frame_graph_executor.upload_texture(
4087            &self.queue,
4088            wgpu::TexelCopyTextureInfo {
4089                texture: &texture,
4090                mip_level: 0,
4091                origin: wgpu::Origin3d::ZERO,
4092                aspect: wgpu::TextureAspect::All,
4093            },
4094            image.pixels(),
4095            wgpu::TexelCopyBufferLayout {
4096                offset: 0,
4097                bytes_per_row: Some(bytes_per_pixel * image.width()),
4098                rows_per_image: Some(image.height()),
4099            },
4100            size,
4101        );
4102        self.frame_stats.record_command_stats(upload_stats);
4103
4104        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
4105        let nearest_bind_group = self.image_bind_group(&view, &self.image_nearest_sampler);
4106        let linear_bind_group = self.image_bind_group(&view, &self.image_linear_sampler);
4107
4108        let bytes = image.pixels().len();
4109        let cached = CachedImageTexture {
4110            alpha_mask,
4111            _texture: texture,
4112            _view: view,
4113            nearest_bind_group,
4114            linear_bind_group,
4115            bytes,
4116        };
4117        let binding = cached.binding(sampling);
4118        if let Some(replaced) = self.image_texture_cache.put(image.id(), cached) {
4119            self.image_texture_cache_bytes = self
4120                .image_texture_cache_bytes
4121                .saturating_sub(replaced.bytes);
4122        }
4123        self.image_texture_cache_bytes += bytes;
4124        while self.image_texture_cache_bytes > MAX_IMAGE_TEXTURE_CACHE_BYTES
4125            && self.image_texture_cache.len() > 1
4126        {
4127            let Some((_, evicted)) = self.image_texture_cache.pop_lru() else {
4128                break;
4129            };
4130            self.image_texture_cache_bytes =
4131                self.image_texture_cache_bytes.saturating_sub(evicted.bytes);
4132        }
4133        Ok(binding)
4134    }
4135
4136    fn image_bind_group(
4137        &self,
4138        view: &wgpu::TextureView,
4139        sampler: &wgpu::Sampler,
4140    ) -> wgpu::BindGroup {
4141        self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4142            label: Some("Image Texture Bind Group"),
4143            layout: &self.image_bind_group_layout,
4144            entries: &[
4145                wgpu::BindGroupEntry {
4146                    binding: 0,
4147                    resource: wgpu::BindingResource::TextureView(view),
4148                },
4149                wgpu::BindGroupEntry {
4150                    binding: 1,
4151                    resource: wgpu::BindingResource::Sampler(sampler),
4152                },
4153            ],
4154        })
4155    }
4156
4157    pub(crate) fn max_texture_dim(&self) -> u32 {
4158        self.effect_renderer.max_texture_dim()
4159    }
4160
4161    /// A pooled texture that outlives the frame: layer cache entries and
4162    /// cached shadow surfaces.
4163    pub(crate) fn acquire_retained_surface(&mut self, width: u32, height: u32) -> OffscreenTarget {
4164        self.effect_renderer
4165            .acquire_offscreen(&self.device, width, height, Some(&self.frame_stats))
4166    }
4167
4168    fn frame_root(
4169        &mut self,
4170        output_mode: OutputMode,
4171        output_view: Option<&wgpu::TextureView>,
4172        output_texture: Option<&wgpu::Texture>,
4173        viewport: (u32, u32),
4174        reads_page: bool,
4175    ) -> FrameRoot {
4176        if let (OutputMode::Display, Some(view), Some(texture)) =
4177            (output_mode, output_view, output_texture)
4178            && surface_is_direct_root(texture, self.composition_format, viewport, reads_page)
4179        {
4180            return FrameRoot::Surface(Rc::new(OffscreenTarget::from_surface(
4181                texture.clone(),
4182                view.clone(),
4183            )));
4184        }
4185        FrameRoot::Composition(self.take_composition_target(viewport.0.max(1), viewport.1.max(1)))
4186    }
4187
4188    fn take_composition_target(&mut self, width: u32, height: u32) -> CompositionTarget {
4189        if let Some(target) = self.composition_target.take()
4190            && target.target.width == width
4191            && target.target.height == height
4192        {
4193            return target;
4194        }
4195        let target = Rc::new(OffscreenTarget::new(
4196            &self.device,
4197            self.composition_format,
4198            width,
4199            height,
4200        ));
4201        let output_bind_group = self.output_converter.bind_group(&self.device, &target.view);
4202        CompositionTarget {
4203            target,
4204            output_bind_group,
4205        }
4206    }
4207
4208    fn transient_offscreen_descriptor(
4209        &self,
4210        label: &'static str,
4211        width: u32,
4212        height: u32,
4213    ) -> FrameTextureDescriptor {
4214        let max_texture_dim = self.max_texture_dim();
4215        FrameTextureDescriptor::render_attachment(
4216            label,
4217            width.min(max_texture_dim),
4218            height.min(max_texture_dim),
4219            self.composition_format,
4220        )
4221    }
4222
4223    /// A texture of the given size that stays transparent: the input of a
4224    /// runtime shader whose layer draws nothing itself, so the shader needs
4225    /// no surface pass and reads the same empty content every frame.
4226    pub(crate) fn transparent_source<C: FrameCommandRecorder>(
4227        &mut self,
4228        recorder: &mut C,
4229        width: u32,
4230        height: u32,
4231    ) -> Rc<OffscreenTarget> {
4232        if let Some(source) = self.transparent_sources.get(&(width, height)) {
4233            return Rc::clone(source);
4234        }
4235        if self.transparent_sources.len() >= MAX_TRANSPARENT_SOURCES {
4236            for (_, source) in self.transparent_sources.drain() {
4237                if let Ok(target) = Rc::try_unwrap(source) {
4238                    self.deferred_offscreen_releases.push(target);
4239                }
4240            }
4241        }
4242        let source = Rc::new(self.acquire_retained_surface(width, height));
4243        self.clear_target(
4244            recorder,
4245            &source.view,
4246            wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
4247        );
4248        self.transparent_sources
4249            .insert((width, height), Rc::clone(&source));
4250        source
4251    }
4252
4253    fn defer_offscreen_release(&mut self, target: OffscreenTarget) {
4254        self.deferred_offscreen_releases.push(target);
4255    }
4256
4257    fn flush_deferred_offscreen_releases(&mut self) {
4258        let layer_cache = &mut self.layer_cache;
4259        let mut retire = |gate: &mut AdmissionGate| {
4260            let seen = gate.end_frame();
4261            if !seen && let Some(dead) = gate.dead_entry() {
4262                layer_cache.remove(&dead);
4263            }
4264            seen
4265        };
4266        self.backdrop_gates.retain(|_, gate| retire(gate));
4267        self.fill_gates.retain(|_, gate| retire(gate));
4268        self.effect_gates.retain(|_, gate| retire(gate));
4269        self.source_gates.retain(|_, gate| retire(gate));
4270        for target in self.deferred_offscreen_releases.drain(..) {
4271            self.effect_renderer.release_offscreen(target);
4272        }
4273        self.layer_cache.end_frame();
4274        for (transient, target) in self.layer_cache.take_released() {
4275            match transient {
4276                Some(descriptor) => self
4277                    .frame_graph_executor
4278                    .return_cached_transient(descriptor, target),
4279                None => self.effect_renderer.release_offscreen(target),
4280            }
4281        }
4282        self.effect_renderer.end_offscreen_frame();
4283        self.frame_graph_executor.end_transient_frame();
4284        self.depth_targets.end_frame();
4285        self.surface_atlas_sizes.end_frame();
4286    }
4287
4288    fn insert_cached_shadow_surface(
4289        &mut self,
4290        key: ShadowSurfaceCacheKey,
4291        target: Rc<OffscreenTarget>,
4292    ) {
4293        let byte_size = offscreen_byte_size(target.width, target.height);
4294        while self.shadow_surface_cache_bytes + byte_size > MAX_SHADOW_SURFACE_CACHE_BYTES {
4295            let Some((_, evicted)) = self.shadow_surface_cache.pop_lru() else {
4296                break;
4297            };
4298            self.shadow_surface_cache_bytes = self
4299                .shadow_surface_cache_bytes
4300                .saturating_sub(evicted.byte_size);
4301        }
4302        let cached = CachedShadowSurface { target, byte_size };
4303        if let Some((_, replaced)) = self.shadow_surface_cache.push(key, cached) {
4304            self.shadow_surface_cache_bytes = self
4305                .shadow_surface_cache_bytes
4306                .saturating_sub(replaced.byte_size);
4307        }
4308        self.shadow_surface_cache_bytes = self.shadow_surface_cache_bytes.saturating_add(byte_size);
4309    }
4310}
4311fn frame_stats_need_warmup_frame(snapshot: &gpu_stats::FrameStatsSnapshot) -> bool {
4312    snapshot.layer_cache_misses > 0
4313        || snapshot.shadow_shape_cache_misses > 0
4314        || snapshot.text_image_cache_misses > 0
4315        || snapshot.text_glyph_atlas_misses > 0
4316}
4317
4318fn update_frame_warmup_budget(pending_frames: &mut u8, snapshot: &gpu_stats::FrameStatsSnapshot) {
4319    if *pending_frames > 0 {
4320        *pending_frames = pending_frames.saturating_sub(1);
4321    } else if frame_stats_need_warmup_frame(snapshot) {
4322        *pending_frames = CACHE_MISS_WARMUP_FRAMES;
4323    }
4324}
4325
4326impl GpuRenderer {
4327    #[expect(clippy::too_many_arguments)]
4328    pub fn render(
4329        &mut self,
4330        texture: &wgpu::Texture,
4331        view: &wgpu::TextureView,
4332        width: u32,
4333        height: u32,
4334        packet: FramePacket,
4335        surface_epoch: u64,
4336        returns: &mut RenderReturns,
4337    ) -> Result<(), String> {
4338        self.render_internal(
4339            width,
4340            height,
4341            packet,
4342            surface_epoch,
4343            returns,
4344            OutputMode::Display,
4345            Some(view),
4346            Some(texture),
4347        )
4348    }
4349
4350    #[expect(clippy::too_many_arguments)]
4351    fn render_internal(
4352        &mut self,
4353        width: u32,
4354        height: u32,
4355        packet: FramePacket,
4356        surface_epoch: u64,
4357        returns: &mut RenderReturns,
4358        output_mode: OutputMode,
4359        output_view: Option<&wgpu::TextureView>,
4360        output_texture: Option<&wgpu::Texture>,
4361    ) -> Result<(), String> {
4362        let cancel_reason = if packet.renderer_epoch != self.renderer_epoch {
4363            Some(CancelReason::RendererEpoch)
4364        } else if packet.surface_epoch != surface_epoch {
4365            Some(CancelReason::SurfaceEpoch)
4366        } else if packet.viewport != (width, height) {
4367            Some(CancelReason::Viewport)
4368        } else {
4369            None
4370        };
4371        if let Some(reason) = cancel_reason {
4372            return Self::cancel_packet(packet, reason, returns);
4373        }
4374        if self.device_errors.take_poison() {
4375            return Self::cancel_packet(packet, CancelReason::DeviceError, returns);
4376        }
4377        returns.frame_id = packet.frame_id;
4378        let render_start = Instant::now();
4379        #[cfg(not(target_arch = "wasm32"))]
4380        let builds_before = thread_builds();
4381        self.warm_requested_shaders();
4382        self.shape_pipelines.begin_frame();
4383        self.recorder.begin_frame();
4384        self.begin_placeholder_frame();
4385        self.viewport_uniforms.begin_frame();
4386        self.run_store.begin_frame(gpu_stats_enabled());
4387        self.begin_text_glyph_run_frame();
4388
4389        let text_cache_len = packet.text_cache_len;
4390        let reads_page = packet.root.contains_backdrop()
4391            || packet
4392                .overlay
4393                .as_ref()
4394                .is_some_and(LayerScene::contains_backdrop);
4395        let frame_root = self.frame_root(
4396            output_mode,
4397            output_view,
4398            output_texture,
4399            (width, height),
4400            reads_page,
4401        );
4402        let root = frame_root.target();
4403        let screenshot_bind_group = output_view.and_then(|_| {
4404            matches!(output_mode, OutputMode::Screenshot).then(|| {
4405                self.screenshot_converter
4406                    .bind_group(&self.device, &root.view)
4407            })
4408        });
4409        let output = frame_root.output(output_view, screenshot_bind_group.as_ref());
4410        let result = self.render_graph(root, packet, returns, output_mode, output);
4411        #[cfg(not(target_arch = "wasm32"))]
4412        self.effect_renderer
4413            .compiler()
4414            .spread_demand(thread_builds() == builds_before);
4415        if let FrameRoot::Composition(composition) = frame_root {
4416            self.composition_target = Some(composition);
4417        }
4418        let after_graph = Instant::now();
4419        self.flush_deferred_offscreen_releases();
4420        self.scratch_glyph_instances.end_frame();
4421
4422        self.frame_stats
4423            .layer_cache_size
4424            .set(self.layer_cache.len() as u32);
4425        self.frame_stats
4426            .layer_cache_bytes
4427            .set(self.layer_cache.bytes());
4428        self.frame_stats.offscreen_pool_size.set(
4429            self.effect_renderer
4430                .retained_offscreen_count()
4431                .saturating_add(self.frame_graph_executor.retained_texture_count())
4432                .saturating_add(usize::from(self.composition_target.is_some())) as u32,
4433        );
4434        self.frame_stats.offscreen_pool_bytes.set(
4435            (self.effect_renderer.retained_offscreen_bytes() as u64)
4436                .saturating_add(self.frame_graph_executor.retained_texture_bytes())
4437                .saturating_add(self.composition_target.as_ref().map_or(0, |target| {
4438                    u64::from(target.target.width)
4439                        .saturating_mul(u64::from(target.target.height))
4440                        .saturating_mul(COMPOSITION_BYTES_PER_PIXEL)
4441                })),
4442        );
4443        self.frame_stats
4444            .text_pool_size
4445            .set(self.text_image_cache.len() as u32);
4446        self.frame_stats
4447            .image_cache_size
4448            .set(self.image_texture_cache.len() as u32);
4449        self.frame_stats.text_cache_size.set(text_cache_len as u32);
4450        self.effect_renderer
4451            .merge_and_reset_debug_counters(&self.frame_stats);
4452        self.frame_graph_executor.reset_upload_allocators();
4453        let snapshot = self.frame_stats.snapshot();
4454        if crate::frame_graph::frame_graph_pass_telemetry_threshold_ms().is_some() {
4455            log::warn!(
4456                "[wgpu-render-stage:frame-stats] layer_hit={} layer_miss={} miss_px={} \
4457                 offscreen_acq={} offscreen_new={} isolated={} draws={}",
4458                snapshot.layer_cache_hits,
4459                snapshot.layer_cache_misses,
4460                snapshot.layer_cache_miss_pixels,
4461                snapshot.offscreen_acquires,
4462                snapshot.offscreen_news,
4463                snapshot.isolated_layer_renders,
4464                snapshot.draw_calls,
4465            );
4466        }
4467        self.last_frame_stats = Some(snapshot);
4468        PRESENTED_FRAMES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4469        update_frame_warmup_budget(&mut self.pending_frame_warmup_frames, &snapshot);
4470        let gpu_stats_on = gpu_stats_enabled();
4471        self.frame_stats
4472            .maybe_print_snapshot(snapshot, &mut self.frame_count, gpu_stats_on);
4473        if gpu_stats_on && self.frame_count.is_multiple_of(60) {
4474            gpu_stats::print_gpu_memory_report(&self.device, self.frame_count);
4475        }
4476        self.frame_graph_executor
4477            .end_pass_timing_frame(&self.device, &self.queue);
4478        self.frame_stats.reset();
4479        let after_stats = Instant::now();
4480        if let Some(total_ms) = should_log_wgpu_render_stage(render_start, after_stats) {
4481            log::warn!(
4482                "[wgpu-render-stage:render] total_ms={total_ms:.2} graph_ms={:.2} cleanup_stats_ms={:.2}",
4483                instant_ms(render_start, after_graph),
4484                instant_ms(after_graph, after_stats),
4485            );
4486        }
4487        if result.is_ok() {
4488            returns.outcome = PresentOutcome::Presented;
4489        }
4490        result
4491    }
4492
4493    /// Returns a packet unrendered, handing its scene back for recycling.
4494    pub(crate) fn cancel_packet(
4495        packet: FramePacket,
4496        reason: CancelReason,
4497        returns: &mut RenderReturns,
4498    ) -> Result<(), String> {
4499        returns.scene = Some(FrameSceneStorage {
4500            root: packet.root,
4501            overlay: packet.overlay,
4502        });
4503        returns.frame_id = packet.frame_id;
4504        returns.outcome = PresentOutcome::Cancelled(reason);
4505        Ok(())
4506    }
4507
4508    pub fn last_frame_stats(&self) -> Option<gpu_stats::FrameStatsSnapshot> {
4509        self.last_frame_stats
4510    }
4511
4512    pub fn gpu_pass_timings(&self) -> crate::pass_timing::GpuPassTimingReport {
4513        self.frame_graph_executor.pass_timing_report()
4514    }
4515
4516    pub fn needs_frame_warmup(&self) -> bool {
4517        self.pending_frame_warmup_frames > 0 || self.placeholder_awaits_frame()
4518    }
4519
4520    #[cfg(target_arch = "wasm32")]
4521    pub(crate) fn pipeline_compiler(&self) -> &PipelineCompiler {
4522        &self.pipeline_compiler
4523    }
4524
4525    /// Whether a frame must follow to replace a placeholder: a pipeline it
4526    /// waited for landed, or, with nothing to wake the app when one does,
4527    /// this frame drew one.
4528    fn placeholder_awaits_frame(&self) -> bool {
4529        #[cfg(not(target_arch = "wasm32"))]
4530        if let Some(landing) = &self.landing {
4531            return landing.landed() || (self.drew_placeholder && !landing.has_wake());
4532        }
4533        self.drew_placeholder
4534    }
4535
4536    /// Starts a frame: once a pipeline a placeholder waited for has landed,
4537    /// the retained layers, which may hold that placeholder, are dropped.
4538    fn begin_placeholder_frame(&mut self) {
4539        self.drew_placeholder = false;
4540        #[cfg(not(target_arch = "wasm32"))]
4541        if let Some(landing) = &self.landing {
4542            self.built_at_frame_start = landing.built();
4543            if landing.take_landed() {
4544                self.layer_cache.clear();
4545            }
4546        }
4547    }
4548
4549    /// Notes an effect drawn as its placeholder, or left out, while its
4550    /// pipelines compile, and waits for the next one to land.
4551    pub(crate) fn note_placeholder(&mut self) {
4552        self.drew_placeholder = true;
4553        self.recorder.hold_first_screen();
4554        self.frame_stats
4555            .placeholder_draws
4556            .set(self.frame_stats.placeholder_draws.get() + 1);
4557        #[cfg(not(target_arch = "wasm32"))]
4558        if let Some(landing) = &self.landing {
4559            landing.await_since(self.built_at_frame_start);
4560        }
4561    }
4562
4563    /// Queues every pipeline the cache file records, as the first frame of
4564    /// an updated build does, and returns whether any is left to build: for
4565    /// a renderer that prepares an app's pipelines without drawing.
4566    #[cfg(not(target_arch = "wasm32"))]
4567    pub(crate) fn prepare_recorded(&mut self) -> bool {
4568        self.effect_renderer.warm_after_first_frame();
4569        if let Some(records) = self.recent_after_update.take() {
4570            self.warm_recent(&records);
4571        }
4572        self.compiling()
4573    }
4574
4575    /// Whether the background compiler has a queued pipeline left to build.
4576    #[cfg(not(target_arch = "wasm32"))]
4577    pub(crate) fn compiling(&self) -> bool {
4578        self.landing
4579            .as_ref()
4580            .is_some_and(|landing| landing.in_flight())
4581    }
4582
4583    /// Whether the last frame drew a placeholder and pipelines are still
4584    /// compiling, so its picture is not final yet.
4585    pub(crate) fn awaits_pipelines(&self) -> bool {
4586        #[cfg(not(target_arch = "wasm32"))]
4587        if let Some(landing) = &self.landing {
4588            return self.drew_placeholder && landing.in_flight();
4589        }
4590        self.drew_placeholder
4591    }
4592
4593    /// The compiler's account of the pipelines it builds, for a runtime that
4594    /// reads it off the present thread.
4595    #[cfg(not(target_arch = "wasm32"))]
4596    pub(crate) fn landing(&self) -> Option<Arc<crate::pipeline_compiler::Landing>> {
4597        self.landing.clone()
4598    }
4599
4600    /// Calls `wake` from the compiling thread when a pipeline a placeholder
4601    /// waited for lands, so an app with nothing else to draw draws again.
4602    #[cfg(not(target_arch = "wasm32"))]
4603    pub(crate) fn wake_on_landing(&self, wake: Box<dyn Fn() + Send + Sync>) {
4604        if let Some(landing) = &self.landing {
4605            landing.wake_with(wake);
4606        }
4607    }
4608
4609    pub fn debug_cpu_allocation_stats(&self) -> DebugCpuAllocationStats {
4610        DebugCpuAllocationStats {
4611            scene_graph_node_count: 0,
4612            scene_graph_heap_bytes: 0,
4613            scene_hits_len: 0,
4614            scene_hits_cap: 0,
4615            scene_node_index_len: 0,
4616            scene_node_index_cap: 0,
4617            text_renderer_pool_len: self.text_image_cache.len(),
4618            text_renderer_pool_cap: self.text_image_cache.cap().get(),
4619            image_texture_cache_len: self.image_texture_cache.len(),
4620            image_texture_cache_cap: self.image_texture_cache.cap().get(),
4621            run_arena_staging_bytes: self.run_store.arena_staging_bytes(),
4622            run_store_bytes: self.run_store.stored_bytes(),
4623            run_store_runs: self.run_store.stored_count(),
4624            scratch_image_vertices_cap: self.scratch_image_vertices.capacity(),
4625            scratch_image_indices_cap: self.scratch_image_indices.capacity(),
4626            scratch_image_cmds_cap: self.scratch_image_cmds.capacity(),
4627            scratch_glyph_instances_cap: self.scratch_glyph_instances.plain.capacity()
4628                + self.scratch_glyph_instances.turned.capacity(),
4629            layer_cache_len: self.layer_cache.len(),
4630            layer_cache_bytes: self.layer_cache.bytes(),
4631        }
4632    }
4633    pub fn render_to_rgba_pixels(
4634        &mut self,
4635        width: u32,
4636        height: u32,
4637        packet: FramePacket,
4638        surface_epoch: u64,
4639        returns: &mut RenderReturns,
4640    ) -> Result<Vec<u8>, String> {
4641        if width == 0 || height == 0 {
4642            return Err("Screenshot size must be non-zero".to_string());
4643        }
4644        let errors_before = self.device_errors.error_count();
4645
4646        let output_texture = crate::offscreen::create_2d_texture(
4647            &self.device,
4648            wgpu::TextureFormat::Rgba8Unorm,
4649            width,
4650            height,
4651            wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
4652            Some("Screenshot Output Texture"),
4653        );
4654        let output_view = output_texture.create_view(&wgpu::TextureViewDescriptor::default());
4655        self.render_internal(
4656            width,
4657            height,
4658            packet,
4659            surface_epoch,
4660            returns,
4661            OutputMode::Screenshot,
4662            Some(&output_view),
4663            None,
4664        )?;
4665        if returns.outcome != PresentOutcome::Presented {
4666            return Err(format!(
4667                "Screenshot rendering did not complete: {:?}",
4668                returns.outcome
4669            ));
4670        }
4671        self.read_screenshot_pixels(&output_texture, errors_before)
4672    }
4673
4674    fn read_screenshot_pixels(
4675        &mut self,
4676        output_texture: &wgpu::Texture,
4677        errors_before: u64,
4678    ) -> Result<Vec<u8>, String> {
4679        let width = output_texture.width();
4680        let height = output_texture.height();
4681        let bytes_per_pixel = 4u32;
4682        let unpadded_bytes_per_row = width
4683            .checked_mul(bytes_per_pixel)
4684            .ok_or_else(|| "Screenshot row byte size overflow".to_string())?;
4685        let padded_bytes_per_row =
4686            align_to(unpadded_bytes_per_row, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT);
4687        let output_buffer_size = padded_bytes_per_row as u64 * height as u64;
4688
4689        let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
4690            label: Some("Screenshot Readback Buffer"),
4691            size: output_buffer_size,
4692            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
4693            mapped_at_creation: false,
4694        });
4695
4696        let device = self.device.clone();
4697        let queue = self.queue.clone();
4698        let mut graph = WgpuFrameGraph::new(Some("Screenshot Copy Encoder"));
4699        let source = graph.import_surface("screenshot-copy-source");
4700        graph.add_fallible_command_pass(Some("Screenshot Copy Pass"), &[source], &[], |context| {
4701            context.encoder.copy_texture_to_buffer(
4702                wgpu::TexelCopyTextureInfo {
4703                    texture: output_texture,
4704                    mip_level: 0,
4705                    origin: wgpu::Origin3d::ZERO,
4706                    aspect: wgpu::TextureAspect::All,
4707                },
4708                wgpu::TexelCopyBufferInfo {
4709                    buffer: &output_buffer,
4710                    layout: wgpu::TexelCopyBufferLayout {
4711                        offset: 0,
4712                        bytes_per_row: Some(padded_bytes_per_row),
4713                        rows_per_image: Some(height),
4714                    },
4715                },
4716                wgpu::Extent3d {
4717                    width,
4718                    height,
4719                    depth_or_array_layers: 1,
4720                },
4721            );
4722            Ok(())
4723        });
4724        let mut executor = std::mem::take(&mut self.frame_graph_executor);
4725        let execution = executor.execute_recorded_graph(&device, &queue, graph);
4726        self.frame_graph_executor = executor;
4727        self.recall_frame_uploads();
4728        let execution = execution.map_err(|error| error.to_string())?;
4729        let submission_index = execution.submission;
4730        let copy_stats = execution.stats;
4731        self.last_frame_stats = self
4732            .last_frame_stats
4733            .map(|snapshot| snapshot.with_command_stats_added(copy_stats));
4734
4735        let buffer_slice = output_buffer.slice(..);
4736        let (tx, rx) = mpsc::channel();
4737        buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
4738            let _ = tx.send(result);
4739        });
4740        self.device
4741            .poll(wgpu::PollType::Wait {
4742                submission_index: Some(submission_index),
4743                timeout: None,
4744            })
4745            .map_err(|error| format!("Screenshot GPU wait failed: {error}"))?;
4746
4747        match rx.recv_timeout(Duration::from_secs(3)) {
4748            Ok(Ok(())) => {}
4749            Ok(Err(err)) => return Err(format!("Screenshot map_async failed: {err:?}")),
4750            Err(err) => return Err(format!("Screenshot readback timed out: {err}")),
4751        }
4752        if self.device_errors.error_count() != errors_before {
4753            output_buffer.unmap();
4754            return Err("GPU errors occurred while capturing the screenshot".to_string());
4755        }
4756
4757        let mapped = buffer_slice
4758            .get_mapped_range()
4759            .map_err(|err| format!("Screenshot readback could not be read: {err}"))?;
4760        let mut pixels = vec![0u8; (width as usize) * (height as usize) * 4];
4761
4762        let src_row_len = padded_bytes_per_row as usize;
4763        let dst_row_len = unpadded_bytes_per_row as usize;
4764        for row in 0..height as usize {
4765            let src_offset = row * src_row_len;
4766            let dst_offset = row * dst_row_len;
4767            pixels[dst_offset..dst_offset + dst_row_len]
4768                .copy_from_slice(&mapped[src_offset..src_offset + dst_row_len]);
4769        }
4770        drop(mapped);
4771        output_buffer.unmap();
4772
4773        self.convert_surface_pixels_to_rgba(&pixels)
4774    }
4775
4776    /// Asks the buffers the submitted frame unmapped back for later frames.
4777    fn recall_frame_uploads(&mut self) {
4778        self.viewport_uniforms.uploads.recall();
4779        self.run_store.recall();
4780    }
4781
4782    fn render_graph(
4783        &mut self,
4784        root_target: &Rc<OffscreenTarget>,
4785        packet: FramePacket,
4786        returns: &mut RenderReturns,
4787        output_mode: OutputMode,
4788        output: Option<(&wgpu::TextureView, &wgpu::BindGroup)>,
4789    ) -> Result<(), String> {
4790        let device = self.device.clone();
4791        let queue = self.queue.clone();
4792        let graph_start = Instant::now();
4793        let FramePacket {
4794            root,
4795            overlay,
4796            root_scale,
4797            clear,
4798            ..
4799        } = packet;
4800        let page = Rc::clone(root_target);
4801
4802        #[cfg(not(target_arch = "wasm32"))]
4803        let (result, submitted) = {
4804            let mut executor = std::mem::take(&mut self.frame_graph_executor);
4805            let mut frame_graph = WgpuFrameGraph::new(Some("Renderer Frame Graph"));
4806            let surface = frame_graph.import_surface("renderer-surface");
4807            frame_graph.add_fallible_recorded_command_pass(
4808                Some("Renderer Frame Pass"),
4809                &[],
4810                &[surface],
4811                |frame_encoder| {
4812                    self.encode_frame(
4813                        frame_encoder,
4814                        &root,
4815                        overlay.as_ref(),
4816                        Rc::clone(&page),
4817                        root_scale,
4818                        clear,
4819                        output_mode,
4820                        output,
4821                    )
4822                },
4823            );
4824            let after_build = Instant::now();
4825            let execution = executor.execute_recorded_graph(&device, &queue, frame_graph);
4826            let after_execute = Instant::now();
4827            self.frame_graph_executor = executor;
4828            self.recall_frame_uploads();
4829            if let Some(total_ms) = should_log_wgpu_render_stage(graph_start, after_execute) {
4830                log::warn!(
4831                    "[wgpu-render-stage:graph] total_ms={total_ms:.2} build_ms={:.2} execute_ms={:.2}",
4832                    instant_ms(graph_start, after_build),
4833                    instant_ms(after_build, after_execute),
4834                );
4835            }
4836            match execution {
4837                Ok(execution) => {
4838                    if execution.stats.pass_count > 0 {
4839                        self.frame_stats.record_command_stats(execution.stats);
4840                    }
4841                    (Ok(()), true)
4842                }
4843                Err(crate::frame_graph::FrameGraphError::NoDeclaredPasses) => (Ok(()), false),
4844                Err(error) => (Err(error.to_string()), false),
4845            }
4846        };
4847
4848        #[cfg(target_arch = "wasm32")]
4849        let (result, submitted) = {
4850            let mut executor = std::mem::take(&mut self.frame_graph_executor);
4851            let (result, execution) = {
4852                let mut frame_encoder =
4853                    executor.begin(&device, &queue, Some("Renderer Frame Encoder"));
4854                let initial_pass_count = frame_encoder.recorded_pass_count();
4855                let result = self.encode_frame(
4856                    &mut frame_encoder,
4857                    &root,
4858                    overlay.as_ref(),
4859                    Rc::clone(&page),
4860                    root_scale,
4861                    clear,
4862                    output_mode,
4863                    output,
4864                );
4865                let execution =
4866                    if result.is_ok() && frame_encoder.recorded_pass_count() > initial_pass_count {
4867                        Some(frame_encoder.finish())
4868                    } else {
4869                        None
4870                    };
4871                (result, execution)
4872            };
4873            let after_execute = Instant::now();
4874            self.frame_graph_executor = executor;
4875            if let Some(total_ms) = should_log_wgpu_render_stage(graph_start, after_execute) {
4876                log::warn!("[wgpu-render-stage:graph] total_ms={total_ms:.2}",);
4877            }
4878            let submitted = execution.is_some();
4879            if let Some(execution) = execution {
4880                self.frame_stats.record_command_stats(execution.stats);
4881            }
4882            (result, submitted)
4883        };
4884        #[cfg(not(target_arch = "wasm32"))]
4885        if submitted {
4886            self.recorder.note_frame_drawn();
4887            self.effect_renderer.warm_after_first_frame();
4888            if let Some(records) = self.recent_after_update.take() {
4889                self.warm_recent(&records);
4890            }
4891        }
4892        if !submitted {
4893            self.run_store.invalidate_uploads();
4894            self.text_glyph_gpu_run_cache.clear();
4895            self.text_glyph_run_arena = GlyphRunArena::new(self.copy_free);
4896        }
4897        returns.scene = Some(FrameSceneStorage { root, overlay });
4898        result
4899    }
4900
4901    /// Records the frame: the root and overlay layer scenes into the frame's
4902    /// target, the output conversion when the target is not the presented
4903    /// image, and the viewport uniforms the recorded passes claimed.
4904    #[expect(clippy::too_many_arguments)]
4905    fn encode_frame<C: FrameCommandRecorder>(
4906        &mut self,
4907        recorder: &mut C,
4908        root: &LayerScene,
4909        overlay: Option<&LayerScene>,
4910        page: Rc<OffscreenTarget>,
4911        root_scale: f32,
4912        clear: wgpu::Color,
4913        output_mode: OutputMode,
4914        output: Option<(&wgpu::TextureView, &wgpu::BindGroup)>,
4915    ) -> Result<(), String> {
4916        FrameExecutor::new(self, recorder).render_frame(
4917            root,
4918            overlay,
4919            page,
4920            root_scale,
4921            wgpu::LoadOp::Clear(clear),
4922        )?;
4923        if let Some((output_view, bind_group)) = output {
4924            match output_mode {
4925                OutputMode::Display => &self.output_converter,
4926                OutputMode::Screenshot => &self.screenshot_converter,
4927            }
4928            .encode(
4929                &self.device,
4930                recorder,
4931                output_view,
4932                bind_group,
4933                self.adapter_backend,
4934                &self.recorder,
4935            );
4936            recorder.record_pass();
4937        }
4938        self.stage_frame_uploads(recorder);
4939        self.shape_pipelines.request_wanted();
4940        self.depth_gate.end_frame();
4941        self.pipeline_compiler.end_frame();
4942        self.viewport_uniforms.uploads.finish_frame();
4943        self.run_store.finish_frame();
4944        Ok(())
4945    }
4946
4947    pub(crate) fn stage_frame_uploads(&mut self, recorder: &mut impl FrameCommandRecorder) {
4948        let mut upload = self.viewport_uniforms.stage_pending(&self.device, recorder);
4949        upload += self.run_store.stage_pending(&self.device, recorder);
4950        upload += self
4951            .text_glyph_run_arena
4952            .stage_pending(&self.device, recorder);
4953        self.run_store.fill_arena_trig(&self.device, recorder);
4954        self.frame_stats.record_command_stats(upload);
4955    }
4956    /// Claims this frame's next viewport uniform slot for `params`.
4957    pub(crate) fn claim_uniform_slot(&mut self, params: ViewportUniformParams) -> usize {
4958        let uniforms = Uniforms::of(params, PlacementData::zeroed(), RunTier::Arena);
4959        self.viewport_uniforms
4960            .claim(&self.device, &self.uniform_bind_group_layout, &uniforms)
4961    }
4962
4963    /// Resolves a blurred shadow at `z` into a texture and queues its
4964    /// composites. The shadow's shapes and texts render into a source the
4965    /// size of their blur footprint, blur in place and take the post-blur
4966    /// cutouts; the source is then blitted over the rect the shadow covers.
4967    /// Shape-only shadows live in the shadow cache, keyed by their content
4968    /// and device placement, so a scrolling card re-blits its cached blur.
4969    /// The blurred shadow texture and whether the cache held it: a
4970    /// shape-only shadow is cached by content and placement, a shadow with
4971    /// text renders every frame.
4972    fn blurred_shadow_source<C: FrameCommandRecorder>(
4973        &mut self,
4974        recorder: &mut C,
4975        shadow: &ShadowDraw,
4976        source_device: DevicePixelBounds,
4977        pixel_radius: f32,
4978        root_scale: f32,
4979        transients: &mut Vec<(FrameTextureDescriptor, Rc<OffscreenTarget>)>,
4980    ) -> Option<(Rc<OffscreenTarget>, bool, SourceContent)> {
4981        let shape_only = shadow.texts.is_empty();
4982        let key = if shape_only {
4983            shape_shadow_surface_cache_key(shadow, source_device, pixel_radius, root_scale)
4984        } else {
4985            None
4986        };
4987        let content = key.map_or(SourceContent::Transient, |key| {
4988            SourceContent::retained(&key)
4989        });
4990        if let Some(entry) = key.and_then(|key| self.shadow_surface_cache.get(&key)) {
4991            return Some((Rc::clone(&entry.target), true, content));
4992        }
4993        // A shadow whose blur compiles is left out until the blur lands.
4994        if !self.effect_renderer.blur_draws_now(
4995            &self.device,
4996            TileMode::Decal,
4997            (source_device.width, source_device.height),
4998            pixel_radius,
4999            pixel_radius,
5000        ) {
5001            self.note_placeholder();
5002            return None;
5003        }
5004        if !shape_only {
5005            self.frame_stats.record_shadow_text_blur_fallback();
5006        }
5007        let source = self.render_shadow_source(
5008            recorder,
5009            shadow,
5010            source_device,
5011            pixel_radius,
5012            root_scale,
5013            key.is_some(),
5014            transients,
5015        )?;
5016        if let Some(key) = key {
5017            self.frame_stats
5018                .record_shadow_shape_cache_miss(source_device.width, source_device.height);
5019            self.frame_stats.maybe_print_shadow_shape_cache_miss(
5020                source_device.width,
5021                source_device.height,
5022                key.content_hash,
5023                pixel_radius,
5024                [source_device.x, source_device.y],
5025                shadow.shapes.as_ref().map_or(0, RunDraw::record_count) as usize,
5026                shadow.clip,
5027            );
5028            self.insert_cached_shadow_surface(key, Rc::clone(&source));
5029        }
5030        Some((source, false, content))
5031    }
5032
5033    #[expect(clippy::too_many_arguments)]
5034    pub(crate) fn resolve_blurred_shadow<C: FrameCommandRecorder>(
5035        &mut self,
5036        recorder: &mut C,
5037        shadow: &ShadowDraw,
5038        z: usize,
5039        root_scale: f32,
5040        target_rect: DeviceRect4,
5041        transients: &mut Vec<(FrameTextureDescriptor, Rc<OffscreenTarget>)>,
5042        resolved: &mut Vec<ResolvedComposite>,
5043    ) {
5044        if !shadow.requires_surface()
5045            || skip_shadow_draws()
5046            || !root_scale.is_finite()
5047            || root_scale <= 0.0
5048        {
5049            return;
5050        }
5051        let Some(bounds) = shadow_draw_bounds(shadow) else {
5052            return;
5053        };
5054        let margin = blur_reach(shadow.blur_radius, root_scale);
5055        let source_bounds = expand_rect(bounds, margin, margin);
5056        let mut visible = source_bounds;
5057        if let Some(clip) = shadow.clip {
5058            let Some(clipped) = visible.intersect(expand_rect(clip, margin, margin)) else {
5059                return;
5060            };
5061            visible = clipped;
5062        }
5063        let target_logical = Rect {
5064            x: target_rect.0 / root_scale,
5065            y: target_rect.1 / root_scale,
5066            width: target_rect.2 / root_scale,
5067            height: target_rect.3 / root_scale,
5068        };
5069        let Some(visible) = visible.intersect(expand_rect(target_logical, margin, margin)) else {
5070            return;
5071        };
5072        let max_texture_dim = self.max_texture_dim();
5073        let shape_only = shadow.texts.is_empty();
5074        let anchor = shadow
5075            .shapes
5076            .as_ref()
5077            .and_then(|run| run.placement.snap_anchor);
5078        let source_device = shape_only
5079            .then(|| {
5080                translation_stable_anchored_device_pixel_bounds(
5081                    source_bounds,
5082                    anchor,
5083                    root_scale,
5084                    max_texture_dim,
5085                )
5086            })
5087            .flatten()
5088            .or_else(|| device_pixel_bounds(visible, root_scale, max_texture_dim));
5089        let Some(source_device) = source_device else {
5090            return;
5091        };
5092        let pixel_radius = shadow.blur_radius * root_scale;
5093        let Some((source, hit, content)) = self.blurred_shadow_source(
5094            recorder,
5095            shadow,
5096            source_device,
5097            pixel_radius,
5098            root_scale,
5099            transients,
5100        ) else {
5101            return;
5102        };
5103        let dest = (
5104            source_device.x,
5105            source_device.y,
5106            source_device.width as f32,
5107            source_device.height as f32,
5108        );
5109        let mut coverage = intersect_device_rects(dest, target_rect);
5110        if let Some(clip) = shadow.clip {
5111            coverage = coverage.and_then(|coverage| {
5112                intersect_device_rects(coverage, anchored_rect_to_device(clip, anchor, root_scale))
5113            });
5114        }
5115        let Some(coverage) = coverage else {
5116            return;
5117        };
5118        if hit {
5119            self.frame_stats.record_shadow_shape_cache_hit(
5120                (coverage.2 as u64).saturating_mul(coverage.3 as u64),
5121            );
5122        }
5123        let rounded_mask = shadow_composite_mask(shadow, anchor, root_scale);
5124        let downscaled =
5125            (source.width, source.height) != (source_device.width, source_device.height);
5126        let (sample_mode, source_viewport) = if downscaled {
5127            (
5128                CompositeSampleMode::Linear,
5129                Some((0.0, 0.0, source.width as f32, source.height as f32)),
5130            )
5131        } else {
5132            (CompositeSampleMode::Nearest, None)
5133        };
5134        resolved.push(ResolvedComposite {
5135            z_index: z,
5136            source,
5137            content,
5138            dest,
5139            scissor: Some(coverage),
5140            kind: ResolvedCompositeKind::Blit {
5141                alpha: 1.0,
5142                blend_mode: BlendMode::SrcOver,
5143                rounded_mask,
5144                sample_mode,
5145                source_viewport,
5146            },
5147        });
5148    }
5149
5150    /// Draws a shadow's shapes and texts into a surface covering `bounds`
5151    /// and blurs it. A wide blur runs at its scratch size and its result
5152    /// stays there, read bilinearly by the composite; a post-blur cutout
5153    /// needs the surface's full size, so the blurred result is interpolated
5154    /// back into it first and the cutout drawn at that size. A retained
5155    /// result feeds the shadow cache; a transient one is registered with
5156    /// the frame's transients and released with them. `None` when the
5157    /// shadow draws nothing.
5158    #[expect(clippy::too_many_arguments)]
5159    fn render_shadow_source<C: FrameCommandRecorder>(
5160        &mut self,
5161        recorder: &mut C,
5162        shadow: &ShadowDraw,
5163        bounds: DevicePixelBounds,
5164        pixel_radius: f32,
5165        root_scale: f32,
5166        retained: bool,
5167        transients: &mut Vec<(FrameTextureDescriptor, Rc<OffscreenTarget>)>,
5168    ) -> Option<Rc<OffscreenTarget>> {
5169        let (width, height) = (bounds.width, bounds.height);
5170        let device = self.device.clone();
5171        let (scratch_width, scratch_height) =
5172            crate::effect_renderer::blur_scratch_size(pixel_radius, pixel_radius, width, height);
5173        let full_size_result = shadow.post_blur_cutouts.is_some()
5174            || (scratch_width, scratch_height) == (width, height);
5175        let (result_width, result_height) = if full_size_result {
5176            (width, height)
5177        } else {
5178            (scratch_width, scratch_height)
5179        };
5180        let result = if retained {
5181            Rc::new(self.acquire_retained_surface(result_width, result_height))
5182        } else {
5183            self.shadow_transient(
5184                recorder,
5185                transients,
5186                "Shadow Result",
5187                result_width,
5188                result_height,
5189            )
5190        };
5191        let source = if full_size_result {
5192            Rc::clone(&result)
5193        } else {
5194            self.shadow_transient(recorder, transients, "Shadow Source", width, height)
5195        };
5196        let offset = [bounds.x, bounds.y];
5197        let target = PassTarget {
5198            view: &source.view,
5199            width,
5200            height,
5201        };
5202        let scene = shadow_scene(
5203            shadow.shapes.as_ref(),
5204            &shadow.texts,
5205            blur_reach(shadow.blur_radius, root_scale),
5206        );
5207        let segment = PassSegment {
5208            scene: &scene,
5209            ops: &scene.draw_ops,
5210            composites: &[],
5211            offset,
5212            scissor: None,
5213            first_run_window: None,
5214            transform: SegmentTransform::IDENTITY,
5215            scale: root_scale,
5216        };
5217        let drew = self.encode_pass(
5218            recorder,
5219            target,
5220            std::slice::from_ref(&segment),
5221            wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
5222            "Shadow Source Pass",
5223        );
5224        match drew {
5225            Ok(true) => {}
5226            Ok(false) => {
5227                drop(source);
5228                if retained && let Ok(target) = Rc::try_unwrap(result) {
5229                    self.defer_offscreen_release(target);
5230                }
5231                return None;
5232            }
5233            Err(error) => {
5234                log::error!("shadow source pass failed: {error}");
5235                return None;
5236            }
5237        }
5238        if pixel_radius > 0.0 {
5239            let scratch_descriptor = self.transient_offscreen_descriptor(
5240                "Shadow Blur Scratch",
5241                scratch_width,
5242                scratch_height,
5243            );
5244            let scratch = recorder.acquire_transient_offscreen(&device, scratch_descriptor);
5245            let blurred = if full_size_result && (scratch_width, scratch_height) != (width, height)
5246            {
5247                Some(self.shadow_transient(
5248                    recorder,
5249                    transients,
5250                    "Shadow Blur Result",
5251                    scratch_width,
5252                    scratch_height,
5253                ))
5254            } else {
5255                None
5256            };
5257            let blur_dest = match &blurred {
5258                Some(blurred) => (&blurred.view, (scratch_width, scratch_height)),
5259                None => (&result.view, (result_width, result_height)),
5260            };
5261            let passes = self.effect_renderer.encode_blur_scissored_ping_pong_passes(
5262                recorder,
5263                &device,
5264                &source,
5265                &scratch,
5266                blur_dest,
5267                pixel_radius,
5268                pixel_radius,
5269                TileMode::Decal,
5270                None,
5271            );
5272            recorder.record_passes(passes);
5273            self.effect_renderer.record_blur_pass();
5274            recorder.release_transient_offscreen(scratch_descriptor, scratch);
5275            if let Some(blurred) = &blurred {
5276                self.effect_renderer
5277                    .encode_upscale_pass(recorder, &device, blurred, &result.view);
5278                recorder.record_pass();
5279            }
5280        }
5281        if let Some(cutout_run) = &shadow.post_blur_cutouts {
5282            let cutouts = shadow_scene(Some(cutout_run), &[], 0.0);
5283            let segment = PassSegment {
5284                scene: &cutouts,
5285                ops: &cutouts.draw_ops,
5286                composites: &[],
5287                offset,
5288                scissor: None,
5289                first_run_window: None,
5290                transform: SegmentTransform::IDENTITY,
5291                scale: root_scale,
5292            };
5293            if let Err(error) = self.encode_pass(
5294                recorder,
5295                target,
5296                std::slice::from_ref(&segment),
5297                wgpu::LoadOp::Load,
5298                "Shadow Cutout Pass",
5299            ) {
5300                log::error!("shadow cutout pass failed: {error}");
5301            }
5302        }
5303        Some(result)
5304    }
5305
5306    /// A transient surface of a shadow's frame, released with the frame's
5307    /// transients.
5308    fn shadow_transient<C: FrameCommandRecorder>(
5309        &self,
5310        recorder: &mut C,
5311        transients: &mut Vec<(FrameTextureDescriptor, Rc<OffscreenTarget>)>,
5312        label: &'static str,
5313        width: u32,
5314        height: u32,
5315    ) -> Rc<OffscreenTarget> {
5316        let descriptor = self.transient_offscreen_descriptor(label, width, height);
5317        let target = Rc::new(recorder.acquire_transient_offscreen(&self.device, descriptor));
5318        transients.push((descriptor, Rc::clone(&target)));
5319        target
5320    }
5321
5322    /// Whether `run` draws from retained buffers keyed by its command.
5323    pub(crate) fn run_is_stored(&self, run: &RunDraw) -> bool {
5324        self.run_store.is_stored(run)
5325    }
5326
5327    /// The pipeline `segment` of a run at `placement` draws with. `clipped`
5328    /// says the variant tests the run's clip: a turned run's, since an
5329    /// unturned one's clip is its paint's scissor (see [`clip_scissor`]).
5330    fn run_pipeline_key(
5331        segment: &RecordSegment,
5332        placement: &crate::scene::Placement,
5333        tier: RunTier,
5334        ablation: ShapeAblation,
5335        turns: ShapeTurns,
5336        (depth, clip): (bool, SegmentClip),
5337        viewport: ViewportUniformParams,
5338    ) -> ShapePipelineKey {
5339        let blend_mode = supported_blend_mode(segment.blend);
5340        let laid = depth
5341            && blend_mode == BlendMode::SrcOver
5342            && !placement.paints()
5343            && clip != SegmentClip::Rounded
5344            && !ablation.material
5345            && !ablation.fill;
5346        ShapePipelineKey {
5347            blend_mode,
5348            tier,
5349            variant: ShapeVariant::of_segment(
5350                segment,
5351                clip,
5352                ablation,
5353                laid,
5354                (
5355                    turns == ShapeTurns::None,
5356                    turns == ShapeTurns::None
5357                        && placement.snap_anchor.is_some()
5358                        && viewport.offset.iter().all(|value| value.fract() == 0.0),
5359                ),
5360            ),
5361            turns,
5362            depth: if depth {
5363                ShapeDepth::Tested
5364            } else {
5365                ShapeDepth::Off
5366            },
5367        }
5368    }
5369
5370    /// Queues for a later frame the shape pipelines `keys` name and the
5371    /// interiors they lay down, handing `awaited` each still to land.
5372    pub(crate) fn warm_shapes_later(
5373        &mut self,
5374        keys: &PipelineDemand,
5375        awaited: &mut impl FnMut(Pending<wgpu::RenderPipeline>),
5376    ) {
5377        for key in keys.keys() {
5378            self.shape_pipelines.warm_later(key, awaited);
5379        }
5380    }
5381
5382    /// Prepares the pipeline of each key `pipelines` holds and, in a pass
5383    /// with a depth buffer, the one laying down its opaque interiors: the
5384    /// compiler's threads build the missing ones after the first while this
5385    /// thread builds that one.
5386    pub(crate) fn ensure_run_pipelines(&mut self, pipelines: &mut PipelineDemand) {
5387        self.shape_pipelines.prefetch(pipelines);
5388        for (key, vertices) in pipelines.drain() {
5389            self.ensure_shape_pipeline(key, vertices);
5390            if let Some(interior) = key.interior() {
5391                self.ensure_shape_pipeline(interior, vertices);
5392            }
5393        }
5394    }
5395
5396    /// Brings a stored run's tables up to date and appends its draws, under
5397    /// a placement uniform of its own, to the pass's run draws `draws`, and
5398    /// the pipelines they draw with to `pipelines`.
5399    #[expect(clippy::too_many_arguments)]
5400    pub(crate) fn prepare_store_run<C: FrameCommandRecorder>(
5401        &mut self,
5402        recorder: &mut C,
5403        run: &RunDraw,
5404        viewport: ViewportUniformParams,
5405        root_scale: f32,
5406        window: &std::ops::Range<u32>,
5407        (depth, mut depth_keys): (bool, Option<&mut PipelineDemand>),
5408        (draws, pipelines): (&mut Vec<RunDrawCall>, &mut PipelineDemand),
5409    ) -> StoreRunBatch {
5410        let placement = &run.placement;
5411        let ablation = self.ablation.shape;
5412        let turns = ShapeTurns::of(viewport.transform, false);
5413        let scissor = clip_scissor(placement, root_scale, viewport);
5414        let clip = SegmentClip::of(placement, scissor.is_some());
5415        let start = draws.len();
5416        self.run_store.stored_run_draws(
5417            &self.device,
5418            run,
5419            &mut |segment| {
5420                let key = |depth| {
5421                    Self::run_pipeline_key(
5422                        segment,
5423                        placement,
5424                        RunTier::Store,
5425                        ablation,
5426                        turns,
5427                        (depth, clip),
5428                        viewport,
5429                    )
5430                };
5431                if let Some(keys) = depth_keys.as_deref_mut() {
5432                    keys.add(key(true), 0);
5433                }
5434                key(depth)
5435            },
5436            draws,
5437        );
5438        window_draws(draws, start, window);
5439        let run_draws = &draws[start..];
5440        let upload_start = Instant::now();
5441        let (upload, fill, tables) = self.run_store.upload_stored(
5442            &self.device,
5443            recorder,
5444            run,
5445            root_scale,
5446            window,
5447            run_draws,
5448        );
5449        if let Some(total_ms) = should_log_wgpu_render_stage(upload_start, Instant::now()) {
5450            log::warn!(
5451                "[wgpu-render-stage:run-upload] total_ms={total_ms:.2} bytes={} records={}",
5452                upload.upload_bytes,
5453                run.tables().shapes.len()
5454            );
5455        }
5456        self.frame_stats.record_command_stats(upload);
5457        if let Some(fill) = fill {
5458            self.frame_stats.add_shape_fill(fill);
5459        }
5460        let uniforms = Uniforms::of(
5461            viewport,
5462            PlacementData::of(&run.placement, root_scale, viewport.transform),
5463            RunTier::Store,
5464        );
5465        let uniform_slot =
5466            self.viewport_uniforms
5467                .claim(&self.device, &self.uniform_bind_group_layout, &uniforms);
5468        for draw in run_draws {
5469            pipelines.add(
5470                draw.key,
5471                crate::run_store::draw_vertices(
5472                    draw.records.end - draw.records.start,
5473                    draw.band_class,
5474                ),
5475            );
5476        }
5477        StoreRunBatch {
5478            tables,
5479            uniform_slot,
5480            draws: start..draws.len(),
5481            clip: scissor,
5482        }
5483    }
5484
5485    pub(crate) fn open_arena(&mut self) -> usize {
5486        self.run_store.open_arena()
5487    }
5488
5489    /// The records the open arena chunk holds so far.
5490    pub(crate) fn open_arena_records(&self) -> u32 {
5491        self.run_store.open_arena_records()
5492    }
5493
5494    pub(crate) fn arena_accepts(&self, chunk: usize, run: &RunDraw) -> bool {
5495        self.run_store.arena_accepts(chunk, run)
5496    }
5497
5498    /// Appends `window` of `run`'s records to the open arena chunk, their
5499    /// placement under `turn`, keyed for a pass whose flat and turned
5500    /// records alternate often when `mixed_turns`, and the pipelines they
5501    /// draw with to `pipelines`. `clip` says how its variant tests its clip:
5502    /// an unturned run leaves a rect clip to its paint's scissor, never a
5503    /// rounded one.
5504    #[expect(clippy::too_many_arguments)]
5505    pub(crate) fn append_arena_run(
5506        &mut self,
5507        chunk: usize,
5508        run: &RunDraw,
5509        window: std::ops::Range<u32>,
5510        root_scale: f32,
5511        viewport: ViewportUniformParams,
5512        mixed_turns: bool,
5513        (depth, clip): (bool, SegmentClip),
5514        (pipelines, mut depth_keys): (&mut PipelineDemand, Option<&mut PipelineDemand>),
5515    ) -> u32 {
5516        let placement = &run.placement;
5517        let ablation = self.ablation.shape;
5518        let turn = viewport.transform;
5519        let turns = ShapeTurns::of(turn, mixed_turns);
5520        self.run_store.append_arena(
5521            chunk,
5522            run,
5523            window,
5524            (root_scale, turn),
5525            &mut |segment| {
5526                let key = |depth| {
5527                    Self::run_pipeline_key(
5528                        segment,
5529                        placement,
5530                        RunTier::Arena,
5531                        ablation,
5532                        turns,
5533                        (depth, clip),
5534                        viewport,
5535                    )
5536                };
5537                if let Some(keys) = depth_keys.as_deref_mut() {
5538                    keys.add(key(true), 0);
5539                }
5540                key(depth)
5541            },
5542            pipelines,
5543        )
5544    }
5545
5546    /// Moves the open chunk's draws since its last cut onto `out`, keeping
5547    /// it open.
5548    pub(crate) fn cut_arena(&mut self, chunk: usize, out: &mut Vec<RunDrawCall>) {
5549        self.run_store.cut_arena(&self.device, chunk, out);
5550    }
5551
5552    /// Uploads the open chunk and moves its draws since its last cut onto
5553    /// `out`.
5554    pub(crate) fn close_arena(&mut self, chunk: usize, out: &mut Vec<RunDrawCall>) {
5555        if let Some(fill) = self.run_store.close_arena(&self.device, chunk, out) {
5556            self.frame_stats.add_shape_fill(fill);
5557        }
5558    }
5559
5560    pub(crate) fn draw_run_calls(
5561        &self,
5562        pass: &mut TrackedPass<'_, '_>,
5563        tables: ArenaBinding<'_>,
5564        uniform_slot: usize,
5565        draws: &[RunDrawCall],
5566        scissor: (u32, u32, u32, u32),
5567        stage: RunStage,
5568    ) -> Result<(), String> {
5569        match stage {
5570            RunStage::Paint => {
5571                if draws.is_empty() {
5572                    return Ok(());
5573                }
5574                self.frame_stats.bump_shapes();
5575                self.record_run_draws(
5576                    pass,
5577                    tables,
5578                    uniform_slot,
5579                    scissor,
5580                    draws.iter().map(PlannedRunDraw::paint),
5581                )
5582            }
5583            RunStage::Interiors => {
5584                let planned = interior_run_draws(draws);
5585                if planned.is_empty() {
5586                    return Ok(());
5587                }
5588                self.frame_stats
5589                    .shape_interior_draws
5590                    .set(self.frame_stats.shape_interior_draws.get() + planned.len() as u32);
5591                self.record_run_draws(pass, tables, uniform_slot, scissor, planned.into_iter())
5592            }
5593        }
5594    }
5595
5596    /// Records `planned` against one batch's tables, uniform and scissor.
5597    fn record_run_draws(
5598        &self,
5599        pass: &mut TrackedPass<'_, '_>,
5600        tables: ArenaBinding<'_>,
5601        uniform_slot: usize,
5602        scissor: (u32, u32, u32, u32),
5603        planned: impl ExactSizeIterator<Item = PlannedRunDraw>,
5604    ) -> Result<(), String> {
5605        self.frame_stats.add_draw_calls(planned.len() as u32);
5606        let (x, y, width, height) = scissor;
5607        pass.set_scissor_rect(x, y, width, height);
5608        self.viewport_uniforms.bind(pass, uniform_slot)?;
5609        pass.set_bind_group(1, tables.bind_group, &tables.offsets[2..]);
5610        for (slot, buffer) in tables.records.into_iter().enumerate() {
5611            pass.set_vertex_buffer(slot as u32, buffer.slice(u64::from(tables.offsets[slot])..));
5612        }
5613        let mut bound_class = None;
5614        for draw in planned {
5615            let key = draw.key;
5616            let (pipeline, fallback) = self
5617                .shape_pipelines
5618                .get(key)
5619                .ok_or_else(|| format!("shape pipeline {key:?} was not prepared"))?;
5620            if fallback {
5621                self.frame_stats
5622                    .shape_pipeline_fallback_draws
5623                    .set(self.frame_stats.shape_pipeline_fallback_draws.get() + 1);
5624            } else if !key.is_general() {
5625                self.frame_stats
5626                    .shape_specialized_draws
5627                    .set(self.frame_stats.shape_specialized_draws.get() + 1);
5628            }
5629            if bound_class != Some(draw.band_class) {
5630                pass.set_index_buffer(self.run_store.strip_index_buffer(draw.band_class).slice(..));
5631                bound_class = Some(draw.band_class);
5632            }
5633            pass.set_pipeline(pipeline);
5634            pass.draw_indexed(draw.indices, 0, draw.records);
5635        }
5636        Ok(())
5637    }
5638
5639    /// Draws a stored-run batch, whose draws are its range of `draws`.
5640    pub(crate) fn draw_store_run(
5641        &self,
5642        pass: &mut TrackedPass<'_, '_>,
5643        batch: &StoreRunBatch,
5644        draws: &[RunDrawCall],
5645        scissor: (u32, u32, u32, u32),
5646        stage: RunStage,
5647    ) -> Result<(), String> {
5648        self.draw_run_calls(
5649            pass,
5650            batch.tables.binding(),
5651            batch.uniform_slot,
5652            &draws[batch.draws.clone()],
5653            scissor,
5654            stage,
5655        )
5656    }
5657
5658    pub(crate) fn draw_arena(
5659        &self,
5660        pass: &mut TrackedPass<'_, '_>,
5661        chunk: usize,
5662        uniform_slot: usize,
5663        draws: &[RunDrawCall],
5664        scissor: (u32, u32, u32, u32),
5665        stage: RunStage,
5666    ) -> Result<(), String> {
5667        self.draw_run_calls(
5668            pass,
5669            self.run_store.arena_binding(chunk),
5670            uniform_slot,
5671            draws,
5672            scissor,
5673            stage,
5674        )
5675    }
5676    #[cfg(not(target_arch = "wasm32"))]
5677    pub(crate) fn surface_format(&self) -> wgpu::TextureFormat {
5678        self.display_format
5679    }
5680
5681    pub fn device_error_count(&self) -> u64 {
5682        self.device_errors.error_count()
5683    }
5684
5685    /// A pass that only applies `load_op` to the target, for a scene with
5686    /// nothing to draw that still needs its clear.
5687    pub(crate) fn clear_target<C: FrameCommandRecorder>(
5688        &self,
5689        recorder: &mut C,
5690        view: &wgpu::TextureView,
5691        load_op: wgpu::LoadOp<wgpu::Color>,
5692    ) {
5693        self.empty_pass(recorder, "Clear Pass", view, load_op);
5694    }
5695
5696    pub(crate) fn empty_pass<C: FrameCommandRecorder>(
5697        &self,
5698        recorder: &mut C,
5699        label: &'static str,
5700        view: &wgpu::TextureView,
5701        load_op: wgpu::LoadOp<wgpu::Color>,
5702    ) {
5703        let pass = recorder.begin_color_pass(label, view, load_op);
5704        drop(pass);
5705        recorder.record_pass();
5706    }
5707    pub(crate) fn draw_image_cmds(
5708        &self,
5709        pass: &mut TrackedPass<'_, '_>,
5710        image_slot: &ImageSlot,
5711        uniform_slot: usize,
5712        cmds: &[ImageDrawCmd],
5713        (blend_mode, depth): (BlendMode, bool),
5714        bound: Option<(u32, u32, u32, u32)>,
5715    ) -> Result<(), String> {
5716        if cmds.is_empty() {
5717            return Ok(());
5718        }
5719        self.frame_stats.bump_images();
5720        self.frame_stats.add_draw_calls(cmds.len() as u32);
5721        let mut bound_pipeline = None;
5722        self.viewport_uniforms.bind(pass, uniform_slot)?;
5723        pass.set_index_buffer(image_slot.indices.slice());
5724        pass.set_vertex_buffer(0, image_slot.vertices.slice());
5725        let mut clips_bound = false;
5726        for cmd in cmds {
5727            let Some((x, y, width, height)) = bounded_scissor(cmd.scissor, bound) else {
5728                continue;
5729            };
5730            pass.set_scissor_rect(x, y, width, height);
5731            let variant = ImageVariant::of(cmd, depth);
5732            if bound_pipeline != Some(variant) {
5733                pass.set_pipeline(self.image_pipeline(blend_mode, variant));
5734                bound_pipeline = Some(variant);
5735            }
5736            pass.set_bind_group(1, &cmd.image.bind_group, &[]);
5737            let instance = match cmd.clip {
5738                Some(clip) => {
5739                    if !clips_bound {
5740                        let clips = image_slot
5741                            .clips
5742                            .as_ref()
5743                            .ok_or_else(|| "rounded image without clip instances".to_string())?;
5744                        pass.set_vertex_buffer(1, clips.slice());
5745                        clips_bound = true;
5746                    }
5747                    clip
5748                }
5749                None => 0,
5750            };
5751            pass.draw_indexed(
5752                cmd.index_start..(cmd.index_start + 6),
5753                0,
5754                instance..instance + 1,
5755            );
5756        }
5757        Ok(())
5758    }
5759
5760    pub(crate) fn draw_glyph_cmds(
5761        &self,
5762        pass: &mut TrackedPass<'_, '_>,
5763        (glyph_slot, turned_slot): (Option<&BufferUpload>, Option<&BufferUpload>),
5764        uniform_slot: usize,
5765        cmds: &[GlyphDrawCmd],
5766        bound: Option<(u32, u32, u32, u32)>,
5767        frame: PassFrame,
5768    ) -> Result<(), String> {
5769        if cmds.is_empty() {
5770            return Ok(());
5771        }
5772        let whole_target = frame.scissor(bound);
5773        self.frame_stats.bump_text();
5774        let mut bound_atlas = None;
5775        // Which shared instances are bound, turned or plain, and which
5776        // pipeline; retained runs draw with a plain one.
5777        let mut shared_bound: Option<bool> = None;
5778        let mut bound_pipeline: Option<GlyphQuads> = None;
5779        let mut bound_run_instances: Option<&wgpu::Buffer> = None;
5780        let mut draws = 0u32;
5781        for draw in GlyphDraws::new(cmds) {
5782            let scissor = match draw.scissor {
5783                Some(scissor) => bounded_scissor(scissor, bound),
5784                None => Some(whole_target),
5785            };
5786            let Some((x, y, width, height)) = scissor else {
5787                continue;
5788            };
5789            pass.set_scissor_rect(x, y, width, height);
5790            if !bound_atlas.is_some_and(|atlas| Rc::ptr_eq(atlas, draw.atlas)) {
5791                pass.set_bind_group(1, draw.atlas.as_ref(), &[]);
5792                bound_atlas = Some(draw.atlas);
5793            }
5794            draws += 1;
5795            match draw.step {
5796                GlyphDrawStep::Shared(instances, quads) => {
5797                    if bound_pipeline != Some(quads) {
5798                        pass.set_pipeline(self.glyph_atlas_pipeline(frame.depth, quads));
5799                        bound_pipeline = Some(quads);
5800                    }
5801                    let turned = quads.turned();
5802                    if shared_bound != Some(turned) {
5803                        let slot =
5804                            if turned { turned_slot } else { glyph_slot }.ok_or_else(|| {
5805                                "shared glyph draw without glyph instances".to_string()
5806                            })?;
5807                        self.viewport_uniforms.bind(pass, uniform_slot)?;
5808                        pass.set_vertex_buffer(0, slot.slice());
5809                        shared_bound = Some(turned);
5810                        bound_run_instances = None;
5811                    }
5812                    pass.draw(0..GLYPH_QUAD_CORNERS, instances);
5813                }
5814                GlyphDrawStep::Retained {
5815                    run,
5816                    uniform_slot: retained_slot,
5817                    aligned,
5818                } => {
5819                    let quads = GlyphQuads::plain(aligned);
5820                    if bound_pipeline != Some(quads) {
5821                        pass.set_pipeline(self.glyph_atlas_pipeline(frame.depth, quads));
5822                        bound_pipeline = Some(quads);
5823                    }
5824                    shared_bound = None;
5825                    self.viewport_uniforms.bind(pass, retained_slot)?;
5826                    let instances = run.span.instance_buffer();
5827                    if bound_run_instances != Some(instances) {
5828                        pass.set_vertex_buffer(0, instances.slice(..));
5829                        bound_run_instances = Some(instances);
5830                    }
5831                    pass.draw(0..GLYPH_QUAD_CORNERS, run.span.instances());
5832                }
5833            }
5834        }
5835        self.frame_stats.add_draw_calls(draws);
5836        Ok(())
5837    }
5838    pub(crate) fn append_image_draw_cmd(
5839        &mut self,
5840        image_draw: &ImageDraw,
5841        viewport: ViewportUniformParams,
5842        root_scale: f32,
5843        (image_vertices, image_indices): (&mut Vec<Vertex>, &mut Vec<u32>),
5844        image_clips: &mut Vec<DeviceRoundedClip>,
5845        image_cmds: &mut Vec<ImageDrawCmd>,
5846    ) -> Result<(), String> {
5847        let snap_delta = image_draw
5848            .snap_anchor
5849            .map(|anchor| snap_delta_for_anchor(anchor, root_scale))
5850            .unwrap_or_default();
5851        let rect = image_draw.rect.translate(snap_delta.x, snap_delta.y);
5852        if rect.width <= 0.0 || rect.height <= 0.0 || image_draw.alpha <= 0.0 {
5853            return Ok(());
5854        }
5855
5856        let (mut tint, cpu_filter) = tint_for_image(image_draw.color_filter, image_draw.alpha);
5857        if tint[3] <= 0.0 {
5858            return Ok(());
5859        }
5860
5861        let filtered_image = cpu_filter
5862            .map(|filter| apply_filter_to_bitmap(&image_draw.image, filter))
5863            .transpose()?;
5864        let prepared_image = filtered_image.as_ref().unwrap_or(&image_draw.image);
5865        if let cranpose_ui_graphics::ImagePixelFormat::Alpha8 { color } = prepared_image.format() {
5866            for (channel, value) in tint[..3].iter_mut().zip(color) {
5867                *channel *= value as f32 / 255.0;
5868            }
5869        }
5870
5871        let geometry = resolve_image_geometry_with_delta(image_draw, root_scale, snap_delta);
5872        let Some(scissor) =
5873            scissor_rect_for_layer(geometry.rect, geometry.clip, root_scale, viewport)
5874        else {
5875            return Ok(());
5876        };
5877
5878        let Some(uv_rect) = image_uv_rect(&image_draw.image, image_draw.src_rect) else {
5879            return Ok(());
5880        };
5881        let device_quad = geometry.device_quad(root_scale);
5882        let index_start =
5883            push_image_quad((image_vertices, image_indices), device_quad, tint, &uv_rect);
5884        let clip = DeviceRoundedClip::of(image_draw, root_scale).map(|clip| {
5885            image_clips.push(clip);
5886            image_clips.len() as u32 - 1
5887        });
5888        image_cmds.push(ImageDrawCmd {
5889            index_start,
5890            scissor,
5891            image: self.image_binding(prepared_image, geometry.sampling)?,
5892            clip,
5893        });
5894        Ok(())
5895    }
5896
5897    /// Uploads a pass's image and glyph quads into the frame's buffers.
5898    pub(crate) fn upload_image_slot<C: FrameCommandRecorder>(
5899        &self,
5900        recorder: &mut C,
5901        vertices: &[Vertex],
5902        indices: &[u32],
5903        clips: &[DeviceRoundedClip],
5904    ) -> ImageSlot {
5905        ImageSlot {
5906            vertices: recorder.upload_buffer(
5907                image_vertex_spec(),
5908                &self.device,
5909                bytemuck::cast_slice(vertices),
5910            ),
5911            indices: recorder.upload_buffer(
5912                image_index_spec(),
5913                &self.device,
5914                bytemuck::cast_slice(indices),
5915            ),
5916            clips: (!clips.is_empty()).then(|| {
5917                recorder.upload_buffer(
5918                    UploadAllocatorSpec::vertex(
5919                        "Image Clip Buffer",
5920                        std::mem::size_of::<DeviceRoundedClip>() as u64,
5921                    ),
5922                    &self.device,
5923                    bytemuck::cast_slice(clips),
5924                )
5925            }),
5926        }
5927    }
5928
5929    pub(crate) fn upload_glyph_instances<C: FrameCommandRecorder>(
5930        &self,
5931        recorder: &mut C,
5932        instances: &[GlyphInstance],
5933    ) -> BufferUpload {
5934        recorder.upload_buffer(
5935            glyph_instance_spec(),
5936            &self.device,
5937            bytemuck::cast_slice(instances),
5938        )
5939    }
5940
5941    pub(crate) fn upload_turned_glyphs<C: FrameCommandRecorder>(
5942        &self,
5943        recorder: &mut C,
5944        glyphs: &[TurnedGlyph],
5945    ) -> BufferUpload {
5946        recorder.upload_buffer(
5947            turned_glyph_spec(),
5948            &self.device,
5949            bytemuck::cast_slice(glyphs),
5950        )
5951    }
5952
5953    fn glyph_atlas_entry_for(
5954        &mut self,
5955        glyph: &SoftwareGlyphAtlasGlyph,
5956    ) -> Result<GlyphAtlasEntry, String> {
5957        if let Some(entry) = self.text_glyph_atlas.upload_glyph(
5958            glyph,
5959            &self.queue,
5960            &mut self.frame_graph_executor,
5961            &mut self.frame_stats,
5962        ) {
5963            return Ok(entry);
5964        }
5965
5966        self.text_glyph_atlas.reset(
5967            &self.device,
5968            &self.image_bind_group_layout,
5969            GlyphSamplers {
5970                nearest: &self.image_nearest_sampler,
5971                linear: &self.image_linear_sampler,
5972            },
5973        );
5974        Err("text glyph atlas filled and was reset".to_string())
5975    }
5976
5977    fn glyph_atlas_entry_for_cached(
5978        &mut self,
5979        glyph: &SoftwareGlyphAtlasPlacement,
5980    ) -> Option<GlyphAtlasEntry> {
5981        let entry = self
5982            .text_glyph_atlas
5983            .entry(&GlyphAtlasSlotKey::new(glyph.key, glyph.color))?;
5984        self.frame_stats.record_text_glyph_atlas_hits(1);
5985        Some(entry)
5986    }
5987
5988    fn glyph_atlas_entry_for_placement(
5989        &mut self,
5990        glyph: &SoftwareGlyphAtlasPlacement,
5991    ) -> Result<GlyphAtlasEntry, String> {
5992        if let Some(entry) = self.glyph_atlas_entry_for_cached(glyph) {
5993            return Ok(entry);
5994        }
5995
5996        let Some(upload_glyph) = self.text_glyph_mask_cache.atlas_glyph_for_placement(glyph) else {
5997            return Err("text glyph placement has no retained raster mask".to_string());
5998        };
5999        self.glyph_atlas_entry_for(&upload_glyph)
6000    }
6001
6002    /// The atlas entries of a run's drawing glyphs: of `cached_glyph_run`,
6003    /// which holds only those, or of the drawing glyphs of `collected_run`,
6004    /// a run just collected, whose new glyphs are uploaded here.
6005    fn prepare_text_glyph_entries(
6006        &mut self,
6007        run_key: TextGlyphRunCacheKey,
6008        atlas_generation: u64,
6009        cached_glyph_run: Option<&RunGlyphs>,
6010        collected_run: &[SoftwareGlyphAtlasRunGlyph],
6011        entries: &mut Vec<GlyphAtlasEntry>,
6012    ) -> Result<Rc<[GlyphAtlasEntry]>, String> {
6013        entries.clear();
6014        let mut seen = RunAtlasEntries::new();
6015        if let Some(glyph_run) = cached_glyph_run {
6016            for glyph in glyph_run.iter() {
6017                entries.push(seen.entry(self, &glyph, |renderer| {
6018                    renderer.glyph_atlas_entry_for_placement(&glyph)
6019                })?);
6020            }
6021        } else {
6022            for run_glyph in collected_run {
6023                entries.push(seen.entry(
6024                    self,
6025                    &run_glyph.placement(),
6026                    |renderer| match run_glyph {
6027                        SoftwareGlyphAtlasRunGlyph::Cached(placement) => {
6028                            renderer.glyph_atlas_entry_for_placement(placement)
6029                        }
6030                        SoftwareGlyphAtlasRunGlyph::New(glyph) => {
6031                            renderer.glyph_atlas_entry_for(glyph)
6032                        }
6033                    },
6034                )?);
6035            }
6036        }
6037
6038        let entries: Rc<[GlyphAtlasEntry]> = Rc::from(entries.as_slice());
6039        if let Some(cached) = self.text_glyph_run_cache.get_mut(&run_key) {
6040            cached.atlas_entries = Some(Rc::clone(&entries));
6041            cached.atlas_generation = atlas_generation;
6042        }
6043        Ok(entries)
6044    }
6045
6046    #[expect(clippy::too_many_arguments)]
6047    fn append_text_glyph_quad_run(
6048        &mut self,
6049        source_raster_rect: Rect,
6050        quads: GlyphRunQuads<'_>,
6051        (clip, cut): (Option<Rect>, Option<[f32; 4]>),
6052        viewport: ViewportUniformParams,
6053        root_scale: f32,
6054        glyph_instances: &mut GlyphInstances,
6055        record_cached_hits: bool,
6056    ) -> (GlyphQuads, GlyphExtent) {
6057        let start = glyph_instances.lens();
6058        let mut extent = GlyphExtent::EMPTY;
6059        let kind = if viewport.transform.is_identity() {
6060            let plain = &mut glyph_instances.plain;
6061            let mut aligned = true;
6062            for_each_visible_glyph(
6063                source_raster_rect,
6064                quads,
6065                (clip, cut),
6066                viewport,
6067                root_scale,
6068                |glyph| {
6069                    aligned &= glyph_rect_aligned(glyph.rect, viewport.offset);
6070                    extent.add(glyph.rect);
6071                    plain.push(glyph);
6072                },
6073            );
6074            GlyphQuads::plain(aligned)
6075        } else {
6076            let turn = viewport.transform;
6077            let turned = &mut glyph_instances.turned;
6078            for_each_visible_glyph(
6079                source_raster_rect,
6080                quads,
6081                (clip, cut),
6082                viewport,
6083                root_scale,
6084                |glyph| turned.push(TurnedGlyph::new(glyph, turn)),
6085            );
6086            GlyphQuads::Turned
6087        };
6088        let end = glyph_instances.lens();
6089        let appended = (end.0 - start.0) + (end.1 - start.1);
6090        if record_cached_hits {
6091            self.frame_stats
6092                .record_text_glyph_atlas_hits(u32::try_from(appended).unwrap_or(u32::MAX));
6093        }
6094        (kind, extent)
6095    }
6096
6097    /// The viewport a retained glyph run draws under: its vertices sit at
6098    /// its raster rect's origin, so an untransformed target moves its offset
6099    /// back by that origin, and a transformed one adds the origin to each
6100    /// vertex before its transform, as the shared path's vertices hold it.
6101    fn retained_glyph_viewport(
6102        viewport: ViewportUniformParams,
6103        source_raster_rect: Rect,
6104    ) -> ViewportUniformParams {
6105        if !viewport.transform.is_identity() {
6106            return ViewportUniformParams {
6107                origin: [source_raster_rect.x, source_raster_rect.y],
6108                ..viewport
6109            };
6110        }
6111        ViewportUniformParams {
6112            offset: [
6113                viewport.offset[0] - source_raster_rect.x,
6114                viewport.offset[1] - source_raster_rect.y,
6115            ],
6116            ..viewport
6117        }
6118    }
6119
6120    fn retained_text_glyph_run(
6121        &mut self,
6122        cache_key: TextGlyphRunCacheKey,
6123    ) -> Option<Rc<CachedGpuTextGlyphRun>> {
6124        let atlas_generation = self.text_glyph_atlas.generation();
6125        let frame = self.text_glyph_run_frame;
6126        self.text_glyph_gpu_run_cache
6127            .get(&cache_key)
6128            .filter(|cached| cached.atlas_generation == atlas_generation)
6129            .inspect(|cached| cached.last_frame.set(frame))
6130            .cloned()
6131    }
6132
6133    /// Opens a frame for text runs: runs no frame drew for
6134    /// [`TEXT_GLYPH_GPU_RUN_IDLE_FRAMES`] leave the GPU cache and those no
6135    /// frame drew for [`TEXT_GLYPH_RUN_IDLE_FRAMES`] the CPU cache, and the
6136    /// arena takes back the quads dropped retained runs held.
6137    fn begin_text_glyph_run_frame(&mut self) {
6138        self.text_glyph_run_frame += 1;
6139        let frame = self.text_glyph_run_frame;
6140        let demand = std::mem::take(&mut self.text_glyph_runs_drawn).saturating_mul(2);
6141        self.text_glyph_run_cache.set_cap(
6142            std::num::NonZeroUsize::new(demand.max(MIN_TEXT_GLYPH_RUN_CACHE_ITEMS))
6143                .unwrap_or(std::num::NonZeroUsize::MIN),
6144        );
6145        evict_idle(
6146            &mut self.text_glyph_gpu_run_cache,
6147            frame,
6148            TEXT_GLYPH_GPU_RUN_IDLE_FRAMES,
6149            |run| run.last_frame.get(),
6150        );
6151        evict_idle(
6152            &mut self.text_glyph_run_cache,
6153            frame,
6154            TEXT_GLYPH_RUN_IDLE_FRAMES,
6155            |run| run.last_frame.get(),
6156        );
6157        self.text_glyph_run_arena.begin_frame();
6158        let evacuating = self.text_glyph_run_arena.evacuating();
6159        if !evacuating.is_empty() {
6160            let moving: Vec<TextGlyphRunCacheKey> = self
6161                .text_glyph_gpu_run_cache
6162                .iter()
6163                .filter(|(_, run)| evacuating.contains(&run.span.chunk()))
6164                .map(|(key, _)| *key)
6165                .collect();
6166            for key in &moving {
6167                self.text_glyph_gpu_run_cache.pop(key);
6168            }
6169        }
6170    }
6171
6172    fn emit_retained_text_glyph_run_if_ready(
6173        &mut self,
6174        cache_key: TextGlyphRunCacheKey,
6175        quads: GlyphRunQuads<'_>,
6176        viewport: ViewportUniformParams,
6177        source_raster_rect: Rect,
6178        scissor: (u32, u32, u32, u32),
6179        glyph_cmds: &mut Vec<GlyphDrawCmd>,
6180    ) -> bool {
6181        let Some(run) = self.retained_text_glyph_run(cache_key).or_else(|| {
6182            if self.ensure_retained_text_glyph_run(cache_key, quads) {
6183                self.retained_text_glyph_run(cache_key)
6184            } else {
6185                None
6186            }
6187        }) else {
6188            return false;
6189        };
6190        let uniform_slot =
6191            self.claim_uniform_slot(Self::retained_glyph_viewport(viewport, source_raster_rect));
6192        self.frame_stats
6193            .record_text_glyph_atlas_hits(u32::try_from(quads.len()).unwrap_or(u32::MAX));
6194        // The run's quads sit at whole texels from its raster origin.
6195        let aligned = viewport.transform.is_identity()
6196            && glyph_rect_aligned(
6197                [source_raster_rect.x, source_raster_rect.y, 0.0, 0.0],
6198                [viewport.offset[0], viewport.offset[1]],
6199            );
6200        glyph_cmds.push(GlyphDrawCmd::retained(
6201            (run, uniform_slot, aligned),
6202            scissor,
6203            self.text_glyph_atlas.bind_group(viewport.transform),
6204        ));
6205        true
6206    }
6207
6208    fn ensure_retained_text_glyph_run(
6209        &mut self,
6210        cache_key: TextGlyphRunCacheKey,
6211        quads: GlyphRunQuads<'_>,
6212    ) -> bool {
6213        let atlas_generation = self.text_glyph_atlas.generation();
6214        if self
6215            .text_glyph_gpu_run_cache
6216            .peek(&cache_key)
6217            .is_some_and(|cached| cached.atlas_generation == atlas_generation)
6218        {
6219            return true;
6220        }
6221
6222        let origin = Rect {
6223            x: 0.0,
6224            y: 0.0,
6225            width: 0.0,
6226            height: 0.0,
6227        };
6228        let Some(span) = self.text_glyph_run_arena.insert(
6229            &self.device,
6230            quads
6231                .iter()
6232                .filter_map(|quad| cached_text_glyph_instance(origin, &quad)),
6233        ) else {
6234            return false;
6235        };
6236        self.text_glyph_gpu_run_cache.put(
6237            cache_key,
6238            Rc::new(CachedGpuTextGlyphRun {
6239                span,
6240                atlas_generation,
6241                last_frame: Cell::new(self.text_glyph_run_frame),
6242            }),
6243        );
6244        true
6245    }
6246    /// Appends the glyph atlas draws of `text_draw`, whose logical geometry
6247    /// `logical` the caller found drawing into a rect `viewport` shows.
6248    /// `false` when the text cannot draw from the atlas (animated motion, or
6249    /// a run the atlas cannot hold): nothing was appended, and the caller
6250    /// draws the text as a rasterized image instead.
6251    pub(crate) fn append_text_glyph_draws(
6252        &mut self,
6253        (text_draw, logical): (&TextDraw, (Rect, f32)),
6254        viewport: ViewportUniformParams,
6255        root_scale: f32,
6256        glyph_instances: &mut GlyphInstances,
6257        glyph_cmds: &mut Vec<GlyphDrawCmd>,
6258    ) -> bool {
6259        let (_, raster_rect, _, text_scale, static_text_motion) =
6260            text_raster_geometry_at(text_draw, logical, root_scale);
6261        if !static_text_motion {
6262            return false;
6263        }
6264        let run_key = Self::text_glyph_run_cache_key(text_draw, raster_rect, text_scale);
6265        let mut collected_run = std::mem::take(&mut self.scratch_text_glyph_run);
6266        let mut generated_entries = std::mem::take(&mut self.scratch_text_glyph_entries);
6267        let initial_instance_len = glyph_instances.lens();
6268        let initial_cmd_len = glyph_cmds.len();
6269        let drew = self
6270            .text_glyph_run(
6271                text_draw,
6272                raster_rect,
6273                text_scale,
6274                run_key,
6275                &mut collected_run,
6276            )
6277            .is_some_and(|run| {
6278                self.append_text_glyph_run(
6279                    TextGlyphRunDraw {
6280                        text_draw,
6281                        raster_rect,
6282                        run_key,
6283                        run,
6284                    },
6285                    (viewport, root_scale),
6286                    (&collected_run, &mut generated_entries),
6287                    glyph_instances,
6288                    glyph_cmds,
6289                )
6290            });
6291        self.scratch_text_glyph_run = collected_run;
6292        self.scratch_text_glyph_entries = generated_entries;
6293        if !drew {
6294            glyph_instances.truncate(initial_instance_len);
6295            glyph_cmds.truncate(initial_cmd_len);
6296        }
6297        drew
6298    }
6299
6300    /// The run of `text_draw` from the cache, or collected into
6301    /// `collected_run` and cached; `None` when the text cannot draw from the
6302    /// atlas.
6303    fn text_glyph_run(
6304        &mut self,
6305        text_draw: &TextDraw,
6306        raster_rect: Rect,
6307        text_scale: f32,
6308        run_key: TextGlyphRunCacheKey,
6309        collected_run: &mut Vec<SoftwareGlyphAtlasRunGlyph>,
6310    ) -> Option<TextGlyphRunLookup> {
6311        let atlas_generation = self.text_glyph_atlas.generation();
6312        let frame = self.text_glyph_run_frame;
6313        self.text_glyph_runs_drawn += 1;
6314        if let Some(cached) = self.text_glyph_run_cache.get(&run_key) {
6315            cached.last_frame.set(frame);
6316            return Some(TextGlyphRunLookup {
6317                glyphs: cached.glyphs.clone(),
6318                bounds: cached.bounds,
6319                cached: true,
6320                entries: (cached.atlas_generation == atlas_generation)
6321                    .then(|| cached.atlas_entries.as_ref().map(Rc::clone))
6322                    .flatten(),
6323            });
6324        }
6325        collected_run.clear();
6326        if collect_solid_text_atlas_run(
6327            text_draw.text.string().as_ref(),
6328            raster_rect,
6329            &text_draw.text_style,
6330            text_draw.color,
6331            text_draw.font_size,
6332            text_scale,
6333            &self.text_fonts,
6334            &mut self.text_glyph_mask_cache,
6335            collected_run,
6336        )
6337        .is_none()
6338        {
6339            log_text_atlas_fallback(text_draw);
6340            return None;
6341        }
6342        let Some(glyphs) = RunGlyphs::of(
6343            collected_run
6344                .iter()
6345                .map(SoftwareGlyphAtlasRunGlyph::placement),
6346            &mut self.run_glyph_scratch,
6347        ) else {
6348            log_text_atlas_fallback(text_draw);
6349            return None;
6350        };
6351        let bounds = GlyphRunBounds::of(glyphs.iter());
6352        self.frame_stats.record_text_glyph_run_collected();
6353        // A run this frame drew stays: the cache grows rather than evict it.
6354        let cache = &mut self.text_glyph_run_cache;
6355        if cache.len() == cache.cap().get()
6356            && cache
6357                .peek_lru()
6358                .is_some_and(|(_, run)| run.last_frame.get() == frame)
6359        {
6360            cache.set_cap(cache.cap().saturating_add(cache.len()));
6361        }
6362        self.text_glyph_run_cache.put(
6363            run_key,
6364            CachedTextGlyphRun {
6365                glyphs: glyphs.clone(),
6366                bounds,
6367                atlas_entries: None,
6368                atlas_generation: 0,
6369                last_frame: Cell::new(frame),
6370            },
6371        );
6372        Some(TextGlyphRunLookup {
6373            glyphs,
6374            bounds,
6375            cached: false,
6376            entries: None,
6377        })
6378    }
6379
6380    /// Appends the draws of a text's run: its retained quads when it has
6381    /// them, else its visible quads into the frame's shared glyphs. `false`
6382    /// when the atlas cannot hold its glyphs.
6383    fn append_text_glyph_run(
6384        &mut self,
6385        draw: TextGlyphRunDraw<'_>,
6386        (viewport, root_scale): (ViewportUniformParams, f32),
6387        (collected_run, generated_entries): (
6388            &[SoftwareGlyphAtlasRunGlyph],
6389            &mut Vec<GlyphAtlasEntry>,
6390        ),
6391        glyph_instances: &mut GlyphInstances,
6392        glyph_cmds: &mut Vec<GlyphDrawCmd>,
6393    ) -> bool {
6394        let TextGlyphRunDraw {
6395            text_draw,
6396            raster_rect,
6397            run_key,
6398            run,
6399        } = draw;
6400        let draw_rect = Rect {
6401            x: raster_rect.x / root_scale,
6402            y: raster_rect.y / root_scale,
6403            width: raster_rect.width / root_scale,
6404            height: raster_rect.height / root_scale,
6405        };
6406        let Some(scissor) = scissor_rect_for_layer(draw_rect, text_draw.clip, root_scale, viewport)
6407        else {
6408            return true;
6409        };
6410        let texel = self.text_glyph_atlas.texel();
6411
6412        // A retained run's quads are uploaded whole: under a turn only the
6413        // shared path cuts them to a clip.
6414        let clip_needs_cut = !viewport.transform.is_identity() && text_draw.clip.is_some();
6415        if let Some(entries) = run.entries.as_ref()
6416            && entries.len() >= RETAINED_TEXT_GLYPH_RUN_MIN_QUADS
6417            && !clip_needs_cut
6418            && self.emit_retained_text_glyph_run_if_ready(
6419                run_key,
6420                GlyphRunQuads {
6421                    glyphs: &run.glyphs,
6422                    entries,
6423                    texel,
6424                    bounds: run.bounds,
6425                },
6426                viewport,
6427                raster_rect,
6428                scissor,
6429                glyph_cmds,
6430            )
6431        {
6432            return true;
6433        }
6434
6435        let turned = !viewport.transform.is_identity();
6436        let instance_start = glyph_instances.len_of(turned);
6437        let (entries, entries_cached) = match run.entries {
6438            Some(entries) => (entries, true),
6439            None => {
6440                let Ok(entries) = self.prepare_text_glyph_entries(
6441                    run_key,
6442                    self.text_glyph_atlas.generation(),
6443                    run.cached.then_some(&run.glyphs),
6444                    collected_run,
6445                    generated_entries,
6446                ) else {
6447                    return false;
6448                };
6449                (entries, false)
6450            }
6451        };
6452        let quads = GlyphRunQuads {
6453            glyphs: &run.glyphs,
6454            entries: &entries,
6455            texel: self.text_glyph_atlas.texel(),
6456            bounds: run.bounds,
6457        };
6458        let cut = glyph_cut_edges(text_draw.clip, scissor, viewport, root_scale);
6459        let (kind, extent) = self.append_text_glyph_quad_run(
6460            raster_rect,
6461            quads,
6462            (text_draw.clip, cut),
6463            viewport,
6464            root_scale,
6465            glyph_instances,
6466            entries_cached,
6467        );
6468        let instance_end = glyph_instances.len_of(turned);
6469        if instance_end > instance_start {
6470            let (clip, bounds) = if turned {
6471                (None, scissor)
6472            } else {
6473                shared_glyph_clip(extent, scissor, viewport)
6474            };
6475            glyph_cmds.push(GlyphDrawCmd::shared(
6476                (instance_start..instance_end, kind),
6477                clip,
6478                bounds,
6479                self.text_glyph_atlas.bind_group(viewport.transform),
6480            ));
6481        }
6482        true
6483    }
6484
6485    #[expect(clippy::too_many_arguments)]
6486    fn append_image_bitmap_draw_cmd(
6487        &mut self,
6488        image: &ImageBitmap,
6489        rect: Rect,
6490        clip: Option<Rect>,
6491        sampling: ImageSampling,
6492        viewport: ViewportUniformParams,
6493        root_scale: f32,
6494        image_vertices: &mut Vec<Vertex>,
6495        image_indices: &mut Vec<u32>,
6496        image_cmds: &mut Vec<ImageDrawCmd>,
6497    ) -> Result<(), String> {
6498        if rect.width <= 0.0 || rect.height <= 0.0 {
6499            return Ok(());
6500        }
6501
6502        let (device_quad, scissor_rect) =
6503            if sampling == ImageSampling::Nearest && root_scale.is_finite() && root_scale > 0.0 {
6504                let left_px = (rect.x * root_scale).round();
6505                let top_px = (rect.y * root_scale).round();
6506                let width_px = (rect.width * root_scale).round().max(1.0);
6507                let height_px = (rect.height * root_scale).round().max(1.0);
6508                let snapped_rect = Rect {
6509                    x: left_px / root_scale,
6510                    y: top_px / root_scale,
6511                    width: width_px / root_scale,
6512                    height: height_px / root_scale,
6513                };
6514                let right_px = left_px + width_px;
6515                let bottom_px = top_px + height_px;
6516                (
6517                    [
6518                        [left_px, top_px],
6519                        [right_px, top_px],
6520                        [left_px, bottom_px],
6521                        [right_px, bottom_px],
6522                    ],
6523                    snapped_rect,
6524                )
6525            } else {
6526                (
6527                    rect_to_quad(rect).map(|[x, y]| [x * root_scale, y * root_scale]),
6528                    rect,
6529                )
6530            };
6531
6532        let Some(scissor) = scissor_rect_for_layer(scissor_rect, clip, root_scale, viewport) else {
6533            return Ok(());
6534        };
6535        let Some(uv_rect) = image_uv_rect(image, None) else {
6536            return Ok(());
6537        };
6538
6539        let index_start = push_image_quad(
6540            (image_vertices, image_indices),
6541            device_quad,
6542            [1.0, 1.0, 1.0, 1.0],
6543            &uv_rect,
6544        );
6545        image_cmds.push(ImageDrawCmd {
6546            index_start,
6547            scissor,
6548            image: self.image_binding(image, sampling)?,
6549            clip: None,
6550        });
6551        Ok(())
6552    }
6553
6554    /// Appends `text_draw` as a rasterized image if `viewport` shows it.
6555    pub(crate) fn append_text_image_draw_cmds(
6556        &mut self,
6557        text_draw: &TextDraw,
6558        viewport: ViewportUniformParams,
6559        root_scale: f32,
6560        image_vertices: &mut Vec<Vertex>,
6561        image_indices: &mut Vec<u32>,
6562        image_cmds: &mut Vec<ImageDrawCmd>,
6563    ) -> Result<(), String> {
6564        let Some((logical_rect, raster_rect, clip, text_scale, static_text_motion)) =
6565            self.text_raster_geometry(text_draw, root_scale)
6566        else {
6567            return Ok(());
6568        };
6569        if !text_draw_is_visible_in_viewport(logical_rect, clip, viewport, root_scale) {
6570            return Ok(());
6571        }
6572
6573        let raster_source = self.text_image_raster_source(
6574            text_draw,
6575            logical_rect,
6576            raster_rect,
6577            clip,
6578            root_scale,
6579            static_text_motion,
6580        );
6581        let source_draw = raster_source.draw.as_ref();
6582        let source_raster_rect = raster_source.raster_rect;
6583        let source_origin = if source_draw.text.string().span_styles().is_empty() {
6584            Point::new(source_raster_rect.x, source_raster_rect.y)
6585        } else {
6586            Point::new(raster_rect.x, raster_rect.y)
6587        };
6588
6589        let mut cache_key = Self::text_image_cache_key(
6590            source_draw,
6591            source_raster_rect,
6592            text_scale,
6593            static_text_motion,
6594        );
6595        if !source_draw.text.string().span_styles().is_empty() {
6596            let mut state = default_hash::new();
6597            cache_key.0.hash(&mut state);
6598            (source_origin.x - source_raster_rect.x)
6599                .to_bits()
6600                .hash(&mut state);
6601            (source_origin.y - source_raster_rect.y)
6602                .to_bits()
6603                .hash(&mut state);
6604            cache_key = TextImageCacheKey(state.finish());
6605        }
6606        let image = if let Some(cached) = self.text_image_cache.get(&cache_key) {
6607            self.frame_stats
6608                .record_text_image_cache_hit(cached.image.width(), cached.image.height());
6609            cached.image.clone()
6610        } else {
6611            let Some(image) = self.rasterize_text_draw_to_image(
6612                source_draw,
6613                source_raster_rect,
6614                source_origin,
6615                text_scale,
6616            ) else {
6617                return Ok(());
6618            };
6619            self.frame_stats
6620                .record_text_image_cache_miss(image.width(), image.height());
6621            self.text_image_cache.put(
6622                cache_key,
6623                CachedTextImage {
6624                    image: image.clone(),
6625                },
6626            );
6627            image
6628        };
6629
6630        let draw_origin = if static_text_motion {
6631            Point::new(
6632                source_raster_rect.x / root_scale,
6633                source_raster_rect.y / root_scale,
6634            )
6635        } else {
6636            Point::new(logical_rect.x, logical_rect.y)
6637        };
6638        let draw_rect = Rect {
6639            x: draw_origin.x,
6640            y: draw_origin.y,
6641            width: image.width() as f32 / root_scale,
6642            height: image.height() as f32 / root_scale,
6643        };
6644        self.append_image_bitmap_draw_cmd(
6645            &image,
6646            draw_rect,
6647            clip,
6648            sampling_under(ImageSampling::Nearest, viewport.transform),
6649            viewport,
6650            root_scale,
6651            image_vertices,
6652            image_indices,
6653            image_cmds,
6654        )
6655    }
6656
6657    fn text_image_raster_source<'a>(
6658        &mut self,
6659        text_draw: &'a TextDraw,
6660        logical_rect: Rect,
6661        raster_rect: Rect,
6662        clip: Option<Rect>,
6663        root_scale: f32,
6664        static_text_motion: bool,
6665    ) -> TextRasterSource<'a> {
6666        let Some(clip) = clip else {
6667            return TextRasterSource {
6668                draw: Cow::Borrowed(text_draw),
6669                raster_rect,
6670            };
6671        };
6672        if !static_text_motion || text_draw.text.string().text().find('\n').is_none() {
6673            return TextRasterSource {
6674                draw: Cow::Borrowed(text_draw),
6675                raster_rect,
6676            };
6677        }
6678
6679        if !text_draw.text.string().span_styles().is_empty() {
6680            let device_clip = Rect {
6681                x: (clip.x * root_scale).floor(),
6682                y: (clip.y * root_scale).floor(),
6683                width: ((clip.x + clip.width) * root_scale).ceil() - (clip.x * root_scale).floor(),
6684                height: ((clip.y + clip.height) * root_scale).ceil()
6685                    - (clip.y * root_scale).floor(),
6686            };
6687            return TextRasterSource {
6688                draw: Cow::Borrowed(text_draw),
6689                raster_rect: raster_rect.intersect(device_clip).unwrap_or(raster_rect),
6690            };
6691        }
6692
6693        let line_starts = self
6694            .text_line_index_cache
6695            .line_starts(text_draw.text.string());
6696        clipped_text_raster_source_with_line_starts(
6697            text_draw,
6698            logical_rect,
6699            raster_rect,
6700            clip,
6701            root_scale,
6702            line_starts.as_ref(),
6703        )
6704    }
6705
6706    fn text_raster_geometry(
6707        &self,
6708        text_draw: &TextDraw,
6709        root_scale: f32,
6710    ) -> Option<(Rect, Rect, Option<Rect>, f32, bool)> {
6711        text_raster_geometry_for_draw(text_draw, root_scale)
6712    }
6713
6714    fn text_image_cache_key(
6715        text_draw: &TextDraw,
6716        raster_rect: Rect,
6717        text_scale: f32,
6718        static_text_motion: bool,
6719    ) -> TextImageCacheKey {
6720        let mut state = default_hash::new();
6721        hash_text_draw_for_cache(text_draw, raster_rect, text_scale, &mut state);
6722        hash_text_raster_geometry_for_cache(raster_rect, static_text_motion, &mut state);
6723        TextImageCacheKey(state.finish())
6724    }
6725
6726    /// The key of a static text's glyph run. A run places its glyphs from
6727    /// the text's origin and aligns its lines within the text block, so the
6728    /// size of the rect it draws in does not change it: only an empty rect,
6729    /// which draws nothing, does.
6730    fn text_glyph_run_cache_key(
6731        text_draw: &TextDraw,
6732        raster_rect: Rect,
6733        text_scale: f32,
6734    ) -> TextGlyphRunCacheKey {
6735        let mut state = default_hash::new();
6736        hash_text_draw_for_cache(text_draw, raster_rect, text_scale, &mut state);
6737        (raster_rect.width <= 0.0 || raster_rect.height <= 0.0).hash(&mut state);
6738        TextGlyphRunCacheKey(state.finish())
6739    }
6740
6741    fn rasterize_text_draw_to_image(
6742        &mut self,
6743        text_draw: &TextDraw,
6744        raster_rect: Rect,
6745        source_origin: Point,
6746        text_scale: f32,
6747    ) -> Option<ImageBitmap> {
6748        rasterize_annotated_text_region(
6749            text_draw.text.string().as_ref(),
6750            raster_rect,
6751            if text_draw.text.string().span_styles().is_empty() {
6752                Point::new(raster_rect.x, raster_rect.y)
6753            } else {
6754                source_origin
6755            },
6756            &text_draw.text_style,
6757            text_draw.color,
6758            text_draw.font_size,
6759            text_scale,
6760            &self.text_fonts,
6761            Some(&mut self.text_glyph_mask_cache),
6762        )
6763    }
6764}
6765
6766struct TextRasterSource<'a> {
6767    draw: Cow<'a, TextDraw>,
6768    raster_rect: Rect,
6769}
6770
6771fn clipped_text_raster_source_with_line_starts<'a>(
6772    text_draw: &'a TextDraw,
6773    logical_rect: Rect,
6774    raster_rect: Rect,
6775    clip: Rect,
6776    root_scale: f32,
6777    line_starts: &[usize],
6778) -> TextRasterSource<'a> {
6779    if line_starts.len() < MIN_MULTILINE_TEXT_LINES_FOR_CLIPPED_RASTER {
6780        return TextRasterSource {
6781            draw: Cow::Borrowed(text_draw),
6782            raster_rect,
6783        };
6784    }
6785
6786    let Some(visible_rect) = logical_rect.intersect(clip) else {
6787        return TextRasterSource {
6788            draw: Cow::Borrowed(text_draw),
6789            raster_rect,
6790        };
6791    };
6792
6793    let line_count = line_starts.len().max(1);
6794    let line_height = logical_rect.height / line_count as f32;
6795    if !line_height.is_finite() || line_height <= 0.0 {
6796        return TextRasterSource {
6797            draw: Cow::Borrowed(text_draw),
6798            raster_rect,
6799        };
6800    }
6801
6802    let visible_top = ((visible_rect.y - logical_rect.y) / line_height).floor() as isize;
6803    let visible_bottom =
6804        ((visible_rect.y + visible_rect.height - logical_rect.y) / line_height).ceil() as isize;
6805    let start_line = visible_top.saturating_sub(1).max(0) as usize;
6806    let end_line = (visible_bottom + 1).max(start_line as isize + 1) as usize;
6807    let end_line = end_line.min(line_count);
6808    if start_line == 0 && end_line >= line_count {
6809        return TextRasterSource {
6810            draw: Cow::Borrowed(text_draw),
6811            raster_rect,
6812        };
6813    }
6814
6815    let byte_start = line_starts[start_line];
6816    let byte_end = line_end_offset(text_draw.text.string().text(), line_starts, end_line - 1);
6817    if byte_start >= byte_end {
6818        return TextRasterSource {
6819            draw: Cow::Borrowed(text_draw),
6820            raster_rect,
6821        };
6822    }
6823
6824    let slice_y = logical_rect.y + start_line as f32 * line_height;
6825    let slice_height = (end_line - start_line) as f32 * line_height;
6826    let mut slice_raster_rect = Rect {
6827        x: logical_rect.x * root_scale,
6828        y: slice_y * root_scale,
6829        width: logical_rect.width * root_scale,
6830        height: slice_height * root_scale,
6831    };
6832    slice_raster_rect.x = slice_raster_rect.x.round();
6833    slice_raster_rect.y = slice_raster_rect.y.round();
6834    slice_raster_rect.width = slice_raster_rect.width.ceil().max(1.0);
6835    slice_raster_rect.height = slice_raster_rect.height.ceil().max(1.0);
6836
6837    let mut sliced_draw = text_draw.clone();
6838    sliced_draw.rect = Rect {
6839        x: logical_rect.x,
6840        y: slice_y,
6841        width: logical_rect.width,
6842        height: slice_height,
6843    };
6844    sliced_draw.text = DrawnText::of(
6845        Arc::new(text_draw.text.string().subsequence(byte_start..byte_end)),
6846        &text_draw.text_style,
6847    );
6848
6849    TextRasterSource {
6850        draw: Cow::Owned(sliced_draw),
6851        raster_rect: slice_raster_rect,
6852    }
6853}
6854
6855fn line_start_offsets(text: &str) -> Vec<usize> {
6856    let mut starts =
6857        Vec::with_capacity(text.as_bytes().iter().filter(|b| **b == b'\n').count() + 1);
6858    starts.push(0);
6859    starts.extend(
6860        text.char_indices()
6861            .filter_map(|(index, ch)| (ch == '\n').then_some(index + ch.len_utf8())),
6862    );
6863    starts
6864}
6865
6866fn line_end_offset(text: &str, line_starts: &[usize], line: usize) -> usize {
6867    line_starts.get(line + 1).copied().unwrap_or(text.len())
6868}
6869
6870fn align_to(value: u32, alignment: u32) -> u32 {
6871    debug_assert!(alignment > 0);
6872    value.div_ceil(alignment) * alignment
6873}
6874
6875impl GpuRenderer {
6876    fn convert_surface_pixels_to_rgba(&self, pixels: &[u8]) -> Result<Vec<u8>, String> {
6877        if !pixels.len().is_multiple_of(4) {
6878            return Err("Screenshot readback has an incomplete pixel".to_string());
6879        }
6880        Ok(pixels.to_vec())
6881    }
6882}
6883
6884/// The scissor of a logical rect in a target whose origin sits at
6885/// `viewport.offset` of the scene's device space, clamped to the target.
6886/// `None` when nothing of the rect lands in the target.
6887pub(crate) fn scissor_rect_for_rect(
6888    rect: Rect,
6889    root_scale: f32,
6890    viewport: ViewportUniformParams,
6891) -> Option<(u32, u32, u32, u32)> {
6892    let width = viewport.width as f32;
6893    let height = viewport.height as f32;
6894    let mut left = canonicalize_device_coordinate(rect.x * root_scale);
6895    let mut top = canonicalize_device_coordinate(rect.y * root_scale);
6896    let mut right = canonicalize_device_coordinate((rect.x + rect.width) * root_scale);
6897    let mut bottom = canonicalize_device_coordinate((rect.y + rect.height) * root_scale);
6898    if !viewport.transform.is_identity() {
6899        let bounds = viewport.transform.target_bounds(Rect {
6900            x: left,
6901            y: top,
6902            width: right - left,
6903            height: bottom - top,
6904        });
6905        (left, top) = (bounds.x, bounds.y);
6906        (right, bottom) = (bounds.x + bounds.width, bounds.y + bounds.height);
6907    }
6908    let left = (left - viewport.offset[0]).clamp(0.0, width).floor();
6909    let top = (top - viewport.offset[1]).clamp(0.0, height).floor();
6910    let right = (right - viewport.offset[0]).clamp(0.0, width).ceil();
6911    let bottom = (bottom - viewport.offset[1]).clamp(0.0, height).ceil();
6912    if right <= left || bottom <= top {
6913        return None;
6914    }
6915    Some((
6916        left as u32,
6917        top as u32,
6918        (right - left) as u32,
6919        (bottom - top) as u32,
6920    ))
6921}
6922
6923fn scissor_rect_for_layer(
6924    rect: Rect,
6925    clip: Option<Rect>,
6926    root_scale: f32,
6927    viewport: ViewportUniformParams,
6928) -> Option<(u32, u32, u32, u32)> {
6929    let clipped_rect = match clip {
6930        Some(clip_rect) => rect.intersect(clip_rect)?,
6931        None => rect,
6932    };
6933    scissor_rect_for_rect(clipped_rect, root_scale, viewport)
6934}
6935fn tint_for_image(
6936    color_filter: Option<ColorFilter>,
6937    alpha: f32,
6938) -> ([f32; 4], Option<ColorFilter>) {
6939    let alpha = alpha.clamp(0.0, 1.0);
6940    match color_filter {
6941        Some(filter) if filter.supports_gpu_vertex_modulation() => {
6942            let Some(tint) = filter.gpu_vertex_tint() else {
6943                return ([1.0, 1.0, 1.0, alpha], Some(filter));
6944            };
6945            (
6946                [
6947                    tint[0].clamp(0.0, 1.0),
6948                    tint[1].clamp(0.0, 1.0),
6949                    tint[2].clamp(0.0, 1.0),
6950                    (tint[3] * alpha).clamp(0.0, 1.0),
6951                ],
6952                None,
6953            )
6954        }
6955        Some(filter) => ([1.0, 1.0, 1.0, alpha], Some(filter)),
6956        None => ([1.0, 1.0, 1.0, alpha], None),
6957    }
6958}
6959
6960/// The rounded clip an image takes in place, in device pixels: one instance
6961/// its draw reads, beside the quad's vertices.
6962#[repr(C)]
6963#[derive(Clone, Copy, Debug, Pod, Zeroable)]
6964pub(crate) struct DeviceRoundedClip {
6965    rect: [f32; 4],
6966    radius: f32,
6967}
6968
6969impl DeviceRoundedClip {
6970    const ATTRIBS: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![
6971        4 => Float32x4,
6972        5 => Float32,
6973    ];
6974
6975    fn desc() -> wgpu::VertexBufferLayout<'static> {
6976        wgpu::VertexBufferLayout {
6977            array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
6978            step_mode: wgpu::VertexStepMode::Instance,
6979            attributes: &Self::ATTRIBS,
6980        }
6981    }
6982
6983    /// The rounded clip `image` takes at `root_scale`, scaled as a run's
6984    /// placement scales its clip.
6985    fn of(image: &ImageDraw, root_scale: f32) -> Option<Self> {
6986        let rounding = image.rounding.filter(|_| image.clip.is_some())?;
6987        let device =
6988            crate::geometry::device_rect(rounding.rect, root_scale, image.snap_anchor.is_some());
6989        Some(Self {
6990            rect: [device.x, device.y, device.width, device.height],
6991            radius: rounding.radius * root_scale,
6992        })
6993    }
6994}
6995
6996/// Appends an image quad over `device_quad` and its two triangles, and
6997/// returns the index its triangles start at.
6998fn push_image_quad(
6999    (vertices, indices): (&mut Vec<Vertex>, &mut Vec<u32>),
7000    device_quad: [[f32; 2]; 4],
7001    color: [f32; 4],
7002    uv_rect: &ImageUvRect,
7003) -> u32 {
7004    let base_vertex = vertices.len() as u32;
7005    let index_start = indices.len() as u32;
7006    indices.extend_from_slice(&[
7007        base_vertex,
7008        base_vertex + 1,
7009        base_vertex + 2,
7010        base_vertex + 2,
7011        base_vertex + 1,
7012        base_vertex + 3,
7013    ]);
7014    let corners = [
7015        [uv_rect.min[0], uv_rect.min[1]],
7016        [uv_rect.max[0], uv_rect.min[1]],
7017        [uv_rect.min[0], uv_rect.max[1]],
7018        [uv_rect.max[0], uv_rect.max[1]],
7019    ];
7020    vertices.extend(
7021        device_quad
7022            .into_iter()
7023            .zip(corners)
7024            .map(|(position, uv)| Vertex {
7025                position,
7026                color,
7027                uv,
7028                uv_bounds: uv_rect.sample_bounds,
7029            }),
7030    );
7031    index_start
7032}
7033
7034fn image_uv_rect(image: &ImageBitmap, src_rect: Option<Rect>) -> Option<ImageUvRect> {
7035    let Some(src) = src_rect else {
7036        return Some(ImageUvRect {
7037            min: [0.0, 0.0],
7038            max: [1.0, 1.0],
7039            sample_bounds: [0.0, 0.0, 1.0, 1.0],
7040        });
7041    };
7042
7043    let (u_min, u_max, u_bound_min, u_bound_max) =
7044        source_axis_uv(src.x, src.width, image.width() as f32)?;
7045    let (v_min, v_max, v_bound_min, v_bound_max) =
7046        source_axis_uv(src.y, src.height, image.height() as f32)?;
7047
7048    Some(ImageUvRect {
7049        min: [u_min, v_min],
7050        max: [u_max, v_max],
7051        sample_bounds: [u_bound_min, v_bound_min, u_bound_max, v_bound_max],
7052    })
7053}
7054
7055/// `entry`'s rect in an atlas whose texel is `texel` of its side, an
7056/// exact power of two: the products are the quotients by the side.
7057fn glyph_atlas_uv_rect(entry: GlyphAtlasEntry, texel: f32) -> ImageUvRect {
7058    let min = [entry.x as f32 * texel, entry.y as f32 * texel];
7059    let max = [
7060        (entry.x + entry.width) as f32 * texel,
7061        (entry.y + entry.height) as f32 * texel,
7062    ];
7063    let center_min = [
7064        (entry.x as f32 + 0.5) * texel,
7065        (entry.y as f32 + 0.5) * texel,
7066    ];
7067    let center_max = [
7068        (entry.x as f32 + entry.width as f32 - 0.5).max(entry.x as f32 + 0.5) * texel,
7069        (entry.y as f32 + entry.height as f32 - 0.5).max(entry.y as f32 + 0.5) * texel,
7070    ];
7071    ImageUvRect {
7072        min,
7073        max,
7074        sample_bounds: [center_min[0], center_min[1], center_max[0], center_max[1]],
7075    }
7076}
7077
7078#[derive(Clone, Copy)]
7079pub(crate) struct ResolvedImageGeometry {
7080    pub(crate) rect: Rect,
7081    pub(crate) clip: Option<Rect>,
7082    pub(crate) sampling: ImageSampling,
7083    quad: [[f32; 2]; 4],
7084    snap_anchored: bool,
7085}
7086
7087pub(crate) fn resolve_image_geometry(image: &ImageDraw, root_scale: f32) -> ResolvedImageGeometry {
7088    let snap_delta = image
7089        .snap_anchor
7090        .map(|anchor| snap_delta_for_anchor(anchor, root_scale))
7091        .unwrap_or_default();
7092    resolve_image_geometry_with_delta(image, root_scale, snap_delta)
7093}
7094
7095fn resolve_image_geometry_with_delta(
7096    image: &ImageDraw,
7097    root_scale: f32,
7098    snap_delta: Point,
7099) -> ResolvedImageGeometry {
7100    let mut rect = image.rect.translate(snap_delta.x, snap_delta.y);
7101    let mut quad = translate_quad(image.quad, snap_delta);
7102    snap_nearest_image_to_device_pixels(image.sampling, &mut rect, &mut quad, root_scale);
7103    ResolvedImageGeometry {
7104        rect,
7105        clip: image.clip,
7106        sampling: image.sampling,
7107        quad,
7108        snap_anchored: image.snap_anchor.is_some(),
7109    }
7110}
7111
7112impl ResolvedImageGeometry {
7113    pub(crate) fn device_quad(self, root_scale: f32) -> [[f32; 2]; 4] {
7114        nearest_image_device_quad(self.sampling, self.quad, root_scale).unwrap_or_else(|| {
7115            if self.snap_anchored {
7116                canonicalized_scaled_quad(self.quad, root_scale)
7117            } else {
7118                scaled_quad(self.quad, root_scale)
7119            }
7120        })
7121    }
7122}
7123
7124fn snap_nearest_image_to_device_pixels(
7125    sampling: ImageSampling,
7126    rect: &mut Rect,
7127    quad: &mut [[f32; 2]; 4],
7128    root_scale: f32,
7129) {
7130    if sampling != ImageSampling::Nearest || !root_scale.is_finite() || root_scale <= 0.0 {
7131        return;
7132    }
7133
7134    let Some(quad_rect) = axis_aligned_quad_rect(*quad) else {
7135        return;
7136    };
7137
7138    let left_px = (quad_rect.x * root_scale).round();
7139    let top_px = (quad_rect.y * root_scale).round();
7140    let width_px = (quad_rect.width * root_scale).round().max(1.0);
7141    let height_px = (quad_rect.height * root_scale).round().max(1.0);
7142    let snapped = Rect {
7143        x: left_px / root_scale,
7144        y: top_px / root_scale,
7145        width: width_px / root_scale,
7146        height: height_px / root_scale,
7147    };
7148
7149    *rect = snapped;
7150    *quad = crate::rect_to_quad(snapped);
7151}
7152
7153fn nearest_image_device_quad(
7154    sampling: ImageSampling,
7155    quad: [[f32; 2]; 4],
7156    root_scale: f32,
7157) -> Option<[[f32; 2]; 4]> {
7158    if sampling != ImageSampling::Nearest || !root_scale.is_finite() || root_scale <= 0.0 {
7159        return None;
7160    }
7161
7162    let rect = axis_aligned_quad_rect(quad)?;
7163    let left_px = (rect.x * root_scale).round();
7164    let top_px = (rect.y * root_scale).round();
7165    let width_px = (rect.width * root_scale).round().max(1.0);
7166    let height_px = (rect.height * root_scale).round().max(1.0);
7167    let right_px = left_px + width_px;
7168    let bottom_px = top_px + height_px;
7169    Some([
7170        [left_px, top_px],
7171        [right_px, top_px],
7172        [left_px, bottom_px],
7173        [right_px, bottom_px],
7174    ])
7175}
7176
7177fn source_axis_uv(start: f32, extent: f32, image_extent: f32) -> Option<(f32, f32, f32, f32)> {
7178    if !start.is_finite()
7179        || !extent.is_finite()
7180        || !image_extent.is_finite()
7181        || extent == 0.0
7182        || image_extent <= 0.0
7183    {
7184        return None;
7185    }
7186
7187    let end = start + extent;
7188    let edge_min = start.min(end).clamp(0.0, image_extent);
7189    let edge_max = start.max(end).clamp(0.0, image_extent);
7190    if edge_max <= edge_min {
7191        return None;
7192    }
7193
7194    let center_min = edge_min + 0.5;
7195    let center_max = edge_max - 0.5;
7196    let (bound_min, bound_max) = if center_min <= center_max {
7197        (center_min, center_max)
7198    } else {
7199        let center = (edge_min + edge_max) * 0.5;
7200        (center, center)
7201    };
7202
7203    Some((
7204        edge_min / image_extent,
7205        edge_max / image_extent,
7206        bound_min / image_extent,
7207        bound_max / image_extent,
7208    ))
7209}
7210
7211fn apply_filter_to_bitmap(image: &ImageBitmap, filter: ColorFilter) -> Result<ImageBitmap, String> {
7212    let pixel_count = image.width() as usize * image.height() as usize;
7213    let mut filtered = Vec::with_capacity(pixel_count * 4);
7214    for index in 0..pixel_count {
7215        let pixel = image.rgba8_pixel(index);
7216        let rgba = [
7217            pixel[0] as f32 / 255.0,
7218            pixel[1] as f32 / 255.0,
7219            pixel[2] as f32 / 255.0,
7220            pixel[3] as f32 / 255.0,
7221        ];
7222        let out = filter.apply_rgba(rgba);
7223        filtered.push((out[0].clamp(0.0, 1.0) * 255.0).round() as u8);
7224        filtered.push((out[1].clamp(0.0, 1.0) * 255.0).round() as u8);
7225        filtered.push((out[2].clamp(0.0, 1.0) * 255.0).round() as u8);
7226        filtered.push((out[3].clamp(0.0, 1.0) * 255.0).round() as u8);
7227    }
7228    ImageBitmap::from_rgba8(image.width(), image.height(), filtered)
7229        .map_err(|error| format!("failed to build filtered bitmap: {error}"))
7230}
7231
7232/// How a text raster samples its texels under a segment transform: as it
7233/// asks on the pixel grid, and filtered once a transform turns it off the
7234/// grid, where no texel lands on a pixel for nearest sampling to keep.
7235fn sampling_under(sampling: ImageSampling, transform: SegmentTransform) -> ImageSampling {
7236    if transform.is_identity() {
7237        sampling
7238    } else {
7239        ImageSampling::Linear
7240    }
7241}
7242
7243/// The rounded mask a shadow's composite applies, in the scene's device
7244/// pixels: an inner shadow masks itself to its fill shape, and a shadow
7245/// lowered out of a clipped layer masks itself to that layer's rounded clip.
7246pub(crate) fn shadow_composite_mask(
7247    shadow: &ShadowDraw,
7248    snap_anchor: Option<SnapAnchor>,
7249    root_scale: f32,
7250) -> Option<RoundedCompositeMask> {
7251    inner_shadow_composite_mask(shadow, root_scale).or_else(|| {
7252        shadow.rounded_clip.map(|clip| RoundedCompositeMask {
7253            rect: mask_rect(anchored_device_rect(clip.rect, snap_anchor, root_scale)),
7254            radii: clip.radii.map(|radius| radius * root_scale),
7255        })
7256    })
7257}
7258
7259/// A scene holding just a shadow's own draws, in the order they arrive, so
7260/// the shadow source renders through the same pass encoder as everything
7261/// else.
7262fn shadow_scene(
7263    shapes: Option<&RunDraw>,
7264    texts: &[TextDraw],
7265    input_margin: f32,
7266) -> CompositorScene {
7267    let mut scene = CompositorScene::new();
7268    if let Some(run) = shapes {
7269        scene.push_run(run.clone());
7270    }
7271    for text in texts {
7272        let z_index = scene.next_z();
7273        scene.draw_ops.push(DrawOp {
7274            z_index,
7275            kind: DrawOpKind::Text(scene.texts.len()),
7276        });
7277        let mut source_text = text.clone();
7278        source_text.clip = source_text
7279            .clip
7280            .map(|clip| expand_rect(clip, input_margin, input_margin));
7281        scene.texts.push(source_text);
7282        scene.next_z += 1;
7283    }
7284    scene
7285}
7286
7287/// The whole device pixels a logical rect covers, or `None` when it covers
7288/// none or more than a texture can hold.
7289fn device_pixel_bounds(
7290    rect: Rect,
7291    root_scale: f32,
7292    max_texture_dim: u32,
7293) -> Option<DevicePixelBounds> {
7294    let x = (rect.x * root_scale).floor();
7295    let y = (rect.y * root_scale).floor();
7296    let right = ((rect.x + rect.width) * root_scale).ceil();
7297    let bottom = ((rect.y + rect.height) * root_scale).ceil();
7298    let width = (right - x).max(0.0) as u32;
7299    let height = (bottom - y).max(0.0) as u32;
7300    if width == 0 || height == 0 || width > max_texture_dim || height > max_texture_dim {
7301        return None;
7302    }
7303    Some(DevicePixelBounds {
7304        x,
7305        y,
7306        width,
7307        height,
7308    })
7309}
7310fn inner_shadow_composite_mask(
7311    shadow: &ShadowDraw,
7312    root_scale: f32,
7313) -> Option<RoundedCompositeMask> {
7314    let run = shadow.shapes.as_ref()?;
7315    if !run
7316        .tables()
7317        .shapes
7318        .iter()
7319        .any(|record| record.blend_mode() == BlendMode::DstOut)
7320    {
7321        return None;
7322    }
7323    let fill = run.tables().shapes.get(0)?;
7324    let rect = run.placement.translated_bounds(fill.stored_rect());
7325    if rect.width <= 0.0 || rect.height <= 0.0 {
7326        return None;
7327    }
7328    let resolved =
7329        cranpose_ui_graphics::RoundedCornerShape::with_radii(cranpose_ui_graphics::CornerRadii {
7330            top_left: fill.radii[0],
7331            top_right: fill.radii[1],
7332            bottom_right: fill.radii[2],
7333            bottom_left: fill.radii[3],
7334        })
7335        .resolve(rect.width, rect.height);
7336    let radii = [
7337        resolved.top_left * root_scale,
7338        resolved.top_right * root_scale,
7339        resolved.bottom_left * root_scale,
7340        resolved.bottom_right * root_scale,
7341    ];
7342
7343    Some(RoundedCompositeMask {
7344        rect: mask_rect(anchored_device_rect(
7345            rect,
7346            run.placement.snap_anchor,
7347            root_scale,
7348        )),
7349        radii,
7350    })
7351}
7352
7353/// One draw call of a run batch's stage: its pipeline, the strip index
7354/// buffer it binds and the indices and records it instances.
7355#[derive(Clone, Debug, PartialEq, Eq)]
7356pub(crate) struct PlannedRunDraw {
7357    pub(crate) key: ShapePipelineKey,
7358    pub(crate) band_class: u8,
7359    pub(crate) indices: std::ops::Range<u32>,
7360    pub(crate) records: std::ops::Range<u32>,
7361}
7362
7363impl PlannedRunDraw {
7364    /// A draw of the paint, as it was recorded.
7365    fn paint(draw: &RunDrawCall) -> Self {
7366        Self {
7367            key: draw.key,
7368            band_class: draw.band_class,
7369            indices: draw.indices(),
7370            records: draw.records.clone(),
7371        }
7372    }
7373}
7374
7375/// The draw calls laying a batch's opaque interiors down: last first, so
7376/// they go down front to back, one call over each stretch of neighbouring
7377/// draws that share the interior pipeline, cut to the span from the stretch's
7378/// first draw holding an occluder to its last. The draws between ride along:
7379/// the pipeline lays any record's exact interior down and skips the rest, so
7380/// taking them costs a few vertices rather than a call, and it shades only a
7381/// record's quad, the first two triangles of every band class's pattern.
7382pub(crate) fn interior_run_draws(draws: &[RunDrawCall]) -> SmallVec<[PlannedRunDraw; 4]> {
7383    struct Stretch {
7384        key: ShapePipelineKey,
7385        start: u32,
7386        span: Option<PlannedRunDraw>,
7387    }
7388    let mut planned: SmallVec<[PlannedRunDraw; 4]> = SmallVec::new();
7389    let mut stretch: Option<Stretch> = None;
7390    for draw in draws.iter().rev() {
7391        let Some(key) = draw.key.interior() else {
7392            planned.extend(stretch.take().and_then(|open| open.span));
7393            continue;
7394        };
7395        if stretch
7396            .as_ref()
7397            .is_some_and(|open| open.key != key || open.start != draw.records.end)
7398        {
7399            planned.extend(stretch.take().and_then(|open| open.span));
7400        }
7401        let open = stretch.get_or_insert(Stretch {
7402            key,
7403            start: draw.records.end,
7404            span: None,
7405        });
7406        open.start = draw.records.start;
7407        if !draw.occluders {
7408            continue;
7409        }
7410        match &mut open.span {
7411            Some(span) => span.records.start = draw.records.start,
7412            None => {
7413                open.span = Some(PlannedRunDraw {
7414                    key,
7415                    band_class: draw.band_class,
7416                    indices: 0..cranpose_ui_graphics::strip_indices(1),
7417                    records: draw.records.clone(),
7418                });
7419            }
7420        }
7421    }
7422    planned.extend(stretch.and_then(|open| open.span));
7423    planned
7424}
7425
7426/// Drops the entries of `cache` no frame drew for more than `idle_frames`
7427/// before `frame`: the least recently used first, which is the order they
7428/// were last drawn in.
7429fn evict_idle<K: Clone + Eq + std::hash::Hash, V>(
7430    cache: &mut BoundedLruCache<K, V>,
7431    frame: u64,
7432    idle_frames: u64,
7433    last_frame: impl Fn(&V) -> u64,
7434) {
7435    while cache
7436        .peek_lru()
7437        .is_some_and(|(_, value)| frame.saturating_sub(last_frame(value)) > idle_frames)
7438    {
7439        cache.pop_lru();
7440    }
7441}
7442
7443/// Cuts the run draws from `start` on to `window` of their records, counted
7444/// across them in order, and drops the draws left with none.
7445fn window_draws(draws: &mut Vec<RunDrawCall>, start: usize, window: &std::ops::Range<u32>) {
7446    let mut relative = 0u32;
7447    let mut kept = start;
7448    for index in start..draws.len() {
7449        let draw = &mut draws[index];
7450        let count = draw.records.end - draw.records.start;
7451        let first = relative;
7452        relative += count;
7453        let keep_start = window.start.max(first).min(first + count);
7454        let keep_end = window.end.min(first + count).max(keep_start);
7455        draw.records =
7456            draw.records.start + (keep_start - first)..draw.records.start + (keep_end - first);
7457        if draw.records.start < draw.records.end {
7458            draws.swap(kept, index);
7459            kept += 1;
7460        }
7461    }
7462    draws.truncate(kept);
7463}
7464
7465#[cfg(test)]
7466#[path = "tests/render_text_bounds_tests.rs"]
7467mod text_bounds_tests;
7468
7469#[cfg(test)]
7470#[path = "tests/render_retained_glyph_tests.rs"]
7471mod retained_glyph_tests;
7472
7473#[cfg(test)]
7474#[path = "tests/frame_clear_tests.rs"]
7475mod frame_clear_tests;
7476
7477#[cfg(test)]
7478#[path = "tests/render_clip_scissor_tests.rs"]
7479mod clip_scissor_tests;
7480
7481#[cfg(test)]
7482#[path = "tests/glyph_kind_tests.rs"]
7483mod glyph_kind_tests;
7484
7485#[cfg(test)]
7486#[path = "tests/glyph_run_bounds_tests.rs"]
7487mod glyph_run_bounds_tests;
7488
7489#[cfg(test)]
7490#[path = "tests/shape_variant_tests.rs"]
7491mod shape_variant_tests;