1#![cfg_attr(windows, allow(dead_code))]
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Pixels, Point,
9 Radians, ScaledFilter, ScaledPixels, Size, bounds_tree::BoundsTree, point,
10};
11use smallvec::SmallVec;
12use std::{
13 fmt::Debug,
14 iter::Peekable,
15 ops::{Add, Range, Sub},
16 slice,
17};
18
19mod plan;
20pub use plan::*;
21mod abi;
22#[doc(hidden)]
23pub use abi::{SCENE_BUFFER_LAYOUTS, SceneBufferLayout};
24
25#[allow(non_camel_case_types, unused)]
27pub type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
28
29pub type DrawOrder = u32;
31
32pub(crate) const DEFAULT_BORDER_DASHED_LENGTH: f32 = 2.0;
33pub(crate) const DEFAULT_BORDER_DASHED_GAP: f32 = 1.0;
34
35#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
41#[repr(u32)]
42pub enum ShaderBool {
43 #[default]
45 Disabled = 0,
46 Enabled = 1,
48}
49
50impl ShaderBool {
51 pub fn is_enabled(self) -> bool {
53 self == Self::Enabled
54 }
55}
56
57impl From<bool> for ShaderBool {
58 fn from(value: bool) -> Self {
59 if value { Self::Enabled } else { Self::Disabled }
60 }
61}
62
63#[derive(Default)]
89pub struct Scene {
90 pub(crate) paint_operations: Vec<PaintOperation>,
91 primitive_bounds: BoundsTree<ScaledPixels>,
92 layer_stack: Vec<Option<DrawOrder>>,
93 filter_depth: usize,
94 pub shadows: Vec<Shadow>,
96 pub quads: Vec<Quad>,
98 pub paths: Vec<Path<ScaledPixels>>,
101 pub underlines: Vec<Underline>,
103 pub monochrome_sprites: Vec<MonochromeSprite>,
105 pub subpixel_sprites: Vec<SubpixelSprite>,
107 pub polychrome_sprites: Vec<PolychromeSprite>,
109 pub surfaces: Vec<PaintSurface>,
111 surface_opacities: Vec<f32>,
112 pub backdrop_filters: Vec<BackdropFilter>,
114 pub filter_boundaries: Vec<FilterBoundary>,
116 render_plan: ScenePlan,
117 is_finished: bool,
118}
119
120impl Scene {
121 pub fn clear(&mut self) {
126 self.paint_operations.clear();
127 self.primitive_bounds.clear();
128 self.layer_stack.clear();
129 self.filter_depth = 0;
130 self.paths.clear();
131 self.shadows.clear();
132 self.quads.clear();
133 self.underlines.clear();
134 self.monochrome_sprites.clear();
135 self.subpixel_sprites.clear();
136 self.polychrome_sprites.clear();
137 self.surfaces.clear();
138 self.surface_opacities.clear();
139 self.backdrop_filters.clear();
140 self.filter_boundaries.clear();
141 self.render_plan.clear();
142 self.is_finished = false;
143 }
144
145 pub fn len(&self) -> usize {
147 self.paint_operations.len()
148 }
149
150 pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
156 self.is_finished = false;
157 let order = self.primitive_bounds.insert(bounds);
158 self.layer_stack.push(Some(order));
159 self.paint_operations
160 .push(PaintOperation::StartLayer(bounds));
161 }
162
163 pub fn pop_layer(&mut self) {
166 self.is_finished = false;
167 self.layer_stack.pop();
168 self.paint_operations.push(PaintOperation::EndLayer);
169 }
170
171 pub fn raise_order_floor(&mut self) {
176 self.is_finished = false;
177 let floor = self.primitive_bounds.max_order() + 1;
178 self.primitive_bounds.set_order_floor(floor);
179 }
180
181 pub(crate) fn is_in_filter_group(&self) -> bool {
182 self.filter_depth > 0
183 }
184
185 pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
209 let mut primitive = primitive.into();
210 self.is_finished = false;
211
212 if self.is_in_filter_group()
215 && let Primitive::SubpixelSprite(sprite) = primitive
216 {
217 primitive = Primitive::MonochromeSprite(MonochromeSprite {
218 order: sprite.order,
219 padding: sprite.padding,
220 bounds: sprite.bounds,
221 content_mask: sprite.content_mask,
222 color: sprite.color,
223 tile: sprite.tile,
224 transformation: sprite.transformation,
225 });
226 }
227
228 let clipped_bounds = primitive
229 .paint_bounds()
230 .intersect(&primitive.content_mask().bounds);
231
232 let is_filter_boundary = matches!(primitive, Primitive::FilterBoundary(_));
236
237 if clipped_bounds.is_empty() && !is_filter_boundary {
238 return;
239 }
240
241 let order = if is_filter_boundary {
242 let order_bounds = if clipped_bounds.is_empty() {
243 *primitive.bounds()
244 } else {
245 clipped_bounds
246 };
247 self.primitive_bounds.insert_above_all(order_bounds)
248 } else {
249 self.layer_stack
250 .last()
251 .copied()
252 .flatten()
253 .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds))
254 };
255 self.push_ordered(primitive, order);
256 }
257
258 fn push_ordered(&mut self, mut primitive: Primitive, order: u32) {
260 match &mut primitive {
261 Primitive::Shadow(shadow) => {
262 shadow.order = order;
263 self.shadows.push(*shadow);
264 }
265 Primitive::Quad(quad) => {
266 quad.order = order;
267 self.quads.push(*quad);
268 }
269 Primitive::Path(path) => {
270 path.order = order;
271 path.id = PathId(self.paths.len());
272 self.paths.push(path.clone());
273 }
274 Primitive::Underline(underline) => {
275 underline.order = order;
276 self.underlines.push(*underline);
277 }
278 Primitive::MonochromeSprite(sprite) => {
279 sprite.order = order;
280 self.monochrome_sprites.push(*sprite);
281 }
282 Primitive::SubpixelSprite(sprite) => {
283 sprite.order = order;
284 self.subpixel_sprites.push(*sprite);
285 }
286 Primitive::PolychromeSprite(sprite) => {
287 sprite.order = order;
288 self.polychrome_sprites.push(*sprite);
289 }
290 Primitive::Surface(surface) => {
291 surface.order = order;
292 self.surfaces.push(surface.clone());
293 self.surface_opacities.push(1.0);
294 }
295 Primitive::BackdropFilter(filter) => {
296 filter.order = order;
297 self.backdrop_filters.push(filter.clone());
298 }
299 Primitive::FilterBoundary(boundary) => {
300 boundary.order = order;
301 self.layer_stack.fill(None);
303 self.primitive_bounds.set_order_floor(order + 1);
304
305 if boundary.is_start {
306 self.filter_depth += 1;
307 } else {
308 self.filter_depth = self.filter_depth.saturating_sub(1);
309 }
310
311 self.filter_boundaries.push(boundary.clone());
312 }
313 }
314
315 self.paint_operations
316 .push(PaintOperation::Primitive(primitive));
317 }
318
319 pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
329 self.replay_with(range, prev_scene, &mut None);
330 }
331
332 pub(crate) fn replay_with(
334 &mut self,
335 range: Range<usize>,
336 prev_scene: &Scene,
337 summary: &mut Option<ReplaySummary>,
338 ) {
339 let operations = &prev_scene.paint_operations[range];
340 if self.layer_stack.iter().any(Option::is_some) {
341 for operation in operations {
343 match operation {
344 PaintOperation::Primitive(primitive) => {
345 self.insert_primitive(primitive.clone())
346 }
347 PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
348 PaintOperation::EndLayer => self.pop_layer(),
349 }
350 }
351 return;
352 }
353
354 let Some(first_order) = operations.iter().find_map(|operation| match operation {
357 PaintOperation::Primitive(primitive) => Some(primitive.order()),
358 _ => None,
359 }) else {
360 self.paint_operations.extend(operations.iter().cloned());
361 return;
362 };
363 let ReplaySummary {
364 union,
365 span,
366 first_offset,
367 has_boundaries,
368 } = *summary.get_or_insert_with(|| ReplaySummary::new(operations));
369 let orders = first_order - first_offset..first_order - first_offset + span;
370 self.is_finished = false;
371 let below = if has_boundaries {
372 self.primitive_bounds.max_order()
374 } else {
375 self.primitive_bounds.max_intersecting(&union)
376 };
377 let first = (below + 1).max(self.primitive_bounds.order_floor());
378 let shift = |order: u32| order - orders.start + first;
379 self.primitive_bounds
380 .insert_with_order(union, shift(orders.end));
381
382 self.paint_operations.reserve(operations.len());
383 for operation in operations {
384 let PaintOperation::Primitive(primitive) = operation else {
385 self.paint_operations.push(operation.clone());
386 continue;
387 };
388 let mut primitive = primitive.clone();
389 if self.is_in_filter_group()
390 && let Primitive::SubpixelSprite(sprite) = primitive
391 {
392 primitive = Primitive::MonochromeSprite(MonochromeSprite {
393 order: sprite.order,
394 padding: sprite.padding,
395 bounds: sprite.bounds,
396 content_mask: sprite.content_mask,
397 color: sprite.color,
398 tile: sprite.tile,
399 transformation: sprite.transformation,
400 });
401 }
402 let order = shift(primitive.order());
403 self.push_ordered(primitive, order);
404 }
405 }
406
407 pub fn finish(&mut self) {
413 self.shadows.sort_by_key(|shadow| shadow.order);
414 self.quads.sort_by_key(|quad| quad.order);
415 self.paths.sort_by_key(|path| path.order);
416 self.underlines.sort_by_key(|underline| underline.order);
417 self.monochrome_sprites
418 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
419 self.subpixel_sprites
420 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
421 self.polychrome_sprites
422 .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
423 self.surfaces.sort_by_key(|surface| surface.order);
424 self.surface_opacities.resize(self.surfaces.len(), 1.0);
425 self.backdrop_filters.sort_by_key(|filter| filter.order);
426 self.filter_boundaries
431 .sort_by_key(|boundary| (boundary.order, !boundary.is_start));
432 let commands = std::mem::take(&mut self.render_plan.commands);
433 self.render_plan = ScenePlan::build(self, commands);
434 self.is_finished = true;
435 }
436
437 #[cfg_attr(
468 all(
469 any(target_os = "linux", target_os = "freebsd"),
470 not(any(feature = "x11", feature = "wayland"))
471 ),
472 allow(dead_code)
473 )]
474 pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
475 self.render_commands().iter().map(|command| match command {
476 RenderCommand::Batch(batch) => batch.clone(),
477 RenderCommand::BeginFilter { boundary_index, .. } => {
478 PrimitiveBatch::FilterBoundary(*boundary_index)
479 }
480 RenderCommand::EndFilter {
481 closing_boundary_index,
482 ..
483 } => PrimitiveBatch::FilterBoundary(*closing_boundary_index),
484 })
485 }
486
487 pub fn render_commands(&self) -> &[RenderCommand] {
497 self.render_plan().commands()
498 }
499
500 pub fn render_plan(&self) -> &ScenePlan {
507 debug_assert!(
508 self.is_finished,
509 "Scene::finish must be called before rendering"
510 );
511 self.render_plan.assert_matches(self);
512 &self.render_plan
513 }
514
515 pub fn surface_opacities(&self) -> &[f32] {
519 &self.surface_opacities
520 }
521
522 pub fn requires_offscreen_rendering(&self) -> bool {
529 self.render_plan().requirements().uses_offscreen_target
530 }
531}
532
533#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
535#[repr(C)]
536pub struct SceneHsla {
537 pub(crate) h: f32,
539 pub(crate) s: f32,
541 pub(crate) l: f32,
543 pub(crate) a: f32,
545}
546impl Into<palette::Hsla> for SceneHsla {
547 fn into(self) -> palette::Hsla {
548 palette::Hsla::new(self.h * 360.0, self.s, self.l, self.a)
549 }
550}
551impl From<palette::Hsla> for SceneHsla {
552 fn from(hsla: palette::Hsla) -> Self {
553 Self {
554 h: hsla.hue.into_positive_degrees() / 360.0,
555 s: hsla.saturation,
556 l: hsla.lightness,
557 a: hsla.alpha,
558 }
559 }
560}
561
562#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
563#[cfg_attr(
564 all(
565 any(target_os = "linux", target_os = "freebsd"),
566 not(any(feature = "x11", feature = "wayland"))
567 ),
568 allow(dead_code)
569)]
570pub(crate) enum PrimitiveKind {
571 FilterBoundaryStart,
574 Shadow,
575 #[default]
576 Quad,
577 Path,
578 Underline,
579 MonochromeSprite,
580 SubpixelSprite,
581 PolychromeSprite,
582 Surface,
583 BackdropFilter,
584 FilterBoundaryEnd,
587}
588
589#[derive(Clone, Copy, Debug)]
592pub(crate) struct ReplaySummary {
593 union: Bounds<ScaledPixels>,
594 span: u32,
596 first_offset: u32,
598 has_boundaries: bool,
599}
600
601impl ReplaySummary {
602 fn new(operations: &[PaintOperation]) -> Self {
604 let mut union: Option<Bounds<ScaledPixels>> = None;
605 let (mut min_order, mut max_order) = (u32::MAX, 0);
606 let mut first = None;
607 let mut has_boundaries = false;
608 for operation in operations {
609 let PaintOperation::Primitive(primitive) = operation else {
610 continue;
611 };
612 let mut bounds = primitive
613 .paint_bounds()
614 .intersect(&primitive.content_mask().bounds);
615 if let Primitive::FilterBoundary(_) = primitive {
616 has_boundaries = true;
617 if bounds.is_empty() {
618 bounds = *primitive.bounds();
619 }
620 }
621 union = Some(union.map_or(bounds, |union| union.union(&bounds)));
622 first.get_or_insert(primitive.order());
623 min_order = min_order.min(primitive.order());
624 max_order = max_order.max(primitive.order());
625 }
626 Self {
627 union: union.expect("a replayed range with a primitive"),
628 span: max_order - min_order,
629 first_offset: first.unwrap_or_default() - min_order,
630 has_boundaries,
631 }
632 }
633}
634
635#[derive(Clone)]
636pub(crate) enum PaintOperation {
637 Primitive(Primitive),
638 StartLayer(Bounds<ScaledPixels>),
639 EndLayer,
640}
641
642#[derive(Clone)]
644pub enum Primitive {
645 Shadow(Shadow),
647 Quad(Quad),
649 Path(Path<ScaledPixels>),
651 Underline(Underline),
653 MonochromeSprite(MonochromeSprite),
655 SubpixelSprite(SubpixelSprite),
657 PolychromeSprite(PolychromeSprite),
659 Surface(PaintSurface),
661 BackdropFilter(BackdropFilter),
663 FilterBoundary(FilterBoundary),
665}
666
667impl Primitive {
668 fn paint_bounds(&self) -> Bounds<ScaledPixels> {
671 match self {
672 Primitive::MonochromeSprite(sprite) => {
673 transformed_bounds(sprite.bounds, sprite.transformation)
674 }
675 Primitive::SubpixelSprite(sprite) => {
676 transformed_bounds(sprite.bounds, sprite.transformation)
677 }
678 _ => *self.bounds(),
679 }
680 }
681
682 pub fn bounds(&self) -> &Bounds<ScaledPixels> {
684 match self {
685 Primitive::Shadow(shadow) => &shadow.bounds,
686 Primitive::Quad(quad) => &quad.bounds,
687 Primitive::Path(path) => &path.bounds,
688 Primitive::Underline(underline) => &underline.bounds,
689 Primitive::MonochromeSprite(sprite) => &sprite.bounds,
690 Primitive::SubpixelSprite(sprite) => &sprite.bounds,
691 Primitive::PolychromeSprite(sprite) => &sprite.bounds,
692 Primitive::Surface(surface) => &surface.bounds,
693 Primitive::BackdropFilter(filter) => &filter.bounds,
694 Primitive::FilterBoundary(boundary) => &boundary.bounds,
695 }
696 }
697
698 fn order(&self) -> DrawOrder {
700 match self {
701 Primitive::Shadow(shadow) => shadow.order,
702 Primitive::Quad(quad) => quad.order,
703 Primitive::Path(path) => path.order,
704 Primitive::Underline(underline) => underline.order,
705 Primitive::MonochromeSprite(sprite) => sprite.order,
706 Primitive::SubpixelSprite(sprite) => sprite.order,
707 Primitive::PolychromeSprite(sprite) => sprite.order,
708 Primitive::Surface(surface) => surface.order,
709 Primitive::BackdropFilter(filter) => filter.order,
710 Primitive::FilterBoundary(boundary) => boundary.order,
711 }
712 }
713
714 pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
716 match self {
717 Primitive::Shadow(shadow) => &shadow.content_mask,
718 Primitive::Quad(quad) => &quad.content_mask,
719 Primitive::Path(path) => &path.content_mask,
720 Primitive::Underline(underline) => &underline.content_mask,
721 Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
722 Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
723 Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
724 Primitive::Surface(surface) => &surface.content_mask,
725 Primitive::BackdropFilter(filter) => &filter.content_mask,
726 Primitive::FilterBoundary(boundary) => &boundary.content_mask,
727 }
728 }
729}
730
731fn transformed_bounds(
732 bounds: Bounds<ScaledPixels>,
733 transformation: TransformationMatrix,
734) -> Bounds<ScaledPixels> {
735 if transformation == TransformationMatrix::unit() {
736 return bounds;
737 }
738
739 let corners = [
740 bounds.origin,
741 bounds.top_right(),
742 bounds.bottom_left(),
743 bounds.bottom_right(),
744 ]
745 .map(|corner| {
746 transformation
747 .apply(corner.map(|value| Pixels(value.0)))
748 .map(|value| ScaledPixels(value.0))
749 });
750 let top_left = corners
751 .iter()
752 .fold(corners[0], |min, corner| min.min(corner));
753 let bottom_right = corners
754 .iter()
755 .fold(corners[0], |max, corner| max.max(corner));
756 Bounds::from_corners(top_left, bottom_right)
757}
758
759#[cfg_attr(
760 all(
761 any(target_os = "linux", target_os = "freebsd"),
762 not(any(feature = "x11", feature = "wayland"))
763 ),
764 allow(dead_code)
765)]
766struct BatchIterator<'a> {
767 shadows_start: usize,
768 shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
769 quads_start: usize,
770 quads_iter: Peekable<slice::Iter<'a, Quad>>,
771 paths_start: usize,
772 paths: &'a [Path<ScaledPixels>],
773 paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
774 underlines_start: usize,
775 underlines_iter: Peekable<slice::Iter<'a, Underline>>,
776 monochrome_sprites_start: usize,
777 monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
778 subpixel_sprites_start: usize,
779 subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
780 polychrome_sprites_start: usize,
781 polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
782 surfaces_start: usize,
783 surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
784 backdrop_filters_start: usize,
785 backdrop_filters_iter: Peekable<slice::Iter<'a, BackdropFilter>>,
786 filter_boundaries_start: usize,
787 filter_boundaries_iter: Peekable<slice::Iter<'a, FilterBoundary>>,
788}
789
790impl<'a> BatchIterator<'a> {
791 fn new(scene: &'a Scene) -> Self {
792 Self {
793 shadows_start: 0,
794 shadows_iter: scene.shadows.iter().peekable(),
795 quads_start: 0,
796 quads_iter: scene.quads.iter().peekable(),
797 paths_start: 0,
798 paths: &scene.paths,
799 paths_iter: scene.paths.iter().peekable(),
800 underlines_start: 0,
801 underlines_iter: scene.underlines.iter().peekable(),
802 monochrome_sprites_start: 0,
803 monochrome_sprites_iter: scene.monochrome_sprites.iter().peekable(),
804 subpixel_sprites_start: 0,
805 subpixel_sprites_iter: scene.subpixel_sprites.iter().peekable(),
806 polychrome_sprites_start: 0,
807 polychrome_sprites_iter: scene.polychrome_sprites.iter().peekable(),
808 surfaces_start: 0,
809 surfaces_iter: scene.surfaces.iter().peekable(),
810 backdrop_filters_start: 0,
811 backdrop_filters_iter: scene.backdrop_filters.iter().peekable(),
812 filter_boundaries_start: 0,
813 filter_boundaries_iter: scene.filter_boundaries.iter().peekable(),
814 }
815 }
816}
817
818fn precedes_limit(
819 order: DrawOrder,
820 kind: PrimitiveKind,
821 limit: Option<(DrawOrder, PrimitiveKind)>,
822) -> bool {
823 limit.is_none_or(|limit| (order, kind) < limit)
824}
825
826fn has_corner_smoothing(corner_smoothing: f32) -> bool {
827 corner_smoothing > 0.0
828}
829
830impl<'a> Iterator for BatchIterator<'a> {
831 type Item = PrimitiveBatch;
832
833 fn next(&mut self) -> Option<Self::Item> {
834 let orders_and_kinds = [
835 (
836 self.shadows_iter.peek().map(|s| s.order),
837 PrimitiveKind::Shadow,
838 ),
839 (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
840 (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
841 (
842 self.underlines_iter.peek().map(|u| u.order),
843 PrimitiveKind::Underline,
844 ),
845 (
846 self.monochrome_sprites_iter.peek().map(|s| s.order),
847 PrimitiveKind::MonochromeSprite,
848 ),
849 (
850 self.subpixel_sprites_iter.peek().map(|s| s.order),
851 PrimitiveKind::SubpixelSprite,
852 ),
853 (
854 self.polychrome_sprites_iter.peek().map(|s| s.order),
855 PrimitiveKind::PolychromeSprite,
856 ),
857 (
858 self.surfaces_iter.peek().map(|s| s.order),
859 PrimitiveKind::Surface,
860 ),
861 (
862 self.backdrop_filters_iter.peek().map(|f| f.order),
863 PrimitiveKind::BackdropFilter,
864 ),
865 (
866 self.filter_boundaries_iter.peek().map(|b| b.order),
867 match self.filter_boundaries_iter.peek() {
871 Some(boundary) if boundary.is_start => PrimitiveKind::FilterBoundaryStart,
872 _ => PrimitiveKind::FilterBoundaryEnd,
873 },
874 ),
875 ];
876 let mut first = None;
878 let mut second = None;
879 for (order, kind) in orders_and_kinds {
880 let Some(order) = order else {
881 continue;
882 };
883 let candidate = (order, kind);
884 if first.is_none_or(|current| candidate < current) {
885 second = first;
886 first = Some(candidate);
887 } else if second.is_none_or(|current| candidate < current) {
888 second = Some(candidate);
889 }
890 }
891 let (_, batch_kind) = first?;
892 let max_order_and_kind = second;
893
894 match batch_kind {
895 PrimitiveKind::Shadow => {
896 let smoothed =
897 has_corner_smoothing(self.shadows_iter.peek().unwrap().corner_smoothing);
898 let shadows_start = self.shadows_start;
899 let mut shadows_end = shadows_start + 1;
900 self.shadows_iter.next();
901 while self
902 .shadows_iter
903 .next_if(|shadow| {
904 precedes_limit(shadow.order, batch_kind, max_order_and_kind)
905 && has_corner_smoothing(shadow.corner_smoothing) == smoothed
906 })
907 .is_some()
908 {
909 shadows_end += 1;
910 }
911 self.shadows_start = shadows_end;
912 Some(PrimitiveBatch::Shadows {
913 range: shadows_start..shadows_end,
914 smoothed,
915 })
916 }
917 PrimitiveKind::Quad => {
918 let smoothed =
919 has_corner_smoothing(self.quads_iter.peek().unwrap().corner_smoothing);
920 let quads_start = self.quads_start;
921 let mut quads_end = quads_start + 1;
922 self.quads_iter.next();
923 while self
924 .quads_iter
925 .next_if(|quad| {
926 precedes_limit(quad.order, batch_kind, max_order_and_kind)
927 && has_corner_smoothing(quad.corner_smoothing) == smoothed
928 })
929 .is_some()
930 {
931 quads_end += 1;
932 }
933 self.quads_start = quads_end;
934 Some(PrimitiveBatch::Quads {
935 range: quads_start..quads_end,
936 smoothed,
937 })
938 }
939 PrimitiveKind::Path => {
940 let paths_start = self.paths_start;
941 let mut paths_end = paths_start + 1;
942 self.paths_iter.next();
943 while self
944 .paths_iter
945 .next_if(|path| precedes_limit(path.order, batch_kind, max_order_and_kind))
946 .is_some()
947 {
948 paths_end += 1;
949 }
950 self.paths_start = paths_end;
951 let range = paths_start..paths_end;
952 let paths = &self.paths[range.clone()];
953 let rasterization_vertex_count = paths.iter().map(|path| path.vertices.len()).sum();
954 let sprite_count = if paths
955 .last()
956 .is_some_and(|path| path.order == paths[0].order)
957 {
958 paths.len()
959 } else {
960 1
961 };
962 Some(PrimitiveBatch::Paths {
963 range,
964 rasterization_vertex_count,
965 sprite_count,
966 })
967 }
968 PrimitiveKind::Underline => {
969 let underlines_start = self.underlines_start;
970 let mut underlines_end = underlines_start + 1;
971 self.underlines_iter.next();
972 while self
973 .underlines_iter
974 .next_if(|underline| {
975 precedes_limit(underline.order, batch_kind, max_order_and_kind)
976 })
977 .is_some()
978 {
979 underlines_end += 1;
980 }
981 self.underlines_start = underlines_end;
982 Some(PrimitiveBatch::Underlines(underlines_start..underlines_end))
983 }
984 PrimitiveKind::MonochromeSprite => {
985 let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
986 let sprites_start = self.monochrome_sprites_start;
987 let mut sprites_end = sprites_start + 1;
988 self.monochrome_sprites_iter.next();
989 while self
990 .monochrome_sprites_iter
991 .next_if(|sprite| {
992 precedes_limit(sprite.order, batch_kind, max_order_and_kind)
993 && sprite.tile.texture_id == texture_id
994 })
995 .is_some()
996 {
997 sprites_end += 1;
998 }
999 self.monochrome_sprites_start = sprites_end;
1000 Some(PrimitiveBatch::MonochromeSprites {
1001 texture_id,
1002 range: sprites_start..sprites_end,
1003 })
1004 }
1005 PrimitiveKind::SubpixelSprite => {
1006 let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id;
1007 let sprites_start = self.subpixel_sprites_start;
1008 let mut sprites_end = sprites_start + 1;
1009 self.subpixel_sprites_iter.next();
1010 while self
1011 .subpixel_sprites_iter
1012 .next_if(|sprite| {
1013 precedes_limit(sprite.order, batch_kind, max_order_and_kind)
1014 && sprite.tile.texture_id == texture_id
1015 })
1016 .is_some()
1017 {
1018 sprites_end += 1;
1019 }
1020 self.subpixel_sprites_start = sprites_end;
1021 Some(PrimitiveBatch::SubpixelSprites {
1022 texture_id,
1023 range: sprites_start..sprites_end,
1024 })
1025 }
1026 PrimitiveKind::PolychromeSprite => {
1027 let first_sprite = self.polychrome_sprites_iter.peek().unwrap();
1028 let texture_id = first_sprite.tile.texture_id;
1029 let smoothed = has_corner_smoothing(first_sprite.corner_smoothing);
1030 let sprites_start = self.polychrome_sprites_start;
1031 let mut sprites_end = sprites_start + 1;
1032 self.polychrome_sprites_iter.next();
1033 while self
1034 .polychrome_sprites_iter
1035 .next_if(|sprite| {
1036 precedes_limit(sprite.order, batch_kind, max_order_and_kind)
1037 && sprite.tile.texture_id == texture_id
1038 && has_corner_smoothing(sprite.corner_smoothing) == smoothed
1039 })
1040 .is_some()
1041 {
1042 sprites_end += 1;
1043 }
1044 self.polychrome_sprites_start = sprites_end;
1045 Some(PrimitiveBatch::PolychromeSprites {
1046 texture_id,
1047 range: sprites_start..sprites_end,
1048 smoothed,
1049 })
1050 }
1051 PrimitiveKind::Surface => {
1052 let surfaces_start = self.surfaces_start;
1053 let mut surfaces_end = surfaces_start + 1;
1054 self.surfaces_iter.next();
1055 while self
1056 .surfaces_iter
1057 .next_if(|surface| {
1058 precedes_limit(surface.order, batch_kind, max_order_and_kind)
1059 })
1060 .is_some()
1061 {
1062 surfaces_end += 1;
1063 }
1064 self.surfaces_start = surfaces_end;
1065 Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
1066 }
1067 PrimitiveKind::BackdropFilter => {
1068 let backdrop_filters_start = self.backdrop_filters_start;
1069 let mut backdrop_filters_end = backdrop_filters_start + 1;
1070 self.backdrop_filters_iter.next();
1071 while self
1072 .backdrop_filters_iter
1073 .next_if(|filter| precedes_limit(filter.order, batch_kind, max_order_and_kind))
1074 .is_some()
1075 {
1076 backdrop_filters_end += 1;
1077 }
1078 self.backdrop_filters_start = backdrop_filters_end;
1079 Some(PrimitiveBatch::BackdropFilters(
1080 backdrop_filters_start..backdrop_filters_end,
1081 ))
1082 }
1083 PrimitiveKind::FilterBoundaryStart | PrimitiveKind::FilterBoundaryEnd => {
1086 let index = self.filter_boundaries_start;
1087 self.filter_boundaries_iter.next();
1088 self.filter_boundaries_start = index + 1;
1089 Some(PrimitiveBatch::FilterBoundary(index))
1090 }
1091 }
1092 }
1093}
1094
1095#[derive(Debug, Copy, Clone)]
1100#[repr(C)]
1101pub struct Quad {
1102 pub order: DrawOrder,
1104 pub border_style: BorderStyle,
1106 pub border_dashed_length: f32,
1108 pub border_dashed_gap: f32,
1110 pub bounds: Bounds<ScaledPixels>,
1112 pub content_mask: ContentMask<ScaledPixels>,
1114 pub background: Background,
1116 pub border_color: Background,
1118 pub corner_radii: Corners<ScaledPixels>,
1121 pub border_widths: Edges<ScaledPixels>,
1123 pub corner_smoothing: f32,
1126 pub padding: u32,
1128}
1129
1130impl Default for Quad {
1131 fn default() -> Self {
1132 Self {
1133 order: Default::default(),
1134 border_style: Default::default(),
1135 border_dashed_length: DEFAULT_BORDER_DASHED_LENGTH,
1136 border_dashed_gap: DEFAULT_BORDER_DASHED_GAP,
1137 bounds: Default::default(),
1138 content_mask: Default::default(),
1139 background: Default::default(),
1140 border_color: Default::default(),
1141 corner_radii: Default::default(),
1142 border_widths: Default::default(),
1143 corner_smoothing: Default::default(),
1144 padding: Default::default(),
1145 }
1146 }
1147}
1148
1149impl From<Quad> for Primitive {
1150 fn from(quad: Quad) -> Self {
1151 Primitive::Quad(quad)
1152 }
1153}
1154
1155#[derive(Debug, Copy, Clone)]
1159#[repr(C)]
1160pub struct Underline {
1161 pub order: DrawOrder,
1163 pub padding: u32,
1165 pub bounds: Bounds<ScaledPixels>,
1167 pub clip_bounds: Bounds<ScaledPixels>,
1170 pub content_mask: ContentMask<ScaledPixels>,
1172 pub color: SceneHsla,
1174 pub thickness: ScaledPixels,
1176 pub wavy: ShaderBool,
1178}
1179
1180impl From<Underline> for Primitive {
1181 fn from(underline: Underline) -> Self {
1182 Primitive::Underline(underline)
1183 }
1184}
1185
1186#[derive(Debug, Copy, Clone)]
1191#[repr(C)]
1192pub struct Shadow {
1193 pub order: DrawOrder,
1195 pub blur_radius: ScaledPixels,
1197 pub bounds: Bounds<ScaledPixels>,
1199 pub corner_radii: Corners<ScaledPixels>,
1201 pub content_mask: ContentMask<ScaledPixels>,
1203 pub color: Background,
1205 pub element_bounds: Bounds<ScaledPixels>,
1207 pub element_corner_radii: Corners<ScaledPixels>,
1209 pub inset: ShaderBool,
1211 pub corner_smoothing: f32,
1213}
1214
1215impl From<Shadow> for Primitive {
1216 fn from(shadow: Shadow) -> Self {
1217 Primitive::Shadow(shadow)
1218 }
1219}
1220
1221#[derive(Default, Debug, Clone)]
1227pub struct BackdropFilter {
1228 pub order: DrawOrder,
1230 pub bounds: Bounds<ScaledPixels>,
1232 pub content_mask: ContentMask<ScaledPixels>,
1234 pub corner_radii: Corners<ScaledPixels>,
1236 pub corner_smoothing: f32,
1238 pub filters: SmallVec<[ScaledFilter; 4]>,
1245 pub opacity: f32,
1247}
1248
1249impl BackdropFilter {
1250 pub fn max_blur_radius(&self) -> f32 {
1252 max_blur_radius(&self.filters)
1253 }
1254}
1255
1256impl From<BackdropFilter> for Primitive {
1257 fn from(filter: BackdropFilter) -> Self {
1258 Primitive::BackdropFilter(filter)
1259 }
1260}
1261
1262#[derive(Debug, Clone)]
1270pub struct FilterBoundary {
1271 pub order: DrawOrder,
1273 pub bounds: Bounds<ScaledPixels>,
1275 pub content_mask: ContentMask<ScaledPixels>,
1277 pub corner_radii: Corners<ScaledPixels>,
1279 pub corner_smoothing: f32,
1281 pub filters: SmallVec<[ScaledFilter; 4]>,
1285 pub opacity: f32,
1287 pub is_start: bool,
1289}
1290
1291impl FilterBoundary {
1292 pub fn max_blur_radius(&self) -> f32 {
1294 max_blur_radius(&self.filters)
1295 }
1296}
1297
1298fn max_blur_radius(filters: &[ScaledFilter]) -> f32 {
1303 filters.iter().fold(0.0, |radius, filter| match filter {
1304 ScaledFilter::Blur(filter_radius) => radius.max(filter_radius.0),
1305 })
1306}
1307
1308impl From<FilterBoundary> for Primitive {
1309 fn from(boundary: FilterBoundary) -> Self {
1310 Primitive::FilterBoundary(boundary)
1311 }
1312}
1313
1314#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1316#[repr(u32)]
1317pub enum BorderStyle {
1318 #[default]
1320 Solid = 0,
1321 Dashed = 1,
1323}
1324
1325#[derive(Debug, Clone, Copy, PartialEq)]
1348#[repr(C)]
1349pub struct TransformationMatrix {
1350 pub rotation_scale: [[f32; 2]; 2],
1353 pub translation: [f32; 2],
1355}
1356
1357impl Eq for TransformationMatrix {}
1358
1359impl TransformationMatrix {
1360 pub fn unit() -> Self {
1362 Self {
1363 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
1364 translation: [0.0, 0.0],
1365 }
1366 }
1367
1368 pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
1371 self.compose(Self {
1372 rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
1373 translation: [point.x.0, point.y.0],
1374 })
1375 }
1376
1377 pub fn rotate(self, angle: Radians) -> Self {
1380 self.compose(Self {
1381 rotation_scale: [
1382 [angle.0.cos(), -angle.0.sin()],
1383 [angle.0.sin(), angle.0.cos()],
1384 ],
1385 translation: [0.0, 0.0],
1386 })
1387 }
1388
1389 pub fn scale(self, size: Size<f32>) -> Self {
1391 self.compose(Self {
1392 rotation_scale: [[size.width, 0.0], [0.0, size.height]],
1393 translation: [0.0, 0.0],
1394 })
1395 }
1396
1397 #[inline]
1400 pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
1401 if other == Self::unit() {
1402 return self;
1403 }
1404 TransformationMatrix {
1406 rotation_scale: [
1407 [
1408 self.rotation_scale[0][0] * other.rotation_scale[0][0]
1409 + self.rotation_scale[0][1] * other.rotation_scale[1][0],
1410 self.rotation_scale[0][0] * other.rotation_scale[0][1]
1411 + self.rotation_scale[0][1] * other.rotation_scale[1][1],
1412 ],
1413 [
1414 self.rotation_scale[1][0] * other.rotation_scale[0][0]
1415 + self.rotation_scale[1][1] * other.rotation_scale[1][0],
1416 self.rotation_scale[1][0] * other.rotation_scale[0][1]
1417 + self.rotation_scale[1][1] * other.rotation_scale[1][1],
1418 ],
1419 ],
1420 translation: [
1421 self.translation[0]
1422 + self.rotation_scale[0][0] * other.translation[0]
1423 + self.rotation_scale[0][1] * other.translation[1],
1424 self.translation[1]
1425 + self.rotation_scale[1][0] * other.translation[0]
1426 + self.rotation_scale[1][1] * other.translation[1],
1427 ],
1428 }
1429 }
1430
1431 pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
1433 let input = [point.x.0, point.y.0];
1434 let mut output = self.translation;
1435 for (i, output_cell) in output.iter_mut().enumerate() {
1436 for (k, input_cell) in input.iter().enumerate() {
1437 *output_cell += self.rotation_scale[i][k] * *input_cell;
1438 }
1439 }
1440 Point::new(output[0].into(), output[1].into())
1441 }
1442}
1443
1444impl Default for TransformationMatrix {
1445 fn default() -> Self {
1446 Self::unit()
1447 }
1448}
1449
1450#[derive(Copy, Clone, Debug)]
1455#[repr(C)]
1456pub struct MonochromeSprite {
1457 pub order: DrawOrder,
1459 pub padding: u32,
1461 pub bounds: Bounds<ScaledPixels>,
1463 pub content_mask: ContentMask<ScaledPixels>,
1465 pub color: SceneHsla,
1467 pub tile: AtlasTile,
1469 pub transformation: TransformationMatrix,
1471}
1472
1473impl From<MonochromeSprite> for Primitive {
1474 fn from(sprite: MonochromeSprite) -> Self {
1475 Primitive::MonochromeSprite(sprite)
1476 }
1477}
1478
1479#[derive(Copy, Clone, Debug)]
1483#[repr(C)]
1484pub struct SubpixelSprite {
1485 pub order: DrawOrder,
1487 pub padding: u32,
1489 pub bounds: Bounds<ScaledPixels>,
1491 pub content_mask: ContentMask<ScaledPixels>,
1493 pub color: SceneHsla,
1495 pub tile: AtlasTile,
1497 pub transformation: TransformationMatrix,
1499}
1500
1501impl From<SubpixelSprite> for Primitive {
1502 fn from(sprite: SubpixelSprite) -> Self {
1503 Primitive::SubpixelSprite(sprite)
1504 }
1505}
1506
1507#[derive(Copy, Clone, Debug)]
1512#[repr(C)]
1513pub struct PolychromeSprite {
1514 pub order: DrawOrder,
1516 pub grayscale: ShaderBool,
1518 pub opacity: f32,
1520 pub corner_smoothing: f32,
1522 pub bounds: Bounds<ScaledPixels>,
1524 pub content_mask: ContentMask<ScaledPixels>,
1526 pub corner_radii: Corners<ScaledPixels>,
1528 pub tile: AtlasTile,
1530}
1531
1532impl From<PolychromeSprite> for Primitive {
1533 fn from(sprite: PolychromeSprite) -> Self {
1534 Primitive::PolychromeSprite(sprite)
1535 }
1536}
1537
1538#[derive(Clone, Debug)]
1542pub struct PaintSurface {
1543 pub order: DrawOrder,
1545 pub bounds: Bounds<ScaledPixels>,
1547 pub content_mask: ContentMask<ScaledPixels>,
1549 pub source: crate::SurfaceSource,
1551}
1552
1553impl From<PaintSurface> for Primitive {
1554 fn from(surface: PaintSurface) -> Self {
1555 Primitive::Surface(surface)
1556 }
1557}
1558
1559#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1562pub struct PathId(pub usize);
1563
1564#[derive(Clone, Debug)]
1580pub struct Path<P: Clone + Debug + Default + PartialEq> {
1581 pub id: PathId,
1583 pub order: DrawOrder,
1585 pub bounds: Bounds<P>,
1587 pub content_mask: ContentMask<P>,
1589 pub vertices: Vec<PathVertex<P>>,
1591 pub color: Background,
1593 start: Point<P>,
1594 current: Point<P>,
1595 contour_count: usize,
1596}
1597
1598impl Path<Pixels> {
1599 pub fn new(start: Point<Pixels>) -> Self {
1602 Self {
1603 id: PathId(0),
1604 order: DrawOrder::default(),
1605 vertices: Vec::new(),
1606 start,
1607 current: start,
1608 bounds: Bounds {
1609 origin: start,
1610 size: Default::default(),
1611 },
1612 content_mask: Default::default(),
1613 color: Default::default(),
1614 contour_count: 0,
1615 }
1616 }
1617
1618 pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
1621 Path {
1622 id: self.id,
1623 order: self.order,
1624 bounds: self.bounds.scale(factor),
1625 content_mask: self.content_mask.scale(factor),
1626 vertices: self
1627 .vertices
1628 .iter()
1629 .map(|vertex| vertex.scale(factor))
1630 .collect(),
1631 start: self.start.map(|start| start.scale(factor)),
1632 current: self.current.scale(factor),
1633 contour_count: self.contour_count,
1634 color: self.color,
1635 }
1636 }
1637
1638 pub fn move_to(&mut self, to: Point<Pixels>) {
1640 self.contour_count += 1;
1641 self.start = to;
1642 self.current = to;
1643 }
1644
1645 pub fn line_to(&mut self, to: Point<Pixels>) {
1650 self.contour_count += 1;
1651 if self.contour_count > 1 {
1652 self.push_triangle(
1653 (self.start, self.current, to),
1654 (point(0., 1.), point(0., 1.), point(0., 1.)),
1655 );
1656 }
1657 self.current = to;
1658 }
1659
1660 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
1662 self.contour_count += 1;
1663 if self.contour_count > 1 {
1664 self.push_triangle(
1665 (self.start, self.current, to),
1666 (point(0., 1.), point(0., 1.), point(0., 1.)),
1667 );
1668 }
1669
1670 self.push_triangle(
1671 (self.current, ctrl, to),
1672 (point(0., 0.), point(0.5, 0.), point(1., 1.)),
1673 );
1674 self.current = to;
1675 }
1676
1677 pub fn push_triangle(
1680 &mut self,
1681 xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
1682 st: (Point<f32>, Point<f32>, Point<f32>),
1683 ) {
1684 self.bounds = self
1685 .bounds
1686 .union(&Bounds {
1687 origin: xy.0,
1688 size: Default::default(),
1689 })
1690 .union(&Bounds {
1691 origin: xy.1,
1692 size: Default::default(),
1693 })
1694 .union(&Bounds {
1695 origin: xy.2,
1696 size: Default::default(),
1697 });
1698
1699 self.vertices.push(PathVertex {
1700 xy_position: xy.0,
1701 st_position: st.0,
1702 content_mask: Default::default(),
1703 });
1704 self.vertices.push(PathVertex {
1705 xy_position: xy.1,
1706 st_position: st.1,
1707 content_mask: Default::default(),
1708 });
1709 self.vertices.push(PathVertex {
1710 xy_position: xy.2,
1711 st_position: st.2,
1712 content_mask: Default::default(),
1713 });
1714 }
1715}
1716
1717impl<T> Path<T>
1718where
1719 T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
1720{
1721 #[allow(unused)]
1723 pub fn clipped_bounds(&self) -> Bounds<T> {
1724 self.bounds.intersect(&self.content_mask.bounds)
1725 }
1726}
1727
1728impl From<Path<ScaledPixels>> for Primitive {
1729 fn from(path: Path<ScaledPixels>) -> Self {
1730 Primitive::Path(path)
1731 }
1732}
1733
1734#[derive(Clone, Debug)]
1736#[repr(C)]
1737pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
1738 pub xy_position: Point<P>,
1740 pub st_position: Point<f32>,
1743 pub content_mask: ContentMask<P>,
1745}
1746
1747impl PathVertex<Pixels> {
1748 pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
1750 PathVertex {
1751 xy_position: self.xy_position.scale(factor),
1752 st_position: self.st_position,
1753 content_mask: self.content_mask.scale(factor),
1754 }
1755 }
1756}
1757
1758#[cfg(test)]
1759mod tests {
1760 use super::*;
1761 use crate::{AtlasTextureKind, DevicePixels, Point, ShaderBool, Size, SurfaceSource, TileId};
1762
1763 fn scaled(value: f32) -> ScaledPixels {
1764 ScaledPixels(value)
1765 }
1766
1767 fn full_bounds() -> Bounds<ScaledPixels> {
1769 Bounds::new(Point::default(), Size::new(scaled(100.0), scaled(100.0)))
1770 }
1771
1772 fn mask() -> ContentMask<ScaledPixels> {
1773 ContentMask {
1774 bounds: full_bounds(),
1775 ..Default::default()
1776 }
1777 }
1778
1779 fn quad() -> Quad {
1780 Quad {
1781 bounds: full_bounds(),
1782 content_mask: mask(),
1783 ..Default::default()
1784 }
1785 }
1786
1787 fn shadow() -> Shadow {
1788 Shadow {
1789 order: 0,
1790 blur_radius: scaled(0.0),
1791 bounds: full_bounds(),
1792 corner_radii: Corners::default(),
1793 content_mask: mask(),
1794 color: Default::default(),
1795 element_bounds: full_bounds(),
1796 element_corner_radii: Corners::default(),
1797 inset: ShaderBool::Disabled,
1798 corner_smoothing: 0.0,
1799 }
1800 }
1801
1802 fn polychrome_sprite(texture_index: u32) -> PolychromeSprite {
1803 PolychromeSprite {
1804 order: 0,
1805 grayscale: ShaderBool::Disabled,
1806 opacity: 1.0,
1807 corner_smoothing: 0.0,
1808 bounds: full_bounds(),
1809 content_mask: mask(),
1810 corner_radii: Corners::default(),
1811 tile: AtlasTile {
1812 texture_id: AtlasTextureId {
1813 index: texture_index,
1814 kind: AtlasTextureKind::Polychrome,
1815 },
1816 tile_id: TileId(0),
1817 padding: 0,
1818 bounds: Bounds::<DevicePixels>::default(),
1819 },
1820 }
1821 }
1822
1823 fn monochrome_sprite(
1824 content_mask: ContentMask<ScaledPixels>,
1825 transformation: TransformationMatrix,
1826 ) -> MonochromeSprite {
1827 MonochromeSprite {
1828 order: 0,
1829 padding: 0,
1830 bounds: Bounds {
1832 origin: Point {
1833 x: scaled(0.0),
1834 y: scaled(0.0),
1835 },
1836 size: Size {
1837 width: scaled(3.0),
1838 height: scaled(2.0),
1839 },
1840 },
1841 content_mask,
1842 color: Default::default(),
1843 tile: AtlasTile {
1844 texture_id: AtlasTextureId {
1845 index: 0,
1846 kind: AtlasTextureKind::Monochrome,
1847 },
1848 tile_id: TileId(0),
1849 padding: 0,
1850 bounds: Bounds::<DevicePixels>::default(),
1851 },
1852 transformation,
1853 }
1854 }
1855
1856 fn rotated_sprite_transform() -> TransformationMatrix {
1858 TransformationMatrix {
1859 rotation_scale: [[0.0, -1.0], [1.0, 0.0]],
1860 translation: [5.0, 1.0],
1861 }
1862 }
1863
1864 fn mask_at(x: f32, y: f32, width: f32, height: f32) -> ContentMask<ScaledPixels> {
1865 ContentMask {
1866 bounds: Bounds {
1867 origin: Point {
1868 x: scaled(x),
1869 y: scaled(y),
1870 },
1871 size: Size {
1872 width: scaled(width),
1873 height: scaled(height),
1874 },
1875 },
1876 ..Default::default()
1877 }
1878 }
1879
1880 #[test]
1881 fn transformed_sprite_is_clipped_by_its_transformed_bounds() {
1882 let mut scene = Scene::default();
1884 scene.insert_primitive(monochrome_sprite(
1885 mask_at(3.0, 1.0, 1.0, 3.0),
1886 rotated_sprite_transform(),
1887 ));
1888 assert_eq!(scene.monochrome_sprites.len(), 1);
1889
1890 let mut scene = Scene::default();
1892 scene.insert_primitive(monochrome_sprite(
1893 mask_at(0.0, 0.0, 1.0, 1.0),
1894 rotated_sprite_transform(),
1895 ));
1896 assert!(scene.monochrome_sprites.is_empty());
1897 }
1898
1899 #[test]
1900 fn transformed_sprite_overlap_preserves_order_against_quads() {
1901 let mut scene = Scene::default();
1902 let mut background = quad();
1903 background.bounds = Bounds {
1904 origin: Point {
1905 x: scaled(3.5),
1906 y: scaled(1.5),
1907 },
1908 size: Size {
1909 width: scaled(1.0),
1910 height: scaled(1.0),
1911 },
1912 };
1913 scene.insert_primitive(background);
1914 scene.insert_primitive(monochrome_sprite(mask(), rotated_sprite_transform()));
1915
1916 assert!(
1917 scene.monochrome_sprites[0].order > scene.quads[0].order,
1918 "the later transformed sprite must sort above the overlapping quad"
1919 );
1920 }
1921
1922 fn batches(scene: &mut Scene) -> Vec<PrimitiveBatch> {
1923 scene.finish();
1924 scene.batches().collect()
1925 }
1926
1927 #[test]
1928 fn smoothing_and_texture_changes_define_batches() {
1929 let mut scene = Scene::default();
1930 for smoothing in [0.0, 0.0, 0.5, 1.0, 0.0] {
1931 let mut quad = quad();
1932 quad.corner_smoothing = smoothing;
1933 scene.insert_primitive(quad);
1934 }
1935
1936 for smoothing in [0.0, 0.0, 0.5, 1.0, 0.0] {
1937 let mut shadow = shadow();
1938 shadow.corner_smoothing = smoothing;
1939 scene.insert_primitive(shadow);
1940 }
1941
1942 for (texture, smoothing) in [
1943 (0, 0.0),
1944 (0, 0.0),
1945 (0, 0.5),
1946 (0, 1.0),
1947 (1, 1.0),
1948 (1, 0.5),
1949 (1, 0.0),
1950 ] {
1951 let mut sprite = polychrome_sprite(texture);
1952 sprite.corner_smoothing = smoothing;
1953 scene.insert_primitive(sprite);
1954 }
1955
1956 let batch_signatures = batches(&mut scene)
1957 .into_iter()
1958 .map(|batch| match batch {
1959 PrimitiveBatch::Quads { range, smoothed } => ("quad", None, range, smoothed),
1960 PrimitiveBatch::Shadows { range, smoothed } => ("shadow", None, range, smoothed),
1961 PrimitiveBatch::PolychromeSprites {
1962 texture_id,
1963 range,
1964 smoothed,
1965 } => ("polychrome", Some(texture_id.index), range, smoothed),
1966 other => panic!("unexpected batch: {other:?}"),
1967 })
1968 .collect::<Vec<_>>();
1969 assert_eq!(
1970 batch_signatures,
1971 vec![
1972 ("quad", None, 0..2, false),
1973 ("quad", None, 2..4, true),
1974 ("quad", None, 4..5, false),
1975 ("shadow", None, 0..2, false),
1976 ("shadow", None, 2..4, true),
1977 ("shadow", None, 4..5, false),
1978 ("polychrome", Some(0), 0..2, false),
1979 ("polychrome", Some(0), 2..4, true),
1980 ("polychrome", Some(1), 4..6, true),
1981 ("polychrome", Some(1), 6..7, false),
1982 ]
1983 );
1984 }
1985
1986 fn detached_quad() -> Quad {
1989 let bounds = Bounds::new(point(scaled(200.0), scaled(200.0)), full_bounds().size);
1990
1991 Quad {
1992 bounds,
1993 content_mask: ContentMask {
1994 bounds,
1995 ..Default::default()
1996 },
1997 ..Default::default()
1998 }
1999 }
2000
2001 fn boundary(is_start: bool) -> FilterBoundary {
2002 FilterBoundary {
2003 order: 0,
2004 bounds: full_bounds(),
2005 content_mask: mask(),
2006 corner_radii: Corners::default(),
2007 corner_smoothing: 0.0,
2008 filters: smallvec::smallvec![ScaledFilter::Blur(scaled(8.0))],
2009 opacity: 1.0,
2010 is_start,
2011 }
2012 }
2013
2014 fn backdrop() -> BackdropFilter {
2015 BackdropFilter {
2016 bounds: full_bounds(),
2017 content_mask: mask(),
2018 corner_radii: Corners::default(),
2019 filters: smallvec::smallvec![ScaledFilter::Blur(scaled(20.0))],
2020 opacity: 1.0,
2021 ..Default::default()
2022 }
2023 }
2024
2025 fn surface() -> PaintSurface {
2026 PaintSurface {
2027 order: 0,
2028 bounds: full_bounds(),
2029 content_mask: mask(),
2030 source: SurfaceSource::Unsupported(Size::default()),
2031 }
2032 }
2033
2034 #[test]
2035 fn backdrop_filter_sorts_before_a_later_overlapping_quad() {
2036 let mut scene = Scene::default();
2037 scene.insert_primitive(quad());
2038 scene.insert_primitive(backdrop());
2039 scene.insert_primitive(quad());
2040
2041 assert!(matches!(
2042 batches(&mut scene).as_slice(),
2043 [
2044 PrimitiveBatch::Quads { .. },
2045 PrimitiveBatch::BackdropFilters(_),
2046 PrimitiveBatch::Quads { .. }
2047 ]
2048 ));
2049 }
2050
2051 #[test]
2052 fn isolation_commands_preserve_order_pairing_and_replay() {
2053 for active_layer in [false, true] {
2054 let mut scene = Scene::default();
2055
2056 if active_layer {
2057 scene.push_layer(full_bounds());
2058 }
2059
2060 scene.insert_primitive(quad());
2061 scene.insert_primitive(boundary(true));
2062 scene.insert_primitive(detached_quad());
2063 scene.insert_primitive(boundary(true));
2064 scene.insert_primitive(quad());
2065 scene.insert_primitive(boundary(true));
2066 scene.insert_primitive(surface());
2067 scene.insert_primitive(boundary(false));
2068 scene.insert_primitive(boundary(false));
2069 scene.insert_primitive(detached_quad());
2070 scene.insert_primitive(boundary(false));
2071 scene.insert_primitive(detached_quad());
2072
2073 if active_layer {
2074 scene.pop_layer();
2075 }
2076
2077 scene.finish();
2078 assert_eq!(scene.render_plan().requirements().isolated_target_count, 3);
2079
2080 let commands: Vec<_> = scene
2081 .render_commands()
2082 .iter()
2083 .map(|command| match command {
2084 RenderCommand::BeginFilter {
2085 boundary_index,
2086 target: FilterRenderTarget::Isolated(target),
2087 } => ("start", Some((*boundary_index, target.as_usize()))),
2088 RenderCommand::EndFilter {
2089 boundary_index,
2090 target: FilterRenderTarget::Isolated(target),
2091 ..
2092 } => ("end", Some((*boundary_index, target.as_usize()))),
2093 RenderCommand::Batch(PrimitiveBatch::Quads { .. }) => ("quad", None),
2094 RenderCommand::Batch(PrimitiveBatch::Surfaces(_)) => ("surface", None),
2095 other => panic!("unexpected command: {other:?}"),
2096 })
2097 .collect();
2098 assert_eq!(
2099 commands,
2100 [
2101 ("quad", None),
2102 ("start", Some((0, 0))),
2103 ("quad", None),
2104 ("start", Some((1, 1))),
2105 ("quad", None),
2106 ("start", Some((2, 2))),
2107 ("surface", None),
2108 ("end", Some((2, 2))),
2109 ("end", Some((1, 1))),
2110 ("quad", None),
2111 ("end", Some((0, 0))),
2112 ("quad", None),
2113 ]
2114 );
2115
2116 let mut replay = Scene::default();
2117 replay.replay(0..scene.len(), &scene);
2118 replay.finish();
2119 assert_eq!(replay.render_commands(), scene.render_commands());
2120 assert_eq!(replay.surface_opacities(), &[1.0]);
2121 assert!(replay.layer_stack.is_empty());
2122 assert!(!replay.is_in_filter_group());
2123 }
2124 }
2125
2126 #[test]
2127 fn unmatched_filter_start_renders_inline() {
2128 let mut scene = Scene::default();
2129 scene.insert_primitive(boundary(true));
2130 scene.insert_primitive(quad());
2131 scene.finish();
2132
2133 let mut commands = scene.render_commands().iter();
2134 assert!(matches!(
2135 commands.next(),
2136 Some(RenderCommand::BeginFilter {
2137 target: FilterRenderTarget::Inline,
2138 ..
2139 })
2140 ));
2141 assert!(matches!(
2142 commands.next(),
2143 Some(RenderCommand::Batch(PrimitiveBatch::Quads { .. }))
2144 ));
2145 assert!(commands.next().is_none());
2146 assert!(!scene.requires_offscreen_rendering());
2147 }
2148
2149 #[test]
2150 fn finish_rebuilds_the_plan_after_direct_primitive_mutation() {
2151 let mut scene = Scene::default();
2152 scene.insert_primitive(quad());
2153 scene.finish();
2154 let commands = scene.render_commands().to_vec();
2155
2156 scene.quads.push(quad());
2158 scene.finish();
2159 assert_ne!(scene.render_commands(), commands);
2160 assert_eq!(scene.render_plan().requirements().instance_batch_count, 1);
2161 }
2162
2163 #[test]
2164 fn render_plan_reuses_its_command_allocation_across_frames() {
2165 let mut scene = Scene::default();
2166 for _ in 0..32 {
2167 scene.insert_primitive(quad());
2168 }
2169 scene.finish();
2170 let capacity = scene.render_plan.commands.capacity();
2171 assert!(capacity >= 32);
2172
2173 scene.clear();
2174 assert_eq!(scene.render_plan.commands.capacity(), capacity);
2175 scene.insert_primitive(quad());
2176 scene.finish();
2177 assert_eq!(scene.render_plan.commands.capacity(), capacity);
2178 }
2179
2180 #[test]
2181 fn maximum_draw_order_is_not_treated_as_an_empty_batch_cursor() {
2182 let mut scene = Scene::default();
2183 let mut quad = quad();
2184 quad.order = DrawOrder::MAX;
2185 scene.quads.push(quad);
2186 scene.finish();
2187
2188 assert!(matches!(
2189 scene.render_commands(),
2190 [RenderCommand::Batch(PrimitiveBatch::Quads { range, smoothed: false })]
2191 if range == &(0..1)
2192 ));
2193 }
2194
2195 #[test]
2196 fn filter_requirements_distinguish_opacity_from_blur() {
2197 for (name, group, content_blur, backdrop_blur) in [
2198 ("ordinary quad", false, false, false),
2199 ("opacity", true, false, false),
2200 ("content blur", true, true, false),
2201 ("backdrop blur", false, false, true),
2202 ("backdrop inside opacity", true, false, true),
2203 ] {
2204 let mut scene = Scene::default();
2205 let mut start = boundary(true);
2206
2207 if !content_blur {
2208 start.filters.clear();
2209 start.opacity = 0.5;
2210 }
2211
2212 if group {
2213 scene.insert_primitive(start.clone());
2214 }
2215
2216 if backdrop_blur {
2217 scene.insert_primitive(backdrop());
2218 }
2219
2220 scene.insert_primitive(quad());
2221
2222 if group {
2223 scene.insert_primitive(FilterBoundary {
2224 is_start: false,
2225 ..start
2226 });
2227 }
2228
2229 scene.finish();
2230
2231 let requirements = scene.render_plan().requirements();
2232 assert_eq!(
2233 (
2234 requirements.uses_offscreen_target,
2235 requirements.uses_blur_target
2236 ),
2237 (group || backdrop_blur, content_blur || backdrop_blur),
2238 "{name}"
2239 );
2240 }
2241 }
2242}