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