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
16pub const RECORD_KIND_RECT: u32 = 0;
18pub const RECORD_KIND_ROUND_RECT: u32 = 1;
20pub 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
36pub const ARC_BUCKETS: usize = 7;
39pub const ARC_BUCKET_SEGMENTS: [u32; ARC_BUCKETS] = [1, 2, 4, 8, 16, 32, 64];
41pub const ARC_RING_SEGMENTS: [u32; 4] = [8, 16, 32, 64];
47pub 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}
61pub const ARC_BAND_MIN_RADIUS: f32 = 11.0;
64pub const ARC_BAND_MIN_INNER_RADIUS: f32 = 1.0;
67pub const BAND_MARGIN: f32 = 1.0;
71pub const BAND_QUAD_MARGIN: f32 = 0.5 + 1.0 / 16.0;
74pub const BAND_ANGULAR_PAD: f32 = 0.001;
79pub const QUAD_VERTICES: u32 = 4;
82pub const QUAD_INDICES: u32 = 6;
84pub const BAND_MIN_SEGMENTS: u32 = 1;
87
88pub const fn strip_vertices(segments: u32) -> u32 {
91 segments * 2 + 2
92}
93
94pub const fn strip_indices(segments: u32) -> u32 {
96 segments * QUAD_INDICES
97}
98
99pub 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}
111pub const BAND_VERTEX_PIXELS: f32 = 32.0;
118const BAND_STRIP_OVERSHOOT: f32 = 1.25;
121const SEGMENT_WASTE_QUADS: u32 = 512;
126
127struct 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 #[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 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 #[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 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
222pub fn band_bucket(segments: u32) -> usize {
224 segments.trailing_zeros() as usize
225}
226
227pub fn band_class_segments(class: u8) -> u32 {
229 ARC_BUCKET_SEGMENTS[class as usize]
230}
231
232#[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
249pub fn band_pays(geometry: &ArcGeometry, rect: Rect) -> bool {
252 band_bucket_for(geometry, &BandRing::of_geometry(geometry), rect).is_some()
253}
254
255pub 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
264pub const BRUSH_KIND_LINEAR: u32 = 1;
266pub const BRUSH_KIND_RADIAL: u32 = 2;
268pub const BRUSH_KIND_SWEEP: u32 = 3;
270
271#[repr(C)]
276#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
277pub struct ShapeRecord {
278 pub rect: [f32; 4],
282 pub radii: [f32; 4],
285 pub color: [f32; 4],
287 pub stroke_width: f32,
289 pub flags: u32,
292 pub brush: u32,
295 pub reserved: u32,
297 pub arc: [f32; 4],
299 pub arc_band: [f32; 4],
302 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 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 pub fn band_cap(&self) -> StrokeCap {
335 STROKE_CAPS[((self.flags >> BAND_CAP_SHIFT) & TWO_BITS) as usize]
336 }
337
338 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 pub fn is_banded(&self) -> bool {
352 self.flags & ARC_BANDED_BIT != 0
353 }
354
355 pub fn band_class(&self) -> usize {
359 ((self.flags >> BAND_CLASS_SHIFT) & BAND_CLASS_MASK) as usize
360 }
361
362 pub fn band_segments(&self) -> u32 {
364 ARC_BUCKET_SEGMENTS[self.band_class()]
365 }
366
367 pub fn has_loose_rect(&self) -> bool {
371 self.flags & ARC_RECT_LOOSE_BIT != 0
372 }
373
374 pub fn stored_rect(&self) -> Rect {
376 row_rect(self.rect)
377 }
378
379 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 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 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#[repr(C)]
446#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
447pub struct BrushRecord {
448 pub kind: u32,
450 pub tile_mode: u32,
452 pub stop_start: u32,
454 pub stop_count: u32,
455 pub params: [f32; 4],
458 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#[repr(C)]
470#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
471pub struct GradientStopRecord {
472 pub color: [f32; 4],
473 pub position: [f32; 4],
475}
476
477#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
479pub enum RecordLane {
480 Shapes,
482 Others,
485 Content,
487}
488
489#[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 pub brushes: u8,
502 pub kinds: u8,
504 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 pub fn uniform_kind(&self) -> Option<u32> {
517 (self.kinds.count_ones() == 1).then(|| self.kinds.trailing_zeros())
518 }
519
520 pub fn uniform_brush(&self) -> Option<u32> {
523 (self.brushes.count_ones() == 1).then(|| self.brushes.trailing_zeros())
524 }
525}
526
527#[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 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 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 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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
601pub struct RecordingSummary {
602 pub has_text: bool,
604 pub has_shadow: bool,
605 pub has_non_shadow: bool,
607 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
650pub 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
660pub 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#[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
710pub enum Recorded {
712 Shape(Rect),
714 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 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 pub fn all_segments(&self) -> Range<u32> {
749 0..self.tables.segments.len() as u32
750 }
751
752 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 pub fn fingerprint(&self) -> u64 {
765 self.tables.fingerprint()
766 }
767
768 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 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 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 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 #[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 #[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 #[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 #[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 #[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#[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 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 pub fn pod_heap_bytes(&self) -> usize {
1304 self.shapes.tables.heap_bytes()
1305 }
1306
1307 pub fn tables(&self) -> &RecordTables {
1309 self.shapes.tables()
1310 }
1311
1312 pub fn shape_recorder(&self) -> &Arc<ShapeRecorder> {
1314 &self.shapes
1315 }
1316
1317 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 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 pub fn all_segments(&self) -> Range<u32> {
1348 0..self.shapes.tables.segments.len() as u32
1349 }
1350
1351 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 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 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 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 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 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 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 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 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 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 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#[derive(Clone, Debug, Default)]
1602pub struct CommandRecorder {
1603 shapes: ShapeRecorder,
1604 content: RecordingContent,
1605}
1606
1607impl CommandRecorder {
1608 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 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 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 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 pub fn is_empty(&self) -> bool {
1650 self.len() == 0
1651 }
1652
1653 pub fn content_markers(&self) -> u32 {
1655 self.content.content_markers
1656 }
1657
1658 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 pub fn reserve_shapes(&mut self, additional: usize) {
1671 self.shapes.tables_mut().shapes.reserve(additional);
1672 }
1673
1674 pub fn push_content(&mut self) {
1676 self.content.content_markers += 1;
1677 self.shapes.push_content_segment();
1678 }
1679
1680 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 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 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 #[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 #[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 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 pub fn merge_summary(&mut self, other: RecordingSummary) {
1763 self.content.summary.merge(other);
1764 }
1765}
1766
1767pub 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
1780fn 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#[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
1853fn 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
1865pub 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}