1use std::{
4 ops,
5 sync::{Arc, atomic::AtomicUsize},
6};
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 BorderSide, ExtendMode, GradientStop, ImageRendering, LineOrientation, LineStyle, MixBlendMode, ReferenceFrameId, RepeatMode,
12 TransformStyle,
13 api_extension::{ApiExtensionId, ApiExtensionPayload},
14 font::{FontId, GlyphInstance, GlyphOptions},
15 image::ImageTextureId,
16 window::FrameId,
17};
18use zng_unit::*;
19
20#[derive(Debug)]
22pub struct DisplayListBuilder {
23 frame_id: FrameId,
24 list: Vec<DisplayItem>,
25
26 clip_len: usize,
27 mask_len: usize,
28 space_len: usize,
29 stacking_len: usize,
30
31 seg_id: SegmentId,
32 seg_id_gen: Arc<AtomicUsize>,
33 segments: Vec<(SegmentId, usize)>,
34 has_reuse_ranges: bool,
35}
36impl DisplayListBuilder {
37 pub fn new(frame_id: FrameId) -> Self {
39 Self::with_capacity(frame_id, 100)
40 }
41
42 pub fn with_capacity(frame_id: FrameId, capacity: usize) -> Self {
44 Self {
45 frame_id,
46 list: Vec::with_capacity(capacity),
47
48 clip_len: 1,
49 mask_len: 1,
50 space_len: 1,
51 stacking_len: 1,
52 seg_id: 0,
53 seg_id_gen: Arc::new(AtomicUsize::new(1)),
54 segments: vec![(0, 0)],
55 has_reuse_ranges: false,
56 }
57 }
58
59 pub fn frame_id(&self) -> FrameId {
61 self.frame_id
62 }
63
64 pub fn start_reuse_range(&mut self) -> ReuseStart {
70 ReuseStart {
71 frame_id: self.frame_id,
72 seg_id: self.seg_id,
73 start: self.list.len(),
74 clip_len: self.clip_len,
75 mask_len: self.mask_len,
76 space_len: self.space_len,
77 stacking_len: self.stacking_len,
78 }
79 }
80
81 pub fn finish_reuse_range(&mut self, start: ReuseStart) -> ReuseRange {
92 assert_eq!(self.frame_id, start.frame_id, "reuse range not started by the same builder");
93 assert_eq!(self.seg_id, start.seg_id, "reuse range not started by the same builder");
94 assert_eq!(
95 self.clip_len, start.clip_len,
96 "reuse range cannot finish before all clips pushed inside it are popped"
97 );
98 assert_eq!(
99 self.mask_len, start.mask_len,
100 "reuse range cannot finish before all masks pushed inside it are popped"
101 );
102 assert_eq!(
103 self.space_len, start.space_len,
104 "reuse range cannot finish before all reference frames pushed inside it are popped"
105 );
106 assert_eq!(
107 self.stacking_len, start.stacking_len,
108 "reuse range cannot finish before all stacking contexts pushed inside it are popped"
109 );
110 debug_assert!(start.start <= self.list.len());
111
112 self.has_reuse_ranges = true;
113
114 ReuseRange {
115 frame_id: self.frame_id,
116 seg_id: self.seg_id,
117 start: start.start,
118 end: self.list.len(),
119 }
120 }
121
122 pub fn push_reuse_range(&mut self, range: &ReuseRange) {
126 if !range.is_empty() {
127 self.list.push(DisplayItem::Reuse {
128 frame_id: range.frame_id,
129 seg_id: range.seg_id,
130 start: range.start,
131 end: range.end,
132 });
133 }
134 }
135
136 pub fn push_reference_frame(
143 &mut self,
144 key: ReferenceFrameId,
145 transform: FrameValue<PxTransform>,
146 transform_style: TransformStyle,
147 is_2d_scale_translation: bool,
148 ) {
149 debug_assert!(key.is_app_generated());
150
151 self.space_len += 1;
152 self.list.push(DisplayItem::PushReferenceFrame {
153 id: key,
154 transform,
155 transform_style,
156 is_2d_scale_translation,
157 });
158 }
159
160 pub fn pop_reference_frame(&mut self) {
164 debug_assert!(self.space_len > 1);
165 self.space_len -= 1;
166 self.list.push(DisplayItem::PopReferenceFrame);
167 }
168
169 pub fn push_stacking_context(&mut self, blend_mode: MixBlendMode, transform_style: TransformStyle, filters: &[FilterOp]) {
175 self.stacking_len += 1;
176 self.list.push(DisplayItem::PushStackingContext {
177 blend_mode,
178 transform_style,
179 filters: filters.to_vec().into_boxed_slice(),
180 })
181 }
182
183 pub fn pop_stacking_context(&mut self) {
187 debug_assert!(self.stacking_len > 1);
188 self.stacking_len -= 1;
189 self.list.push(DisplayItem::PopStackingContext);
190 }
191
192 pub fn push_clip_rect(&mut self, clip_rect: PxRect, clip_out: bool) {
196 self.clip_len += 1;
197 self.list.push(DisplayItem::PushClipRect { clip_rect, clip_out });
198 }
199
200 pub fn push_clip_rounded_rect(&mut self, clip_rect: PxRect, corners: PxCornerRadius, clip_out: bool) {
206 self.clip_len += 1;
207 self.list.push(DisplayItem::PushClipRoundedRect {
208 clip_rect,
209 corners,
210 clip_out,
211 });
212 }
213
214 pub fn pop_clip(&mut self) {
219 debug_assert!(self.clip_len > 1);
220 self.clip_len -= 1;
221 self.list.push(DisplayItem::PopClip);
222 }
223
224 pub fn push_mask(&mut self, image_id: ImageTextureId, rect: PxRect) {
228 self.mask_len += 1;
229 self.list.push(DisplayItem::PushMask { image_id, rect })
230 }
231
232 pub fn pop_mask(&mut self) {
237 debug_assert!(self.mask_len > 1);
238 self.mask_len -= 1;
239 self.list.push(DisplayItem::PopMask);
240 }
241
242 #[expect(clippy::too_many_arguments)]
244 pub fn push_border(
245 &mut self,
246 bounds: PxRect,
247 widths: PxSideOffsets,
248 top: BorderSide,
249 right: BorderSide,
250 bottom: BorderSide,
251 left: BorderSide,
252 radius: PxCornerRadius,
253 ) {
254 self.list.push(DisplayItem::Border {
255 bounds,
256 widths,
257 sides: [top, right, bottom, left],
258 radius,
259 })
260 }
261
262 #[expect(clippy::too_many_arguments)]
264 pub fn push_nine_patch_border(
265 &mut self,
266 bounds: PxRect,
267 source: NinePatchSource,
268 widths: PxSideOffsets,
269 img_size: PxSize,
270 slice: PxSideOffsets,
271 fill: bool,
272 repeat_horizontal: RepeatMode,
273 repeat_vertical: RepeatMode,
274 ) {
275 self.list.push(DisplayItem::NinePatchBorder {
276 bounds,
277 source,
278 widths,
279 img_size,
280 slice,
281 fill,
282 repeat_horizontal,
283 repeat_vertical,
284 })
285 }
286
287 pub fn push_text(
289 &mut self,
290 clip_rect: PxRect,
291 font_id: FontId,
292 glyphs: &[GlyphInstance],
293 color: FrameValue<Rgba>,
294 options: GlyphOptions,
295 ) {
296 self.list.push(DisplayItem::Text {
297 clip_rect,
298 font_id,
299 glyphs: glyphs.to_vec().into_boxed_slice(),
300 color,
301 options,
302 });
303 }
304
305 pub fn push_image(
307 &mut self,
308 clip_rect: PxRect,
309 image_id: ImageTextureId,
310 image_size: PxSize,
311 tile_size: PxSize,
312 tile_spacing: PxSize,
313 rendering: ImageRendering,
314 ) {
315 self.list.push(DisplayItem::Image {
316 clip_rect,
317 image_id,
318 image_size,
319 rendering,
320 tile_size,
321 tile_spacing,
322 })
323 }
324
325 pub fn push_color(&mut self, clip_rect: PxRect, color: FrameValue<Rgba>) {
327 self.list.push(DisplayItem::Color { clip_rect, color })
328 }
329
330 pub fn push_backdrop_filter(&mut self, clip_rect: PxRect, filters: &[FilterOp]) {
332 self.list.push(DisplayItem::BackdropFilter {
333 clip_rect,
334 filters: filters.to_vec().into_boxed_slice(),
335 })
336 }
337
338 #[expect(clippy::too_many_arguments)]
340 pub fn push_linear_gradient(
341 &mut self,
342 clip_rect: PxRect,
343 start_point: euclid::Point2D<f32, Px>,
344 end_point: euclid::Point2D<f32, Px>,
345 extend_mode: ExtendMode,
346 stops: &[GradientStop],
347 tile_origin: PxPoint,
348 tile_size: PxSize,
349 tile_spacing: PxSize,
350 ) {
351 self.list.push(DisplayItem::LinearGradient {
352 clip_rect,
353 start_point,
354 end_point,
355 extend_mode,
356 stops: stops.to_vec().into_boxed_slice(),
357 tile_origin,
358 tile_size,
359 tile_spacing,
360 })
361 }
362
363 #[expect(clippy::too_many_arguments)]
365 pub fn push_radial_gradient(
366 &mut self,
367 clip_rect: PxRect,
368 center: euclid::Point2D<f32, Px>,
369 radius: euclid::Size2D<f32, Px>,
370 start_offset: f32,
371 end_offset: f32,
372 extend_mode: ExtendMode,
373 stops: &[GradientStop],
374 tile_origin: PxPoint,
375 tile_size: PxSize,
376 tile_spacing: PxSize,
377 ) {
378 self.list.push(DisplayItem::RadialGradient {
379 clip_rect,
380 center,
381 radius,
382 start_offset,
383 end_offset,
384 extend_mode,
385 stops: stops.to_vec().into_boxed_slice(),
386 tile_origin,
387 tile_size,
388 tile_spacing,
389 });
390 }
391
392 #[expect(clippy::too_many_arguments)]
394 pub fn push_conic_gradient(
395 &mut self,
396 clip_rect: PxRect,
397 center: euclid::Point2D<f32, Px>,
398 angle: AngleRadian,
399 start_offset: f32,
400 end_offset: f32,
401 extend_mode: ExtendMode,
402 stops: &[GradientStop],
403 tile_origin: PxPoint,
404 tile_size: PxSize,
405 tile_spacing: PxSize,
406 ) {
407 self.list.push(DisplayItem::ConicGradient {
408 clip_rect,
409 center,
410 angle,
411 start_offset,
412 end_offset,
413 extend_mode,
414 stops: stops.to_vec().into_boxed_slice(),
415 tile_origin,
416 tile_size,
417 tile_spacing,
418 });
419 }
420
421 pub fn push_line(&mut self, clip_rect: PxRect, color: Rgba, style: LineStyle, orientation: LineOrientation) {
423 self.list.push(DisplayItem::Line {
424 clip_rect,
425 color,
426 style,
427 orientation,
428 })
429 }
430
431 pub fn push_extension(&mut self, extension_id: ApiExtensionId, payload: ApiExtensionPayload) {
445 self.list.push(DisplayItem::PushExtension { extension_id, payload })
446 }
447
448 pub fn pop_extension(&mut self, extension_id: ApiExtensionId) {
452 self.list.push(DisplayItem::PopExtension { extension_id })
453 }
454
455 pub fn set_backface_visibility(&mut self, visible: bool) {
457 self.list.push(DisplayItem::SetBackfaceVisibility { visible })
458 }
459
460 pub fn len(&self) -> usize {
462 self.list.len()
463 }
464
465 pub fn is_empty(&self) -> bool {
467 self.list.is_empty()
468 }
469
470 pub fn parallel_split(&self) -> Self {
475 Self {
476 frame_id: self.frame_id,
477 list: vec![],
478 clip_len: 1,
479 mask_len: 1,
480 space_len: 1,
481 stacking_len: 1,
482 seg_id: self.seg_id_gen.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
483 seg_id_gen: self.seg_id_gen.clone(),
484 segments: vec![],
485 has_reuse_ranges: false,
486 }
487 }
488
489 pub fn parallel_fold(&mut self, mut split: Self) {
500 assert!(
501 Arc::ptr_eq(&self.seg_id_gen, &split.seg_id_gen),
502 "cannot fold list not split from this one or parent"
503 );
504 assert_eq!(split.space_len, 1);
505 assert_eq!(split.clip_len, 1);
506 assert_eq!(split.mask_len, 1);
507 assert_eq!(split.stacking_len, 1);
508
509 if !self.list.is_empty() {
510 for (_, offset) in &mut split.segments {
511 *offset += self.list.len();
512 }
513 }
514
515 if self.segments.is_empty() {
516 self.segments = split.segments;
517 } else {
518 self.segments.append(&mut split.segments);
519 }
520 if split.has_reuse_ranges {
521 self.segments.push((split.seg_id, self.list.len()));
522 }
523
524 if self.list.is_empty() {
525 self.list = split.list;
526 } else {
527 self.list.append(&mut split.list);
528 }
529 }
530
531 pub fn finalize(self) -> DisplayList {
533 DisplayList {
534 frame_id: self.frame_id,
535 list: self.list,
536 segments: self.segments,
537 }
538 }
539}
540
541pub type SegmentId = usize;
543
544pub struct ReuseStart {
548 frame_id: FrameId,
549 seg_id: SegmentId,
550 start: usize,
551
552 clip_len: usize,
553 mask_len: usize,
554 space_len: usize,
555 stacking_len: usize,
556}
557
558#[derive(Debug, Clone)]
564pub struct ReuseRange {
565 frame_id: FrameId,
566 seg_id: SegmentId,
567 start: usize,
568 end: usize,
569}
570impl ReuseRange {
571 pub fn frame_id(&self) -> FrameId {
573 self.frame_id
574 }
575
576 pub fn is_empty(&self) -> bool {
578 self.start == self.end
579 }
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize)]
586pub struct DisplayList {
587 frame_id: FrameId,
588 list: Vec<DisplayItem>,
589 segments: Vec<(SegmentId, usize)>,
590}
591impl DisplayList {
592 pub fn frame_id(&self) -> FrameId {
594 self.frame_id
595 }
596
597 pub fn into_parts(self) -> (FrameId, Vec<DisplayItem>, Vec<(SegmentId, usize)>) {
599 (self.frame_id, self.list, self.segments)
600 }
601}
602impl ops::Deref for DisplayList {
603 type Target = [DisplayItem];
604
605 fn deref(&self) -> &Self::Target {
606 &self.list
607 }
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
616#[serde(transparent)]
617pub struct FrameValueId(u64);
618
619impl FrameValueId {
620 pub const INVALID: Self = Self(0);
622 pub const fn first() -> Self {
624 Self(1)
625 }
626 #[must_use]
632 pub const fn next(self) -> Self {
633 let r = Self(self.0.wrapping_add(1));
634 if r.0 == Self::INVALID.0 { Self::first() } else { r }
635 }
636 #[must_use]
640 pub fn incr(&mut self) -> Self {
641 std::mem::replace(self, self.next())
642 }
643 pub const fn get(self) -> u64 {
645 self.0
646 }
647 pub const fn from_raw(id: u64) -> Self {
658 Self(id)
659 }
660}
661
662#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
666#[non_exhaustive]
667pub enum FrameValue<T> {
668 Bind {
670 id: FrameValueId,
672 value: T,
674 animating: bool,
676 },
677 Value(T),
679}
680impl<T> FrameValue<T> {
681 pub fn value(&self) -> &T {
683 match self {
684 FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
685 }
686 }
687
688 pub fn into_value(self) -> T {
690 match self {
691 FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
692 }
693 }
694
695 fn update_bindable(value: &mut T, animating: &mut bool, update: &FrameValueUpdate<T>) -> bool
697 where
698 T: PartialEq + Copy,
699 {
700 let need_frame = (*animating != update.animating) || (!*animating && *value != update.value);
708
709 *animating = update.animating;
710 *value = update.value;
711
712 need_frame
713 }
714
715 fn update_value(value: &mut T, update: &FrameValueUpdate<T>) -> bool
717 where
718 T: PartialEq + Copy,
719 {
720 if value != &update.value {
721 *value = update.value;
722 true
723 } else {
724 false
725 }
726 }
727}
728impl<T> From<T> for FrameValue<T> {
729 fn from(value: T) -> Self {
730 FrameValue::Value(value)
731 }
732}
733
734#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
736#[non_exhaustive]
737pub struct FrameValueUpdate<T> {
738 pub id: FrameValueId,
740 pub value: T,
742 pub animating: bool,
744}
745impl<T> FrameValueUpdate<T> {
746 pub fn new(id: FrameValueId, value: T, animating: bool) -> Self {
748 Self { id, value, animating }
749 }
750}
751
752#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
754#[non_exhaustive]
755pub enum FilterOp {
756 Blur(f32, f32),
758 Brightness(f32),
760 Contrast(f32),
762 Grayscale(f32),
764 HueRotate(f32),
766 Invert(f32),
768 Opacity(FrameValue<f32>),
770 Saturate(f32),
772 Sepia(f32),
774 DropShadow {
776 offset: euclid::Vector2D<f32, Px>,
778 color: Rgba,
780 blur_radius: f32,
782 },
783 ColorMatrix([f32; 20]),
787 Flood(Rgba),
789}
790
791#[allow(missing_docs)]
793#[derive(Debug, Clone, Serialize, Deserialize)]
794#[non_exhaustive]
795pub enum DisplayItem {
796 Reuse {
797 frame_id: FrameId,
798 seg_id: SegmentId,
799 start: usize,
800 end: usize,
801 },
802 PushReferenceFrame {
803 id: ReferenceFrameId,
804 transform: FrameValue<PxTransform>,
805 transform_style: TransformStyle,
806 is_2d_scale_translation: bool,
807 },
808 PopReferenceFrame,
809
810 PushStackingContext {
811 transform_style: TransformStyle,
812 blend_mode: MixBlendMode,
813 filters: Box<[FilterOp]>,
814 },
815 PopStackingContext,
816
817 PushClipRect {
818 clip_rect: PxRect,
819 clip_out: bool,
820 },
821 PushClipRoundedRect {
822 clip_rect: PxRect,
823 corners: PxCornerRadius,
824 clip_out: bool,
825 },
826 PopClip,
827 PushMask {
828 image_id: ImageTextureId,
829 rect: PxRect,
830 },
831 PopMask,
832
833 Border {
834 bounds: PxRect,
835 widths: PxSideOffsets,
836 sides: [BorderSide; 4],
837 radius: PxCornerRadius,
838 },
839 NinePatchBorder {
840 bounds: PxRect,
841 source: NinePatchSource,
842 widths: PxSideOffsets,
843 img_size: PxSize,
844 slice: PxSideOffsets,
845 fill: bool,
846 repeat_horizontal: RepeatMode,
847 repeat_vertical: RepeatMode,
848 },
849
850 Text {
851 clip_rect: PxRect,
852 font_id: FontId,
853 glyphs: Box<[GlyphInstance]>,
854 color: FrameValue<Rgba>,
855 options: GlyphOptions,
856 },
857
858 Image {
859 clip_rect: PxRect,
860 image_id: ImageTextureId,
861 image_size: PxSize,
862 rendering: ImageRendering,
863 tile_size: PxSize,
864 tile_spacing: PxSize,
865 },
866
867 Color {
868 clip_rect: PxRect,
869 color: FrameValue<Rgba>,
870 },
871 BackdropFilter {
872 clip_rect: PxRect,
873 filters: Box<[FilterOp]>,
874 },
875
876 LinearGradient {
877 clip_rect: PxRect,
878 start_point: euclid::Point2D<f32, Px>,
879 end_point: euclid::Point2D<f32, Px>,
880 extend_mode: ExtendMode,
881 stops: Box<[GradientStop]>,
882 tile_origin: PxPoint,
883 tile_size: PxSize,
884 tile_spacing: PxSize,
885 },
886 RadialGradient {
887 clip_rect: PxRect,
888 center: euclid::Point2D<f32, Px>,
889 radius: euclid::Size2D<f32, Px>,
890 start_offset: f32,
891 end_offset: f32,
892 extend_mode: ExtendMode,
893 stops: Box<[GradientStop]>,
894 tile_origin: PxPoint,
895 tile_size: PxSize,
896 tile_spacing: PxSize,
897 },
898 ConicGradient {
899 clip_rect: PxRect,
900 center: euclid::Point2D<f32, Px>,
901 angle: AngleRadian,
902 start_offset: f32,
903 end_offset: f32,
904 extend_mode: ExtendMode,
905 stops: Box<[GradientStop]>,
906 tile_origin: PxPoint,
907 tile_size: PxSize,
908 tile_spacing: PxSize,
909 },
910
911 Line {
912 clip_rect: PxRect,
913 color: Rgba,
914 style: LineStyle,
915 orientation: LineOrientation,
916 },
917
918 PushExtension {
919 extension_id: ApiExtensionId,
920 payload: ApiExtensionPayload,
921 },
922 PopExtension {
923 extension_id: ApiExtensionId,
924 },
925
926 SetBackfaceVisibility {
927 visible: bool,
928 },
929}
930impl DisplayItem {
931 pub fn update_transform(&mut self, t: &FrameValueUpdate<PxTransform>) -> bool {
933 match self {
934 DisplayItem::PushReferenceFrame {
935 transform:
936 FrameValue::Bind {
937 id,
938 value,
939 animating: animation,
940 },
941 ..
942 } if *id == t.id => FrameValue::update_bindable(value, animation, t),
943 _ => false,
944 }
945 }
946
947 pub fn update_float(&mut self, t: &FrameValueUpdate<f32>) -> bool {
949 match self {
950 DisplayItem::PushStackingContext { filters, .. } => {
951 let mut new_frame = false;
952 for filter in filters.iter_mut() {
953 match filter {
954 FilterOp::Opacity(FrameValue::Bind {
955 id,
956 value,
957 animating: animation,
958 }) if *id == t.id => {
959 new_frame |= FrameValue::update_bindable(value, animation, t);
960 }
961 _ => {}
962 }
963 }
964 new_frame
965 }
966 _ => false,
967 }
968 }
969
970 pub fn update_color(&mut self, t: &FrameValueUpdate<Rgba>) -> bool {
972 match self {
973 DisplayItem::Color {
974 color:
975 FrameValue::Bind {
976 id,
977 value,
978 animating: animation,
979 },
980 ..
981 } if *id == t.id => FrameValue::update_bindable(value, animation, t),
982 DisplayItem::Text {
983 color: FrameValue::Bind { id, value, .. },
984 ..
985 } if *id == t.id => FrameValue::update_value(value, t),
986 _ => false,
987 }
988 }
989}
990
991#[allow(missing_docs)]
993#[derive(Debug, Clone, Serialize, Deserialize)]
994pub enum NinePatchSource {
995 Image {
996 image_id: ImageTextureId,
997 rendering: ImageRendering,
998 },
999 LinearGradient {
1000 start_point: euclid::Point2D<f32, Px>,
1001 end_point: euclid::Point2D<f32, Px>,
1002 extend_mode: ExtendMode,
1003 stops: Box<[GradientStop]>,
1004 },
1005 RadialGradient {
1006 center: euclid::Point2D<f32, Px>,
1007 radius: euclid::Size2D<f32, Px>,
1008 start_offset: f32,
1009 end_offset: f32,
1010 extend_mode: ExtendMode,
1011 stops: Box<[GradientStop]>,
1012 },
1013 ConicGradient {
1014 center: euclid::Point2D<f32, Px>,
1015 angle: AngleRadian,
1016 start_offset: f32,
1017 end_offset: f32,
1018 extend_mode: ExtendMode,
1019 stops: Box<[GradientStop]>,
1020 },
1021}