Skip to main content

gpui/
scene.rs

1// todo("windows"): remove
2#![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/// A [`PathVertex`] in scaled pixels.
26#[allow(non_camel_case_types, unused)]
27pub type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
28
29/// The order in which primitives are drawn. Lower values are drawn first.
30pub 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/// A `bool` stored as a four-byte integer, so its layout matches the GPU's view of it.
36///
37/// Scene structs use this instead of [`bool`] for flags that the shaders read. Convert with
38/// [`ShaderBool::from`]. [`Underline::wavy`], [`Shadow::inset`] and [`PolychromeSprite::grayscale`]
39/// are `ShaderBool`s.
40#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
41#[repr(u32)]
42pub enum ShaderBool {
43    /// The flag is disabled.
44    #[default]
45    Disabled = 0,
46    /// The flag is enabled.
47    Enabled = 1,
48}
49
50impl ShaderBool {
51    /// Returns `true` if this flag is [`ShaderBool::Enabled`].
52    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/// The primitives painted for one frame, ready for a renderer to consume.
64///
65/// [`crate::Window`] fills a scene while it paints. You rarely create one yourself, but it is
66/// public so tests and custom renderers can inspect the result. Add primitives with
67/// [`Scene::insert_primitive`] in paint order, then call [`Scene::finish`] before reading the
68/// renderer-facing vectors: `finish` sorts them by draw order and compiles the render plan.
69///
70/// # Examples
71///
72/// ```
73/// # use gpui::{Bounds, ContentMask, Quad, Scene, ScaledPixels, point, rgb, size};
74/// let bounds = Bounds {
75///     origin: point(ScaledPixels(0.), ScaledPixels(0.)),
76///     size: size(ScaledPixels(100.), ScaledPixels(40.)),
77/// };
78/// let mut scene = Scene::default();
79/// scene.insert_primitive(Quad {
80///     bounds,
81///     content_mask: ContentMask { bounds, ..Default::default() },
82///     background: rgb(0x2563eb).into(),
83///     ..Default::default()
84/// });
85/// scene.finish();
86/// assert_eq!(scene.quads.len(), 1);
87/// ```
88#[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    /// [`Shadow`] primitives. Sorted by draw order after [`Scene::finish`].
95    pub shadows: Vec<Shadow>,
96    /// [`Quad`] primitives. Sorted by draw order after [`Scene::finish`].
97    pub quads: Vec<Quad>,
98    /// [`Path`] primitives. A [`PathId`] is the index the path had when it was inserted, which
99    /// [`Scene::finish`] can change by sorting this vector.
100    pub paths: Vec<Path<ScaledPixels>>,
101    /// [`Underline`] primitives. Sorted by draw order after [`Scene::finish`].
102    pub underlines: Vec<Underline>,
103    /// Single-color sprites, such as icons and glyph masks.
104    pub monochrome_sprites: Vec<MonochromeSprite>,
105    /// Glyphs drawn with subpixel anti-aliasing.
106    pub subpixel_sprites: Vec<SubpixelSprite>,
107    /// Full-color sprites, such as images and color emoji.
108    pub polychrome_sprites: Vec<PolychromeSprite>,
109    /// [`crate::PaintSurface`] primitives, such as video frames.
110    pub surfaces: Vec<PaintSurface>,
111    surface_opacities: Vec<f32>,
112    /// [`crate::BackdropFilter`] primitives.
113    pub backdrop_filters: Vec<BackdropFilter>,
114    /// Start and end markers of content-filter groups. They always come in matched pairs.
115    pub filter_boundaries: Vec<FilterBoundary>,
116    render_plan: ScenePlan,
117    is_finished: bool,
118}
119
120impl Scene {
121    /// Removes every primitive and resets the scene so that it can be painted again.
122    ///
123    /// The vectors keep their capacity, so a scene that is cleared and refilled each frame
124    /// reuses its allocations.
125    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    /// Returns the number of paint operations in this scene, including layer boundaries.
146    pub fn len(&self) -> usize {
147        self.paint_operations.len()
148    }
149
150    /// Starts a layer. Every primitive inserted until the matching [`Scene::pop_layer`] receives
151    /// the layer's draw order, so the layer's contents are not interleaved with surrounding
152    /// primitives. Within a layer, the renderer still groups work by primitive type.
153    ///
154    /// [`crate::Window::paint_layer`] calls this for you.
155    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    /// Ends the layer started by the most recent [`Scene::push_layer`]. The scene does not check
164    /// that the calls are balanced, so callers must keep the nesting correct.
165    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    /// Makes every primitive inserted after this call draw above every primitive inserted before it.
172    ///
173    /// Use this to put overlays, such as tooltips and drag images, above the rest of the scene. The
174    /// renderer draws primitives in ascending draw order.
175    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    /// Adds a primitive in paint order and assigns it a draw order.
186    ///
187    /// A primitive whose bounds are empty after clipping to its content mask is dropped. A
188    /// transformed sprite is clipped by its transformed bounds. Filter boundaries are never
189    /// dropped. Inside a layer started with [`Scene::push_layer`], the primitive takes the layer's
190    /// draw order. Otherwise it takes one more than the highest draw order of any primitive that
191    /// it overlaps, so it draws above them. In either case the order is at least the floor set by
192    /// [`Scene::raise_order_floor`].
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// # use gpui::{Bounds, Quad, Scene, ScaledPixels, point, size};
198    /// let mut scene = Scene::default();
199    /// let bounds = Bounds {
200    ///     origin: point(ScaledPixels(0.), ScaledPixels(0.)),
201    ///     size: size(ScaledPixels(100.), ScaledPixels(40.)),
202    /// };
203    /// // The default content mask is empty, so this quad is clipped away.
204    /// scene.insert_primitive(Quad { bounds, ..Default::default() });
205    /// scene.finish();
206    /// assert!(scene.quads.is_empty());
207    /// ```
208    pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
209        let mut primitive = primitive.into();
210        self.is_finished = false;
211
212        // Cached glyph batches may have been recorded outside isolation. The monochrome
213        // pipeline also accepts RGB atlas masks and provides a scalar-alpha fallback.
214        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        // Isolation boundaries stay paired even when their clipped region is empty.
233        // Each marker sorts above prior content, and its order floor keeps following
234        // content inside the intended group or after its closing marker.
235        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    /// Stores `primitive` at `order`, updating the filter-group bookkeeping a marker carries.
259    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                // Every boundary breaks active layer batching and starts a new order segment.
302                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    /// Inserts the paint operations in `range` from `prev_scene` into this scene, as if they had been
320    /// painted again.
321    ///
322    /// Use this to reuse the output of an earlier frame for a view that hasn't changed. Draw orders
323    /// are assigned again in this scene.
324    ///
325    /// # Panics
326    ///
327    /// Panics if `range` extends past the end of `prev_scene`'s paint operations.
328    pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
329        self.replay_with(range, prev_scene, &mut None);
330    }
331
332    /// [`Scene::replay`], with the range's summary kept in `summary` for the next replay.
333    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            // Primitives in an open layer take its order, which a shift can't express.
342            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        // The range keeps the relative orders it was painted with, shifted as one block so it
355        // draws above whatever it overlaps here, and stands in the bounds tree as one leaf.
356        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            // Isolation markers must sort above everything painted so far.
373            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    /// Sorts the primitives by draw order and compiles the renderer's plan.
408    ///
409    /// Call this after painting and before rendering. Adding a primitive after `finish` marks the
410    /// scene unfinished again; call `finish` again before reading [`Scene::batches`] or
411    /// [`Scene::render_commands`].
412    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        // Markers normally get distinct, monotonically-increasing orders (children overlap
427        // their group bounds and so sort strictly between the start and end). The `!is_start`
428        // tiebreak only matters for a degenerate empty group whose start and end tie: it keeps
429        // the start (false = 0) ahead of the end (true = 1) so the pair stays well-formed.
430        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    /// Returns the draw batches of a finished scene, in drawing order.
438    ///
439    /// Each batch is a run of one primitive type that the renderer draws in one call.
440    /// [`PrimitiveBatch::FilterBoundary`] entries mark the start and end of content-filter groups.
441    /// Use [`Scene::render_commands`] when you also need the offscreen target for each group.
442    ///
443    /// # Panics
444    ///
445    /// In debug builds, panics if the scene hasn't been finished.
446    ///
447    /// # Examples
448    ///
449    /// ```
450    /// # use gpui::{Bounds, ContentMask, PrimitiveBatch, Quad, Scene, ScaledPixels, point, size};
451    /// let bounds = Bounds {
452    ///     origin: point(ScaledPixels(0.), ScaledPixels(0.)),
453    ///     size: size(ScaledPixels(100.), ScaledPixels(40.)),
454    /// };
455    /// let mut scene = Scene::default();
456    /// scene.insert_primitive(Quad {
457    ///     bounds,
458    ///     content_mask: ContentMask { bounds, ..Default::default() },
459    ///     ..Default::default()
460    /// });
461    /// scene.finish();
462    /// assert_eq!(
463    ///     scene.batches().collect::<Vec<_>>(),
464    ///     vec![PrimitiveBatch::Quads { range: 0..1, smoothed: false }],
465    /// );
466    /// ```
467    #[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    /// Returns the render commands of a finished scene, in drawing order.
488    ///
489    /// Commands are primitive batches, plus begin and end markers for content-filter groups that say
490    /// whether each group draws into an isolated target. Renderers use these instead of
491    /// [`Scene::batches`] when they manage offscreen targets.
492    ///
493    /// # Panics
494    ///
495    /// In debug builds, panics if the scene hasn't been finished.
496    pub fn render_commands(&self) -> &[RenderCommand] {
497        self.render_plan().commands()
498    }
499
500    /// Returns the compiled [`ScenePlan`] of a finished scene.
501    ///
502    /// # Panics
503    ///
504    /// In debug builds, panics if the scene hasn't been finished, or if its primitive storage changed
505    /// after [`Scene::finish`].
506    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    /// Returns the opacity of each entry in [`Scene::surfaces`], in the same order.
516    ///
517    /// Surfaces are fully opaque here; group opacity is applied when compositing.
518    pub fn surface_opacities(&self) -> &[f32] {
519        &self.surface_opacities
520    }
521
522    /// Returns `true` if rendering this scene needs an offscreen target. That is the case when it has
523    /// backdrop filters, content filters, or grouped opacity.
524    ///
525    /// # Panics
526    ///
527    /// In debug builds, panics if the scene hasn't been finished.
528    pub fn requires_offscreen_rendering(&self) -> bool {
529        self.render_plan().requirements().uses_offscreen_target
530    }
531}
532
533/// Internal representation of [`palette::Hsla`] with the layout provided to the renderer.
534#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
535#[repr(C)]
536pub struct SceneHsla {
537    /// Hue, in a range from 0 to 1
538    pub(crate) h: f32,
539    /// Saturation, in a range from 0 to 1
540    pub(crate) s: f32,
541    /// Lightness, in a range from 0 to 1
542    pub(crate) l: f32,
543    /// Alpha, in a range from 0 to 1
544    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    // Lowest discriminant: at an equal order, a content-filter group-start is emitted before
572    // the group's own content so the renderer redirects rendering before any child draws.
573    FilterBoundaryStart,
574    Shadow,
575    #[default]
576    Quad,
577    Path,
578    Underline,
579    MonochromeSprite,
580    SubpixelSprite,
581    PolychromeSprite,
582    Surface,
583    BackdropFilter,
584    // Highest discriminant: at an equal order, a group-end is emitted after the group's content
585    // so the renderer composites the filtered group only once every child has been drawn.
586    FilterBoundaryEnd,
587}
588
589/// What [`Scene::replay`] needs to know about a range of paint operations. It doesn't change
590/// when the range is shifted as one block, so a view keeps it from one replay to the next.
591#[derive(Clone, Copy, Debug)]
592pub(crate) struct ReplaySummary {
593    union: Bounds<ScaledPixels>,
594    /// The highest draw order in the range minus the lowest.
595    span: u32,
596    /// The first primitive's draw order minus the lowest.
597    first_offset: u32,
598    has_boundaries: bool,
599}
600
601impl ReplaySummary {
602    /// Summarizes `operations`, which must contain a primitive.
603    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/// Any primitive that [`Scene::insert_primitive`] accepts.
643#[derive(Clone)]
644pub enum Primitive {
645    /// A [`Shadow`].
646    Shadow(Shadow),
647    /// A [`Quad`].
648    Quad(Quad),
649    /// A [`Path`].
650    Path(Path<ScaledPixels>),
651    /// An [`Underline`].
652    Underline(Underline),
653    /// A [`MonochromeSprite`].
654    MonochromeSprite(MonochromeSprite),
655    /// A [`SubpixelSprite`].
656    SubpixelSprite(SubpixelSprite),
657    /// A [`PolychromeSprite`].
658    PolychromeSprite(PolychromeSprite),
659    /// A [`crate::PaintSurface`].
660    Surface(PaintSurface),
661    /// A [`crate::BackdropFilter`].
662    BackdropFilter(BackdropFilter),
663    /// A [`FilterBoundary`] marker.
664    FilterBoundary(FilterBoundary),
665}
666
667impl Primitive {
668    /// The screen-space bounds of the primitive, which for transformed sprites differ from
669    /// their stored bounds.
670    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    /// Returns the bounds of this primitive, in scaled pixels.
683    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    /// The draw order the scene assigned when the primitive was inserted.
699    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    /// Returns the content mask that clips this primitive.
715    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                // The same vec yields both start and end markers; the discriminant decides
868                // where the next marker sorts relative to draw batches at an equal order
869                // (start before content, end after).
870                match self.filter_boundaries_iter.peek() {
871                    Some(boundary) if boundary.is_start => PrimitiveKind::FilterBoundaryStart,
872                    _ => PrimitiveKind::FilterBoundaryEnd,
873                },
874            ),
875        ];
876        // Find the two lowest live cursors in one fixed-size scan.
877        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            // Boundaries are emitted one at a time (never merged) so the renderer can switch
1084            // render targets at exactly the right point in the batch stream.
1085            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/// A filled rectangle with an optional border and rounded corners, in scaled pixels.
1096///
1097/// This is the scene form of a [`crate::PaintQuad`]. [`crate::Window::paint_quad`] creates it for you, so build a
1098/// [`crate::PaintQuad`] with [`crate::fill`], [`crate::outline`] or [`crate::quad`] instead.
1099#[derive(Debug, Copy, Clone)]
1100#[repr(C)]
1101pub struct Quad {
1102    /// The draw order. [`Scene::insert_primitive`] assigns it. Set it to `0` when you build a quad.
1103    pub order: DrawOrder,
1104    /// Whether the border is [`BorderStyle::Solid`] or [`BorderStyle::Dashed`].
1105    pub border_style: BorderStyle,
1106    /// The length of each dash of a dashed border, as a multiple of the border width.
1107    pub border_dashed_length: f32,
1108    /// The gap between dashes of a dashed border, as a multiple of the border width.
1109    pub border_dashed_gap: f32,
1110    /// The rectangle to fill, in scaled pixels.
1111    pub bounds: Bounds<ScaledPixels>,
1112    /// The region that clips this quad.
1113    pub content_mask: ContentMask<ScaledPixels>,
1114    /// The fill. Use a transparent background to draw only the border.
1115    pub background: Background,
1116    /// The color of the border.
1117    pub border_color: Background,
1118    /// The radius of each corner, in scaled pixels. A radius larger than half the quad's size
1119    /// produces sharp corners where the arcs meet.
1120    pub corner_radii: Corners<ScaledPixels>,
1121    /// The width of each border edge, in scaled pixels.
1122    pub border_widths: Edges<ScaledPixels>,
1123    /// How smooth the corners are, from `0.0` for circular arcs to `1.0`. Set by
1124    /// [`crate::PaintQuad::corner_smoothing`] when passed to [`crate::Window::paint_quad`].
1125    pub corner_smoothing: f32,
1126    /// Padding for the GPU struct layout. [`crate::Window`] always sets it to `0`.
1127    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/// A horizontal line under or through text, which can be wavy.
1156///
1157/// Produced by [`crate::Window::paint_underline`] and [`crate::Window::paint_strikethrough`].
1158#[derive(Debug, Copy, Clone)]
1159#[repr(C)]
1160pub struct Underline {
1161    /// The draw order. [`Scene::insert_primitive`] assigns it.
1162    pub order: DrawOrder,
1163    /// Padding for the GPU struct layout. [`crate::Window`] always sets it to `0`.
1164    pub padding: u32,
1165    /// The line's full rectangle, in scaled pixels. This remains the phase origin for wavy lines.
1166    pub bounds: Bounds<ScaledPixels>,
1167    /// The rectangle the line is clipped to, within `content_mask.bounds`. Narrowing it splits a
1168    /// line around exclusions without moving a wavy line's phase or the content mask's fade.
1169    pub clip_bounds: Bounds<ScaledPixels>,
1170    /// The region that clips this line and supplies its edge fade.
1171    pub content_mask: ContentMask<ScaledPixels>,
1172    /// The color of the line.
1173    pub color: SceneHsla,
1174    /// The thickness of the line, in scaled pixels.
1175    pub thickness: ScaledPixels,
1176    /// Draws a wavy line when [`ShaderBool::Enabled`].
1177    pub wavy: ShaderBool,
1178}
1179
1180impl From<Underline> for Primitive {
1181    fn from(underline: Underline) -> Self {
1182        Primitive::Underline(underline)
1183    }
1184}
1185
1186/// A blurred shadow of a rounded rectangle. It is drawn outside the element, or inside it when
1187/// [`Shadow::inset`] is set.
1188///
1189/// Produced by [`crate::Window::paint_drop_shadows`] and [`crate::Window::paint_inset_shadows`].
1190#[derive(Debug, Copy, Clone)]
1191#[repr(C)]
1192pub struct Shadow {
1193    /// The draw order. [`Scene::insert_primitive`] assigns it.
1194    pub order: DrawOrder,
1195    /// The blur radius, in scaled pixels.
1196    pub blur_radius: ScaledPixels,
1197    /// The rectangle that the shadow is drawn for, in scaled pixels.
1198    pub bounds: Bounds<ScaledPixels>,
1199    /// The corner radii of the shadow, in scaled pixels.
1200    pub corner_radii: Corners<ScaledPixels>,
1201    /// The region that clips this shadow.
1202    pub content_mask: ContentMask<ScaledPixels>,
1203    /// The color of the shadow.
1204    pub color: Background,
1205    /// The bounds of the element that casts the shadow, in scaled pixels.
1206    pub element_bounds: Bounds<ScaledPixels>,
1207    /// The corner radii of [`Shadow::element_bounds`], in scaled pixels.
1208    pub element_corner_radii: Corners<ScaledPixels>,
1209    /// Whether this shadow is rendered inside the element instead of outside it.
1210    pub inset: ShaderBool,
1211    /// How smooth the corners are, from `0.0` for circular arcs to `1.0`.
1212    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/// A filter applied to the content already painted behind `bounds`, composited into a rounded
1222/// rectangle. A blur gives the frosted-glass effect.
1223///
1224/// Produced by [`crate::Window::paint_backdrop_filter`]. It is only emitted when its filter chain is not
1225/// empty, since identity filters are dropped at paint time.
1226#[derive(Default, Debug, Clone)]
1227pub struct BackdropFilter {
1228    /// The draw order. [`Scene::insert_primitive`] assigns it.
1229    pub order: DrawOrder,
1230    /// The rectangle that receives the filtered backdrop, in scaled pixels.
1231    pub bounds: Bounds<ScaledPixels>,
1232    /// The region that clips this filter.
1233    pub content_mask: ContentMask<ScaledPixels>,
1234    /// The corner radii of [`BackdropFilter::bounds`], in scaled pixels.
1235    pub corner_radii: Corners<ScaledPixels>,
1236    /// How smooth the corners are, from `0.0` for circular arcs to `1.0`.
1237    pub corner_smoothing: f32,
1238    /// The filter chain applied to the backdrop, in scene (device-pixel) space. Identity filters
1239    /// are dropped at paint time, so a `BackdropFilter` is only emitted when this is non-empty.
1240    ///
1241    /// Inline capacity is 4: a `SmallVec<[ScaledFilter; 4]>` is the same size as capacity 1 here
1242    /// (the heap repr already occupies that space), so chains up to 4 filters avoid allocating
1243    /// at no extra struct size.
1244    pub filters: SmallVec<[ScaledFilter; 4]>,
1245    /// The element opacity, multiplied into the composited result.
1246    pub opacity: f32,
1247}
1248
1249impl BackdropFilter {
1250    /// Returns the largest Gaussian blur radius in this filter chain, in device pixels.
1251    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/// The start or end marker of a content-filter or opacity isolation group.
1263///
1264/// The primitives between a matched pair are drawn into an offscreen target, filtered as one layer,
1265/// and composited back into `bounds`. Deeper groups allocate additional isolation targets.
1266///
1267/// Produced by [`crate::Window::with_filter_layer`], which inserts a start marker, paints its contents, and
1268/// inserts the end marker.
1269#[derive(Debug, Clone)]
1270pub struct FilterBoundary {
1271    /// The draw order. [`Scene::insert_primitive`] assigns it.
1272    pub order: DrawOrder,
1273    /// The rectangle that the filtered group is composited into, in scaled pixels.
1274    pub bounds: Bounds<ScaledPixels>,
1275    /// The region that clips this group.
1276    pub content_mask: ContentMask<ScaledPixels>,
1277    /// The corner radii of [`FilterBoundary::bounds`], in scaled pixels.
1278    pub corner_radii: Corners<ScaledPixels>,
1279    /// How smooth the corners are, from `0.0` for circular arcs to `1.0`.
1280    pub corner_smoothing: f32,
1281    /// The filter chain applied to the isolated group, in scene (device-pixel) space. Identity
1282    /// filters are dropped at paint time. An empty chain represents an opacity-only group.
1283    /// Inline capacity 4 (same struct size as 1 here — see [`BackdropFilter::filters`]).
1284    pub filters: SmallVec<[ScaledFilter; 4]>,
1285    /// Opacity applied once when compositing the completed group into its parent.
1286    pub opacity: f32,
1287    /// `true` for the start marker, which opens the group. `false` for the end marker, which closes it.
1288    pub is_start: bool,
1289}
1290
1291impl FilterBoundary {
1292    /// Returns the largest Gaussian blur radius in this filter chain, in device pixels.
1293    pub fn max_blur_radius(&self) -> f32 {
1294        max_blur_radius(&self.filters)
1295    }
1296}
1297
1298/// Returns the largest blur radius in a scene-space filter chain.
1299///
1300/// This match is deliberately exhaustive so adding a filter requires one shared scheduling
1301/// decision instead of three backend-specific implementations that can drift.
1302fn 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/// The style of a [`Quad`] border.
1315#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
1316#[repr(u32)]
1317pub enum BorderStyle {
1318    /// A solid border.
1319    #[default]
1320    Solid = 0,
1321    /// A dashed border.
1322    Dashed = 1,
1323}
1324
1325/// A 2D affine transform in scaled pixels: a 2x2 linear part and a translation.
1326///
1327/// Sprites carry one in [`MonochromeSprite::transformation`] and [`SubpixelSprite::transformation`].
1328/// Start from [`TransformationMatrix::unit`] and chain calls to build a transform. When you chain
1329/// calls, the transform from the last call is applied to a point first.
1330///
1331/// # Examples
1332///
1333/// ```
1334/// # use gpui::{ScaledPixels, TransformationMatrix, point, px, size};
1335/// let moved = TransformationMatrix::unit().translate(point(ScaledPixels(10.), ScaledPixels(5.)));
1336/// assert_eq!(moved.apply(point(px(1.), px(2.))), point(px(11.), px(7.)));
1337///
1338/// let scaled = TransformationMatrix::unit().scale(size(2., 3.));
1339/// assert_eq!(scaled.apply(point(px(1.), px(1.))), point(px(2.), px(3.)));
1340///
1341/// // The translation is applied first, then the scale.
1342/// let chained = TransformationMatrix::unit()
1343///     .scale(size(2., 3.))
1344///     .translate(point(ScaledPixels(10.), ScaledPixels(5.)));
1345/// assert_eq!(chained.apply(point(px(1.), px(1.))), point(px(22.), px(18.)));
1346/// ```
1347#[derive(Debug, Clone, Copy, PartialEq)]
1348#[repr(C)]
1349pub struct TransformationMatrix {
1350    /// 2x2 matrix containing rotation and scale,
1351    /// stored row-major
1352    pub rotation_scale: [[f32; 2]; 2],
1353    /// translation vector
1354    pub translation: [f32; 2],
1355}
1356
1357impl Eq for TransformationMatrix {}
1358
1359impl TransformationMatrix {
1360    /// Returns the identity transform, which leaves points unchanged.
1361    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    /// Returns this transform combined with a translation by `point`. The translation is applied to
1369    /// points before this transform.
1370    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    /// Returns this transform combined with a clockwise rotation by `angle` around the origin. Clockwise
1378    /// is on screen, where the y axis points down.
1379    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    /// Returns this transform combined with a scale by `size` around the origin.
1390    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    /// Returns `self` combined with `other`, so that `other` is applied to points first. This is the
1398    /// matrix product `self * other`.
1399    #[inline]
1400    pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
1401        if other == Self::unit() {
1402            return self;
1403        }
1404        // Perform matrix multiplication
1405        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    /// Returns `point` transformed by this matrix.
1432    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/// A single-color sprite: an alpha mask from the sprite atlas, tinted with `color`.
1451///
1452/// Produced by [`crate::Window::paint_svg`] for icons, and by [`crate::Window::paint_glyph`] for glyphs that are
1453/// drawn as alpha masks.
1454#[derive(Copy, Clone, Debug)]
1455#[repr(C)]
1456pub struct MonochromeSprite {
1457    /// The draw order. [`Scene::insert_primitive`] assigns it.
1458    pub order: DrawOrder,
1459    /// Padding for the GPU struct layout. [`crate::Window`] always sets it to `0`.
1460    pub padding: u32,
1461    /// The rectangle to draw into, in scaled pixels.
1462    pub bounds: Bounds<ScaledPixels>,
1463    /// The region that clips this sprite.
1464    pub content_mask: ContentMask<ScaledPixels>,
1465    /// The tint color.
1466    pub color: SceneHsla,
1467    /// The atlas tile that holds the mask.
1468    pub tile: AtlasTile,
1469    /// The transform applied to the sprite, around its origin.
1470    pub transformation: TransformationMatrix,
1471}
1472
1473impl From<MonochromeSprite> for Primitive {
1474    fn from(sprite: MonochromeSprite) -> Self {
1475        Primitive::MonochromeSprite(sprite)
1476    }
1477}
1478
1479/// A glyph drawn with subpixel anti-aliasing, tinted with `color`.
1480///
1481/// Produced by [`crate::Window::paint_glyph`] for glyphs that use subpixel rendering.
1482#[derive(Copy, Clone, Debug)]
1483#[repr(C)]
1484pub struct SubpixelSprite {
1485    /// The draw order. [`Scene::insert_primitive`] assigns it.
1486    pub order: DrawOrder,
1487    /// Padding for the GPU struct layout. [`crate::Window`] always sets it to `0`.
1488    pub padding: u32,
1489    /// The rectangle to draw into, in scaled pixels.
1490    pub bounds: Bounds<ScaledPixels>,
1491    /// The region that clips this sprite.
1492    pub content_mask: ContentMask<ScaledPixels>,
1493    /// The tint color.
1494    pub color: SceneHsla,
1495    /// The atlas tile that holds the glyph.
1496    pub tile: AtlasTile,
1497    /// The transform applied to the sprite, around its origin.
1498    pub transformation: TransformationMatrix,
1499}
1500
1501impl From<SubpixelSprite> for Primitive {
1502    fn from(sprite: SubpixelSprite) -> Self {
1503        Primitive::SubpixelSprite(sprite)
1504    }
1505}
1506
1507/// A sprite whose colors come from the atlas as they are, such as an image or a color emoji.
1508///
1509/// Produced by [`crate::Window::paint_image`] and [`crate::Window::paint_emoji`], and by [`crate::Window::paint_glyph`]
1510/// for color glyphs.
1511#[derive(Copy, Clone, Debug)]
1512#[repr(C)]
1513pub struct PolychromeSprite {
1514    /// The draw order. [`Scene::insert_primitive`] assigns it.
1515    pub order: DrawOrder,
1516    /// Draws the sprite in grayscale when [`ShaderBool::Enabled`].
1517    pub grayscale: ShaderBool,
1518    /// The opacity, multiplied into the sprite's alpha.
1519    pub opacity: f32,
1520    /// How smooth the corners are, from `0.0` for circular arcs to `1.0`.
1521    pub corner_smoothing: f32,
1522    /// The rectangle to draw into, in scaled pixels.
1523    pub bounds: Bounds<ScaledPixels>,
1524    /// The region that clips this sprite.
1525    pub content_mask: ContentMask<ScaledPixels>,
1526    /// The corner radii of [`PolychromeSprite::bounds`], in scaled pixels.
1527    pub corner_radii: Corners<ScaledPixels>,
1528    /// The atlas tile that holds the pixels.
1529    pub tile: AtlasTile,
1530}
1531
1532impl From<PolychromeSprite> for Primitive {
1533    fn from(sprite: PolychromeSprite) -> Self {
1534        Primitive::PolychromeSprite(sprite)
1535    }
1536}
1537
1538/// A surface produced outside GPUI, such as a video frame, drawn as a rectangle.
1539///
1540/// Produced by [`crate::Window::paint_surface`].
1541#[derive(Clone, Debug)]
1542pub struct PaintSurface {
1543    /// The draw order. [`Scene::insert_primitive`] assigns it.
1544    pub order: DrawOrder,
1545    /// The rectangle to draw into, in scaled pixels.
1546    pub bounds: Bounds<ScaledPixels>,
1547    /// The region that clips this surface.
1548    pub content_mask: ContentMask<ScaledPixels>,
1549    /// Where the pixels come from.
1550    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/// Identifies a [`Path`] by the index it had in [`Scene::paths`] when the path was inserted. The
1560/// scene assigns it at that point, and [`Scene::finish`] may sort the paths afterwards.
1561#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1562pub struct PathId(pub usize);
1563
1564/// A filled shape, stored as a triangle mesh.
1565///
1566/// Use [`crate::PathBuilder`] to build a path from lines and curves, and [`crate::Window::paint_path`] to paint
1567/// it. The methods on this type add triangles one at a time, which is lower level than
1568/// [`crate::PathBuilder`].
1569///
1570/// # Examples
1571///
1572/// ```
1573/// # use gpui::{Path, point, px};
1574/// let mut path = Path::new(point(px(0.), px(0.)));
1575/// path.line_to(point(px(10.), px(0.)));
1576/// path.line_to(point(px(10.), px(10.)));
1577/// assert_eq!(path.vertices.len(), 3);
1578/// ```
1579#[derive(Clone, Debug)]
1580pub struct Path<P: Clone + Debug + Default + PartialEq> {
1581    /// The index this path had in [`Scene::paths`] when it was inserted.
1582    pub id: PathId,
1583    /// The draw order. [`Scene::insert_primitive`] assigns it.
1584    pub order: DrawOrder,
1585    /// The bounding box of all vertices.
1586    pub bounds: Bounds<P>,
1587    /// The region that clips this path.
1588    pub content_mask: ContentMask<P>,
1589    /// The triangle vertices, three per triangle.
1590    pub vertices: Vec<PathVertex<P>>,
1591    /// The fill color or gradient.
1592    pub color: Background,
1593    start: Point<P>,
1594    current: Point<P>,
1595    contour_count: usize,
1596}
1597
1598impl Path<Pixels> {
1599    /// Creates an empty path whose current point is `start`. The path has no area until it has a
1600    /// second segment.
1601    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    /// Returns a copy of this path with every coordinate multiplied by `factor`. Use it to convert
1619    /// logical pixels to scaled pixels.
1620    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    /// Moves the current point to `to` and starts a new contour there. Nothing is drawn.
1639    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    /// Draws a straight line from the current point to `to`.
1646    ///
1647    /// The first call after [`Path::new`] only moves the current point. Each later call adds a triangle
1648    /// made from the contour's start point, the current point and `to`.
1649    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    /// Draws a quadratic curve from the current point to `to`, bent toward the control point `ctrl`.
1661    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    /// Appends one triangle, with positions and shader coordinates for each corner, and grows
1678    /// [`Path::bounds`] to include it.
1679    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    /// Returns [`Path::bounds`] intersected with the content mask.
1722    #[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/// A vertex of a [`Path`] triangle.
1735#[derive(Clone, Debug)]
1736#[repr(C)]
1737pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
1738    /// The position of the vertex.
1739    pub xy_position: Point<P>,
1740    /// Shader coordinates that tell the renderer where the vertex sits on a curve. Straight edges
1741    /// use `(0, 1)`. A curve from [`Path::curve_to`] uses `(0, 0)`, `(0.5, 0)` and `(1, 1)`.
1742    pub st_position: Point<f32>,
1743    /// The content mask of this vertex. [`crate::Window::paint_path`] leaves it at its default.
1744    pub content_mask: ContentMask<P>,
1745}
1746
1747impl PathVertex<Pixels> {
1748    /// Returns this vertex with its position and content mask multiplied by `factor`.
1749    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    /// Default primitives overlap so their draw orders follow insertion order.
1768    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            // Three wide and two tall, before the transformation.
1831            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    /// Rotates a quarter turn, so the sprite covers x in 3..5 and y in 1..4.
1857    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        // The mask only overlaps the rotated sprite.
1883        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        // The mask only overlaps the sprite before it is rotated.
1891        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    /// A 100x100 quad whose bounds don't overlap `full_bounds()` (used to exercise the
1987    /// order-reuse path: non-overlapping content reuses low draw-orders).
1988    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        // Primitive storage is public, so direct mutation must change the compiled plan.
2157        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}