Skip to main content

cranpose_ui_graphics/
record.rs

1use std::{
2    cell::OnceCell,
3    hash::{Hash, Hasher},
4    ops::Range,
5    sync::{Arc, OnceLock},
6};
7
8use bytemuck::{Pod, Zeroable};
9
10use crate::{
11    ArcGeometry, BlendMode, Brush, Color, CornerRadii, DrawPrimitive, FxHasher, LineGeometry,
12    Point, Rect, RenderHash, ShapeRecordBody, ShapeRecordCurve, ShapeRecords, Stroke, StrokeCap,
13    StrokeJoin, TAU, TileMode, Trapezoid, arc_band,
14    float::{at_least, within},
15    vertex_gradient,
16};
17
18/// The kind bits of [`ShapeRecord::flags`]: a plain rect.
19pub const RECORD_KIND_RECT: u32 = 0;
20/// The kind bits of [`ShapeRecord::flags`]: a rounded rect.
21pub const RECORD_KIND_ROUND_RECT: u32 = 1;
22/// The kind bits of [`ShapeRecord::flags`]: an arc band or annular sector.
23pub const RECORD_KIND_ARC: u32 = 2;
24/// The kind bits of [`ShapeRecord::flags`]: a stroked straight segment, or
25/// a slice of a path fill, whose cap bits are [`TRAPEZOID_CAP`].
26pub const RECORD_KIND_LINE: u32 = 3;
27/// The cap bits of a line record that is a slice of a path fill: no stroke
28/// cap takes them. Its join bits hold which of its sides are open.
29pub const TRAPEZOID_CAP: u32 = 3;
30
31const KIND_SHIFT: u32 = 0;
32const STROKED_BIT: u32 = 1 << 2;
33const CAP_SHIFT: u32 = 3;
34const JOIN_SHIFT: u32 = 5;
35const BLEND_SHIFT: u32 = 8;
36const BAND_CAP_SHIFT: u32 = 16;
37const ARC_DEGENERATE_BIT: u32 = 1 << 18;
38const ARC_RECT_LOOSE_BIT: u32 = 1 << 19;
39const ARC_BANDED_BIT: u32 = 1 << 20;
40const BAND_CLASS_SHIFT: u32 = 21;
41const BAND_CLASS_MASK: u32 = 0b111;
42const VERTEX_GRADIENT_BIT: u32 = 1 << 24;
43const OPEN_LEFT_BIT: u32 = 1 << JOIN_SHIFT;
44const OPEN_RIGHT_BIT: u32 = 2 << JOIN_SHIFT;
45const RECT_FILL_MASK: u32 = STROKED_BIT | (RECORD_KIND_ARC << KIND_SHIFT);
46const NO_SEGMENT_KEY: u32 = u32::MAX;
47
48/// How many segment-count buckets band-drawn arcs fall into: one per
49/// power-of-two segment count of [`ARC_BUCKET_SEGMENTS`].
50pub const ARC_BUCKETS: usize = 7;
51/// The strip segments a bucket's arcs are drawn with.
52pub const ARC_BUCKET_SEGMENTS: [u32; ARC_BUCKETS] = [1, 2, 4, 8, 16, 32, 64];
53/// The segments a full ring takes with an outer radius up to each of
54/// [`ARC_RING_RADII`], and above the last: the angular step a band at that
55/// radius keeps whatever its sweep, so its chords overshoot the circle by
56/// the same few pixels and a short arc takes only the segments its sweep
57/// needs.
58pub const ARC_RING_SEGMENTS: [u32; 4] = [8, 16, 32, 64];
59/// The outer radius, in the command's units, above which a full ring moves
60/// to the next of [`ARC_RING_SEGMENTS`].
61pub const ARC_RING_RADII: [f32; 3] = [24.0, 96.0, 384.0];
62const ARC_RING_SEGMENTS_PER_RADIAN: [f32; ARC_RING_SEGMENTS.len()] = ring_segments_per_radian();
63
64const fn ring_segments_per_radian() -> [f32; ARC_RING_SEGMENTS.len()] {
65    let mut out = [0.0; ARC_RING_SEGMENTS.len()];
66    let mut index = 0;
67    while index < ARC_RING_SEGMENTS.len() {
68        out[index] = ARC_RING_SEGMENTS[index] as f32 / TAU;
69        index += 1;
70    }
71    out
72}
73/// Bands narrower than this radius draw as their quad: the pixels a band
74/// would save cost less than its vertices.
75pub const ARC_BAND_MIN_RADIUS: f32 = 11.0;
76/// A band whose inner radius is within the strip's margin of the centre is
77/// a disc: its strip would be the whole disc's quad and more.
78pub const ARC_BAND_MIN_INNER_RADIUS: f32 = 1.0;
79/// The device pixels a band's quad or strip, and a line's quad, extend past
80/// the shape on each side: the half pixel the fragment stage anti-aliases
81/// plus a sixteenth pixel for rasterization rounding. `band_position` in
82/// `shape.wgsl` pads by the same amount.
83pub const BAND_MARGIN: f32 = 0.5 + 1.0 / 16.0;
84/// The widest angle one segment of a band's strip spans. Past a quarter
85/// turn a strip's outer polygon is larger than the square around the band's
86/// padded disc, so a band whose padded sweep needs wider segments than its
87/// class has, which only a render scale below the recording's can make,
88/// draws that square instead.
89pub const BAND_MAX_STEP: f32 = std::f32::consts::FRAC_PI_2;
90/// The radians a band's strip extends past each end of its sweep beyond
91/// the angle the ring's padded half-width subtends at its padded inner
92/// radius, which covers every cap and the margin; float slack only.
93/// `band_position` pads by the same amount.
94pub const BAND_ANGULAR_PAD: f32 = 0.001;
95/// The vertices of one quad: its four corners, which its two triangles
96/// share through the strip index pattern.
97pub const QUAD_VERTICES: u32 = 4;
98/// The indices one quad's two triangles take.
99pub const QUAD_INDICES: u32 = 6;
100/// The fewest strip segments a band draws with. A short band's strip and a
101/// rectangular shape both use four vertices in the same segment class.
102pub const BAND_MIN_SEGMENTS: u32 = 1;
103
104/// The vertices a strip of `segments` quads shares: an inner and an
105/// outer vertex at each of its `segments + 1` boundaries.
106pub const fn strip_vertices(segments: u32) -> u32 {
107    segments * 2 + 2
108}
109
110/// The indices a strip of `segments` quads draws with.
111pub const fn strip_indices(segments: u32) -> u32 {
112    segments * QUAD_INDICES
113}
114
115/// The index pattern of one record's strip of `segments` quads over its
116/// [`strip_vertices`]: vertex `2b` sits at boundary `b` on the inner
117/// radius and `2b + 1` on the outer, and quad `j` is the triangles
118/// `(2j, 2j + 1, 2j + 2)` and `(2j + 2, 2j + 1, 2j + 3)`. A quad-drawn
119/// record is the pattern's first quad over the rect's corners: vertex
120/// `2x + y` is corner `(x, y)`.
121pub fn strip_index_pattern(segments: u32) -> impl Iterator<Item = u32> {
122    (0..segments).flat_map(|quad| {
123        let base = quad * 2;
124        [base, base + 1, base + 2, base + 2, base + 1, base + 3]
125    })
126}
127/// What one strip vertex costs against fill, in pixels of the command's
128/// units. A tiling GPU writes every vertex's varyings, sixteen vectors
129/// here, to memory before it shades a pixel, and reads the record the
130/// vertex came from; a pixel reads and writes a few bytes and runs the
131/// distance field once. A band of many segments over few pixels is dearer
132/// than the quad it replaces.
133pub const BAND_VERTEX_PIXELS: f32 = 32.0;
134/// The strip's outer vertices ride out past the padded ring so its chords
135/// circumscribe the circle; the estimate allows for that and the margin.
136const BAND_STRIP_OVERSHOOT: f32 = 1.25;
137/// The quads a segment may leave pinned before it is cut: a segment
138/// draws every record at its largest band class, so a quad among rings
139/// or a ring among quads costs pinned vertices, while a cut costs a
140/// draw call, which on a low-end GPU is worth about this many quads.
141const SEGMENT_WASTE_QUADS: u32 = 512;
142
143/// The ring a band's strip is built around, in the command's units: what
144/// `band_position` derives from a record, derived here from the geometry the
145/// record is made of or from the record itself, with the padded sweep
146/// the strip covers computed once.
147struct BandRing {
148    mid: f32,
149    ring_half: f32,
150    range: f32,
151    segments_per_radian: f32,
152}
153
154impl BandRing {
155    fn of_geometry(geometry: &ArcGeometry) -> Self {
156        Self::new(
157            geometry.inner_radius,
158            geometry.outer_radius,
159            geometry.start_angle,
160            geometry.sweep_angle,
161        )
162    }
163
164    fn new(inner: f32, outer: f32, start: f32, sweep: f32) -> Self {
165        let mid = (outer + inner) * 0.5;
166        let ring_half = at_least((outer - inner) * 0.5, 0.0) + BAND_MARGIN;
167        let (_, range) = band_padded_range(mid, ring_half, start, sweep);
168        Self {
169            mid,
170            ring_half,
171            range,
172            segments_per_radian: Self::segments_per_radian(outer),
173        }
174    }
175
176    /// The segments per radian a full ring takes at `outer_radius`: the
177    /// angular step every band at that radius keeps.
178    #[inline]
179    fn segments_per_radian(outer_radius: f32) -> f32 {
180        let bucket = usize::from(outer_radius > ARC_RING_RADII[0])
181            + usize::from(outer_radius > ARC_RING_RADII[1])
182            + usize::from(outer_radius > ARC_RING_RADII[2]);
183        ARC_RING_SEGMENTS_PER_RADIAN[bucket]
184    }
185
186    /// The fewest of [`ARC_BUCKET_SEGMENTS`] whose step over the padded
187    /// sweep stays within the ring step at this radius.
188    #[inline]
189    fn segments(&self) -> u32 {
190        let exact = self.range * self.segments_per_radian;
191        if exact <= BAND_MIN_SEGMENTS as f32 {
192            return BAND_MIN_SEGMENTS;
193        }
194        let floor = exact as u32;
195        let needed = if (floor as f32) < exact {
196            floor + 1
197        } else {
198            floor
199        };
200        needed
201            .max(BAND_MIN_SEGMENTS)
202            .next_power_of_two()
203            .min(ARC_BUCKET_SEGMENTS[ARC_BUCKETS - 1])
204    }
205
206    /// The strip's cost in pixels: the padded ring's area over the padded
207    /// sweep, plus what its `segments` quads' vertices cost.
208    fn strip_pixels(&self, segments: u32) -> f32 {
209        self.range * self.mid * (self.ring_half + self.ring_half) * BAND_STRIP_OVERSHOOT
210            + vertex_pixels(strip_vertices(segments))
211    }
212}
213
214fn vertex_pixels(vertices: u32) -> f32 {
215    vertices as f32 * BAND_VERTEX_PIXELS
216}
217
218/// Where a band's strip starts and the padded sweep it covers, for a band of
219/// centre-line radius `mid` and padded half-width `ring_half` over `start`
220/// and `sweep`, in one unit: the angular pad is bounded above (atan(x) is at
221/// most x), so no transcendental per band, and a sweep the pad closes is the
222/// full circle. The vertex stage takes it in device pixels, where the
223/// [`BAND_MARGIN`] the half-width includes is measured.
224pub fn band_padded_range(mid: f32, ring_half: f32, start: f32, sweep: f32) -> (f32, f32) {
225    let inner_padded = mid - ring_half;
226    if inner_padded <= 0.0 {
227        return (0.0, TAU);
228    }
229    let pad = ring_half / inner_padded + BAND_ANGULAR_PAD;
230    let padded = sweep + pad + pad;
231    if padded < TAU {
232        (start - pad, padded)
233    } else {
234        (0.0, TAU)
235    }
236}
237
238/// The bucket of [`ARC_BUCKET_SEGMENTS`] a strip of `segments` draws from.
239pub fn band_bucket(segments: u32) -> usize {
240    segments.trailing_zeros() as usize
241}
242
243/// The strip segments every record of band class `class` is drawn at.
244pub fn band_class_segments(class: u8) -> u32 {
245    ARC_BUCKET_SEGMENTS[class as usize]
246}
247
248/// The bucket an arc's band draws from when its strip costs less than
249/// `rect`, the quad it would otherwise draw: the pixels each rasterizes
250/// plus what their vertices cost a tiling GPU. `None` when the quad is
251/// cheaper.
252#[inline]
253fn band_bucket_for(
254    geometry: &ArcGeometry,
255    degenerate: bool,
256    ring: &BandRing,
257    rect: Rect,
258) -> Option<usize> {
259    if degenerate
260        || geometry.outer_radius < ARC_BAND_MIN_RADIUS
261        || geometry.inner_radius <= ARC_BAND_MIN_INNER_RADIUS
262    {
263        return None;
264    }
265    let segments = ring.segments();
266    (ring.strip_pixels(segments) < rect.width * rect.height + vertex_pixels(QUAD_VERTICES))
267        .then(|| band_bucket(segments))
268}
269
270/// Whether an arc's band strip costs less than `rect`, the quad it would
271/// otherwise draw; see [`ShapeRecord::is_banded`].
272pub fn band_pays(geometry: &ArcGeometry, rect: Rect) -> bool {
273    band_bucket_for(
274        geometry,
275        geometry.is_degenerate(),
276        &BandRing::of_geometry(geometry),
277        rect,
278    )
279    .is_some()
280}
281
282/// The fragment program's shape kinds: a filled rect or round rect, a
283/// stroked one, an arc band, a line segment and a slice of a path fill.
284pub const FRAGMENT_KIND_FILL: u32 = 0;
285pub const FRAGMENT_KIND_STROKE: u32 = 1;
286pub const FRAGMENT_KIND_ARC: u32 = 2;
287pub const FRAGMENT_KIND_LINE: u32 = 3;
288pub const FRAGMENT_KIND_TRAPEZOID: u32 = 4;
289
290const TWO_BITS: u32 = 0b11;
291const BLEND_MASK: u32 = 0xff;
292
293/// The brush kinds of [`BrushRecord::kind`].
294pub const BRUSH_KIND_LINEAR: u32 = 1;
295/// See [`BRUSH_KIND_LINEAR`].
296pub const BRUSH_KIND_RADIAL: u32 = 2;
297/// See [`BRUSH_KIND_LINEAR`].
298pub const BRUSH_KIND_SWEEP: u32 = 3;
299
300/// One complete recorded shape in the draw command's local space.
301/// Every value the app passed is kept verbatim, so the record
302/// materialises back into the exact [`DrawPrimitive`] the call described,
303/// and the derived arc values the fragment stage needs sit beside them.
304#[repr(C)]
305#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
306pub struct ShapeRecord {
307    /// The primitive's rect: the app's rect for rects; for arcs the band's
308    /// tight bounds, or, when [`Self::has_loose_rect`], the disc around the
309    /// band that the tight bounds are derived from on demand.
310    pub rect: [f32; 4],
311    /// Rects: the corner radii, top-left, top-right, bottom-right,
312    /// bottom-left. Arcs: zero; the GPU derives the band's trig from
313    /// [`Self::arc_normalized`], see [`arc_trig`].
314    pub radii: [f32; 4],
315    /// The solid colour, or the first stop of a gradient brush.
316    pub color: [f32; 4],
317    /// The stroke width when [`Self::is_stroked`].
318    pub stroke_width: f32,
319    /// Kind, stroke, cap, join, blend mode and arc facts, packed; read them
320    /// through the accessors.
321    pub flags: u32,
322    /// `0` for a solid brush, otherwise one plus the index into the
323    /// recording's brush table.
324    pub brush: u32,
325    /// Keeps the record at a whole number of 16-byte rows.
326    pub reserved: u32,
327    /// Arcs: centre x, centre y, radius and inner radius as the app drew them.
328    pub arc: [f32; 4],
329    /// Arcs: start angle and sweep as the app drew them, then the normalised
330    /// band's inner and outer radius.
331    pub arc_band: [f32; 4],
332    /// Arcs: the normalised start angle and sweep, then where the band's
333    /// strip starts and the padded sweep it covers.
334    pub arc_normalized: [f32; 4],
335}
336
337impl ShapeRecord {
338    pub fn kind(&self) -> u32 {
339        (self.flags >> KIND_SHIFT) & TWO_BITS
340    }
341
342    pub fn is_stroked(&self) -> bool {
343        self.flags & STROKED_BIT != 0
344    }
345
346    /// Which coverage program the fragment stage runs for this record.
347    pub fn fragment_kind(&self) -> u32 {
348        fragment_kind(self.flags)
349    }
350
351    pub fn stroke(&self) -> Option<Stroke> {
352        self.is_stroked().then(|| Stroke {
353            width: self.stroke_width,
354            cap: STROKE_CAPS[((self.flags >> CAP_SHIFT) & TWO_BITS) as usize],
355            join: STROKE_JOINS[((self.flags >> JOIN_SHIFT) & TWO_BITS) as usize],
356        })
357    }
358
359    pub fn blend_mode(&self) -> BlendMode {
360        BlendMode::ALL[((self.flags >> BLEND_SHIFT) & BLEND_MASK) as usize]
361    }
362
363    /// The cap of the normalised arc band.
364    pub fn band_cap(&self) -> StrokeCap {
365        STROKE_CAPS[((self.flags >> BAND_CAP_SHIFT) & TWO_BITS) as usize]
366    }
367
368    /// An arc whose band draws nothing: zero sweep, zero width or a
369    /// non-finite input. The GPU path collapses it; the primitive still
370    /// materialises as the app drew it.
371    pub fn is_degenerate_arc(&self) -> bool {
372        self.flags & ARC_DEGENERATE_BIT != 0
373    }
374
375    pub fn is_gradient(&self) -> bool {
376        self.brush != 0
377    }
378
379    /// A rect or rounded-rect fill whose brush is a linear gradient of two
380    /// stops, clamped or mirrored, with every corner of the rect within the
381    /// stops' span and within `0..=1`: its colour is affine over the whole
382    /// quad. The renderer may interpolate corner colours for an aligned,
383    /// unturned placement while preserving gradient dithering.
384    /// It is still [`Self::is_gradient`].
385    pub fn is_vertex_gradient(&self) -> bool {
386        self.flags & VERTEX_GRADIENT_BIT != 0
387    }
388
389    /// An arc wide enough to draw as a band strip from the vertex stage;
390    /// its band class sits in its flags.
391    pub fn is_banded(&self) -> bool {
392        self.flags & ARC_BANDED_BIT != 0
393    }
394
395    /// The band class this record was filed in: the index into
396    /// [`ARC_BUCKET_SEGMENTS`] of the strip segments it draws with when
397    /// [`Self::is_banded`], zero otherwise.
398    pub fn band_class(&self) -> usize {
399        ((self.flags >> BAND_CLASS_SHIFT) & BAND_CLASS_MASK) as usize
400    }
401
402    /// The strip segments this record draws with when [`Self::is_banded`].
403    pub fn band_segments(&self) -> u32 {
404        ARC_BUCKET_SEGMENTS[self.band_class()]
405    }
406
407    /// An arc recorded by the draw scope: its rect is the disc around the
408    /// band, and the tight cap-aware bounds the primitive carries are
409    /// derived when asked for, never on the recording path.
410    pub fn has_loose_rect(&self) -> bool {
411        self.flags & ARC_RECT_LOOSE_BIT != 0
412    }
413
414    /// The rect as stored: loose for a scope-recorded arc.
415    pub fn stored_rect(&self) -> Rect {
416        row_rect(self.rect)
417    }
418
419    /// The rect the materialised primitive carries: the tight band bounds
420    /// for a scope-recorded arc, the stored rect otherwise.
421    pub fn rect_value(&self) -> Rect {
422        match self.arc_geometry() {
423            Some(geometry) if self.has_loose_rect() => geometry.bounds(),
424            _ => self.stored_rect(),
425        }
426    }
427
428    /// The rect this record's pixels can reach: its rect grown by half the
429    /// stroke width, or a line's own bounds, which its end caps can reach
430    /// past on a slant.
431    pub fn coverage_rect(&self) -> Rect {
432        match self.line_geometry() {
433            Some(line) => line.bounds(),
434            None => expand_rect(self.rect_value(), self.half_stroke()),
435        }
436    }
437
438    fn half_stroke(&self) -> f32 {
439        if self.is_stroked() {
440            self.stroke_width * 0.5
441        } else {
442            0.0
443        }
444    }
445
446    /// The segment the fragment stage draws; `None` for any other kind.
447    pub fn line_geometry(&self) -> Option<LineGeometry> {
448        (self.kind() == RECORD_KIND_LINE && !is_trapezoid(self.flags)).then(|| LineGeometry {
449            start: Point::new(self.arc[0], self.arc[1]),
450            end: Point::new(self.arc_band[2], self.arc_band[3]),
451            half_width: self.stroke_width * 0.5,
452            cap: STROKE_CAPS[((self.flags >> CAP_SHIFT) & TWO_BITS) as usize],
453        })
454    }
455
456    /// The slice of a path fill the fragment stage draws; `None` for any
457    /// other kind. Its corners ride in the arc column, its sides in the
458    /// curve's, and the rect its brush resolves against in the radii.
459    pub fn trapezoid(&self) -> Option<Trapezoid> {
460        is_trapezoid(self.flags).then(|| Trapezoid {
461            left: self.arc_normalized[0],
462            right: self.arc_normalized[1],
463            top: [self.arc[0], self.arc[1]],
464            bottom: [self.arc_band[2], self.arc_band[3]],
465            open_left: self.flags & OPEN_LEFT_BIT != 0,
466            open_right: self.flags & OPEN_RIGHT_BIT != 0,
467        })
468    }
469
470    /// The normalised band the fragment stage draws; `None` for rects.
471    pub fn arc_geometry(&self) -> Option<ArcGeometry> {
472        (self.kind() == RECORD_KIND_ARC).then(|| ArcGeometry {
473            center: Point::new(self.arc[0], self.arc[1]),
474            inner_radius: self.arc_band[2],
475            outer_radius: self.arc_band[3],
476            start_angle: self.arc_normalized[0],
477            sweep_angle: self.arc_normalized[1],
478            cap: self.band_cap(),
479        })
480    }
481}
482
483/// The area of the larger of the two bands a fill's corners leave whole,
484/// as the shape shader's `fill_bands` takes it: the band between the left
485/// and right corners at full height or the one between the top and bottom
486/// corners at full width. `radii` is top-left, top-right, bottom-right,
487/// bottom-left, as recorded.
488fn band_interior_area(width: f32, height: f32, radii: [f32; 4]) -> f32 {
489    let [top_left, top_right, bottom_right, bottom_left] =
490        radii.map(|radius| at_least(radius, 0.0));
491    let across = inset_area(
492        width,
493        height,
494        top_left.max(bottom_left) + top_right.max(bottom_right),
495        0.0,
496    );
497    let down = inset_area(
498        width,
499        height,
500        0.0,
501        top_left.max(top_right) + bottom_left.max(bottom_right),
502    );
503    across.max(down)
504}
505
506/// The area of a fill's interior, where its coverage is 1, as the shape
507/// shader's `fill_interior` takes it: the larger of its bands and the rect
508/// inset past every corner's arc, whose corner a corner of radius `r`
509/// reaches `r (1 - 1/√2)` in from each side.
510fn fill_interior_area(width: f32, height: f32, radii: [f32; 4]) -> f32 {
511    let [top_left, top_right, bottom_right, bottom_left] =
512        radii.map(|radius| at_least(radius, 0.0) * (1.0 - std::f32::consts::FRAC_1_SQRT_2));
513    band_interior_area(width, height, radii).max(inset_area(
514        width,
515        height,
516        top_left.max(bottom_left) + top_right.max(bottom_right),
517        top_left.max(top_right) + bottom_left.max(bottom_right),
518    ))
519}
520
521/// The area of a `width` × `height` rect less `less_width` and `less_height`.
522fn inset_area(width: f32, height: f32, less_width: f32, less_height: f32) -> f32 {
523    at_least(width - less_width, 0.0) * at_least(height - less_height, 0.0)
524}
525
526/// Whether a record is a rounded fill whose bands cover at least half of
527/// its rect: a gradient batch tests them per fragment (see
528/// [`band_interior_area`]), and a solid one tests no interior.
529fn interior_repays(body: &ShapeRecordBody, curve: &ShapeRecordCurve) -> bool {
530    if fragment_kind(body.flags) != FRAGMENT_KIND_FILL {
531        return false;
532    }
533    let [top_left, top_right, bottom_right, bottom_left] = curve.radii;
534    let rounded = top_left.max(top_right).max(bottom_right.max(bottom_left)) > 0.0;
535    let [_, _, width, height] = body.rect;
536    let area = width * height;
537    rounded && area > 0.0 && band_interior_area(width, height, curve.radii) * 2.0 >= area
538}
539
540/// The least interior, in square logical pixels, that a solid fill lays
541/// down ahead of the paint: a card or a background, not a chip, a glyph-
542/// sized dot or a circle, whose interior is empty.
543pub const OCCLUDER_MIN_AREA: f32 = 1024.0;
544
545/// Whether `body` is a solid, opaque fill whose interior, where its
546/// coverage is 1, spans at least [`OCCLUDER_MIN_AREA`].
547pub(crate) fn interior_occludes(body: &ShapeRecordBody, curve: &ShapeRecordCurve) -> bool {
548    if fragment_kind(body.flags) != FRAGMENT_KIND_FILL || body.brush != 0 || body.color[3] < 1.0 {
549        return false;
550    }
551    let [_, _, width, height] = body.rect;
552    fill_interior_area(width, height, curve.radii) >= OCCLUDER_MIN_AREA
553}
554
555fn fragment_kind(flags: u32) -> u32 {
556    let kind = (flags >> KIND_SHIFT) & TWO_BITS;
557    if kind == RECORD_KIND_ARC {
558        FRAGMENT_KIND_ARC
559    } else if is_trapezoid(flags) {
560        FRAGMENT_KIND_TRAPEZOID
561    } else if kind == RECORD_KIND_LINE {
562        FRAGMENT_KIND_LINE
563    } else if flags & STROKED_BIT != 0 {
564        FRAGMENT_KIND_STROKE
565    } else {
566        FRAGMENT_KIND_FILL
567    }
568}
569
570fn is_trapezoid(flags: u32) -> bool {
571    (flags >> KIND_SHIFT) & TWO_BITS == RECORD_KIND_LINE
572        && (flags >> CAP_SHIFT) & TWO_BITS == TRAPEZOID_CAP
573}
574
575const STROKE_CAPS: [StrokeCap; 3] = [StrokeCap::Butt, StrokeCap::Round, StrokeCap::Square];
576const STROKE_JOINS: [StrokeJoin; 3] = [StrokeJoin::Miter, StrokeJoin::Round, StrokeJoin::Bevel];
577const TILE_MODES: [TileMode; 4] = [
578    TileMode::Clamp,
579    TileMode::Repeated,
580    TileMode::Mirror,
581    TileMode::Decal,
582];
583
584#[inline]
585fn pack_flags(kind: u32, stroke: Option<Stroke>, blend: BlendMode, band_cap: StrokeCap) -> u32 {
586    let mut flags = (kind << KIND_SHIFT) | ((blend as u32) << BLEND_SHIFT);
587    if let Some(stroke) = stroke {
588        flags |=
589            STROKED_BIT | ((stroke.cap as u32) << CAP_SHIFT) | ((stroke.join as u32) << JOIN_SHIFT);
590    }
591    flags | ((band_cap as u32) << BAND_CAP_SHIFT)
592}
593
594/// A gradient brush of a recording, addressed by [`ShapeRecord::brush`].
595#[repr(C)]
596#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
597pub struct BrushRecord {
598    /// One of the `BRUSH_KIND_*` constants.
599    pub kind: u32,
600    /// The [`TileMode`] as its declaration index.
601    pub tile_mode: u32,
602    /// The brush's stops in [`CommandRecording::stops`].
603    pub stop_start: u32,
604    pub stop_count: u32,
605    /// Linear: start x, start y, end x, end y. Radial: centre x, centre y,
606    /// radius, 0. Sweep: centre x, centre y, 0, 0.
607    pub params: [f32; 4],
608    /// The app's explicit stop positions in
609    /// [`RecordTables::explicit_stops`], when it gave any; `explicit_len`
610    /// is `u32::MAX` when it gave none.
611    pub explicit_start: u32,
612    pub explicit_len: u32,
613    pub reserved: [u32; 2],
614}
615
616const NO_EXPLICIT_STOPS: u32 = u32::MAX;
617
618#[derive(Clone, Copy, PartialEq, Eq)]
619enum BrushShading {
620    Solid,
621    Fragment,
622    Vertex,
623}
624
625impl BrushShading {
626    fn flag(self) -> u32 {
627        if self == Self::Vertex {
628            VERTEX_GRADIENT_BIT
629        } else {
630            0
631        }
632    }
633}
634
635/// One gradient stop in the layout the shader reads.
636#[repr(C)]
637#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
638pub struct GradientStopRecord {
639    pub color: [f32; 4],
640    /// The position in `x`; the rest keeps the 16-byte row.
641    pub position: [f32; 4],
642}
643
644/// Which lane a segment's entries live in.
645#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
646pub enum RecordLane {
647    /// Entries in [`CommandRecording::shapes`].
648    Shapes,
649    /// Entries in [`CommandRecording::others`]: images, text, shadows and
650    /// nested blends.
651    Others,
652    /// One `draw_content` marker; carries no entry.
653    Content,
654}
655
656/// A run of consecutive entries of one lane that a single draw can take:
657/// shapes sharing a blend mode and a brush class, or a run of other
658/// primitives.
659#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
660pub struct RecordSegment {
661    pub lane: RecordLane,
662    pub start: u32,
663    pub count: u32,
664    pub blend: BlendMode,
665    pub gradient: bool,
666    /// Whether the segment's records are gradients their vertices shade
667    /// (see [`ShapeRecord::is_vertex_gradient`]), which are not `gradient`:
668    /// the draw that takes them dithers their fragments.
669    pub vertex_gradient: bool,
670    /// One bit per `BRUSH_KIND_*` present in the segment; bit 0 stands for
671    /// the records drawn without a brush.
672    pub brushes: u8,
673    /// One bit per `FRAGMENT_KIND_*` present in the segment.
674    pub kinds: u8,
675    /// The band class every record of the segment is drawn at: the index
676    /// into [`ARC_BUCKET_SEGMENTS`] of the strip each record is instanced
677    /// over, so one draw covers the segment in record order.
678    pub band_class: u8,
679    /// Whether a rounded fill in the segment has an interior, where coverage
680    /// is 1 without its distance field, covering at least half of its rect:
681    /// the only records that repay shading their interior apart.
682    pub interiors: bool,
683    /// Whether a solid fill in the segment has an interior of at least
684    /// [`OCCLUDER_MIN_AREA`]: one a pass lays down ahead of its paint, so
685    /// what it hides is never shaded.
686    pub occluders: bool,
687    /// Whether a fill in the segment has an interior that repays shading
688    /// apart (see `interiors`) that no pass lays down ahead of its paint: a
689    /// gradient's or a translucent colour's.
690    pub bare_interiors: bool,
691}
692
693impl RecordSegment {
694    pub fn range(&self) -> Range<usize> {
695        self.start as usize..(self.start + self.count) as usize
696    }
697
698    /// The one kind every record of the segment has, if they agree.
699    pub fn uniform_kind(&self) -> Option<u32> {
700        (self.kinds.count_ones() == 1).then(|| self.kinds.trailing_zeros())
701    }
702
703    /// The one brush kind every record of the segment paints with, if they
704    /// agree; zero when none of them has a brush.
705    pub fn uniform_brush(&self) -> Option<u32> {
706        (self.brushes.count_ones() == 1).then(|| self.brushes.trailing_zeros())
707    }
708}
709
710/// The POD half of a recording: what the GPU reads, shared with the
711/// renderer behind an `Arc` so a packet carries it without a copy.
712#[derive(Clone, Debug, Default)]
713pub struct RecordTables {
714    pub shapes: ShapeRecords,
715    pub brushes: Vec<BrushRecord>,
716    pub stops: Vec<GradientStopRecord>,
717    pub explicit_stops: Vec<f32>,
718    pub segments: Vec<RecordSegment>,
719    fingerprint: OnceLock<u64>,
720}
721
722impl PartialEq for RecordTables {
723    fn eq(&self, other: &Self) -> bool {
724        self.shapes == other.shapes
725            && self.brushes == other.brushes
726            && self.stops == other.stops
727            && self.explicit_stops == other.explicit_stops
728            && self.segments == other.segments
729    }
730}
731
732impl RecordTables {
733    fn clear(&mut self) {
734        self.shapes.clear();
735        self.brushes.clear();
736        self.stops.clear();
737        self.explicit_stops.clear();
738        self.segments.clear();
739        self.fingerprint.take();
740    }
741
742    /// Gives back the room growth left in every table; see
743    /// [`crate::shape_records::trim_growth`]. The content, and so the
744    /// fingerprint, stay.
745    fn trim_growth(&mut self) {
746        use crate::shape_records::trim_growth;
747        self.shapes.trim_growth();
748        trim_growth(&mut self.brushes);
749        trim_growth(&mut self.stops);
750        trim_growth(&mut self.explicit_stops);
751        trim_growth(&mut self.segments);
752    }
753
754    /// Empty tables with this one's capacities, so a recording that starts
755    /// while a scene still holds the last one grows nothing.
756    fn with_capacity_of(&self) -> Self {
757        Self {
758            shapes: ShapeRecords::with_capacity(self.shapes.capacity()),
759            brushes: Vec::with_capacity(self.brushes.capacity()),
760            stops: Vec::with_capacity(self.stops.capacity()),
761            explicit_stops: Vec::with_capacity(self.explicit_stops.capacity()),
762            segments: Vec::with_capacity(self.segments.capacity()),
763            fingerprint: OnceLock::new(),
764        }
765    }
766
767    /// A hash of everything the GPU reads and of the segments, computed
768    /// the first time a cache asks and kept while the tables stand.
769    pub fn fingerprint(&self) -> u64 {
770        *self.fingerprint.get_or_init(|| {
771            let mut hasher = FxHasher::default();
772            hasher.write(bytemuck::cast_slice(self.shapes.bodies()));
773            hasher.write(bytemuck::cast_slice(self.shapes.curves()));
774            hasher.write(self.shapes.source_bytes());
775            hasher.write(bytemuck::cast_slice(&self.brushes));
776            hasher.write(bytemuck::cast_slice(&self.stops));
777            hasher.write(bytemuck::cast_slice(&self.explicit_stops));
778            self.segments.hash(&mut hasher);
779            hasher.finish()
780        })
781    }
782
783    /// The heap the tables hold, capacity included.
784    pub fn heap_bytes(&self) -> usize {
785        self.shapes.heap_bytes()
786            + self.brushes.capacity() * std::mem::size_of::<BrushRecord>()
787            + self.stops.capacity() * std::mem::size_of::<GradientStopRecord>()
788            + self.explicit_stops.capacity() * std::mem::size_of::<f32>()
789            + self.segments.capacity() * std::mem::size_of::<RecordSegment>()
790    }
791}
792
793/// What a recording contains, answered once while recording so no consumer
794/// rescans it.
795#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
796pub struct RecordingSummary {
797    /// Any text, including inside a blend: glyph masks want rigid snapping.
798    pub has_text: bool,
799    /// Any shadow, including inside a blend.
800    pub has_shadow: bool,
801    /// Any drawable primitive besides a shadow, including inside a blend.
802    pub has_non_shadow: bool,
803    /// Any image or text, including inside a blend: content that resamples
804    /// badly on a fractionally offset surface.
805    pub has_pixel_sensitive: bool,
806    /// Any entry in the shape tables.
807    pub has_shapes: bool,
808    /// Any primitive outside the shape tables.
809    pub has_others: bool,
810    /// Any marker where a with-content command draws its node's content.
811    pub has_content_markers: bool,
812}
813
814impl RecordingSummary {
815    fn note(&mut self, primitive: &DrawPrimitive) {
816        let primitive = unwrap_blend(primitive);
817        if matches!(primitive, DrawPrimitive::Shadow(_)) {
818            self.has_shadow = true;
819            return;
820        }
821        if matches!(primitive, DrawPrimitive::Content) {
822            return;
823        }
824        self.has_non_shadow = true;
825        match primitive {
826            DrawPrimitive::Text(_) => {
827                self.has_text = true;
828                self.has_pixel_sensitive = true;
829            }
830            DrawPrimitive::Image { .. } => self.has_pixel_sensitive = true,
831            _ => {}
832        }
833    }
834
835    fn merge(&mut self, other: Self) {
836        self.has_text |= other.has_text;
837        self.has_shadow |= other.has_shadow;
838        self.has_non_shadow |= other.has_non_shadow;
839        self.has_pixel_sensitive |= other.has_pixel_sensitive;
840        self.has_shapes |= other.has_shapes;
841        self.has_others |= other.has_others;
842        self.has_content_markers |= other.has_content_markers;
843    }
844
845    /// Whether the entries are shapes alone, with no other primitive or
846    /// content marker between them, so one run draws them all.
847    pub fn shapes_only(&self) -> bool {
848        self.has_shapes && !self.has_others && !self.has_content_markers
849    }
850}
851
852fn unwrap_blend(mut primitive: &DrawPrimitive) -> &DrawPrimitive {
853    while let DrawPrimitive::Blend {
854        primitive: inner, ..
855    } = primitive
856    {
857        primitive = inner;
858    }
859    primitive
860}
861
862/// `rect` grown by `margin` on every side.
863pub fn expand_rect(rect: Rect, margin: f32) -> Rect {
864    Rect {
865        x: rect.x - margin,
866        y: rect.y - margin,
867        width: rect.width + margin * 2.0,
868        height: rect.height + margin * 2.0,
869    }
870}
871
872/// The rect a primitive's pixels can reach, or `None` when it has none of
873/// its own (a content marker or a shadow).
874pub fn primitive_coverage_rect(primitive: &DrawPrimitive) -> Option<Rect> {
875    match primitive {
876        DrawPrimitive::Blend { primitive, .. } => primitive_coverage_rect(primitive),
877        DrawPrimitive::Rect { rect, stroke, .. }
878        | DrawPrimitive::RoundRect { rect, stroke, .. }
879        | DrawPrimitive::Arc { rect, stroke, .. } => {
880            let half_stroke = stroke.as_ref().map_or(0.0, |stroke| stroke.width * 0.5);
881            Some(expand_rect(*rect, half_stroke))
882        }
883        DrawPrimitive::Line {
884            start, end, stroke, ..
885        } => Some(LineGeometry::new(*start, *end, *stroke).bounds()),
886        DrawPrimitive::Trapezoid { trapezoid, .. } => Some(trapezoid.bounds()),
887        DrawPrimitive::Image { rect, .. } => Some(*rect),
888        DrawPrimitive::Text(text) => Some(text.rect),
889        DrawPrimitive::Content | DrawPrimitive::Shadow(_) => None,
890    }
891}
892
893/// The POD half of a recording as it is written: the shape records with
894/// their brush and stop tables and coalesced segments, and the bounds
895/// those records reach. Plain data throughout, so a scene carries one
896/// across threads; [`CommandRecording`] wraps it with the other lane.
897#[derive(Clone, Debug)]
898pub struct ShapeRecorder {
899    tables: RecordTables,
900    last_segment_key: u32,
901    segment_waste: u32,
902    min: [f32; 2],
903    max: [f32; 2],
904}
905
906impl Default for ShapeRecorder {
907    fn default() -> Self {
908        Self {
909            tables: RecordTables::default(),
910            last_segment_key: NO_SEGMENT_KEY,
911            segment_waste: 0,
912            min: [f32::INFINITY; 2],
913            max: [f32::NEG_INFINITY; 2],
914        }
915    }
916}
917
918impl PartialEq for ShapeRecorder {
919    fn eq(&self, other: &Self) -> bool {
920        self.tables == other.tables
921    }
922}
923
924/// What [`ShapeRecorder::push_primitive`] did with a primitive.
925pub enum Recorded {
926    /// A shape was recorded; its coverage rect.
927    Shape(Rect),
928    /// The primitive is not a shape and is handed back.
929    Other(DrawPrimitive),
930}
931
932#[inline]
933fn extend_segment_in(tables: &mut RecordTables, extend: bool, opened: RecordSegment) {
934    if extend {
935        let last = tables.segments.last_mut().expect("a keyed segment exists");
936        last.count += 1;
937        last.brushes |= opened.brushes;
938        last.kinds |= opened.kinds;
939        last.band_class = last.band_class.max(opened.band_class);
940        last.interiors |= opened.interiors;
941        last.occluders |= opened.occluders;
942        last.bare_interiors |= opened.bare_interiors;
943        return;
944    }
945    tables.segments.push(opened);
946}
947
948impl ShapeRecorder {
949    /// The owned shape columns and their brush, stop and segment tables.
950    pub fn tables(&self) -> &RecordTables {
951        &self.tables
952    }
953
954    fn tables_mut(&mut self) -> &mut RecordTables {
955        let tables = &mut self.tables;
956        tables.fingerprint.take();
957        tables
958    }
959
960    pub fn is_empty(&self) -> bool {
961        self.tables.shapes.is_empty()
962    }
963
964    /// Every segment, the range a run of the whole recording draws.
965    pub fn all_segments(&self) -> Range<u32> {
966        0..self.tables.segments.len() as u32
967    }
968
969    /// The rect every recorded shape's pixels lie within; `None` when
970    /// nothing was recorded.
971    pub fn bounds(&self) -> Option<Rect> {
972        (self.min[0] <= self.max[0] && self.min[1] <= self.max[1]).then(|| Rect {
973            x: self.min[0],
974            y: self.min[1],
975            width: self.max[0] - self.min[0],
976            height: self.max[1] - self.min[1],
977        })
978    }
979
980    /// The tables' fingerprint; see [`RecordTables::fingerprint`].
981    pub fn fingerprint(&self) -> u64 {
982        self.tables.fingerprint()
983    }
984
985    /// Empties the recorder while retaining its table capacities.
986    pub fn clear(&mut self) {
987        self.tables.clear();
988        self.last_segment_key = NO_SEGMENT_KEY;
989        self.segment_waste = 0;
990        self.min = [f32::INFINITY; 2];
991        self.max = [f32::NEG_INFINITY; 2];
992    }
993
994    /// Records a rect, rounded rect or arc, blended or not; hands any
995    /// other primitive back untouched.
996    pub fn push_primitive(&mut self, primitive: DrawPrimitive) -> Recorded {
997        match primitive {
998            DrawPrimitive::Blend {
999                primitive,
1000                blend_mode,
1001            } => match self.push_shape_primitive(*primitive, blend_mode) {
1002                Recorded::Other(inner) => Recorded::Other(DrawPrimitive::Blend {
1003                    primitive: Box::new(inner),
1004                    blend_mode,
1005                }),
1006                recorded => recorded,
1007            },
1008            other => self.push_shape_primitive(other, BlendMode::SrcOver),
1009        }
1010    }
1011
1012    /// Records a rect, rounded rect or arc with the supplied blend mode.
1013    /// Returns any other primitive untouched, including blend wrappers.
1014    pub fn push_shape_primitive(
1015        &mut self,
1016        primitive: DrawPrimitive,
1017        blend_mode: BlendMode,
1018    ) -> Recorded {
1019        Recorded::Shape(match primitive {
1020            DrawPrimitive::Rect {
1021                rect,
1022                brush,
1023                stroke,
1024            } => self.push_rect(rect, &brush, stroke, blend_mode),
1025            DrawPrimitive::RoundRect {
1026                rect,
1027                brush,
1028                radii,
1029                stroke,
1030            } => self.push_round_rect(rect, &brush, radii, stroke, blend_mode),
1031            DrawPrimitive::Arc {
1032                rect,
1033                brush,
1034                center,
1035                radius,
1036                start_angle,
1037                sweep_angle,
1038                stroke,
1039                inner_radius,
1040            } => self.push_arc(
1041                rect,
1042                &ArcRecordArgs {
1043                    brush: &brush,
1044                    center,
1045                    radius,
1046                    start_angle,
1047                    sweep_angle,
1048                    stroke,
1049                    inner_radius,
1050                    blend_mode,
1051                },
1052            ),
1053            DrawPrimitive::Line {
1054                brush,
1055                start,
1056                end,
1057                stroke,
1058                ..
1059            } => self.push_line(
1060                &LineGeometry::new(start, end, stroke),
1061                &brush,
1062                stroke,
1063                blend_mode,
1064            ),
1065            DrawPrimitive::Trapezoid {
1066                rect,
1067                brush,
1068                trapezoid,
1069            } => self.push_trapezoids(rect, &[trapezoid], &brush, blend_mode),
1070            other => return Recorded::Other(other),
1071        })
1072    }
1073
1074    fn push_content_segment(&mut self) {
1075        self.last_segment_key = NO_SEGMENT_KEY;
1076        self.tables_mut().segments.push(RecordSegment {
1077            lane: RecordLane::Content,
1078            start: 0,
1079            count: 1,
1080            blend: BlendMode::SrcOver,
1081            gradient: false,
1082            vertex_gradient: false,
1083            brushes: 0,
1084            kinds: 0,
1085            band_class: 0,
1086            interiors: false,
1087            occluders: false,
1088            bare_interiors: false,
1089        });
1090    }
1091
1092    /// Records the segment `line` stroked with `stroke` and returns the
1093    /// pixels it can reach. The ends ride in the arc columns, and the unit
1094    /// direction and half length the vertex stage orients its quad by in
1095    /// the curve column; a degenerate segment is kept, as the app drew it,
1096    /// and draws nothing.
1097    pub fn push_line(
1098        &mut self,
1099        line: &LineGeometry,
1100        brush: &Brush,
1101        stroke: Stroke,
1102        blend: BlendMode,
1103    ) -> Rect {
1104        let (handle, color) = self.intern_brush(brush);
1105        let mut flags = pack_flags(RECORD_KIND_LINE, Some(stroke), blend, StrokeCap::Butt);
1106        if line.is_degenerate() {
1107            flags |= ARC_DEGENERATE_BIT;
1108        }
1109        let frame = line.frame();
1110        let ends = [line.start.x, line.start.y, line.end.x, line.end.y];
1111        // A square end on a slant reaches past half the width, which is
1112        // all `push_shape` grows the ends' box by.
1113        let bounds = line.bounds();
1114        self.include_bounds(bounds);
1115        self.push_shape(
1116            ShapeRecordBody {
1117                rect: rect_row(line.end_bounds()),
1118                color,
1119                stroke_width: stroke.width,
1120                flags,
1121                brush: handle,
1122                placement: 0,
1123                arc_geometry: ends,
1124            },
1125            ShapeRecordCurve {
1126                radii: [0.0; 4],
1127                arc_normalized: [frame.direction.x, frame.direction.y, frame.half_length, 0.0],
1128            },
1129            ends,
1130            blend,
1131            None,
1132        );
1133        bounds
1134    }
1135
1136    /// Records the slices of a path fill, each painted by `brush` resolved
1137    /// against `brush_rect`, and returns the box they reach. The brush is
1138    /// interned once for them all. A slice's corners ride in the arc
1139    /// column; its sides and width, which is never zero, in the curve's
1140    /// normalised column, so no slice's row equals a band's, whose trig
1141    /// the GPU writes into its radii; and the brush rect in the radii.
1142    pub fn push_trapezoids(
1143        &mut self,
1144        brush_rect: Rect,
1145        trapezoids: &[Trapezoid],
1146        brush: &Brush,
1147        blend: BlendMode,
1148    ) -> Rect {
1149        let (handle, color) = self.intern_brush(brush);
1150        let flags = pack_flags(RECORD_KIND_LINE, None, blend, StrokeCap::Butt)
1151            | (TRAPEZOID_CAP << CAP_SHIFT);
1152        let radii = rect_row(brush_rect);
1153        // Every slice shares the brush and the rect it resolves against, so
1154        // one shading serves them all.
1155        let shading = self.brush_shading(
1156            &ShapeRecordBody {
1157                brush: handle,
1158                flags,
1159                ..ShapeRecordBody::zeroed()
1160            },
1161            &ShapeRecordCurve {
1162                radii,
1163                arc_normalized: [0.0; 4],
1164            },
1165        );
1166        let mut reach: Option<Rect> = None;
1167        for trapezoid in trapezoids {
1168            let bounds = trapezoid.bounds();
1169            let open = (u32::from(trapezoid.open_left) * OPEN_LEFT_BIT)
1170                | (u32::from(trapezoid.open_right) * OPEN_RIGHT_BIT);
1171            self.push_shaded(
1172                ShapeRecordBody {
1173                    rect: rect_row(bounds),
1174                    color,
1175                    stroke_width: 0.0,
1176                    flags: flags | open,
1177                    brush: handle,
1178                    placement: 0,
1179                    arc_geometry: [
1180                        trapezoid.top[0],
1181                        trapezoid.top[1],
1182                        trapezoid.bottom[0],
1183                        trapezoid.bottom[1],
1184                    ],
1185                },
1186                ShapeRecordCurve {
1187                    radii,
1188                    arc_normalized: [
1189                        trapezoid.left,
1190                        trapezoid.right,
1191                        trapezoid.right - trapezoid.left,
1192                        0.0,
1193                    ],
1194                },
1195                [0.0; 4],
1196                (blend, shading),
1197                None,
1198            );
1199            reach = Some(reach.map_or(bounds, |reach| reach.union(bounds)));
1200        }
1201        reach.unwrap_or(Rect::EMPTY)
1202    }
1203
1204    #[inline]
1205    pub fn push_rect(
1206        &mut self,
1207        rect: Rect,
1208        brush: &Brush,
1209        stroke: Option<Stroke>,
1210        blend: BlendMode,
1211    ) -> Rect {
1212        let (handle, color) = self.intern_brush(brush);
1213        self.push_shape(
1214            ShapeRecordBody {
1215                rect: rect_row(rect),
1216                color,
1217                stroke_width: stroke.map_or(0.0, |stroke| stroke.width),
1218                flags: pack_flags(RECORD_KIND_RECT, stroke, blend, StrokeCap::Butt),
1219                brush: handle,
1220                placement: 0,
1221                arc_geometry: [0.0; 4],
1222            },
1223            ShapeRecordCurve {
1224                radii: [0.0; 4],
1225                arc_normalized: [0.0; 4],
1226            },
1227            [0.0; 4],
1228            blend,
1229            None,
1230        )
1231    }
1232
1233    pub fn push_round_rect(
1234        &mut self,
1235        rect: Rect,
1236        brush: &Brush,
1237        radii: CornerRadii,
1238        stroke: Option<Stroke>,
1239        blend: BlendMode,
1240    ) -> Rect {
1241        let (handle, color) = self.intern_brush(brush);
1242        let mut flags = pack_flags(RECORD_KIND_ROUND_RECT, stroke, blend, StrokeCap::Butt);
1243        let mut arc_geometry = [0.0; 4];
1244        let mut source = [0.0; 4];
1245        let mut arc_normalized = [0.0; 4];
1246        let mut bucket = None;
1247        if let Some(ring) = stroked_circle_ring(rect, radii, stroke)
1248            && let band = BandRing::of_geometry(&ring)
1249            && let Some(ring_bucket) = band_bucket_for(
1250                &ring,
1251                ring.is_degenerate(),
1252                &band,
1253                expand_rect(rect, ring.half_thickness()),
1254            )
1255        {
1256            flags |= ARC_BANDED_BIT;
1257            arc_geometry = [
1258                ring.center.x,
1259                ring.center.y,
1260                ring.inner_radius,
1261                ring.outer_radius,
1262            ];
1263            source = [ring.mid_radius(), ring.inner_radius, 0.0, TAU];
1264            arc_normalized = [0.0, TAU, 0.0, 0.0];
1265            bucket = Some(ring_bucket);
1266        }
1267        self.push_shape(
1268            ShapeRecordBody {
1269                rect: rect_row(rect),
1270                color,
1271                stroke_width: stroke.map_or(0.0, |stroke| stroke.width),
1272                flags,
1273                brush: handle,
1274                placement: 0,
1275                arc_geometry,
1276            },
1277            ShapeRecordCurve {
1278                radii: [
1279                    radii.top_left,
1280                    radii.top_right,
1281                    radii.bottom_right,
1282                    radii.bottom_left,
1283                ],
1284                arc_normalized,
1285            },
1286            source,
1287            blend,
1288            bucket,
1289        )
1290    }
1291
1292    /// Records an arc band or annular sector with the rect the primitive
1293    /// carries.
1294    pub fn push_arc(&mut self, rect: Rect, args: &ArcRecordArgs<'_>) -> Rect {
1295        let geometry = normalized_band(args);
1296        self.push_arc_band(args, &geometry, Some(rect), geometry.is_degenerate())
1297    }
1298
1299    /// Records an arc the draw scope drew, whose band the scope already
1300    /// normalised: the record keeps the disc around the band as its rect
1301    /// and derives the primitive's tight bounds only when asked.
1302    ///
1303    /// The scope records only bands that enclose area, so the band is known
1304    /// not to be degenerate.
1305    #[inline]
1306    pub fn push_scope_arc(&mut self, args: &ArcRecordArgs<'_>, geometry: &ArcGeometry) -> Rect {
1307        debug_assert!(
1308            !geometry.is_degenerate(),
1309            "the scope skips degenerate bands"
1310        );
1311        self.push_arc_band(args, geometry, None, false)
1312    }
1313
1314    #[inline]
1315    fn push_arc_band(
1316        &mut self,
1317        args: &ArcRecordArgs<'_>,
1318        geometry: &ArcGeometry,
1319        rect: Option<Rect>,
1320        degenerate: bool,
1321    ) -> Rect {
1322        let (handle, color) = self.intern_brush(args.brush);
1323        let mut flags = pack_flags(RECORD_KIND_ARC, args.stroke, args.blend_mode, geometry.cap);
1324        if degenerate {
1325            flags |= ARC_DEGENERATE_BIT;
1326        }
1327        let rect = rect.unwrap_or_else(|| {
1328            flags |= ARC_RECT_LOOSE_BIT;
1329            band_disc(geometry)
1330        });
1331        let ring = BandRing::of_geometry(geometry);
1332        let bucket = band_bucket_for(geometry, degenerate, &ring, rect);
1333        if bucket.is_some() {
1334            flags |= ARC_BANDED_BIT;
1335        }
1336        self.push_shape(
1337            ShapeRecordBody {
1338                rect: rect_row(rect),
1339                color,
1340                stroke_width: args.stroke.map_or(0.0, |stroke| stroke.width),
1341                flags,
1342                brush: handle,
1343                placement: 0,
1344                arc_geometry: [
1345                    args.center.x,
1346                    args.center.y,
1347                    geometry.inner_radius,
1348                    geometry.outer_radius,
1349                ],
1350            },
1351            ShapeRecordCurve {
1352                radii: [0.0; 4],
1353                arc_normalized: [geometry.start_angle, geometry.sweep_angle, 0.0, 0.0],
1354            },
1355            [
1356                args.radius,
1357                args.inner_radius,
1358                args.start_angle,
1359                args.sweep_angle,
1360            ],
1361            args.blend_mode,
1362            bucket,
1363        )
1364    }
1365
1366    #[inline(always)]
1367    fn push_shape(
1368        &mut self,
1369        body: ShapeRecordBody,
1370        curve: ShapeRecordCurve,
1371        source: [f32; 4],
1372        blend: BlendMode,
1373        band_bucket: Option<usize>,
1374    ) -> Rect {
1375        let shading = self.brush_shading(&body, &curve);
1376        self.push_shaded(body, curve, source, (blend, shading), band_bucket)
1377    }
1378
1379    /// [`Self::push_shape`] for a record whose brush shading is known.
1380    fn push_shaded(
1381        &mut self,
1382        mut body: ShapeRecordBody,
1383        curve: ShapeRecordCurve,
1384        source: [f32; 4],
1385        (blend, shading): (BlendMode, BrushShading),
1386        band_bucket: Option<usize>,
1387    ) -> Rect {
1388        let half_stroke = if body.flags & STROKED_BIT != 0 {
1389            body.stroke_width * 0.5
1390        } else {
1391            0.0
1392        };
1393        let coverage = expand_rect(row_rect(body.rect), half_stroke);
1394        self.include_bounds(coverage);
1395        let index = self.tables.shapes.len() as u32;
1396        body.flags |= shading.flag();
1397        let brush_bit = 1u8
1398            << match body.brush {
1399                0 => 0,
1400                index => self.tables.brushes[index as usize - 1].kind,
1401            };
1402        let kind_bit = 1u8 << fragment_kind(body.flags);
1403        let band_class = band_bucket.unwrap_or(0) as u8;
1404        body.flags |= u32::from(band_class) << BAND_CLASS_SHIFT;
1405        let extend =
1406            self.note_segment_key(RecordLane::Shapes, blend, shading, is_trapezoid(body.flags))
1407                && self.segment_takes_class(band_class);
1408        if !extend {
1409            self.segment_waste = 0;
1410        }
1411        // A record joining a segment matters to its interior flags only
1412        // where it could set one still clear, so its interior is measured
1413        // only then: thousands of small shapes join a segment the first
1414        // repaying one has already marked.
1415        let joined = extend.then(|| self.tables.segments.last()).flatten();
1416        let translucent = body.brush != 0 || body.color[3] < 1.0;
1417        let interiors = !joined
1418            .is_some_and(|segment| segment.interiors && (segment.bare_interiors || !translucent))
1419            && interior_repays(&body, &curve);
1420        let occluders =
1421            !joined.is_some_and(|segment| segment.occluders) && interior_occludes(&body, &curve);
1422        let bare_interiors = interiors && translucent;
1423        let tables = self.tables_mut();
1424        tables.shapes.push(body, curve, source);
1425        extend_segment_in(
1426            tables,
1427            extend,
1428            RecordSegment {
1429                lane: RecordLane::Shapes,
1430                start: index,
1431                count: 1,
1432                blend,
1433                gradient: shading == BrushShading::Fragment,
1434                vertex_gradient: shading == BrushShading::Vertex,
1435                brushes: brush_bit,
1436                kinds: kind_bit,
1437                band_class,
1438                interiors,
1439                occluders,
1440                bare_interiors,
1441            },
1442        );
1443        coverage
1444    }
1445
1446    fn extend_segment(&mut self, lane: RecordLane, index: u32, blend: BlendMode, kind_bit: u8) {
1447        let extend = self.note_segment_key(lane, blend, BrushShading::Solid, false);
1448        if !extend {
1449            self.segment_waste = 0;
1450        }
1451        extend_segment_in(
1452            self.tables_mut(),
1453            extend,
1454            RecordSegment {
1455                lane,
1456                start: index,
1457                count: 1,
1458                blend,
1459                gradient: false,
1460                vertex_gradient: false,
1461                brushes: 0,
1462                kinds: kind_bit,
1463                band_class: 0,
1464                interiors: false,
1465                occluders: false,
1466                bare_interiors: false,
1467            },
1468        );
1469    }
1470
1471    /// Whether the open segment takes a record of `band_class` without
1472    /// leaving more than [`SEGMENT_WASTE_QUADS`] pinned: a larger class
1473    /// raises every earlier record's budget, a smaller one collapses its
1474    /// own surplus. Accounts the waste when it does.
1475    #[inline]
1476    fn segment_takes_class(&mut self, band_class: u8) -> bool {
1477        let last = self.tables.segments.last().expect("a keyed segment exists");
1478        let held = ARC_BUCKET_SEGMENTS[last.band_class as usize];
1479        let wanted = ARC_BUCKET_SEGMENTS[band_class as usize];
1480        let waste = if wanted > held {
1481            last.count * (wanted - held)
1482        } else {
1483            held - wanted
1484        };
1485        if self.segment_waste + waste > SEGMENT_WASTE_QUADS {
1486            return false;
1487        }
1488        self.segment_waste += waste;
1489        true
1490    }
1491
1492    /// Whether the next record continues the open segment, and makes its
1493    /// key the open one. Slices of path fills keep segments of their own: a
1494    /// slice whose gradient rides on its vertices is exact only in the
1495    /// pipelines that draw slices alone.
1496    #[inline]
1497    fn note_segment_key(
1498        &mut self,
1499        lane: RecordLane,
1500        blend: BlendMode,
1501        shading: BrushShading,
1502        slice: bool,
1503    ) -> bool {
1504        let key = ((lane as u32) << 16)
1505            | (u32::from(slice) << 15)
1506            | ((blend as u32) << 2)
1507            | shading as u32;
1508        let extend = key == self.last_segment_key;
1509        self.last_segment_key = key;
1510        extend
1511    }
1512
1513    /// Widens the bounds to `rect`; plain compares, not the NaN-ordering
1514    /// `f32::min`, which armv7 lowers to a libm call per edge.
1515    #[inline]
1516    fn include_bounds(&mut self, rect: Rect) {
1517        let right = rect.x + rect.width;
1518        let bottom = rect.y + rect.height;
1519        if rect.x < self.min[0] {
1520            self.min[0] = rect.x;
1521        }
1522        if rect.y < self.min[1] {
1523            self.min[1] = rect.y;
1524        }
1525        if right > self.max[0] {
1526            self.max[0] = right;
1527        }
1528        if bottom > self.max[1] {
1529            self.max[1] = bottom;
1530        }
1531    }
1532
1533    #[inline]
1534    /// How a record's brush is shaded: solid, from its vertices, or per
1535    /// fragment. A rect fill takes a linear gradient from its vertices where
1536    /// the gradient is affine over its quad; a slice of a path fill where it
1537    /// runs between two stops and clamps, which the slice's fragments clamp
1538    /// exactly as the gradient sampler does.
1539    fn brush_shading(&self, body: &ShapeRecordBody, curve: &ShapeRecordCurve) -> BrushShading {
1540        if body.brush == 0 {
1541            return BrushShading::Solid;
1542        }
1543        let tables = &self.tables;
1544        let Some(brush) = tables.brushes.get(body.brush as usize - 1) else {
1545            return BrushShading::Fragment;
1546        };
1547        let vertex = if is_trapezoid(body.flags) {
1548            vertex_gradient::clamps_between_two_stops(brush, &tables.stops, curve.radii)
1549        } else {
1550            body.flags & RECT_FILL_MASK == 0
1551                && vertex_gradient::spans_quad(brush, &tables.stops, body.rect)
1552        };
1553        if vertex {
1554            BrushShading::Vertex
1555        } else {
1556            BrushShading::Fragment
1557        }
1558    }
1559
1560    /// The brush handle a record carries and its colour row: the colour
1561    /// itself for a solid brush, the first stop for a gradient, which is
1562    /// interned into the brush and stop tables.
1563    #[inline]
1564    fn intern_brush(&mut self, brush: &Brush) -> (u32, [f32; 4]) {
1565        match brush {
1566            Brush::Solid(color) => (0, [color.0, color.1, color.2, color.3]),
1567            gradient => self.intern_gradient(gradient),
1568        }
1569    }
1570
1571    #[cold]
1572    #[inline(never)]
1573    fn intern_gradient(&mut self, brush: &Brush) -> (u32, [f32; 4]) {
1574        let (kind, tile_mode, params, colors, stops) = match brush {
1575            Brush::Solid(color) => return (0, [color.0, color.1, color.2, color.3]),
1576            Brush::LinearGradient {
1577                colors,
1578                stops,
1579                start,
1580                end,
1581                tile_mode,
1582            } => (
1583                BRUSH_KIND_LINEAR,
1584                *tile_mode,
1585                [start.x, start.y, end.x, end.y],
1586                colors,
1587                stops,
1588            ),
1589            Brush::RadialGradient {
1590                colors,
1591                stops,
1592                center,
1593                radius,
1594                tile_mode,
1595            } => (
1596                BRUSH_KIND_RADIAL,
1597                *tile_mode,
1598                [center.x, center.y, *radius, 0.0],
1599                colors,
1600                stops,
1601            ),
1602            Brush::SweepGradient {
1603                colors,
1604                stops,
1605                center,
1606            } => (
1607                BRUSH_KIND_SWEEP,
1608                TileMode::Clamp,
1609                [center.x, center.y, 0.0, 0.0],
1610                colors,
1611                stops,
1612            ),
1613        };
1614        let tables = self.tables_mut();
1615        let stop_start = tables.stops.len() as u32;
1616        let count = colors.len();
1617        let positions = stops.as_deref().filter(|values| values.len() == count);
1618        for (index, color) in colors.iter().enumerate() {
1619            let position = positions.map_or_else(
1620                || {
1621                    if count <= 1 {
1622                        0.0
1623                    } else {
1624                        index as f32 / (count - 1) as f32
1625                    }
1626                },
1627                |values| values[index],
1628            );
1629            tables.stops.push(GradientStopRecord {
1630                color: [color.0, color.1, color.2, color.3],
1631                position: [position, 0.0, 0.0, 0.0],
1632            });
1633        }
1634        let (explicit_start, explicit_len) = match stops {
1635            Some(values) => {
1636                let start = tables.explicit_stops.len() as u32;
1637                tables.explicit_stops.extend_from_slice(values);
1638                (start, values.len() as u32)
1639            }
1640            None => (0, NO_EXPLICIT_STOPS),
1641        };
1642        let record = BrushRecord {
1643            kind,
1644            tile_mode: tile_mode as u32,
1645            stop_start,
1646            stop_count: count as u32,
1647            params,
1648            explicit_start,
1649            explicit_len,
1650            reserved: [0; 2],
1651        };
1652        tables.brushes.push(record);
1653        let handle = tables.brushes.len() as u32;
1654        let first = colors.first().copied().unwrap_or(Color(0.0, 0.0, 0.0, 0.0));
1655        (handle, [first.0, first.1, first.2, first.3])
1656    }
1657}
1658
1659/// A completed draw command with shared shape data, ordered segments and
1660/// non-shape primitives. Bounds and content metadata are recorded while drawing;
1661/// the complete fingerprint is computed when a cache first requests it.
1662/// Use [`CommandRecorder`] to build a command and [`Self::into_recorder`] to edit it.
1663#[derive(Clone, Debug)]
1664pub struct CommandRecording {
1665    shapes: Arc<ShapeRecorder>,
1666    content: RecordingContent,
1667    fingerprint: OnceCell<u64>,
1668}
1669
1670#[derive(Clone, Debug)]
1671struct RecordingContent {
1672    others: Vec<DrawPrimitive>,
1673    min: [f32; 2],
1674    max: [f32; 2],
1675    summary: RecordingSummary,
1676    content_markers: u32,
1677}
1678
1679impl Default for RecordingContent {
1680    fn default() -> Self {
1681        Self {
1682            others: Vec::new(),
1683            min: [f32::INFINITY; 2],
1684            max: [f32::NEG_INFINITY; 2],
1685            summary: RecordingSummary::default(),
1686            content_markers: 0,
1687        }
1688    }
1689}
1690
1691thread_local! {
1692    /// The shapes every empty recording shares, so making one allocates
1693    /// nothing.
1694    static NO_SHAPES: Arc<ShapeRecorder> = Arc::new(ShapeRecorder::default());
1695}
1696
1697impl Default for CommandRecording {
1698    fn default() -> Self {
1699        Self {
1700            shapes: NO_SHAPES.with(Arc::clone),
1701            content: RecordingContent::default(),
1702            fingerprint: OnceCell::new(),
1703        }
1704    }
1705}
1706
1707impl PartialEq for CommandRecording {
1708    fn eq(&self, other: &Self) -> bool {
1709        self.shapes == other.shapes
1710            && self.content.others == other.content.others
1711            && self.content.content_markers == other.content.content_markers
1712    }
1713}
1714
1715impl CommandRecording {
1716    /// Takes this recording when no retained reader shares its shape data.
1717    ///
1718    /// The returned recording keeps the existing data and capacities. This
1719    /// recording is reset to an empty value. Pass the returned recording to
1720    /// [`CommandRecorder::reusing`] to clear it for another draw. If another
1721    /// strong or weak reference shares the shape data, this returns `None` and
1722    /// leaves the recording unchanged.
1723    ///
1724    /// ```
1725    /// use cranpose_ui_graphics::{Brush, Color, CommandRecorder, DrawPrimitive, Rect};
1726    ///
1727    /// let mut builder = CommandRecorder::default();
1728    /// builder.push_primitive(DrawPrimitive::Rect {
1729    ///     rect: Rect {
1730    ///         x: 0.0,
1731    ///         y: 0.0,
1732    ///         width: 1.0,
1733    ///         height: 1.0,
1734    ///     },
1735    ///     brush: Brush::Solid(Color::RED),
1736    ///     stroke: None,
1737    /// });
1738    /// let mut completed = builder.finish();
1739    /// if let Some(storage) = completed.try_take_reusable() {
1740    ///     let mut recorder = CommandRecorder::reusing(storage);
1741    ///     recorder.push_content();
1742    ///     let _next = recorder.finish();
1743    /// }
1744    /// ```
1745    pub fn try_take_reusable(&mut self) -> Option<Self> {
1746        Arc::get_mut(&mut self.shapes)?;
1747        Some(std::mem::take(self))
1748    }
1749
1750    /// Returns owned command data for further recording.
1751    /// Shape data is copied only when a retained reader still shares it.
1752    pub fn into_recorder(self) -> CommandRecorder {
1753        let (shapes, vessel) = take_shapes(self.shapes, ShapeRecorder::clone);
1754        CommandRecorder {
1755            shapes,
1756            content: self.content,
1757            vessel,
1758        }
1759    }
1760
1761    pub fn from_primitives(primitives: impl IntoIterator<Item = DrawPrimitive>) -> Self {
1762        CommandRecorder::from_primitives(primitives).finish()
1763    }
1764
1765    pub fn shape_capacity(&self) -> usize {
1766        self.shapes.tables.shapes.capacity()
1767    }
1768
1769    /// The heap the POD tables hold, capacity included.
1770    pub fn pod_heap_bytes(&self) -> usize {
1771        self.shapes.tables.heap_bytes()
1772    }
1773
1774    /// The published shape columns and their brush, stop and segment tables.
1775    pub fn tables(&self) -> &RecordTables {
1776        self.shapes.tables()
1777    }
1778
1779    /// Shared ownership of the immutable shape recording.
1780    pub fn shape_recorder(&self) -> &Arc<ShapeRecorder> {
1781        &self.shapes
1782    }
1783
1784    /// The entries inside `segments` that draw, content markers left out.
1785    pub fn len_in(&self, segments: &Range<u32>) -> usize {
1786        self.segments_in(segments)
1787            .filter(|segment| segment.lane != RecordLane::Content)
1788            .map(|segment| segment.count as usize)
1789            .sum()
1790    }
1791
1792    /// The shape columns, with complete records available on demand.
1793    pub fn shapes(&self) -> &ShapeRecords {
1794        &self.shapes.tables.shapes
1795    }
1796
1797    pub fn brushes(&self) -> &[BrushRecord] {
1798        &self.shapes.tables.brushes
1799    }
1800
1801    pub fn stops(&self) -> &[GradientStopRecord] {
1802        &self.shapes.tables.stops
1803    }
1804
1805    pub fn others(&self) -> &[DrawPrimitive] {
1806        &self.content.others
1807    }
1808
1809    pub fn segments(&self) -> &[RecordSegment] {
1810        &self.shapes.tables.segments
1811    }
1812
1813    /// Every segment, the range a placement without a content split draws.
1814    pub fn all_segments(&self) -> Range<u32> {
1815        0..self.shapes.tables.segments.len() as u32
1816    }
1817
1818    /// A rect containing every entry's coverage rect: exact for rects and
1819    /// the other lanes, the disc around the band for a scope-recorded arc.
1820    pub fn bounds(&self) -> Option<Rect> {
1821        let others = (self.content.min[0] <= self.content.max[0]
1822            && self.content.min[1] <= self.content.max[1])
1823            .then(|| Rect {
1824                x: self.content.min[0],
1825                y: self.content.min[1],
1826                width: self.content.max[0] - self.content.min[0],
1827                height: self.content.max[1] - self.content.min[1],
1828            });
1829        match (self.shapes.bounds(), others) {
1830            (Some(shapes), Some(others)) => Some(union_rect(shapes, others)),
1831            (shapes, others) => shapes.or(others),
1832        }
1833    }
1834
1835    pub fn summary(&self) -> RecordingSummary {
1836        self.content.summary
1837    }
1838
1839    pub fn content_markers(&self) -> u32 {
1840        self.content.content_markers
1841    }
1842
1843    /// Entries of every lane, content markers included.
1844    pub fn len(&self) -> usize {
1845        self.shapes.tables.shapes.len()
1846            + self.content.others.len()
1847            + self.content.content_markers as usize
1848    }
1849
1850    pub fn is_empty(&self) -> bool {
1851        self.len() == 0
1852    }
1853
1854    /// A hash over every entry, table and marker in recorded order, so two
1855    /// recordings with equal fingerprints draw the same pixels. Computed
1856    /// the first time it is asked for, so a recording nothing caches never
1857    /// pays for it.
1858    pub fn fingerprint(&self) -> u64 {
1859        *self.fingerprint.get_or_init(|| {
1860            let mut hasher = FxHasher::default();
1861            hasher.write_u64(self.shapes.fingerprint());
1862            for primitive in &self.content.others {
1863                hasher.write_u64(primitive.render_hash());
1864            }
1865            hasher.write_u32(self.content.content_markers);
1866            hasher.finish()
1867        })
1868    }
1869
1870    /// The segments of `segments` that draw anything.
1871    pub fn is_empty_in(&self, segments: &Range<u32>) -> bool {
1872        !self
1873            .segments_in(segments)
1874            .any(|segment| segment.lane != RecordLane::Content && segment.count > 0)
1875    }
1876
1877    pub fn segments_in(&self, segments: &Range<u32>) -> std::slice::Iter<'_, RecordSegment> {
1878        self.shapes.tables.segments[segments.start as usize..segments.end as usize].iter()
1879    }
1880
1881    /// The summary of the entries inside `segments` only.
1882    pub fn summary_in(&self, segments: &Range<u32>) -> RecordingSummary {
1883        if *segments == self.all_segments() {
1884            return self.content.summary;
1885        }
1886        let mut summary = RecordingSummary::default();
1887        for segment in self.segments_in(segments) {
1888            match segment.lane {
1889                RecordLane::Shapes if segment.count > 0 => {
1890                    summary.has_non_shadow = true;
1891                    summary.has_shapes = true;
1892                }
1893                RecordLane::Others => {
1894                    summary.has_others = true;
1895                    for primitive in &self.content.others[segment.range()] {
1896                        summary.note(primitive);
1897                    }
1898                }
1899                RecordLane::Content => summary.has_content_markers = true,
1900                RecordLane::Shapes => {}
1901            }
1902        }
1903        summary
1904    }
1905
1906    /// The segments before the last content marker (`behind`) or after it;
1907    /// with no marker, behind is empty and overlay is everything.
1908    pub fn content_split(&self, behind: bool) -> Range<u32> {
1909        let last_marker = self
1910            .shapes
1911            .tables
1912            .segments
1913            .iter()
1914            .rposition(|segment| segment.lane == RecordLane::Content);
1915        match (last_marker, behind) {
1916            (Some(index), true) => 0..index as u32,
1917            (Some(index), false) => index as u32 + 1..self.shapes.tables.segments.len() as u32,
1918            (None, true) => 0..0,
1919            (None, false) => self.all_segments(),
1920        }
1921    }
1922
1923    /// The coverage rect of every entry inside `segments`.
1924    pub fn coverage_rects(&self, segments: Range<u32>) -> impl Iterator<Item = Rect> + '_ {
1925        self.segments_in(&segments).flat_map(move |segment| {
1926            segment.range().filter_map(move |index| match segment.lane {
1927                RecordLane::Shapes => self
1928                    .shapes
1929                    .tables
1930                    .shapes
1931                    .get(index)
1932                    .map(|record| record.coverage_rect()),
1933                RecordLane::Others => primitive_coverage_rect(&self.content.others[index]),
1934                RecordLane::Content => None,
1935            })
1936        })
1937    }
1938
1939    /// The primitives inside `segments`, materialised in recorded order,
1940    /// content markers left out.
1941    pub fn primitives(&self, segments: Range<u32>) -> impl Iterator<Item = DrawPrimitive> + '_ {
1942        self.segments_in(&segments)
1943            .flat_map(|segment| self.segment_primitives(segment, false))
1944    }
1945
1946    /// Every primitive in recorded order, content markers included.
1947    pub fn primitives_with_markers(&self) -> impl Iterator<Item = DrawPrimitive> + '_ {
1948        self.shapes
1949            .tables
1950            .segments
1951            .iter()
1952            .flat_map(|segment| self.segment_primitives(segment, true))
1953    }
1954
1955    pub fn into_primitives_with_markers(self) -> Vec<DrawPrimitive> {
1956        self.primitives_with_markers().collect()
1957    }
1958
1959    fn segment_primitives<'a>(
1960        &'a self,
1961        segment: &RecordSegment,
1962        markers: bool,
1963    ) -> impl Iterator<Item = DrawPrimitive> + use<'a> {
1964        let lane = segment.lane;
1965        segment.range().filter_map(move |index| match lane {
1966            RecordLane::Shapes => Some(self.materialize_shape(index)),
1967            RecordLane::Others => Some(self.content.others[index].clone()),
1968            RecordLane::Content => markers.then_some(DrawPrimitive::Content),
1969        })
1970    }
1971
1972    /// The exact [`DrawPrimitive`] the record was made from.
1973    pub fn materialize_shape(&self, index: usize) -> DrawPrimitive {
1974        let record = self
1975            .shapes
1976            .tables
1977            .shapes
1978            .get(index)
1979            .expect("recorded shape index");
1980        let rect = record.rect_value();
1981        let brush = self.brush_of(&record);
1982        let stroke = record.stroke();
1983        let primitive = match (record.kind(), record.trapezoid()) {
1984            (_, Some(trapezoid)) => DrawPrimitive::Trapezoid {
1985                rect: row_rect(record.radii),
1986                brush,
1987                trapezoid,
1988            },
1989            (RECORD_KIND_ROUND_RECT, _) => DrawPrimitive::RoundRect {
1990                rect,
1991                brush,
1992                radii: CornerRadii {
1993                    top_left: record.radii[0],
1994                    top_right: record.radii[1],
1995                    bottom_right: record.radii[2],
1996                    bottom_left: record.radii[3],
1997                },
1998                stroke,
1999            },
2000            (RECORD_KIND_LINE, _) => DrawPrimitive::Line {
2001                rect,
2002                brush,
2003                start: Point::new(record.arc[0], record.arc[1]),
2004                end: Point::new(record.arc_band[2], record.arc_band[3]),
2005                stroke: stroke.unwrap_or_default(),
2006            },
2007            (RECORD_KIND_ARC, _) => DrawPrimitive::Arc {
2008                rect,
2009                brush,
2010                center: Point::new(record.arc[0], record.arc[1]),
2011                radius: record.arc[2],
2012                start_angle: record.arc_band[0],
2013                sweep_angle: record.arc_band[1],
2014                stroke,
2015                inner_radius: record.arc[3],
2016            },
2017            _ => DrawPrimitive::Rect {
2018                rect,
2019                brush,
2020                stroke,
2021            },
2022        };
2023        let blend_mode = record.blend_mode();
2024        if blend_mode == BlendMode::SrcOver {
2025            primitive
2026        } else {
2027            DrawPrimitive::Blend {
2028                primitive: Box::new(primitive),
2029                blend_mode,
2030            }
2031        }
2032    }
2033
2034    /// The brush of a record: its solid colour, or the gradient rebuilt
2035    /// from the tables exactly as the app gave it.
2036    pub fn brush_of(&self, record: &ShapeRecord) -> Brush {
2037        if record.brush == 0 {
2038            return Brush::Solid(Color(
2039                record.color[0],
2040                record.color[1],
2041                record.color[2],
2042                record.color[3],
2043            ));
2044        }
2045        let tables = &self.shapes.tables;
2046        let brush = &tables.brushes[record.brush as usize - 1];
2047        let colors = tables.stops
2048            [brush.stop_start as usize..(brush.stop_start + brush.stop_count) as usize]
2049            .iter()
2050            .map(|stop| Color(stop.color[0], stop.color[1], stop.color[2], stop.color[3]))
2051            .collect();
2052        let stops = (brush.explicit_len != NO_EXPLICIT_STOPS).then(|| {
2053            tables.explicit_stops[brush.explicit_start as usize
2054                ..(brush.explicit_start + brush.explicit_len) as usize]
2055                .to_vec()
2056        });
2057        let [a, b, c, d] = brush.params;
2058        let tile_mode = TILE_MODES[brush.tile_mode as usize];
2059        match brush.kind {
2060            BRUSH_KIND_RADIAL => Brush::RadialGradient {
2061                colors,
2062                stops,
2063                center: Point::new(a, b),
2064                radius: c,
2065                tile_mode,
2066            },
2067            BRUSH_KIND_SWEEP => Brush::SweepGradient {
2068                colors,
2069                stops,
2070                center: Point::new(a, b),
2071            },
2072            _ => Brush::LinearGradient {
2073                colors,
2074                stops,
2075                start: Point::new(a, b),
2076                end: Point::new(c, d),
2077                tile_mode,
2078            },
2079        }
2080    }
2081}
2082
2083/// Mutable command data owned exclusively while a draw scope records it.
2084/// Publishing with [`Self::finish`] shares the completed shape data without copying it.
2085#[derive(Debug, Default)]
2086pub struct CommandRecorder {
2087    shapes: ShapeRecorder,
2088    content: RecordingContent,
2089    /// The emptied shape allocation of the recording this recorder took its
2090    /// data from, which [`Self::finish`] fills again instead of allocating.
2091    vessel: Option<Arc<ShapeRecorder>>,
2092}
2093
2094impl Clone for CommandRecorder {
2095    fn clone(&self) -> Self {
2096        Self {
2097            shapes: self.shapes.clone(),
2098            content: self.content.clone(),
2099            vessel: None,
2100        }
2101    }
2102}
2103
2104/// The shapes `shared` holds and, when nothing else holds it, its emptied
2105/// allocation; otherwise `copy` of them, for a retained reader keeps its own.
2106fn take_shapes(
2107    mut shared: Arc<ShapeRecorder>,
2108    copy: impl FnOnce(&ShapeRecorder) -> ShapeRecorder,
2109) -> (ShapeRecorder, Option<Arc<ShapeRecorder>>) {
2110    match Arc::get_mut(&mut shared) {
2111        Some(shapes) => (std::mem::take(shapes), Some(shared)),
2112        None => (copy(&shared), None),
2113    }
2114}
2115
2116/// `shapes` shared, in `vessel` when it is still the only handle to it.
2117fn fill_vessel(vessel: Option<Arc<ShapeRecorder>>, shapes: ShapeRecorder) -> Arc<ShapeRecorder> {
2118    let Some(mut vessel) = vessel else {
2119        return Arc::new(shapes);
2120    };
2121    match Arc::get_mut(&mut vessel) {
2122        Some(slot) => {
2123            *slot = shapes;
2124            vessel
2125        }
2126        None => Arc::new(shapes),
2127    }
2128}
2129
2130impl CommandRecorder {
2131    /// Records primitives in order into owned command data.
2132    pub fn from_primitives(primitives: impl IntoIterator<Item = DrawPrimitive>) -> Self {
2133        let mut recorder = Self::default();
2134        for primitive in primitives {
2135            recorder.push_primitive(primitive);
2136        }
2137        recorder
2138    }
2139
2140    /// Reuses a completed command's buffer capacities for an empty recording.
2141    /// Retained readers keep their original shape data.
2142    pub fn reusing(recording: CommandRecording) -> Self {
2143        let (shapes, vessel) = take_shapes(recording.shapes, |shared| ShapeRecorder {
2144            tables: shared.tables.with_capacity_of(),
2145            ..ShapeRecorder::default()
2146        });
2147        let mut recorder = Self {
2148            shapes,
2149            content: recording.content,
2150            vessel,
2151        };
2152        recorder.clear();
2153        recorder
2154    }
2155
2156    /// Publishes completed command data without copying its shape columns.
2157    /// A recording that took no earlier one's allocation gives back the
2158    /// room its growth left: drawing it again then refills that allocation.
2159    pub fn finish(mut self) -> CommandRecording {
2160        if self.vessel.is_none() {
2161            self.shapes.tables.trim_growth();
2162        }
2163        CommandRecording {
2164            shapes: fill_vessel(self.vessel, self.shapes),
2165            content: self.content,
2166            fingerprint: OnceCell::new(),
2167        }
2168    }
2169
2170    /// Every recorded entry, including content markers.
2171    pub fn len(&self) -> usize {
2172        self.shapes.tables.shapes.len()
2173            + self.content.others.len()
2174            + self.content.content_markers as usize
2175    }
2176
2177    /// Whether this recorder contains no entries.
2178    pub fn is_empty(&self) -> bool {
2179        self.len() == 0
2180    }
2181
2182    /// The number of recorded content markers.
2183    pub fn content_markers(&self) -> u32 {
2184        self.content.content_markers
2185    }
2186
2187    /// Empties the recording, keeping every buffer's capacity for the next
2188    /// recording into it.
2189    pub fn clear(&mut self) {
2190        self.shapes.clear();
2191        self.content.others.clear();
2192        self.content.min = [f32::INFINITY; 2];
2193        self.content.max = [f32::NEG_INFINITY; 2];
2194        self.content.summary = RecordingSummary::default();
2195        self.content.content_markers = 0;
2196    }
2197
2198    /// Reserves capacity for additional shape records.
2199    pub fn reserve_shapes(&mut self, additional: usize) {
2200        self.shapes.tables_mut().shapes.reserve(additional);
2201    }
2202
2203    /// Records a content marker at the current command position.
2204    pub fn push_content(&mut self) {
2205        self.content.content_markers += 1;
2206        self.content.summary.has_content_markers = true;
2207        self.shapes.push_content_segment();
2208    }
2209
2210    /// Records a primitive the way the draw scope would have: shapes and
2211    /// blended shapes become records, everything else joins the others lane.
2212    pub fn push_primitive(&mut self, primitive: DrawPrimitive) {
2213        if matches!(primitive, DrawPrimitive::Content) {
2214            self.push_content();
2215            return;
2216        }
2217        match self.shapes.push_primitive(primitive) {
2218            Recorded::Shape(_) => self.note_shape(),
2219            Recorded::Other(other) => self.push_other(other),
2220        }
2221    }
2222
2223    #[inline]
2224    fn note_shape(&mut self) {
2225        self.content.summary.has_non_shadow = true;
2226        self.content.summary.has_shapes = true;
2227    }
2228
2229    fn include_bounds(&mut self, rect: Rect) {
2230        self.content.min[0] = self.content.min[0].min(rect.x);
2231        self.content.min[1] = self.content.min[1].min(rect.y);
2232        self.content.max[0] = self.content.max[0].max(rect.x + rect.width);
2233        self.content.max[1] = self.content.max[1].max(rect.y + rect.height);
2234    }
2235
2236    /// Records a rectangle with its brush, optional stroke and blend mode.
2237    pub fn push_rect(
2238        &mut self,
2239        rect: Rect,
2240        brush: &Brush,
2241        stroke: Option<Stroke>,
2242        blend: BlendMode,
2243    ) {
2244        self.shapes.push_rect(rect, brush, stroke, blend);
2245        self.note_shape();
2246    }
2247
2248    /// Records a rounded rectangle with its corner radii and paint.
2249    pub fn push_round_rect(
2250        &mut self,
2251        rect: Rect,
2252        brush: &Brush,
2253        radii: CornerRadii,
2254        stroke: Option<Stroke>,
2255        blend: BlendMode,
2256    ) {
2257        self.shapes
2258            .push_round_rect(rect, brush, radii, stroke, blend);
2259        self.note_shape();
2260    }
2261
2262    /// Records an arc band or annular sector with the rect the primitive
2263    /// carries.
2264    #[inline]
2265    pub fn push_arc(&mut self, rect: Rect, args: &ArcRecordArgs<'_>) {
2266        self.shapes.push_arc(rect, args);
2267        self.note_shape();
2268    }
2269
2270    /// Records an arc the draw scope drew, whose band the scope already
2271    /// normalised: the record keeps the disc around the band as its rect
2272    /// and derives the primitive's tight bounds only when asked.
2273    #[inline]
2274    pub fn push_scope_arc(&mut self, args: &ArcRecordArgs<'_>, geometry: &ArcGeometry) {
2275        self.shapes.push_scope_arc(args, geometry);
2276        self.note_shape();
2277    }
2278
2279    /// Records the segment `line` a draw scope stroked.
2280    pub fn push_line(
2281        &mut self,
2282        line: &LineGeometry,
2283        brush: &Brush,
2284        stroke: Stroke,
2285        blend: BlendMode,
2286    ) {
2287        self.shapes.push_line(line, brush, stroke, blend);
2288        self.note_shape();
2289    }
2290
2291    /// Records the slices of a path fill a draw scope cut; see
2292    /// [`ShapeRecorder::push_trapezoids`].
2293    pub fn push_trapezoids(
2294        &mut self,
2295        brush_rect: Rect,
2296        trapezoids: &[Trapezoid],
2297        brush: &Brush,
2298        blend: BlendMode,
2299    ) {
2300        if trapezoids.is_empty() {
2301            return;
2302        }
2303        self.shapes
2304            .push_trapezoids(brush_rect, trapezoids, brush, blend);
2305        self.note_shape();
2306    }
2307
2308    /// Records a primitive in the non-shape lane and updates its metadata.
2309    pub fn push_other(&mut self, primitive: DrawPrimitive) {
2310        self.content.summary.has_others = true;
2311        self.content.summary.note(&primitive);
2312        if let Some(rect) = primitive_coverage_rect(&primitive) {
2313            self.include_bounds(rect);
2314        }
2315        let index = self.content.others.len() as u32;
2316        self.content.others.push(primitive);
2317        self.shapes
2318            .extend_segment(RecordLane::Others, index, BlendMode::SrcOver, 0);
2319    }
2320
2321    /// Folds `other`'s summary into this recording's, for callers that
2322    /// combine recordings.
2323    pub fn merge_summary(&mut self, other: RecordingSummary) {
2324        self.content.summary.merge(other);
2325    }
2326}
2327
2328/// The arc as the app drew it, the arguments of
2329/// [`CommandRecorder::push_arc`].
2330pub struct ArcRecordArgs<'a> {
2331    pub brush: &'a Brush,
2332    pub center: Point,
2333    pub radius: f32,
2334    pub start_angle: f32,
2335    pub sweep_angle: f32,
2336    pub stroke: Option<Stroke>,
2337    pub inner_radius: f32,
2338    pub blend_mode: BlendMode,
2339}
2340
2341/// The ring a stroked round rect draws when it is a circle: a square
2342/// whose four radii are half its side, stroked. Its band is the stroke.
2343fn stroked_circle_ring(
2344    rect: Rect,
2345    radii: CornerRadii,
2346    stroke: Option<Stroke>,
2347) -> Option<ArcGeometry> {
2348    const CIRCLE_TOLERANCE: f32 = 0.01;
2349    let stroke = stroke?;
2350    if rect.width.to_bits() != rect.height.to_bits() || !stroke.is_visible() {
2351        return None;
2352    }
2353    let half = rect.width * 0.5;
2354    let radius = radii.top_left;
2355    if !radius.is_finite()
2356        || radius <= 0.0
2357        || (radius - half).abs() > CIRCLE_TOLERANCE
2358        || radii.top_right.to_bits() != radius.to_bits()
2359        || radii.bottom_right.to_bits() != radius.to_bits()
2360        || radii.bottom_left.to_bits() != radius.to_bits()
2361    {
2362        return None;
2363    }
2364    let half_width = stroke.half_width();
2365    Some(ArcGeometry::new(
2366        Point::new(rect.x + half, rect.y + half),
2367        (half - half_width).max(0.0),
2368        half + half_width,
2369        0.0,
2370        TAU,
2371        StrokeCap::Round,
2372    ))
2373}
2374
2375fn union_rect(a: Rect, b: Rect) -> Rect {
2376    let x = a.x.min(b.x);
2377    let y = a.y.min(b.y);
2378    Rect {
2379        x,
2380        y,
2381        width: (a.x + a.width).max(b.x + b.width) - x,
2382        height: (a.y + a.height).max(b.y + b.height) - y,
2383    }
2384}
2385
2386fn rect_row(rect: Rect) -> [f32; 4] {
2387    [rect.x, rect.y, rect.width, rect.height]
2388}
2389
2390fn row_rect(row: [f32; 4]) -> Rect {
2391    Rect {
2392        x: row[0],
2393        y: row[1],
2394        width: row[2],
2395        height: row[3],
2396    }
2397}
2398
2399/// The band a primitive's arc arguments describe, normalised as the
2400/// fragment stage draws it.
2401#[inline(always)]
2402pub fn normalized_band(args: &ArcRecordArgs<'_>) -> ArcGeometry {
2403    ArcGeometry::of_band(
2404        args.center,
2405        arc_band(args.radius, args.inner_radius, args.stroke),
2406        args.start_angle,
2407        args.sweep_angle,
2408    )
2409}
2410
2411/// The disc around a band: the square of its outer radius plus the cap's
2412/// reach, which contains the band's tight bounds.
2413fn band_disc(geometry: &ArcGeometry) -> Rect {
2414    let reach = geometry.outer_radius + geometry.half_thickness();
2415    Rect {
2416        x: geometry.center.x - reach,
2417        y: geometry.center.y - reach,
2418        width: reach + reach,
2419        height: reach + reach,
2420    }
2421}
2422
2423/// The trig the GPU derives for a band from its normalized start and sweep:
2424/// the mid-angle sine and cosine and the half-sweep sine and cosine, with the
2425/// full circle's sentinel. Arc records leave their `radii` zero for the GPU
2426/// to fill; the renderer's CPU mirrors of the band geometry call this.
2427pub fn arc_trig(start_angle: f32, sweep_angle: f32) -> [f32; 4] {
2428    if sweep_angle >= TAU && start_angle == 0.0 {
2429        return [0.0, -1.0, 0.0, -1.0];
2430    }
2431    let half_sweep = within(sweep_angle, 0.0, TAU) * 0.5;
2432    let (mid_sin, mid_cos) = (start_angle + half_sweep).sin_cos();
2433    let (half_sin, half_cos) = half_sweep.sin_cos();
2434    [mid_sin, mid_cos, at_least(half_sin, 0.0), half_cos]
2435}
2436
2437#[cfg(test)]
2438#[path = "tests/record_tests.rs"]
2439mod tests;
2440
2441#[cfg(test)]
2442#[path = "tests/record_band_tests.rs"]
2443mod band_tests;