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    /// Empty tables with this one's capacities, so a recording that starts
743    /// while a scene still holds the last one grows nothing.
744    fn with_capacity_of(&self) -> Self {
745        Self {
746            shapes: ShapeRecords::with_capacity(self.shapes.capacity()),
747            brushes: Vec::with_capacity(self.brushes.capacity()),
748            stops: Vec::with_capacity(self.stops.capacity()),
749            explicit_stops: Vec::with_capacity(self.explicit_stops.capacity()),
750            segments: Vec::with_capacity(self.segments.capacity()),
751            fingerprint: OnceLock::new(),
752        }
753    }
754
755    /// A hash of everything the GPU reads and of the segments, computed
756    /// the first time a cache asks and kept while the tables stand.
757    pub fn fingerprint(&self) -> u64 {
758        *self.fingerprint.get_or_init(|| {
759            let mut hasher = FxHasher::default();
760            hasher.write(bytemuck::cast_slice(self.shapes.bodies()));
761            hasher.write(bytemuck::cast_slice(self.shapes.curves()));
762            hasher.write(self.shapes.source_bytes());
763            hasher.write(bytemuck::cast_slice(&self.brushes));
764            hasher.write(bytemuck::cast_slice(&self.stops));
765            hasher.write(bytemuck::cast_slice(&self.explicit_stops));
766            self.segments.hash(&mut hasher);
767            hasher.finish()
768        })
769    }
770
771    /// The heap the tables hold, capacity included.
772    pub fn heap_bytes(&self) -> usize {
773        self.shapes.heap_bytes()
774            + self.brushes.capacity() * std::mem::size_of::<BrushRecord>()
775            + self.stops.capacity() * std::mem::size_of::<GradientStopRecord>()
776            + self.explicit_stops.capacity() * std::mem::size_of::<f32>()
777            + self.segments.capacity() * std::mem::size_of::<RecordSegment>()
778    }
779}
780
781/// What a recording contains, answered once while recording so no consumer
782/// rescans it.
783#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
784pub struct RecordingSummary {
785    /// Any text, including inside a blend: glyph masks want rigid snapping.
786    pub has_text: bool,
787    /// Any shadow, including inside a blend.
788    pub has_shadow: bool,
789    /// Any drawable primitive besides a shadow, including inside a blend.
790    pub has_non_shadow: bool,
791    /// Any image or text, including inside a blend: content that resamples
792    /// badly on a fractionally offset surface.
793    pub has_pixel_sensitive: bool,
794    /// Any entry in the shape tables.
795    pub has_shapes: bool,
796    /// Any primitive outside the shape tables.
797    pub has_others: bool,
798    /// Any marker where a with-content command draws its node's content.
799    pub has_content_markers: bool,
800}
801
802impl RecordingSummary {
803    fn note(&mut self, primitive: &DrawPrimitive) {
804        let primitive = unwrap_blend(primitive);
805        if matches!(primitive, DrawPrimitive::Shadow(_)) {
806            self.has_shadow = true;
807            return;
808        }
809        if matches!(primitive, DrawPrimitive::Content) {
810            return;
811        }
812        self.has_non_shadow = true;
813        match primitive {
814            DrawPrimitive::Text(_) => {
815                self.has_text = true;
816                self.has_pixel_sensitive = true;
817            }
818            DrawPrimitive::Image { .. } => self.has_pixel_sensitive = true,
819            _ => {}
820        }
821    }
822
823    fn merge(&mut self, other: Self) {
824        self.has_text |= other.has_text;
825        self.has_shadow |= other.has_shadow;
826        self.has_non_shadow |= other.has_non_shadow;
827        self.has_pixel_sensitive |= other.has_pixel_sensitive;
828        self.has_shapes |= other.has_shapes;
829        self.has_others |= other.has_others;
830        self.has_content_markers |= other.has_content_markers;
831    }
832
833    /// Whether the entries are shapes alone, with no other primitive or
834    /// content marker between them, so one run draws them all.
835    pub fn shapes_only(&self) -> bool {
836        self.has_shapes && !self.has_others && !self.has_content_markers
837    }
838}
839
840fn unwrap_blend(mut primitive: &DrawPrimitive) -> &DrawPrimitive {
841    while let DrawPrimitive::Blend {
842        primitive: inner, ..
843    } = primitive
844    {
845        primitive = inner;
846    }
847    primitive
848}
849
850/// `rect` grown by `margin` on every side.
851pub fn expand_rect(rect: Rect, margin: f32) -> Rect {
852    Rect {
853        x: rect.x - margin,
854        y: rect.y - margin,
855        width: rect.width + margin * 2.0,
856        height: rect.height + margin * 2.0,
857    }
858}
859
860/// The rect a primitive's pixels can reach, or `None` when it has none of
861/// its own (a content marker or a shadow).
862pub fn primitive_coverage_rect(primitive: &DrawPrimitive) -> Option<Rect> {
863    match primitive {
864        DrawPrimitive::Blend { primitive, .. } => primitive_coverage_rect(primitive),
865        DrawPrimitive::Rect { rect, stroke, .. }
866        | DrawPrimitive::RoundRect { rect, stroke, .. }
867        | DrawPrimitive::Arc { rect, stroke, .. } => {
868            let half_stroke = stroke.as_ref().map_or(0.0, |stroke| stroke.width * 0.5);
869            Some(expand_rect(*rect, half_stroke))
870        }
871        DrawPrimitive::Line {
872            start, end, stroke, ..
873        } => Some(LineGeometry::new(*start, *end, *stroke).bounds()),
874        DrawPrimitive::Trapezoid { trapezoid, .. } => Some(trapezoid.bounds()),
875        DrawPrimitive::Image { rect, .. } => Some(*rect),
876        DrawPrimitive::Text(text) => Some(text.rect),
877        DrawPrimitive::Content | DrawPrimitive::Shadow(_) => None,
878    }
879}
880
881/// The POD half of a recording as it is written: the shape records with
882/// their brush and stop tables and coalesced segments, and the bounds
883/// those records reach. Plain data throughout, so a scene carries one
884/// across threads; [`CommandRecording`] wraps it with the other lane.
885#[derive(Clone, Debug)]
886pub struct ShapeRecorder {
887    tables: RecordTables,
888    last_segment_key: u32,
889    segment_waste: u32,
890    min: [f32; 2],
891    max: [f32; 2],
892}
893
894impl Default for ShapeRecorder {
895    fn default() -> Self {
896        Self {
897            tables: RecordTables::default(),
898            last_segment_key: NO_SEGMENT_KEY,
899            segment_waste: 0,
900            min: [f32::INFINITY; 2],
901            max: [f32::NEG_INFINITY; 2],
902        }
903    }
904}
905
906impl PartialEq for ShapeRecorder {
907    fn eq(&self, other: &Self) -> bool {
908        self.tables == other.tables
909    }
910}
911
912/// What [`ShapeRecorder::push_primitive`] did with a primitive.
913pub enum Recorded {
914    /// A shape was recorded; its coverage rect.
915    Shape(Rect),
916    /// The primitive is not a shape and is handed back.
917    Other(DrawPrimitive),
918}
919
920#[inline]
921fn extend_segment_in(tables: &mut RecordTables, extend: bool, opened: RecordSegment) {
922    if extend {
923        let last = tables.segments.last_mut().expect("a keyed segment exists");
924        last.count += 1;
925        last.brushes |= opened.brushes;
926        last.kinds |= opened.kinds;
927        last.band_class = last.band_class.max(opened.band_class);
928        last.interiors |= opened.interiors;
929        last.occluders |= opened.occluders;
930        last.bare_interiors |= opened.bare_interiors;
931        return;
932    }
933    tables.segments.push(opened);
934}
935
936impl ShapeRecorder {
937    /// The owned shape columns and their brush, stop and segment tables.
938    pub fn tables(&self) -> &RecordTables {
939        &self.tables
940    }
941
942    fn tables_mut(&mut self) -> &mut RecordTables {
943        let tables = &mut self.tables;
944        tables.fingerprint.take();
945        tables
946    }
947
948    pub fn is_empty(&self) -> bool {
949        self.tables.shapes.is_empty()
950    }
951
952    /// Every segment, the range a run of the whole recording draws.
953    pub fn all_segments(&self) -> Range<u32> {
954        0..self.tables.segments.len() as u32
955    }
956
957    /// The rect every recorded shape's pixels lie within; `None` when
958    /// nothing was recorded.
959    pub fn bounds(&self) -> Option<Rect> {
960        (self.min[0] <= self.max[0] && self.min[1] <= self.max[1]).then(|| Rect {
961            x: self.min[0],
962            y: self.min[1],
963            width: self.max[0] - self.min[0],
964            height: self.max[1] - self.min[1],
965        })
966    }
967
968    /// The tables' fingerprint; see [`RecordTables::fingerprint`].
969    pub fn fingerprint(&self) -> u64 {
970        self.tables.fingerprint()
971    }
972
973    /// Empties the recorder while retaining its table capacities.
974    pub fn clear(&mut self) {
975        self.tables.clear();
976        self.last_segment_key = NO_SEGMENT_KEY;
977        self.segment_waste = 0;
978        self.min = [f32::INFINITY; 2];
979        self.max = [f32::NEG_INFINITY; 2];
980    }
981
982    /// Records a rect, rounded rect or arc, blended or not; hands any
983    /// other primitive back untouched.
984    pub fn push_primitive(&mut self, primitive: DrawPrimitive) -> Recorded {
985        match primitive {
986            DrawPrimitive::Blend {
987                primitive,
988                blend_mode,
989            } => match self.push_shape_primitive(*primitive, blend_mode) {
990                Recorded::Other(inner) => Recorded::Other(DrawPrimitive::Blend {
991                    primitive: Box::new(inner),
992                    blend_mode,
993                }),
994                recorded => recorded,
995            },
996            other => self.push_shape_primitive(other, BlendMode::SrcOver),
997        }
998    }
999
1000    /// Records a rect, rounded rect or arc with the supplied blend mode.
1001    /// Returns any other primitive untouched, including blend wrappers.
1002    pub fn push_shape_primitive(
1003        &mut self,
1004        primitive: DrawPrimitive,
1005        blend_mode: BlendMode,
1006    ) -> Recorded {
1007        Recorded::Shape(match primitive {
1008            DrawPrimitive::Rect {
1009                rect,
1010                brush,
1011                stroke,
1012            } => self.push_rect(rect, &brush, stroke, blend_mode),
1013            DrawPrimitive::RoundRect {
1014                rect,
1015                brush,
1016                radii,
1017                stroke,
1018            } => self.push_round_rect(rect, &brush, radii, stroke, blend_mode),
1019            DrawPrimitive::Arc {
1020                rect,
1021                brush,
1022                center,
1023                radius,
1024                start_angle,
1025                sweep_angle,
1026                stroke,
1027                inner_radius,
1028            } => self.push_arc(
1029                rect,
1030                &ArcRecordArgs {
1031                    brush: &brush,
1032                    center,
1033                    radius,
1034                    start_angle,
1035                    sweep_angle,
1036                    stroke,
1037                    inner_radius,
1038                    blend_mode,
1039                },
1040            ),
1041            DrawPrimitive::Line {
1042                brush,
1043                start,
1044                end,
1045                stroke,
1046                ..
1047            } => self.push_line(
1048                &LineGeometry::new(start, end, stroke),
1049                &brush,
1050                stroke,
1051                blend_mode,
1052            ),
1053            DrawPrimitive::Trapezoid {
1054                rect,
1055                brush,
1056                trapezoid,
1057            } => self.push_trapezoids(rect, &[trapezoid], &brush, blend_mode),
1058            other => return Recorded::Other(other),
1059        })
1060    }
1061
1062    fn push_content_segment(&mut self) {
1063        self.last_segment_key = NO_SEGMENT_KEY;
1064        self.tables_mut().segments.push(RecordSegment {
1065            lane: RecordLane::Content,
1066            start: 0,
1067            count: 1,
1068            blend: BlendMode::SrcOver,
1069            gradient: false,
1070            vertex_gradient: false,
1071            brushes: 0,
1072            kinds: 0,
1073            band_class: 0,
1074            interiors: false,
1075            occluders: false,
1076            bare_interiors: false,
1077        });
1078    }
1079
1080    /// Records the segment `line` stroked with `stroke` and returns the
1081    /// pixels it can reach. The ends ride in the arc columns, and the unit
1082    /// direction and half length the vertex stage orients its quad by in
1083    /// the curve column; a degenerate segment is kept, as the app drew it,
1084    /// and draws nothing.
1085    pub fn push_line(
1086        &mut self,
1087        line: &LineGeometry,
1088        brush: &Brush,
1089        stroke: Stroke,
1090        blend: BlendMode,
1091    ) -> Rect {
1092        let (handle, color) = self.intern_brush(brush);
1093        let mut flags = pack_flags(RECORD_KIND_LINE, Some(stroke), blend, StrokeCap::Butt);
1094        if line.is_degenerate() {
1095            flags |= ARC_DEGENERATE_BIT;
1096        }
1097        let frame = line.frame();
1098        let ends = [line.start.x, line.start.y, line.end.x, line.end.y];
1099        // A square end on a slant reaches past half the width, which is
1100        // all `push_shape` grows the ends' box by.
1101        let bounds = line.bounds();
1102        self.include_bounds(bounds);
1103        self.push_shape(
1104            ShapeRecordBody {
1105                rect: rect_row(line.end_bounds()),
1106                color,
1107                stroke_width: stroke.width,
1108                flags,
1109                brush: handle,
1110                placement: 0,
1111                arc_geometry: ends,
1112            },
1113            ShapeRecordCurve {
1114                radii: [0.0; 4],
1115                arc_normalized: [frame.direction.x, frame.direction.y, frame.half_length, 0.0],
1116            },
1117            ends,
1118            blend,
1119            None,
1120        );
1121        bounds
1122    }
1123
1124    /// Records the slices of a path fill, each painted by `brush` resolved
1125    /// against `brush_rect`, and returns the box they reach. The brush is
1126    /// interned once for them all. A slice's corners ride in the arc
1127    /// column; its sides and width, which is never zero, in the curve's
1128    /// normalised column, so no slice's row equals a band's, whose trig
1129    /// the GPU writes into its radii; and the brush rect in the radii.
1130    pub fn push_trapezoids(
1131        &mut self,
1132        brush_rect: Rect,
1133        trapezoids: &[Trapezoid],
1134        brush: &Brush,
1135        blend: BlendMode,
1136    ) -> Rect {
1137        let (handle, color) = self.intern_brush(brush);
1138        let flags = pack_flags(RECORD_KIND_LINE, None, blend, StrokeCap::Butt)
1139            | (TRAPEZOID_CAP << CAP_SHIFT);
1140        let radii = rect_row(brush_rect);
1141        // Every slice shares the brush and the rect it resolves against, so
1142        // one shading serves them all.
1143        let shading = self.brush_shading(
1144            &ShapeRecordBody {
1145                brush: handle,
1146                flags,
1147                ..ShapeRecordBody::zeroed()
1148            },
1149            &ShapeRecordCurve {
1150                radii,
1151                arc_normalized: [0.0; 4],
1152            },
1153        );
1154        let mut reach: Option<Rect> = None;
1155        for trapezoid in trapezoids {
1156            let bounds = trapezoid.bounds();
1157            let open = (u32::from(trapezoid.open_left) * OPEN_LEFT_BIT)
1158                | (u32::from(trapezoid.open_right) * OPEN_RIGHT_BIT);
1159            self.push_shaded(
1160                ShapeRecordBody {
1161                    rect: rect_row(bounds),
1162                    color,
1163                    stroke_width: 0.0,
1164                    flags: flags | open,
1165                    brush: handle,
1166                    placement: 0,
1167                    arc_geometry: [
1168                        trapezoid.top[0],
1169                        trapezoid.top[1],
1170                        trapezoid.bottom[0],
1171                        trapezoid.bottom[1],
1172                    ],
1173                },
1174                ShapeRecordCurve {
1175                    radii,
1176                    arc_normalized: [
1177                        trapezoid.left,
1178                        trapezoid.right,
1179                        trapezoid.right - trapezoid.left,
1180                        0.0,
1181                    ],
1182                },
1183                [0.0; 4],
1184                (blend, shading),
1185                None,
1186            );
1187            reach = Some(reach.map_or(bounds, |reach| reach.union(bounds)));
1188        }
1189        reach.unwrap_or(Rect::EMPTY)
1190    }
1191
1192    #[inline]
1193    pub fn push_rect(
1194        &mut self,
1195        rect: Rect,
1196        brush: &Brush,
1197        stroke: Option<Stroke>,
1198        blend: BlendMode,
1199    ) -> Rect {
1200        let (handle, color) = self.intern_brush(brush);
1201        self.push_shape(
1202            ShapeRecordBody {
1203                rect: rect_row(rect),
1204                color,
1205                stroke_width: stroke.map_or(0.0, |stroke| stroke.width),
1206                flags: pack_flags(RECORD_KIND_RECT, stroke, blend, StrokeCap::Butt),
1207                brush: handle,
1208                placement: 0,
1209                arc_geometry: [0.0; 4],
1210            },
1211            ShapeRecordCurve {
1212                radii: [0.0; 4],
1213                arc_normalized: [0.0; 4],
1214            },
1215            [0.0; 4],
1216            blend,
1217            None,
1218        )
1219    }
1220
1221    pub fn push_round_rect(
1222        &mut self,
1223        rect: Rect,
1224        brush: &Brush,
1225        radii: CornerRadii,
1226        stroke: Option<Stroke>,
1227        blend: BlendMode,
1228    ) -> Rect {
1229        let (handle, color) = self.intern_brush(brush);
1230        let mut flags = pack_flags(RECORD_KIND_ROUND_RECT, stroke, blend, StrokeCap::Butt);
1231        let mut arc_geometry = [0.0; 4];
1232        let mut source = [0.0; 4];
1233        let mut arc_normalized = [0.0; 4];
1234        let mut bucket = None;
1235        if let Some(ring) = stroked_circle_ring(rect, radii, stroke)
1236            && let band = BandRing::of_geometry(&ring)
1237            && let Some(ring_bucket) = band_bucket_for(
1238                &ring,
1239                ring.is_degenerate(),
1240                &band,
1241                expand_rect(rect, ring.half_thickness()),
1242            )
1243        {
1244            flags |= ARC_BANDED_BIT;
1245            arc_geometry = [
1246                ring.center.x,
1247                ring.center.y,
1248                ring.inner_radius,
1249                ring.outer_radius,
1250            ];
1251            source = [ring.mid_radius(), ring.inner_radius, 0.0, TAU];
1252            arc_normalized = [0.0, TAU, 0.0, 0.0];
1253            bucket = Some(ring_bucket);
1254        }
1255        self.push_shape(
1256            ShapeRecordBody {
1257                rect: rect_row(rect),
1258                color,
1259                stroke_width: stroke.map_or(0.0, |stroke| stroke.width),
1260                flags,
1261                brush: handle,
1262                placement: 0,
1263                arc_geometry,
1264            },
1265            ShapeRecordCurve {
1266                radii: [
1267                    radii.top_left,
1268                    radii.top_right,
1269                    radii.bottom_right,
1270                    radii.bottom_left,
1271                ],
1272                arc_normalized,
1273            },
1274            source,
1275            blend,
1276            bucket,
1277        )
1278    }
1279
1280    /// Records an arc band or annular sector with the rect the primitive
1281    /// carries.
1282    pub fn push_arc(&mut self, rect: Rect, args: &ArcRecordArgs<'_>) -> Rect {
1283        let geometry = normalized_band(args);
1284        self.push_arc_band(args, &geometry, Some(rect), geometry.is_degenerate())
1285    }
1286
1287    /// Records an arc the draw scope drew, whose band the scope already
1288    /// normalised: the record keeps the disc around the band as its rect
1289    /// and derives the primitive's tight bounds only when asked.
1290    ///
1291    /// The scope records only bands that enclose area, so the band is known
1292    /// not to be degenerate.
1293    #[inline]
1294    pub fn push_scope_arc(&mut self, args: &ArcRecordArgs<'_>, geometry: &ArcGeometry) -> Rect {
1295        debug_assert!(
1296            !geometry.is_degenerate(),
1297            "the scope skips degenerate bands"
1298        );
1299        self.push_arc_band(args, geometry, None, false)
1300    }
1301
1302    #[inline]
1303    fn push_arc_band(
1304        &mut self,
1305        args: &ArcRecordArgs<'_>,
1306        geometry: &ArcGeometry,
1307        rect: Option<Rect>,
1308        degenerate: bool,
1309    ) -> Rect {
1310        let (handle, color) = self.intern_brush(args.brush);
1311        let mut flags = pack_flags(RECORD_KIND_ARC, args.stroke, args.blend_mode, geometry.cap);
1312        if degenerate {
1313            flags |= ARC_DEGENERATE_BIT;
1314        }
1315        let rect = rect.unwrap_or_else(|| {
1316            flags |= ARC_RECT_LOOSE_BIT;
1317            band_disc(geometry)
1318        });
1319        let ring = BandRing::of_geometry(geometry);
1320        let bucket = band_bucket_for(geometry, degenerate, &ring, rect);
1321        if bucket.is_some() {
1322            flags |= ARC_BANDED_BIT;
1323        }
1324        self.push_shape(
1325            ShapeRecordBody {
1326                rect: rect_row(rect),
1327                color,
1328                stroke_width: args.stroke.map_or(0.0, |stroke| stroke.width),
1329                flags,
1330                brush: handle,
1331                placement: 0,
1332                arc_geometry: [
1333                    args.center.x,
1334                    args.center.y,
1335                    geometry.inner_radius,
1336                    geometry.outer_radius,
1337                ],
1338            },
1339            ShapeRecordCurve {
1340                radii: [0.0; 4],
1341                arc_normalized: [geometry.start_angle, geometry.sweep_angle, 0.0, 0.0],
1342            },
1343            [
1344                args.radius,
1345                args.inner_radius,
1346                args.start_angle,
1347                args.sweep_angle,
1348            ],
1349            args.blend_mode,
1350            bucket,
1351        )
1352    }
1353
1354    #[inline(always)]
1355    fn push_shape(
1356        &mut self,
1357        body: ShapeRecordBody,
1358        curve: ShapeRecordCurve,
1359        source: [f32; 4],
1360        blend: BlendMode,
1361        band_bucket: Option<usize>,
1362    ) -> Rect {
1363        let shading = self.brush_shading(&body, &curve);
1364        self.push_shaded(body, curve, source, (blend, shading), band_bucket)
1365    }
1366
1367    /// [`Self::push_shape`] for a record whose brush shading is known.
1368    fn push_shaded(
1369        &mut self,
1370        mut body: ShapeRecordBody,
1371        curve: ShapeRecordCurve,
1372        source: [f32; 4],
1373        (blend, shading): (BlendMode, BrushShading),
1374        band_bucket: Option<usize>,
1375    ) -> Rect {
1376        let half_stroke = if body.flags & STROKED_BIT != 0 {
1377            body.stroke_width * 0.5
1378        } else {
1379            0.0
1380        };
1381        let coverage = expand_rect(row_rect(body.rect), half_stroke);
1382        self.include_bounds(coverage);
1383        let index = self.tables.shapes.len() as u32;
1384        body.flags |= shading.flag();
1385        let brush_bit = 1u8
1386            << match body.brush {
1387                0 => 0,
1388                index => self.tables.brushes[index as usize - 1].kind,
1389            };
1390        let kind_bit = 1u8 << fragment_kind(body.flags);
1391        let band_class = band_bucket.unwrap_or(0) as u8;
1392        body.flags |= u32::from(band_class) << BAND_CLASS_SHIFT;
1393        let extend =
1394            self.note_segment_key(RecordLane::Shapes, blend, shading, is_trapezoid(body.flags))
1395                && self.segment_takes_class(band_class);
1396        if !extend {
1397            self.segment_waste = 0;
1398        }
1399        // A record joining a segment matters to its interior flags only
1400        // where it could set one still clear, so its interior is measured
1401        // only then: thousands of small shapes join a segment the first
1402        // repaying one has already marked.
1403        let joined = extend.then(|| self.tables.segments.last()).flatten();
1404        let translucent = body.brush != 0 || body.color[3] < 1.0;
1405        let interiors = !joined
1406            .is_some_and(|segment| segment.interiors && (segment.bare_interiors || !translucent))
1407            && interior_repays(&body, &curve);
1408        let occluders =
1409            !joined.is_some_and(|segment| segment.occluders) && interior_occludes(&body, &curve);
1410        let bare_interiors = interiors && translucent;
1411        let tables = self.tables_mut();
1412        tables.shapes.push(body, curve, source);
1413        extend_segment_in(
1414            tables,
1415            extend,
1416            RecordSegment {
1417                lane: RecordLane::Shapes,
1418                start: index,
1419                count: 1,
1420                blend,
1421                gradient: shading == BrushShading::Fragment,
1422                vertex_gradient: shading == BrushShading::Vertex,
1423                brushes: brush_bit,
1424                kinds: kind_bit,
1425                band_class,
1426                interiors,
1427                occluders,
1428                bare_interiors,
1429            },
1430        );
1431        coverage
1432    }
1433
1434    fn extend_segment(&mut self, lane: RecordLane, index: u32, blend: BlendMode, kind_bit: u8) {
1435        let extend = self.note_segment_key(lane, blend, BrushShading::Solid, false);
1436        if !extend {
1437            self.segment_waste = 0;
1438        }
1439        extend_segment_in(
1440            self.tables_mut(),
1441            extend,
1442            RecordSegment {
1443                lane,
1444                start: index,
1445                count: 1,
1446                blend,
1447                gradient: false,
1448                vertex_gradient: false,
1449                brushes: 0,
1450                kinds: kind_bit,
1451                band_class: 0,
1452                interiors: false,
1453                occluders: false,
1454                bare_interiors: false,
1455            },
1456        );
1457    }
1458
1459    /// Whether the open segment takes a record of `band_class` without
1460    /// leaving more than [`SEGMENT_WASTE_QUADS`] pinned: a larger class
1461    /// raises every earlier record's budget, a smaller one collapses its
1462    /// own surplus. Accounts the waste when it does.
1463    #[inline]
1464    fn segment_takes_class(&mut self, band_class: u8) -> bool {
1465        let last = self.tables.segments.last().expect("a keyed segment exists");
1466        let held = ARC_BUCKET_SEGMENTS[last.band_class as usize];
1467        let wanted = ARC_BUCKET_SEGMENTS[band_class as usize];
1468        let waste = if wanted > held {
1469            last.count * (wanted - held)
1470        } else {
1471            held - wanted
1472        };
1473        if self.segment_waste + waste > SEGMENT_WASTE_QUADS {
1474            return false;
1475        }
1476        self.segment_waste += waste;
1477        true
1478    }
1479
1480    /// Whether the next record continues the open segment, and makes its
1481    /// key the open one. Slices of path fills keep segments of their own: a
1482    /// slice whose gradient rides on its vertices is exact only in the
1483    /// pipelines that draw slices alone.
1484    #[inline]
1485    fn note_segment_key(
1486        &mut self,
1487        lane: RecordLane,
1488        blend: BlendMode,
1489        shading: BrushShading,
1490        slice: bool,
1491    ) -> bool {
1492        let key = ((lane as u32) << 16)
1493            | (u32::from(slice) << 15)
1494            | ((blend as u32) << 2)
1495            | shading as u32;
1496        let extend = key == self.last_segment_key;
1497        self.last_segment_key = key;
1498        extend
1499    }
1500
1501    /// Widens the bounds to `rect`; plain compares, not the NaN-ordering
1502    /// `f32::min`, which armv7 lowers to a libm call per edge.
1503    #[inline]
1504    fn include_bounds(&mut self, rect: Rect) {
1505        let right = rect.x + rect.width;
1506        let bottom = rect.y + rect.height;
1507        if rect.x < self.min[0] {
1508            self.min[0] = rect.x;
1509        }
1510        if rect.y < self.min[1] {
1511            self.min[1] = rect.y;
1512        }
1513        if right > self.max[0] {
1514            self.max[0] = right;
1515        }
1516        if bottom > self.max[1] {
1517            self.max[1] = bottom;
1518        }
1519    }
1520
1521    #[inline]
1522    /// How a record's brush is shaded: solid, from its vertices, or per
1523    /// fragment. A rect fill takes a linear gradient from its vertices where
1524    /// the gradient is affine over its quad; a slice of a path fill where it
1525    /// runs between two stops and clamps, which the slice's fragments clamp
1526    /// exactly as the gradient sampler does.
1527    fn brush_shading(&self, body: &ShapeRecordBody, curve: &ShapeRecordCurve) -> BrushShading {
1528        if body.brush == 0 {
1529            return BrushShading::Solid;
1530        }
1531        let tables = &self.tables;
1532        let Some(brush) = tables.brushes.get(body.brush as usize - 1) else {
1533            return BrushShading::Fragment;
1534        };
1535        let vertex = if is_trapezoid(body.flags) {
1536            vertex_gradient::clamps_between_two_stops(brush, &tables.stops, curve.radii)
1537        } else {
1538            body.flags & RECT_FILL_MASK == 0
1539                && vertex_gradient::spans_quad(brush, &tables.stops, body.rect)
1540        };
1541        if vertex {
1542            BrushShading::Vertex
1543        } else {
1544            BrushShading::Fragment
1545        }
1546    }
1547
1548    /// The brush handle a record carries and its colour row: the colour
1549    /// itself for a solid brush, the first stop for a gradient, which is
1550    /// interned into the brush and stop tables.
1551    #[inline]
1552    fn intern_brush(&mut self, brush: &Brush) -> (u32, [f32; 4]) {
1553        match brush {
1554            Brush::Solid(color) => (0, [color.0, color.1, color.2, color.3]),
1555            gradient => self.intern_gradient(gradient),
1556        }
1557    }
1558
1559    #[cold]
1560    #[inline(never)]
1561    fn intern_gradient(&mut self, brush: &Brush) -> (u32, [f32; 4]) {
1562        let (kind, tile_mode, params, colors, stops) = match brush {
1563            Brush::Solid(color) => return (0, [color.0, color.1, color.2, color.3]),
1564            Brush::LinearGradient {
1565                colors,
1566                stops,
1567                start,
1568                end,
1569                tile_mode,
1570            } => (
1571                BRUSH_KIND_LINEAR,
1572                *tile_mode,
1573                [start.x, start.y, end.x, end.y],
1574                colors,
1575                stops,
1576            ),
1577            Brush::RadialGradient {
1578                colors,
1579                stops,
1580                center,
1581                radius,
1582                tile_mode,
1583            } => (
1584                BRUSH_KIND_RADIAL,
1585                *tile_mode,
1586                [center.x, center.y, *radius, 0.0],
1587                colors,
1588                stops,
1589            ),
1590            Brush::SweepGradient {
1591                colors,
1592                stops,
1593                center,
1594            } => (
1595                BRUSH_KIND_SWEEP,
1596                TileMode::Clamp,
1597                [center.x, center.y, 0.0, 0.0],
1598                colors,
1599                stops,
1600            ),
1601        };
1602        let tables = self.tables_mut();
1603        let stop_start = tables.stops.len() as u32;
1604        let count = colors.len();
1605        let positions = stops.as_deref().filter(|values| values.len() == count);
1606        for (index, color) in colors.iter().enumerate() {
1607            let position = positions.map_or_else(
1608                || {
1609                    if count <= 1 {
1610                        0.0
1611                    } else {
1612                        index as f32 / (count - 1) as f32
1613                    }
1614                },
1615                |values| values[index],
1616            );
1617            tables.stops.push(GradientStopRecord {
1618                color: [color.0, color.1, color.2, color.3],
1619                position: [position, 0.0, 0.0, 0.0],
1620            });
1621        }
1622        let (explicit_start, explicit_len) = match stops {
1623            Some(values) => {
1624                let start = tables.explicit_stops.len() as u32;
1625                tables.explicit_stops.extend_from_slice(values);
1626                (start, values.len() as u32)
1627            }
1628            None => (0, NO_EXPLICIT_STOPS),
1629        };
1630        let record = BrushRecord {
1631            kind,
1632            tile_mode: tile_mode as u32,
1633            stop_start,
1634            stop_count: count as u32,
1635            params,
1636            explicit_start,
1637            explicit_len,
1638            reserved: [0; 2],
1639        };
1640        tables.brushes.push(record);
1641        let handle = tables.brushes.len() as u32;
1642        let first = colors.first().copied().unwrap_or(Color(0.0, 0.0, 0.0, 0.0));
1643        (handle, [first.0, first.1, first.2, first.3])
1644    }
1645}
1646
1647/// A completed draw command with shared shape data, ordered segments and
1648/// non-shape primitives. Bounds and content metadata are recorded while drawing;
1649/// the complete fingerprint is computed when a cache first requests it.
1650/// Use [`CommandRecorder`] to build a command and [`Self::into_recorder`] to edit it.
1651#[derive(Clone, Debug)]
1652pub struct CommandRecording {
1653    shapes: Arc<ShapeRecorder>,
1654    content: RecordingContent,
1655    fingerprint: OnceCell<u64>,
1656}
1657
1658#[derive(Clone, Debug)]
1659struct RecordingContent {
1660    others: Vec<DrawPrimitive>,
1661    min: [f32; 2],
1662    max: [f32; 2],
1663    summary: RecordingSummary,
1664    content_markers: u32,
1665}
1666
1667impl Default for RecordingContent {
1668    fn default() -> Self {
1669        Self {
1670            others: Vec::new(),
1671            min: [f32::INFINITY; 2],
1672            max: [f32::NEG_INFINITY; 2],
1673            summary: RecordingSummary::default(),
1674            content_markers: 0,
1675        }
1676    }
1677}
1678
1679thread_local! {
1680    /// The shapes every empty recording shares, so making one allocates
1681    /// nothing.
1682    static NO_SHAPES: Arc<ShapeRecorder> = Arc::new(ShapeRecorder::default());
1683}
1684
1685impl Default for CommandRecording {
1686    fn default() -> Self {
1687        Self {
1688            shapes: NO_SHAPES.with(Arc::clone),
1689            content: RecordingContent::default(),
1690            fingerprint: OnceCell::new(),
1691        }
1692    }
1693}
1694
1695impl PartialEq for CommandRecording {
1696    fn eq(&self, other: &Self) -> bool {
1697        self.shapes == other.shapes
1698            && self.content.others == other.content.others
1699            && self.content.content_markers == other.content.content_markers
1700    }
1701}
1702
1703impl CommandRecording {
1704    /// Takes this recording when no retained reader shares its shape data.
1705    ///
1706    /// The returned recording keeps the existing data and capacities. This
1707    /// recording is reset to an empty value. Pass the returned recording to
1708    /// [`CommandRecorder::reusing`] to clear it for another draw. If another
1709    /// strong or weak reference shares the shape data, this returns `None` and
1710    /// leaves the recording unchanged.
1711    ///
1712    /// ```
1713    /// use cranpose_ui_graphics::{Brush, Color, CommandRecorder, DrawPrimitive, Rect};
1714    ///
1715    /// let mut builder = CommandRecorder::default();
1716    /// builder.push_primitive(DrawPrimitive::Rect {
1717    ///     rect: Rect {
1718    ///         x: 0.0,
1719    ///         y: 0.0,
1720    ///         width: 1.0,
1721    ///         height: 1.0,
1722    ///     },
1723    ///     brush: Brush::Solid(Color::RED),
1724    ///     stroke: None,
1725    /// });
1726    /// let mut completed = builder.finish();
1727    /// if let Some(storage) = completed.try_take_reusable() {
1728    ///     let mut recorder = CommandRecorder::reusing(storage);
1729    ///     recorder.push_content();
1730    ///     let _next = recorder.finish();
1731    /// }
1732    /// ```
1733    pub fn try_take_reusable(&mut self) -> Option<Self> {
1734        Arc::get_mut(&mut self.shapes)?;
1735        Some(std::mem::take(self))
1736    }
1737
1738    /// Returns owned command data for further recording.
1739    /// Shape data is copied only when a retained reader still shares it.
1740    pub fn into_recorder(self) -> CommandRecorder {
1741        let (shapes, vessel) = take_shapes(self.shapes, ShapeRecorder::clone);
1742        CommandRecorder {
1743            shapes,
1744            content: self.content,
1745            vessel,
1746        }
1747    }
1748
1749    pub fn from_primitives(primitives: impl IntoIterator<Item = DrawPrimitive>) -> Self {
1750        CommandRecorder::from_primitives(primitives).finish()
1751    }
1752
1753    pub fn shape_capacity(&self) -> usize {
1754        self.shapes.tables.shapes.capacity()
1755    }
1756
1757    /// The heap the POD tables hold, capacity included.
1758    pub fn pod_heap_bytes(&self) -> usize {
1759        self.shapes.tables.heap_bytes()
1760    }
1761
1762    /// The published shape columns and their brush, stop and segment tables.
1763    pub fn tables(&self) -> &RecordTables {
1764        self.shapes.tables()
1765    }
1766
1767    /// Shared ownership of the immutable shape recording.
1768    pub fn shape_recorder(&self) -> &Arc<ShapeRecorder> {
1769        &self.shapes
1770    }
1771
1772    /// The entries inside `segments` that draw, content markers left out.
1773    pub fn len_in(&self, segments: &Range<u32>) -> usize {
1774        self.segments_in(segments)
1775            .filter(|segment| segment.lane != RecordLane::Content)
1776            .map(|segment| segment.count as usize)
1777            .sum()
1778    }
1779
1780    /// The shape columns, with complete records available on demand.
1781    pub fn shapes(&self) -> &ShapeRecords {
1782        &self.shapes.tables.shapes
1783    }
1784
1785    pub fn brushes(&self) -> &[BrushRecord] {
1786        &self.shapes.tables.brushes
1787    }
1788
1789    pub fn stops(&self) -> &[GradientStopRecord] {
1790        &self.shapes.tables.stops
1791    }
1792
1793    pub fn others(&self) -> &[DrawPrimitive] {
1794        &self.content.others
1795    }
1796
1797    pub fn segments(&self) -> &[RecordSegment] {
1798        &self.shapes.tables.segments
1799    }
1800
1801    /// Every segment, the range a placement without a content split draws.
1802    pub fn all_segments(&self) -> Range<u32> {
1803        0..self.shapes.tables.segments.len() as u32
1804    }
1805
1806    /// A rect containing every entry's coverage rect: exact for rects and
1807    /// the other lanes, the disc around the band for a scope-recorded arc.
1808    pub fn bounds(&self) -> Option<Rect> {
1809        let others = (self.content.min[0] <= self.content.max[0]
1810            && self.content.min[1] <= self.content.max[1])
1811            .then(|| Rect {
1812                x: self.content.min[0],
1813                y: self.content.min[1],
1814                width: self.content.max[0] - self.content.min[0],
1815                height: self.content.max[1] - self.content.min[1],
1816            });
1817        match (self.shapes.bounds(), others) {
1818            (Some(shapes), Some(others)) => Some(union_rect(shapes, others)),
1819            (shapes, others) => shapes.or(others),
1820        }
1821    }
1822
1823    pub fn summary(&self) -> RecordingSummary {
1824        self.content.summary
1825    }
1826
1827    pub fn content_markers(&self) -> u32 {
1828        self.content.content_markers
1829    }
1830
1831    /// Entries of every lane, content markers included.
1832    pub fn len(&self) -> usize {
1833        self.shapes.tables.shapes.len()
1834            + self.content.others.len()
1835            + self.content.content_markers as usize
1836    }
1837
1838    pub fn is_empty(&self) -> bool {
1839        self.len() == 0
1840    }
1841
1842    /// A hash over every entry, table and marker in recorded order, so two
1843    /// recordings with equal fingerprints draw the same pixels. Computed
1844    /// the first time it is asked for, so a recording nothing caches never
1845    /// pays for it.
1846    pub fn fingerprint(&self) -> u64 {
1847        *self.fingerprint.get_or_init(|| {
1848            let mut hasher = FxHasher::default();
1849            hasher.write_u64(self.shapes.fingerprint());
1850            for primitive in &self.content.others {
1851                hasher.write_u64(primitive.render_hash());
1852            }
1853            hasher.write_u32(self.content.content_markers);
1854            hasher.finish()
1855        })
1856    }
1857
1858    /// The segments of `segments` that draw anything.
1859    pub fn is_empty_in(&self, segments: &Range<u32>) -> bool {
1860        !self
1861            .segments_in(segments)
1862            .any(|segment| segment.lane != RecordLane::Content && segment.count > 0)
1863    }
1864
1865    pub fn segments_in(&self, segments: &Range<u32>) -> std::slice::Iter<'_, RecordSegment> {
1866        self.shapes.tables.segments[segments.start as usize..segments.end as usize].iter()
1867    }
1868
1869    /// The summary of the entries inside `segments` only.
1870    pub fn summary_in(&self, segments: &Range<u32>) -> RecordingSummary {
1871        if *segments == self.all_segments() {
1872            return self.content.summary;
1873        }
1874        let mut summary = RecordingSummary::default();
1875        for segment in self.segments_in(segments) {
1876            match segment.lane {
1877                RecordLane::Shapes if segment.count > 0 => {
1878                    summary.has_non_shadow = true;
1879                    summary.has_shapes = true;
1880                }
1881                RecordLane::Others => {
1882                    summary.has_others = true;
1883                    for primitive in &self.content.others[segment.range()] {
1884                        summary.note(primitive);
1885                    }
1886                }
1887                RecordLane::Content => summary.has_content_markers = true,
1888                RecordLane::Shapes => {}
1889            }
1890        }
1891        summary
1892    }
1893
1894    /// The segments before the last content marker (`behind`) or after it;
1895    /// with no marker, behind is empty and overlay is everything.
1896    pub fn content_split(&self, behind: bool) -> Range<u32> {
1897        let last_marker = self
1898            .shapes
1899            .tables
1900            .segments
1901            .iter()
1902            .rposition(|segment| segment.lane == RecordLane::Content);
1903        match (last_marker, behind) {
1904            (Some(index), true) => 0..index as u32,
1905            (Some(index), false) => index as u32 + 1..self.shapes.tables.segments.len() as u32,
1906            (None, true) => 0..0,
1907            (None, false) => self.all_segments(),
1908        }
1909    }
1910
1911    /// The coverage rect of every entry inside `segments`.
1912    pub fn coverage_rects(&self, segments: Range<u32>) -> impl Iterator<Item = Rect> + '_ {
1913        self.segments_in(&segments).flat_map(move |segment| {
1914            segment.range().filter_map(move |index| match segment.lane {
1915                RecordLane::Shapes => self
1916                    .shapes
1917                    .tables
1918                    .shapes
1919                    .get(index)
1920                    .map(|record| record.coverage_rect()),
1921                RecordLane::Others => primitive_coverage_rect(&self.content.others[index]),
1922                RecordLane::Content => None,
1923            })
1924        })
1925    }
1926
1927    /// The primitives inside `segments`, materialised in recorded order,
1928    /// content markers left out.
1929    pub fn primitives(&self, segments: Range<u32>) -> impl Iterator<Item = DrawPrimitive> + '_ {
1930        self.segments_in(&segments)
1931            .flat_map(|segment| self.segment_primitives(segment, false))
1932    }
1933
1934    /// Every primitive in recorded order, content markers included.
1935    pub fn primitives_with_markers(&self) -> impl Iterator<Item = DrawPrimitive> + '_ {
1936        self.shapes
1937            .tables
1938            .segments
1939            .iter()
1940            .flat_map(|segment| self.segment_primitives(segment, true))
1941    }
1942
1943    pub fn into_primitives_with_markers(self) -> Vec<DrawPrimitive> {
1944        self.primitives_with_markers().collect()
1945    }
1946
1947    fn segment_primitives<'a>(
1948        &'a self,
1949        segment: &RecordSegment,
1950        markers: bool,
1951    ) -> impl Iterator<Item = DrawPrimitive> + use<'a> {
1952        let lane = segment.lane;
1953        segment.range().filter_map(move |index| match lane {
1954            RecordLane::Shapes => Some(self.materialize_shape(index)),
1955            RecordLane::Others => Some(self.content.others[index].clone()),
1956            RecordLane::Content => markers.then_some(DrawPrimitive::Content),
1957        })
1958    }
1959
1960    /// The exact [`DrawPrimitive`] the record was made from.
1961    pub fn materialize_shape(&self, index: usize) -> DrawPrimitive {
1962        let record = self
1963            .shapes
1964            .tables
1965            .shapes
1966            .get(index)
1967            .expect("recorded shape index");
1968        let rect = record.rect_value();
1969        let brush = self.brush_of(&record);
1970        let stroke = record.stroke();
1971        let primitive = match (record.kind(), record.trapezoid()) {
1972            (_, Some(trapezoid)) => DrawPrimitive::Trapezoid {
1973                rect: row_rect(record.radii),
1974                brush,
1975                trapezoid,
1976            },
1977            (RECORD_KIND_ROUND_RECT, _) => DrawPrimitive::RoundRect {
1978                rect,
1979                brush,
1980                radii: CornerRadii {
1981                    top_left: record.radii[0],
1982                    top_right: record.radii[1],
1983                    bottom_right: record.radii[2],
1984                    bottom_left: record.radii[3],
1985                },
1986                stroke,
1987            },
1988            (RECORD_KIND_LINE, _) => DrawPrimitive::Line {
1989                rect,
1990                brush,
1991                start: Point::new(record.arc[0], record.arc[1]),
1992                end: Point::new(record.arc_band[2], record.arc_band[3]),
1993                stroke: stroke.unwrap_or_default(),
1994            },
1995            (RECORD_KIND_ARC, _) => DrawPrimitive::Arc {
1996                rect,
1997                brush,
1998                center: Point::new(record.arc[0], record.arc[1]),
1999                radius: record.arc[2],
2000                start_angle: record.arc_band[0],
2001                sweep_angle: record.arc_band[1],
2002                stroke,
2003                inner_radius: record.arc[3],
2004            },
2005            _ => DrawPrimitive::Rect {
2006                rect,
2007                brush,
2008                stroke,
2009            },
2010        };
2011        let blend_mode = record.blend_mode();
2012        if blend_mode == BlendMode::SrcOver {
2013            primitive
2014        } else {
2015            DrawPrimitive::Blend {
2016                primitive: Box::new(primitive),
2017                blend_mode,
2018            }
2019        }
2020    }
2021
2022    /// The brush of a record: its solid colour, or the gradient rebuilt
2023    /// from the tables exactly as the app gave it.
2024    pub fn brush_of(&self, record: &ShapeRecord) -> Brush {
2025        if record.brush == 0 {
2026            return Brush::Solid(Color(
2027                record.color[0],
2028                record.color[1],
2029                record.color[2],
2030                record.color[3],
2031            ));
2032        }
2033        let tables = &self.shapes.tables;
2034        let brush = &tables.brushes[record.brush as usize - 1];
2035        let colors = tables.stops
2036            [brush.stop_start as usize..(brush.stop_start + brush.stop_count) as usize]
2037            .iter()
2038            .map(|stop| Color(stop.color[0], stop.color[1], stop.color[2], stop.color[3]))
2039            .collect();
2040        let stops = (brush.explicit_len != NO_EXPLICIT_STOPS).then(|| {
2041            tables.explicit_stops[brush.explicit_start as usize
2042                ..(brush.explicit_start + brush.explicit_len) as usize]
2043                .to_vec()
2044        });
2045        let [a, b, c, d] = brush.params;
2046        let tile_mode = TILE_MODES[brush.tile_mode as usize];
2047        match brush.kind {
2048            BRUSH_KIND_RADIAL => Brush::RadialGradient {
2049                colors,
2050                stops,
2051                center: Point::new(a, b),
2052                radius: c,
2053                tile_mode,
2054            },
2055            BRUSH_KIND_SWEEP => Brush::SweepGradient {
2056                colors,
2057                stops,
2058                center: Point::new(a, b),
2059            },
2060            _ => Brush::LinearGradient {
2061                colors,
2062                stops,
2063                start: Point::new(a, b),
2064                end: Point::new(c, d),
2065                tile_mode,
2066            },
2067        }
2068    }
2069}
2070
2071/// Mutable command data owned exclusively while a draw scope records it.
2072/// Publishing with [`Self::finish`] shares the completed shape data without copying it.
2073#[derive(Debug, Default)]
2074pub struct CommandRecorder {
2075    shapes: ShapeRecorder,
2076    content: RecordingContent,
2077    /// The emptied shape allocation of the recording this recorder took its
2078    /// data from, which [`Self::finish`] fills again instead of allocating.
2079    vessel: Option<Arc<ShapeRecorder>>,
2080}
2081
2082impl Clone for CommandRecorder {
2083    fn clone(&self) -> Self {
2084        Self {
2085            shapes: self.shapes.clone(),
2086            content: self.content.clone(),
2087            vessel: None,
2088        }
2089    }
2090}
2091
2092/// The shapes `shared` holds and, when nothing else holds it, its emptied
2093/// allocation; otherwise `copy` of them, for a retained reader keeps its own.
2094fn take_shapes(
2095    mut shared: Arc<ShapeRecorder>,
2096    copy: impl FnOnce(&ShapeRecorder) -> ShapeRecorder,
2097) -> (ShapeRecorder, Option<Arc<ShapeRecorder>>) {
2098    match Arc::get_mut(&mut shared) {
2099        Some(shapes) => (std::mem::take(shapes), Some(shared)),
2100        None => (copy(&shared), None),
2101    }
2102}
2103
2104/// `shapes` shared, in `vessel` when it is still the only handle to it.
2105fn fill_vessel(vessel: Option<Arc<ShapeRecorder>>, shapes: ShapeRecorder) -> Arc<ShapeRecorder> {
2106    let Some(mut vessel) = vessel else {
2107        return Arc::new(shapes);
2108    };
2109    match Arc::get_mut(&mut vessel) {
2110        Some(slot) => {
2111            *slot = shapes;
2112            vessel
2113        }
2114        None => Arc::new(shapes),
2115    }
2116}
2117
2118impl CommandRecorder {
2119    /// Records primitives in order into owned command data.
2120    pub fn from_primitives(primitives: impl IntoIterator<Item = DrawPrimitive>) -> Self {
2121        let mut recorder = Self::default();
2122        for primitive in primitives {
2123            recorder.push_primitive(primitive);
2124        }
2125        recorder
2126    }
2127
2128    /// Reuses a completed command's buffer capacities for an empty recording.
2129    /// Retained readers keep their original shape data.
2130    pub fn reusing(recording: CommandRecording) -> Self {
2131        let (shapes, vessel) = take_shapes(recording.shapes, |shared| ShapeRecorder {
2132            tables: shared.tables.with_capacity_of(),
2133            ..ShapeRecorder::default()
2134        });
2135        let mut recorder = Self {
2136            shapes,
2137            content: recording.content,
2138            vessel,
2139        };
2140        recorder.clear();
2141        recorder
2142    }
2143
2144    /// Publishes completed command data without copying its shape columns.
2145    pub fn finish(self) -> CommandRecording {
2146        CommandRecording {
2147            shapes: fill_vessel(self.vessel, self.shapes),
2148            content: self.content,
2149            fingerprint: OnceCell::new(),
2150        }
2151    }
2152
2153    /// Every recorded entry, including content markers.
2154    pub fn len(&self) -> usize {
2155        self.shapes.tables.shapes.len()
2156            + self.content.others.len()
2157            + self.content.content_markers as usize
2158    }
2159
2160    /// Whether this recorder contains no entries.
2161    pub fn is_empty(&self) -> bool {
2162        self.len() == 0
2163    }
2164
2165    /// The number of recorded content markers.
2166    pub fn content_markers(&self) -> u32 {
2167        self.content.content_markers
2168    }
2169
2170    /// Empties the recording, keeping every buffer's capacity for the next
2171    /// recording into it.
2172    pub fn clear(&mut self) {
2173        self.shapes.clear();
2174        self.content.others.clear();
2175        self.content.min = [f32::INFINITY; 2];
2176        self.content.max = [f32::NEG_INFINITY; 2];
2177        self.content.summary = RecordingSummary::default();
2178        self.content.content_markers = 0;
2179    }
2180
2181    /// Reserves capacity for additional shape records.
2182    pub fn reserve_shapes(&mut self, additional: usize) {
2183        self.shapes.tables_mut().shapes.reserve(additional);
2184    }
2185
2186    /// Records a content marker at the current command position.
2187    pub fn push_content(&mut self) {
2188        self.content.content_markers += 1;
2189        self.content.summary.has_content_markers = true;
2190        self.shapes.push_content_segment();
2191    }
2192
2193    /// Records a primitive the way the draw scope would have: shapes and
2194    /// blended shapes become records, everything else joins the others lane.
2195    pub fn push_primitive(&mut self, primitive: DrawPrimitive) {
2196        if matches!(primitive, DrawPrimitive::Content) {
2197            self.push_content();
2198            return;
2199        }
2200        match self.shapes.push_primitive(primitive) {
2201            Recorded::Shape(_) => self.note_shape(),
2202            Recorded::Other(other) => self.push_other(other),
2203        }
2204    }
2205
2206    #[inline]
2207    fn note_shape(&mut self) {
2208        self.content.summary.has_non_shadow = true;
2209        self.content.summary.has_shapes = true;
2210    }
2211
2212    fn include_bounds(&mut self, rect: Rect) {
2213        self.content.min[0] = self.content.min[0].min(rect.x);
2214        self.content.min[1] = self.content.min[1].min(rect.y);
2215        self.content.max[0] = self.content.max[0].max(rect.x + rect.width);
2216        self.content.max[1] = self.content.max[1].max(rect.y + rect.height);
2217    }
2218
2219    /// Records a rectangle with its brush, optional stroke and blend mode.
2220    pub fn push_rect(
2221        &mut self,
2222        rect: Rect,
2223        brush: &Brush,
2224        stroke: Option<Stroke>,
2225        blend: BlendMode,
2226    ) {
2227        self.shapes.push_rect(rect, brush, stroke, blend);
2228        self.note_shape();
2229    }
2230
2231    /// Records a rounded rectangle with its corner radii and paint.
2232    pub fn push_round_rect(
2233        &mut self,
2234        rect: Rect,
2235        brush: &Brush,
2236        radii: CornerRadii,
2237        stroke: Option<Stroke>,
2238        blend: BlendMode,
2239    ) {
2240        self.shapes
2241            .push_round_rect(rect, brush, radii, stroke, blend);
2242        self.note_shape();
2243    }
2244
2245    /// Records an arc band or annular sector with the rect the primitive
2246    /// carries.
2247    #[inline]
2248    pub fn push_arc(&mut self, rect: Rect, args: &ArcRecordArgs<'_>) {
2249        self.shapes.push_arc(rect, args);
2250        self.note_shape();
2251    }
2252
2253    /// Records an arc the draw scope drew, whose band the scope already
2254    /// normalised: the record keeps the disc around the band as its rect
2255    /// and derives the primitive's tight bounds only when asked.
2256    #[inline]
2257    pub fn push_scope_arc(&mut self, args: &ArcRecordArgs<'_>, geometry: &ArcGeometry) {
2258        self.shapes.push_scope_arc(args, geometry);
2259        self.note_shape();
2260    }
2261
2262    /// Records the segment `line` a draw scope stroked.
2263    pub fn push_line(
2264        &mut self,
2265        line: &LineGeometry,
2266        brush: &Brush,
2267        stroke: Stroke,
2268        blend: BlendMode,
2269    ) {
2270        self.shapes.push_line(line, brush, stroke, blend);
2271        self.note_shape();
2272    }
2273
2274    /// Records the slices of a path fill a draw scope cut; see
2275    /// [`ShapeRecorder::push_trapezoids`].
2276    pub fn push_trapezoids(
2277        &mut self,
2278        brush_rect: Rect,
2279        trapezoids: &[Trapezoid],
2280        brush: &Brush,
2281        blend: BlendMode,
2282    ) {
2283        if trapezoids.is_empty() {
2284            return;
2285        }
2286        self.shapes
2287            .push_trapezoids(brush_rect, trapezoids, brush, blend);
2288        self.note_shape();
2289    }
2290
2291    /// Records a primitive in the non-shape lane and updates its metadata.
2292    pub fn push_other(&mut self, primitive: DrawPrimitive) {
2293        self.content.summary.has_others = true;
2294        self.content.summary.note(&primitive);
2295        if let Some(rect) = primitive_coverage_rect(&primitive) {
2296            self.include_bounds(rect);
2297        }
2298        let index = self.content.others.len() as u32;
2299        self.content.others.push(primitive);
2300        self.shapes
2301            .extend_segment(RecordLane::Others, index, BlendMode::SrcOver, 0);
2302    }
2303
2304    /// Folds `other`'s summary into this recording's, for callers that
2305    /// combine recordings.
2306    pub fn merge_summary(&mut self, other: RecordingSummary) {
2307        self.content.summary.merge(other);
2308    }
2309}
2310
2311/// The arc as the app drew it, the arguments of
2312/// [`CommandRecorder::push_arc`].
2313pub struct ArcRecordArgs<'a> {
2314    pub brush: &'a Brush,
2315    pub center: Point,
2316    pub radius: f32,
2317    pub start_angle: f32,
2318    pub sweep_angle: f32,
2319    pub stroke: Option<Stroke>,
2320    pub inner_radius: f32,
2321    pub blend_mode: BlendMode,
2322}
2323
2324/// The ring a stroked round rect draws when it is a circle: a square
2325/// whose four radii are half its side, stroked. Its band is the stroke.
2326fn stroked_circle_ring(
2327    rect: Rect,
2328    radii: CornerRadii,
2329    stroke: Option<Stroke>,
2330) -> Option<ArcGeometry> {
2331    const CIRCLE_TOLERANCE: f32 = 0.01;
2332    let stroke = stroke?;
2333    if rect.width.to_bits() != rect.height.to_bits() || !stroke.is_visible() {
2334        return None;
2335    }
2336    let half = rect.width * 0.5;
2337    let radius = radii.top_left;
2338    if !radius.is_finite()
2339        || radius <= 0.0
2340        || (radius - half).abs() > CIRCLE_TOLERANCE
2341        || radii.top_right.to_bits() != radius.to_bits()
2342        || radii.bottom_right.to_bits() != radius.to_bits()
2343        || radii.bottom_left.to_bits() != radius.to_bits()
2344    {
2345        return None;
2346    }
2347    let half_width = stroke.half_width();
2348    Some(ArcGeometry::new(
2349        Point::new(rect.x + half, rect.y + half),
2350        (half - half_width).max(0.0),
2351        half + half_width,
2352        0.0,
2353        TAU,
2354        StrokeCap::Round,
2355    ))
2356}
2357
2358fn union_rect(a: Rect, b: Rect) -> Rect {
2359    let x = a.x.min(b.x);
2360    let y = a.y.min(b.y);
2361    Rect {
2362        x,
2363        y,
2364        width: (a.x + a.width).max(b.x + b.width) - x,
2365        height: (a.y + a.height).max(b.y + b.height) - y,
2366    }
2367}
2368
2369fn rect_row(rect: Rect) -> [f32; 4] {
2370    [rect.x, rect.y, rect.width, rect.height]
2371}
2372
2373fn row_rect(row: [f32; 4]) -> Rect {
2374    Rect {
2375        x: row[0],
2376        y: row[1],
2377        width: row[2],
2378        height: row[3],
2379    }
2380}
2381
2382/// The band a primitive's arc arguments describe, normalised as the
2383/// fragment stage draws it.
2384#[inline(always)]
2385pub fn normalized_band(args: &ArcRecordArgs<'_>) -> ArcGeometry {
2386    ArcGeometry::of_band(
2387        args.center,
2388        arc_band(args.radius, args.inner_radius, args.stroke),
2389        args.start_angle,
2390        args.sweep_angle,
2391    )
2392}
2393
2394/// The disc around a band: the square of its outer radius plus the cap's
2395/// reach, which contains the band's tight bounds.
2396fn band_disc(geometry: &ArcGeometry) -> Rect {
2397    let reach = geometry.outer_radius + geometry.half_thickness();
2398    Rect {
2399        x: geometry.center.x - reach,
2400        y: geometry.center.y - reach,
2401        width: reach + reach,
2402        height: reach + reach,
2403    }
2404}
2405
2406/// The trig the GPU derives for a band from its normalized start and sweep:
2407/// the mid-angle sine and cosine and the half-sweep sine and cosine, with the
2408/// full circle's sentinel. Arc records leave their `radii` zero for the GPU
2409/// to fill; the renderer's CPU mirrors of the band geometry call this.
2410pub fn arc_trig(start_angle: f32, sweep_angle: f32) -> [f32; 4] {
2411    if sweep_angle >= TAU && start_angle == 0.0 {
2412        return [0.0, -1.0, 0.0, -1.0];
2413    }
2414    let half_sweep = within(sweep_angle, 0.0, TAU) * 0.5;
2415    let (mid_sin, mid_cos) = (start_angle + half_sweep).sin_cos();
2416    let (half_sin, half_cos) = half_sweep.sin_cos();
2417    [mid_sin, mid_cos, at_least(half_sin, 0.0), half_cos]
2418}
2419
2420#[cfg(test)]
2421#[path = "tests/record_tests.rs"]
2422mod tests;
2423
2424#[cfg(test)]
2425#[path = "tests/record_band_tests.rs"]
2426mod band_tests;