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
19#[allow(non_camel_case_types, unused)]
20#[expect(missing_docs)]
21pub type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
22
23#[expect(missing_docs)]
24pub type DrawOrder = u32;
25
26/// A boolean stored as a `u32` so that GPU-facing structs contain no
27/// compiler-inserted padding bytes, which would be undefined behavior to
28/// reinterpret as `&[u8]` when writing instance buffers. Guaranteed to be
29/// `0` or `1` by construction; shaders read it as a `u32`/`uint`.
30#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
31#[repr(transparent)]
32pub struct PaddedBool32(u32);
33
34impl From<bool> for PaddedBool32 {
35    fn from(value: bool) -> Self {
36        PaddedBool32(value as u32)
37    }
38}
39
40#[derive(Default)]
41#[expect(missing_docs)]
42pub struct Scene {
43    pub(crate) paint_operations: Vec<PaintOperation>,
44    primitive_bounds: BoundsTree<ScaledPixels>,
45    layer_stack: Vec<DrawOrder>,
46    pub shadows: Vec<Shadow>,
47    pub quads: Vec<Quad>,
48    pub paths: Vec<Path<ScaledPixels>>,
49    pub underlines: Vec<Underline>,
50    pub monochrome_sprites: Vec<MonochromeSprite>,
51    pub subpixel_sprites: Vec<SubpixelSprite>,
52    pub polychrome_sprites: Vec<PolychromeSprite>,
53    pub surfaces: Vec<PaintSurface>,
54    pub backdrop_filters: Vec<BackdropFilter>,
55    pub filter_boundaries: Vec<FilterBoundary>,
56}
57
58#[expect(missing_docs)]
59impl Scene {
60    pub fn clear(&mut self) {
61        self.paint_operations.clear();
62        self.primitive_bounds.clear();
63        self.layer_stack.clear();
64        self.paths.clear();
65        self.shadows.clear();
66        self.quads.clear();
67        self.underlines.clear();
68        self.monochrome_sprites.clear();
69        self.subpixel_sprites.clear();
70        self.polychrome_sprites.clear();
71        self.surfaces.clear();
72        self.backdrop_filters.clear();
73        self.filter_boundaries.clear();
74    }
75
76    pub fn len(&self) -> usize {
77        self.paint_operations.len()
78    }
79
80    pub fn push_layer(&mut self, bounds: Bounds<ScaledPixels>) {
81        let order = self.primitive_bounds.insert(bounds);
82        self.layer_stack.push(order);
83        self.paint_operations
84            .push(PaintOperation::StartLayer(bounds));
85    }
86
87    pub fn pop_layer(&mut self) {
88        self.layer_stack.pop();
89        self.paint_operations.push(PaintOperation::EndLayer);
90    }
91
92    /// Raise the draw-order floor so every primitive inserted afterwards sorts above everything
93    /// inserted before. Called before painting deferred draws so overlays (tooltips, popovers,
94    /// drag images) sort above the main scene — and a deferred backdrop's order can't fall inside
95    /// a content-filter (`filter`) order range left behind by the main scene.
96    pub fn raise_order_floor(&mut self) {
97        let floor = self.primitive_bounds.max_order() + 1;
98        self.primitive_bounds.set_order_floor(floor);
99    }
100
101    pub fn insert_primitive(&mut self, primitive: impl Into<Primitive>) {
102        let mut primitive = primitive.into();
103        let clipped_bounds = primitive
104            .bounds()
105            .intersect(&primitive.content_mask().bounds);
106
107        // Content-filter boundaries must always be inserted as matched pairs — dropping one
108        // (e.g. for an empty clipped region) would orphan its partner and corrupt the renderer's
109        // target stack. Each marker takes an order strictly above ALL prior content, so the start
110        // sorts after everything painted before it and the element's own children (which overlap
111        // the marker bounds) sort strictly above the start. This keeps a marker's order range from
112        // colliding with unrelated non-overlapping content that reuses low orderings (e.g. a
113        // background grid), which would otherwise sweep that content into the group. Content
114        // painted *after* the group is held above it by raising the order floor when the end
115        // marker is inserted (see below) — otherwise a later non-overlapping sibling could reuse a
116        // low order that lands inside the start..end range and be swept into the group.
117        let is_filter_boundary = matches!(primitive, Primitive::FilterBoundary(_));
118
119        if clipped_bounds.is_empty() && !is_filter_boundary {
120            return;
121        }
122
123        let order = if is_filter_boundary {
124            let order_bounds = if clipped_bounds.is_empty() {
125                *primitive.bounds()
126            } else {
127                clipped_bounds
128            };
129            self.primitive_bounds.insert_above_all(order_bounds)
130        } else {
131            self.layer_stack
132                .last()
133                .copied()
134                .unwrap_or_else(|| self.primitive_bounds.insert(clipped_bounds))
135        };
136        match &mut primitive {
137            Primitive::Shadow(shadow) => {
138                shadow.order = order;
139                self.shadows.push(*shadow);
140            }
141            Primitive::Quad(quad) => {
142                quad.order = order;
143                self.quads.push(*quad);
144            }
145            Primitive::Path(path) => {
146                path.order = order;
147                path.id = PathId(self.paths.len());
148                self.paths.push(path.clone());
149            }
150            Primitive::Underline(underline) => {
151                underline.order = order;
152                self.underlines.push(*underline);
153            }
154            Primitive::MonochromeSprite(sprite) => {
155                sprite.order = order;
156                self.monochrome_sprites.push(*sprite);
157            }
158            Primitive::SubpixelSprite(sprite) => {
159                sprite.order = order;
160                self.subpixel_sprites.push(*sprite);
161            }
162            Primitive::PolychromeSprite(sprite) => {
163                sprite.order = order;
164                self.polychrome_sprites.push(*sprite);
165            }
166            Primitive::Surface(surface) => {
167                surface.order = order;
168                self.surfaces.push(surface.clone());
169            }
170            Primitive::BackdropFilter(filter) => {
171                filter.order = order;
172                self.backdrop_filters.push(filter.clone());
173            }
174            Primitive::FilterBoundary(boundary) => {
175                boundary.order = order;
176                if !boundary.is_start {
177                    // A closed content-filter group is a draw-order barrier: everything painted
178                    // afterwards must sort above the group's end marker so it can't fall back
179                    // inside the group's order range (subsequent non-overlapping content otherwise
180                    // reuses a low order). Mirrors the floor raised before deferred draws in
181                    // `raise_order_floor`.
182                    self.primitive_bounds.set_order_floor(order + 1);
183                }
184                self.filter_boundaries.push(boundary.clone());
185            }
186        }
187        self.paint_operations
188            .push(PaintOperation::Primitive(primitive));
189    }
190
191    pub fn replay(&mut self, range: Range<usize>, prev_scene: &Scene) {
192        for operation in &prev_scene.paint_operations[range] {
193            match operation {
194                PaintOperation::Primitive(primitive) => self.insert_primitive(primitive.clone()),
195                PaintOperation::StartLayer(bounds) => self.push_layer(*bounds),
196                PaintOperation::EndLayer => self.pop_layer(),
197            }
198        }
199    }
200
201    pub fn finish(&mut self) {
202        self.shadows.sort_by_key(|shadow| shadow.order);
203        self.quads.sort_by_key(|quad| quad.order);
204        self.paths.sort_by_key(|path| path.order);
205        self.underlines.sort_by_key(|underline| underline.order);
206        self.monochrome_sprites
207            .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
208        self.subpixel_sprites
209            .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
210        self.polychrome_sprites
211            .sort_by_key(|sprite| (sprite.order, sprite.tile.tile_id));
212        self.surfaces.sort_by_key(|surface| surface.order);
213        self.backdrop_filters.sort_by_key(|filter| filter.order);
214        // Markers normally get distinct, monotonically-increasing orders (children overlap
215        // their group bounds and so sort strictly between the start and end). The `!is_start`
216        // tiebreak only matters for a degenerate empty group whose start and end tie: it keeps
217        // the start (false = 0) ahead of the end (true = 1) so the pair stays well-formed.
218        self.filter_boundaries
219            .sort_by_key(|boundary| (boundary.order, !boundary.is_start));
220    }
221
222    #[cfg_attr(
223        all(
224            any(target_os = "linux", target_os = "freebsd"),
225            not(any(feature = "x11", feature = "wayland"))
226        ),
227        allow(dead_code)
228    )]
229    pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> + '_ {
230        BatchIterator {
231            shadows_start: 0,
232            shadows_iter: self.shadows.iter().peekable(),
233            quads_start: 0,
234            quads_iter: self.quads.iter().peekable(),
235            paths_start: 0,
236            paths_iter: self.paths.iter().peekable(),
237            underlines_start: 0,
238            underlines_iter: self.underlines.iter().peekable(),
239            monochrome_sprites_start: 0,
240            monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
241            subpixel_sprites_start: 0,
242            subpixel_sprites_iter: self.subpixel_sprites.iter().peekable(),
243            polychrome_sprites_start: 0,
244            polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
245            surfaces_start: 0,
246            surfaces_iter: self.surfaces.iter().peekable(),
247            backdrop_filters_start: 0,
248            backdrop_filters_iter: self.backdrop_filters.iter().peekable(),
249            filter_boundaries_start: 0,
250            filter_boundaries_iter: self.filter_boundaries.iter().peekable(),
251        }
252    }
253}
254
255/// Internal representation of [`palette::Hsla`] which is layout sensitive, as its provided to the renderer.
256#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
257#[repr(C)]
258pub struct SceneHsla {
259    /// Hue, in a range from 0 to 1
260    pub(crate) h: f32,
261    /// Saturation, in a range from 0 to 1
262    pub(crate) s: f32,
263    /// Lightness, in a range from 0 to 1
264    pub(crate) l: f32,
265    /// Alpha, in a range from 0 to 1
266    pub(crate) a: f32,
267}
268impl Into<palette::Hsla> for SceneHsla {
269    fn into(self) -> palette::Hsla {
270        palette::Hsla::new(self.h * 360.0, self.s, self.l, self.a)
271    }
272}
273impl From<palette::Hsla> for SceneHsla {
274    fn from(hsla: palette::Hsla) -> Self {
275        Self {
276            h: hsla.hue.into_positive_degrees() / 360.0,
277            s: hsla.saturation,
278            l: hsla.lightness,
279            a: hsla.alpha,
280        }
281    }
282}
283
284#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
285#[cfg_attr(
286    all(
287        any(target_os = "linux", target_os = "freebsd"),
288        not(any(feature = "x11", feature = "wayland"))
289    ),
290    allow(dead_code)
291)]
292pub(crate) enum PrimitiveKind {
293    // Lowest discriminant: at an equal order, a content-filter group-start is emitted before
294    // the group's own content so the renderer redirects rendering before any child draws.
295    FilterBoundaryStart,
296    Shadow,
297    #[default]
298    Quad,
299    Path,
300    Underline,
301    MonochromeSprite,
302    SubpixelSprite,
303    PolychromeSprite,
304    Surface,
305    BackdropFilter,
306    // Highest discriminant: at an equal order, a group-end is emitted after the group's content
307    // so the renderer composites the filtered group only once every child has been drawn.
308    FilterBoundaryEnd,
309}
310
311pub(crate) enum PaintOperation {
312    Primitive(Primitive),
313    StartLayer(Bounds<ScaledPixels>),
314    EndLayer,
315}
316
317#[derive(Clone)]
318#[expect(missing_docs)]
319pub enum Primitive {
320    Shadow(Shadow),
321    Quad(Quad),
322    Path(Path<ScaledPixels>),
323    Underline(Underline),
324    MonochromeSprite(MonochromeSprite),
325    SubpixelSprite(SubpixelSprite),
326    PolychromeSprite(PolychromeSprite),
327    Surface(PaintSurface),
328    BackdropFilter(BackdropFilter),
329    FilterBoundary(FilterBoundary),
330}
331
332#[expect(missing_docs)]
333impl Primitive {
334    pub fn bounds(&self) -> &Bounds<ScaledPixels> {
335        match self {
336            Primitive::Shadow(shadow) => &shadow.bounds,
337            Primitive::Quad(quad) => &quad.bounds,
338            Primitive::Path(path) => &path.bounds,
339            Primitive::Underline(underline) => &underline.bounds,
340            Primitive::MonochromeSprite(sprite) => &sprite.bounds,
341            Primitive::SubpixelSprite(sprite) => &sprite.bounds,
342            Primitive::PolychromeSprite(sprite) => &sprite.bounds,
343            Primitive::Surface(surface) => &surface.bounds,
344            Primitive::BackdropFilter(filter) => &filter.bounds,
345            Primitive::FilterBoundary(boundary) => &boundary.bounds,
346        }
347    }
348
349    pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
350        match self {
351            Primitive::Shadow(shadow) => &shadow.content_mask,
352            Primitive::Quad(quad) => &quad.content_mask,
353            Primitive::Path(path) => &path.content_mask,
354            Primitive::Underline(underline) => &underline.content_mask,
355            Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
356            Primitive::SubpixelSprite(sprite) => &sprite.content_mask,
357            Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
358            Primitive::Surface(surface) => &surface.content_mask,
359            Primitive::BackdropFilter(filter) => &filter.content_mask,
360            Primitive::FilterBoundary(boundary) => &boundary.content_mask,
361        }
362    }
363}
364
365#[cfg_attr(
366    all(
367        any(target_os = "linux", target_os = "freebsd"),
368        not(any(feature = "x11", feature = "wayland"))
369    ),
370    allow(dead_code)
371)]
372struct BatchIterator<'a> {
373    shadows_start: usize,
374    shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
375    quads_start: usize,
376    quads_iter: Peekable<slice::Iter<'a, Quad>>,
377    paths_start: usize,
378    paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
379    underlines_start: usize,
380    underlines_iter: Peekable<slice::Iter<'a, Underline>>,
381    monochrome_sprites_start: usize,
382    monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
383    subpixel_sprites_start: usize,
384    subpixel_sprites_iter: Peekable<slice::Iter<'a, SubpixelSprite>>,
385    polychrome_sprites_start: usize,
386    polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
387    surfaces_start: usize,
388    surfaces_iter: Peekable<slice::Iter<'a, PaintSurface>>,
389    backdrop_filters_start: usize,
390    backdrop_filters_iter: Peekable<slice::Iter<'a, BackdropFilter>>,
391    filter_boundaries_start: usize,
392    filter_boundaries_iter: Peekable<slice::Iter<'a, FilterBoundary>>,
393}
394
395impl<'a> Iterator for BatchIterator<'a> {
396    type Item = PrimitiveBatch;
397
398    fn next(&mut self) -> Option<Self::Item> {
399        let mut orders_and_kinds = [
400            (
401                self.shadows_iter.peek().map(|s| s.order),
402                PrimitiveKind::Shadow,
403            ),
404            (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
405            (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
406            (
407                self.underlines_iter.peek().map(|u| u.order),
408                PrimitiveKind::Underline,
409            ),
410            (
411                self.monochrome_sprites_iter.peek().map(|s| s.order),
412                PrimitiveKind::MonochromeSprite,
413            ),
414            (
415                self.subpixel_sprites_iter.peek().map(|s| s.order),
416                PrimitiveKind::SubpixelSprite,
417            ),
418            (
419                self.polychrome_sprites_iter.peek().map(|s| s.order),
420                PrimitiveKind::PolychromeSprite,
421            ),
422            (
423                self.surfaces_iter.peek().map(|s| s.order),
424                PrimitiveKind::Surface,
425            ),
426            (
427                self.backdrop_filters_iter.peek().map(|f| f.order),
428                PrimitiveKind::BackdropFilter,
429            ),
430            (
431                self.filter_boundaries_iter.peek().map(|b| b.order),
432                // The same vec yields both start and end markers; the discriminant decides
433                // where the next marker sorts relative to draw batches at an equal order
434                // (start before content, end after).
435                match self.filter_boundaries_iter.peek() {
436                    Some(boundary) if boundary.is_start => PrimitiveKind::FilterBoundaryStart,
437                    _ => PrimitiveKind::FilterBoundaryEnd,
438                },
439            ),
440        ];
441        orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind));
442
443        let first = orders_and_kinds[0];
444        let second = orders_and_kinds[1];
445        let (batch_kind, max_order_and_kind) = if first.0.is_some() {
446            (first.1, (second.0.unwrap_or(u32::MAX), second.1))
447        } else {
448            return None;
449        };
450
451        match batch_kind {
452            PrimitiveKind::Shadow => {
453                let shadows_start = self.shadows_start;
454                let mut shadows_end = shadows_start + 1;
455                self.shadows_iter.next();
456                while self
457                    .shadows_iter
458                    .next_if(|shadow| (shadow.order, batch_kind) < max_order_and_kind)
459                    .is_some()
460                {
461                    shadows_end += 1;
462                }
463                self.shadows_start = shadows_end;
464                Some(PrimitiveBatch::Shadows(shadows_start..shadows_end))
465            }
466            PrimitiveKind::Quad => {
467                let quads_start = self.quads_start;
468                let mut quads_end = quads_start + 1;
469                self.quads_iter.next();
470                while self
471                    .quads_iter
472                    .next_if(|quad| (quad.order, batch_kind) < max_order_and_kind)
473                    .is_some()
474                {
475                    quads_end += 1;
476                }
477                self.quads_start = quads_end;
478                Some(PrimitiveBatch::Quads(quads_start..quads_end))
479            }
480            PrimitiveKind::Path => {
481                let paths_start = self.paths_start;
482                let mut paths_end = paths_start + 1;
483                self.paths_iter.next();
484                while self
485                    .paths_iter
486                    .next_if(|path| (path.order, batch_kind) < max_order_and_kind)
487                    .is_some()
488                {
489                    paths_end += 1;
490                }
491                self.paths_start = paths_end;
492                Some(PrimitiveBatch::Paths(paths_start..paths_end))
493            }
494            PrimitiveKind::Underline => {
495                let underlines_start = self.underlines_start;
496                let mut underlines_end = underlines_start + 1;
497                self.underlines_iter.next();
498                while self
499                    .underlines_iter
500                    .next_if(|underline| (underline.order, batch_kind) < max_order_and_kind)
501                    .is_some()
502                {
503                    underlines_end += 1;
504                }
505                self.underlines_start = underlines_end;
506                Some(PrimitiveBatch::Underlines(underlines_start..underlines_end))
507            }
508            PrimitiveKind::MonochromeSprite => {
509                let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id;
510                let sprites_start = self.monochrome_sprites_start;
511                let mut sprites_end = sprites_start + 1;
512                self.monochrome_sprites_iter.next();
513                while self
514                    .monochrome_sprites_iter
515                    .next_if(|sprite| {
516                        (sprite.order, batch_kind) < max_order_and_kind
517                            && sprite.tile.texture_id == texture_id
518                    })
519                    .is_some()
520                {
521                    sprites_end += 1;
522                }
523                self.monochrome_sprites_start = sprites_end;
524                Some(PrimitiveBatch::MonochromeSprites {
525                    texture_id,
526                    range: sprites_start..sprites_end,
527                })
528            }
529            PrimitiveKind::SubpixelSprite => {
530                let texture_id = self.subpixel_sprites_iter.peek().unwrap().tile.texture_id;
531                let sprites_start = self.subpixel_sprites_start;
532                let mut sprites_end = sprites_start + 1;
533                self.subpixel_sprites_iter.next();
534                while self
535                    .subpixel_sprites_iter
536                    .next_if(|sprite| {
537                        (sprite.order, batch_kind) < max_order_and_kind
538                            && sprite.tile.texture_id == texture_id
539                    })
540                    .is_some()
541                {
542                    sprites_end += 1;
543                }
544                self.subpixel_sprites_start = sprites_end;
545                Some(PrimitiveBatch::SubpixelSprites {
546                    texture_id,
547                    range: sprites_start..sprites_end,
548                })
549            }
550            PrimitiveKind::PolychromeSprite => {
551                let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id;
552                let sprites_start = self.polychrome_sprites_start;
553                let mut sprites_end = sprites_start + 1;
554                self.polychrome_sprites_iter.next();
555                while self
556                    .polychrome_sprites_iter
557                    .next_if(|sprite| {
558                        (sprite.order, batch_kind) < max_order_and_kind
559                            && sprite.tile.texture_id == texture_id
560                    })
561                    .is_some()
562                {
563                    sprites_end += 1;
564                }
565                self.polychrome_sprites_start = sprites_end;
566                Some(PrimitiveBatch::PolychromeSprites {
567                    texture_id,
568                    range: sprites_start..sprites_end,
569                })
570            }
571            PrimitiveKind::Surface => {
572                let surfaces_start = self.surfaces_start;
573                let mut surfaces_end = surfaces_start + 1;
574                self.surfaces_iter.next();
575                while self
576                    .surfaces_iter
577                    .next_if(|surface| (surface.order, batch_kind) < max_order_and_kind)
578                    .is_some()
579                {
580                    surfaces_end += 1;
581                }
582                self.surfaces_start = surfaces_end;
583                Some(PrimitiveBatch::Surfaces(surfaces_start..surfaces_end))
584            }
585            PrimitiveKind::BackdropFilter => {
586                let backdrop_filters_start = self.backdrop_filters_start;
587                let mut backdrop_filters_end = backdrop_filters_start + 1;
588                self.backdrop_filters_iter.next();
589                while self
590                    .backdrop_filters_iter
591                    .next_if(|filter| (filter.order, batch_kind) < max_order_and_kind)
592                    .is_some()
593                {
594                    backdrop_filters_end += 1;
595                }
596                self.backdrop_filters_start = backdrop_filters_end;
597                Some(PrimitiveBatch::BackdropFilters(
598                    backdrop_filters_start..backdrop_filters_end,
599                ))
600            }
601            // Boundaries are emitted one at a time (never merged) so the renderer can switch
602            // render targets at exactly the right point in the batch stream.
603            PrimitiveKind::FilterBoundaryStart | PrimitiveKind::FilterBoundaryEnd => {
604                let index = self.filter_boundaries_start;
605                self.filter_boundaries_iter.next();
606                self.filter_boundaries_start = index + 1;
607                Some(PrimitiveBatch::FilterBoundary(index))
608            }
609        }
610    }
611}
612
613#[derive(Debug)]
614#[cfg_attr(
615    all(
616        any(target_os = "linux", target_os = "freebsd"),
617        not(any(feature = "x11", feature = "wayland"))
618    ),
619    allow(dead_code)
620)]
621#[allow(missing_docs)]
622pub enum PrimitiveBatch {
623    Shadows(Range<usize>),
624    Quads(Range<usize>),
625    Paths(Range<usize>),
626    Underlines(Range<usize>),
627    MonochromeSprites {
628        texture_id: AtlasTextureId,
629        range: Range<usize>,
630    },
631    #[cfg_attr(target_os = "macos", allow(dead_code))]
632    SubpixelSprites {
633        texture_id: AtlasTextureId,
634        range: Range<usize>,
635    },
636    PolychromeSprites {
637        texture_id: AtlasTextureId,
638        range: Range<usize>,
639    },
640    Surfaces(Range<usize>),
641    BackdropFilters(Range<usize>),
642    /// A single content-filter group boundary; index into [`Scene::filter_boundaries`]. Read
643    /// `is_start` to tell whether this opens the group (switch render target) or closes it
644    /// (filter the offscreen target and composite it back).
645    FilterBoundary(usize),
646}
647
648impl PrimitiveBatch {
649    #[expect(missing_docs)]
650    pub fn label(&self) -> String {
651        match self {
652            Self::Shadows(range) => format!("shadows ({})", range.len()),
653            Self::Quads(range) => format!("quads ({})", range.len()),
654            Self::Paths(range) => format!("paths ({})", range.len()),
655            Self::Underlines(range) => format!("underlines ({})", range.len()),
656            Self::MonochromeSprites { texture_id, range } => {
657                format!(
658                    "monochrome sprites ({}) on atlas {}",
659                    range.len(),
660                    texture_id.index
661                )
662            }
663            Self::SubpixelSprites { texture_id, range } => {
664                format!(
665                    "subpixel sprites ({}) on atlas {}",
666                    range.len(),
667                    texture_id.index
668                )
669            }
670            Self::PolychromeSprites { texture_id, range } => {
671                format!(
672                    "polychrome sprites ({}) on atlas {}",
673                    range.len(),
674                    texture_id.index
675                )
676            }
677            Self::Surfaces(range) => format!("surfaces ({})", range.len()),
678            Self::BackdropFilters(range) => format!("backdrop filters ({})", range.len()),
679            Self::FilterBoundary(ix) => format!("filter boundary ({ix})"),
680        }
681    }
682}
683
684#[derive(Default, Debug, Copy, Clone)]
685#[repr(C)]
686#[expect(missing_docs)]
687pub struct Quad {
688    pub order: DrawOrder,
689    pub border_style: BorderStyle,
690    pub bounds: Bounds<ScaledPixels>,
691    pub content_mask: ContentMask<ScaledPixels>,
692    pub background: Background,
693    pub border_color: SceneHsla,
694    pub corner_radii: Corners<ScaledPixels>,
695    pub border_widths: Edges<ScaledPixels>,
696}
697
698impl From<Quad> for Primitive {
699    fn from(quad: Quad) -> Self {
700        Primitive::Quad(quad)
701    }
702}
703
704#[derive(Debug, Copy, Clone)]
705#[repr(C)]
706#[expect(missing_docs)]
707pub struct Underline {
708    pub order: DrawOrder,
709    pub pad: u32, // align to 8 bytes
710    pub bounds: Bounds<ScaledPixels>,
711    pub content_mask: ContentMask<ScaledPixels>,
712    pub color: SceneHsla,
713    pub thickness: ScaledPixels,
714    pub wavy: PaddedBool32,
715}
716
717impl From<Underline> for Primitive {
718    fn from(underline: Underline) -> Self {
719        Primitive::Underline(underline)
720    }
721}
722
723#[derive(Debug, Copy, Clone)]
724#[repr(C)]
725#[expect(missing_docs)]
726pub struct Shadow {
727    pub order: DrawOrder,
728    pub blur_radius: ScaledPixels,
729    pub bounds: Bounds<ScaledPixels>,
730    pub corner_radii: Corners<ScaledPixels>,
731    pub content_mask: ContentMask<ScaledPixels>,
732    pub color: SceneHsla,
733    pub element_bounds: Bounds<ScaledPixels>,
734    pub element_corner_radii: Corners<ScaledPixels>,
735    /// 0 = drop shadow (rendered outside the element), 1 = inset shadow (rendered inside).
736    pub inset: u32,
737    pub pad: u32, // align to 8 bytes
738}
739
740impl From<Shadow> for Primitive {
741    fn from(shadow: Shadow) -> Self {
742        Primitive::Shadow(shadow)
743    }
744}
745
746/// A backdrop filter blurs (and may otherwise filter) the content already rendered behind
747/// `bounds`, compositing the result into a rounded rectangle — the frosted-glass effect.
748/// Emitted by [`crate::Window::paint_backdrop_filter`]; produces the CSS `backdrop-filter` effect.
749#[derive(Default, Debug, Clone)]
750#[expect(missing_docs)]
751pub struct BackdropFilter {
752    pub order: DrawOrder,
753    pub bounds: Bounds<ScaledPixels>,
754    pub content_mask: ContentMask<ScaledPixels>,
755    pub corner_radii: Corners<ScaledPixels>,
756    /// The filter chain applied to the backdrop, in scene (device-pixel) space. Identity filters
757    /// are dropped at paint time, so a `BackdropFilter` is only emitted when this is non-empty.
758    ///
759    /// Inline capacity is 4: a `SmallVec<[ScaledFilter; 4]>` is the same size as capacity 1 here
760    /// (the heap repr already occupies that space), so chains up to 4 filters avoid allocating
761    /// at no extra struct size.
762    pub filters: SmallVec<[ScaledFilter; 4]>,
763    /// Element opacity captured at paint time, multiplied into the composited result.
764    pub opacity: f32,
765}
766
767impl From<BackdropFilter> for Primitive {
768    fn from(filter: BackdropFilter) -> Self {
769        Primitive::BackdropFilter(filter)
770    }
771}
772
773/// The start or end marker of a content-filter (`filter`) isolation group. The element's
774/// subtree is painted between a matched start/end pair; the renderer redirects that span into
775/// an offscreen target, filters it, and composites it back at `bounds`. Produces the CSS
776/// `filter` effect (e.g. blurring the element and its children as a single group).
777#[derive(Debug, Clone)]
778#[expect(missing_docs)]
779pub struct FilterBoundary {
780    pub order: DrawOrder,
781    pub bounds: Bounds<ScaledPixels>,
782    pub content_mask: ContentMask<ScaledPixels>,
783    pub corner_radii: Corners<ScaledPixels>,
784    /// The filter chain applied to the isolated group, in scene (device-pixel) space. Identity
785    /// filters are dropped at paint time, so a `FilterBoundary` is only emitted when non-empty.
786    /// Inline capacity 4 (same struct size as 1 here — see [`BackdropFilter::filters`]).
787    pub filters: SmallVec<[ScaledFilter; 4]>,
788    pub opacity: f32,
789    /// `true` for the start marker (opens the group), `false` for the end marker (closes it).
790    pub is_start: bool,
791}
792
793impl From<FilterBoundary> for Primitive {
794    fn from(boundary: FilterBoundary) -> Self {
795        Primitive::FilterBoundary(boundary)
796    }
797}
798
799/// The style of a border.
800#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
801#[repr(C)]
802pub enum BorderStyle {
803    /// A solid border.
804    #[default]
805    Solid = 0,
806    /// A dashed border.
807    Dashed = 1,
808}
809
810/// A data type representing a 2 dimensional transformation that can be applied to an element.
811#[derive(Debug, Clone, Copy, PartialEq)]
812#[repr(C)]
813pub struct TransformationMatrix {
814    /// 2x2 matrix containing rotation and scale,
815    /// stored row-major
816    pub rotation_scale: [[f32; 2]; 2],
817    /// translation vector
818    pub translation: [f32; 2],
819}
820
821impl Eq for TransformationMatrix {}
822
823impl TransformationMatrix {
824    /// The unit matrix, has no effect.
825    pub fn unit() -> Self {
826        Self {
827            rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
828            translation: [0.0, 0.0],
829        }
830    }
831
832    /// Move the origin by a given point
833    pub fn translate(mut self, point: Point<ScaledPixels>) -> Self {
834        self.compose(Self {
835            rotation_scale: [[1.0, 0.0], [0.0, 1.0]],
836            translation: [point.x.0, point.y.0],
837        })
838    }
839
840    /// Clockwise rotation in radians around the origin
841    pub fn rotate(self, angle: Radians) -> Self {
842        self.compose(Self {
843            rotation_scale: [
844                [angle.0.cos(), -angle.0.sin()],
845                [angle.0.sin(), angle.0.cos()],
846            ],
847            translation: [0.0, 0.0],
848        })
849    }
850
851    /// Scale around the origin
852    pub fn scale(self, size: Size<f32>) -> Self {
853        self.compose(Self {
854            rotation_scale: [[size.width, 0.0], [0.0, size.height]],
855            translation: [0.0, 0.0],
856        })
857    }
858
859    /// Perform matrix multiplication with another transformation
860    /// to produce a new transformation that is the result of
861    /// applying both transformations: first, `other`, then `self`.
862    #[inline]
863    pub fn compose(self, other: TransformationMatrix) -> TransformationMatrix {
864        if other == Self::unit() {
865            return self;
866        }
867        // Perform matrix multiplication
868        TransformationMatrix {
869            rotation_scale: [
870                [
871                    self.rotation_scale[0][0] * other.rotation_scale[0][0]
872                        + self.rotation_scale[0][1] * other.rotation_scale[1][0],
873                    self.rotation_scale[0][0] * other.rotation_scale[0][1]
874                        + self.rotation_scale[0][1] * other.rotation_scale[1][1],
875                ],
876                [
877                    self.rotation_scale[1][0] * other.rotation_scale[0][0]
878                        + self.rotation_scale[1][1] * other.rotation_scale[1][0],
879                    self.rotation_scale[1][0] * other.rotation_scale[0][1]
880                        + self.rotation_scale[1][1] * other.rotation_scale[1][1],
881                ],
882            ],
883            translation: [
884                self.translation[0]
885                    + self.rotation_scale[0][0] * other.translation[0]
886                    + self.rotation_scale[0][1] * other.translation[1],
887                self.translation[1]
888                    + self.rotation_scale[1][0] * other.translation[0]
889                    + self.rotation_scale[1][1] * other.translation[1],
890            ],
891        }
892    }
893
894    /// Apply transformation to a point, mainly useful for debugging
895    pub fn apply(&self, point: Point<Pixels>) -> Point<Pixels> {
896        let input = [point.x.0, point.y.0];
897        let mut output = self.translation;
898        for (i, output_cell) in output.iter_mut().enumerate() {
899            for (k, input_cell) in input.iter().enumerate() {
900                *output_cell += self.rotation_scale[i][k] * *input_cell;
901            }
902        }
903        Point::new(output[0].into(), output[1].into())
904    }
905}
906
907impl Default for TransformationMatrix {
908    fn default() -> Self {
909        Self::unit()
910    }
911}
912
913#[derive(Copy, Clone, Debug)]
914#[repr(C)]
915#[expect(missing_docs)]
916pub struct MonochromeSprite {
917    pub order: DrawOrder,
918    pub pad: u32,
919    pub bounds: Bounds<ScaledPixels>,
920    pub content_mask: ContentMask<ScaledPixels>,
921    pub color: SceneHsla,
922    pub tile: AtlasTile,
923    pub transformation: TransformationMatrix,
924}
925
926impl From<MonochromeSprite> for Primitive {
927    fn from(sprite: MonochromeSprite) -> Self {
928        Primitive::MonochromeSprite(sprite)
929    }
930}
931
932#[derive(Copy, Clone, Debug)]
933#[repr(C)]
934#[expect(missing_docs)]
935pub struct SubpixelSprite {
936    pub order: DrawOrder,
937    pub pad: u32, // align to 8 bytes
938    pub bounds: Bounds<ScaledPixels>,
939    pub content_mask: ContentMask<ScaledPixels>,
940    pub color: SceneHsla,
941    pub tile: AtlasTile,
942    pub transformation: TransformationMatrix,
943}
944
945impl From<SubpixelSprite> for Primitive {
946    fn from(sprite: SubpixelSprite) -> Self {
947        Primitive::SubpixelSprite(sprite)
948    }
949}
950
951#[derive(Copy, Clone, Debug)]
952#[repr(C)]
953#[expect(missing_docs)]
954pub struct PolychromeSprite {
955    pub order: DrawOrder,
956    pub pad: u32,
957    pub grayscale: PaddedBool32,
958    pub opacity: f32,
959    pub bounds: Bounds<ScaledPixels>,
960    pub content_mask: ContentMask<ScaledPixels>,
961    pub corner_radii: Corners<ScaledPixels>,
962    pub tile: AtlasTile,
963}
964
965impl From<PolychromeSprite> for Primitive {
966    fn from(sprite: PolychromeSprite) -> Self {
967        Primitive::PolychromeSprite(sprite)
968    }
969}
970
971#[derive(Clone, Debug)]
972#[allow(missing_docs)]
973pub struct PaintSurface {
974    pub order: DrawOrder,
975    pub bounds: Bounds<ScaledPixels>,
976    pub content_mask: ContentMask<ScaledPixels>,
977    #[cfg(target_os = "macos")]
978    pub image_buffer: core_video::pixel_buffer::CVPixelBuffer,
979    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
980    pub texture: std::sync::Arc<dyn std::any::Any + Send + Sync>,
981    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
982    pub texture_size: Size<crate::DevicePixels>,
983}
984
985impl From<PaintSurface> for Primitive {
986    fn from(surface: PaintSurface) -> Self {
987        Primitive::Surface(surface)
988    }
989}
990
991#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
992#[expect(missing_docs)]
993pub struct PathId(pub usize);
994
995/// A line made up of a series of vertices and control points.
996#[derive(Clone, Debug)]
997#[expect(missing_docs)]
998pub struct Path<P: Clone + Debug + Default + PartialEq> {
999    pub id: PathId,
1000    pub order: DrawOrder,
1001    pub bounds: Bounds<P>,
1002    pub content_mask: ContentMask<P>,
1003    pub vertices: Vec<PathVertex<P>>,
1004    pub color: Background,
1005    start: Point<P>,
1006    current: Point<P>,
1007    contour_count: usize,
1008}
1009
1010impl Path<Pixels> {
1011    /// Create a new path with the given starting point.
1012    pub fn new(start: Point<Pixels>) -> Self {
1013        Self {
1014            id: PathId(0),
1015            order: DrawOrder::default(),
1016            vertices: Vec::new(),
1017            start,
1018            current: start,
1019            bounds: Bounds {
1020                origin: start,
1021                size: Default::default(),
1022            },
1023            content_mask: Default::default(),
1024            color: Default::default(),
1025            contour_count: 0,
1026        }
1027    }
1028
1029    /// Scale this path by the given factor.
1030    pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
1031        Path {
1032            id: self.id,
1033            order: self.order,
1034            bounds: self.bounds.scale(factor),
1035            content_mask: self.content_mask.scale(factor),
1036            vertices: self
1037                .vertices
1038                .iter()
1039                .map(|vertex| vertex.scale(factor))
1040                .collect(),
1041            start: self.start.map(|start| start.scale(factor)),
1042            current: self.current.scale(factor),
1043            contour_count: self.contour_count,
1044            color: self.color,
1045        }
1046    }
1047
1048    /// Move the start, current point to the given point.
1049    pub fn move_to(&mut self, to: Point<Pixels>) {
1050        self.contour_count += 1;
1051        self.start = to;
1052        self.current = to;
1053    }
1054
1055    /// Draw a straight line from the current point to the given point.
1056    pub fn line_to(&mut self, to: Point<Pixels>) {
1057        self.contour_count += 1;
1058        if self.contour_count > 1 {
1059            self.push_triangle(
1060                (self.start, self.current, to),
1061                (point(0., 1.), point(0., 1.), point(0., 1.)),
1062            );
1063        }
1064        self.current = to;
1065    }
1066
1067    /// Draw a curve from the current point to the given point, using the given control point.
1068    pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
1069        self.contour_count += 1;
1070        if self.contour_count > 1 {
1071            self.push_triangle(
1072                (self.start, self.current, to),
1073                (point(0., 1.), point(0., 1.), point(0., 1.)),
1074            );
1075        }
1076
1077        self.push_triangle(
1078            (self.current, ctrl, to),
1079            (point(0., 0.), point(0.5, 0.), point(1., 1.)),
1080        );
1081        self.current = to;
1082    }
1083
1084    /// Push a triangle to the Path.
1085    pub fn push_triangle(
1086        &mut self,
1087        xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
1088        st: (Point<f32>, Point<f32>, Point<f32>),
1089    ) {
1090        self.bounds = self
1091            .bounds
1092            .union(&Bounds {
1093                origin: xy.0,
1094                size: Default::default(),
1095            })
1096            .union(&Bounds {
1097                origin: xy.1,
1098                size: Default::default(),
1099            })
1100            .union(&Bounds {
1101                origin: xy.2,
1102                size: Default::default(),
1103            });
1104
1105        self.vertices.push(PathVertex {
1106            xy_position: xy.0,
1107            st_position: st.0,
1108            content_mask: Default::default(),
1109        });
1110        self.vertices.push(PathVertex {
1111            xy_position: xy.1,
1112            st_position: st.1,
1113            content_mask: Default::default(),
1114        });
1115        self.vertices.push(PathVertex {
1116            xy_position: xy.2,
1117            st_position: st.2,
1118            content_mask: Default::default(),
1119        });
1120    }
1121}
1122
1123impl<T> Path<T>
1124where
1125    T: Clone + Debug + Default + PartialEq + PartialOrd + Add<T, Output = T> + Sub<Output = T>,
1126{
1127    #[allow(unused)]
1128    #[expect(missing_docs)]
1129    pub fn clipped_bounds(&self) -> Bounds<T> {
1130        self.bounds.intersect(&self.content_mask.bounds)
1131    }
1132}
1133
1134impl From<Path<ScaledPixels>> for Primitive {
1135    fn from(path: Path<ScaledPixels>) -> Self {
1136        Primitive::Path(path)
1137    }
1138}
1139
1140#[derive(Clone, Debug)]
1141#[repr(C)]
1142#[expect(missing_docs)]
1143pub struct PathVertex<P: Clone + Debug + Default + PartialEq> {
1144    pub xy_position: Point<P>,
1145    pub st_position: Point<f32>,
1146    pub content_mask: ContentMask<P>,
1147}
1148
1149#[expect(missing_docs)]
1150impl PathVertex<Pixels> {
1151    pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
1152        PathVertex {
1153            xy_position: self.xy_position.scale(factor),
1154            st_position: self.st_position,
1155            content_mask: self.content_mask.scale(factor),
1156        }
1157    }
1158}
1159
1160#[cfg(test)]
1161mod tests {
1162    use super::*;
1163    use crate::{Point, Size};
1164
1165    fn sp(value: f32) -> ScaledPixels {
1166        ScaledPixels(value)
1167    }
1168
1169    /// All test primitives cover the same region so the bounds tree assigns strictly
1170    /// increasing orders in insertion order — making the expected batch order deterministic.
1171    fn full_bounds() -> Bounds<ScaledPixels> {
1172        Bounds {
1173            origin: Point {
1174                x: sp(0.0),
1175                y: sp(0.0),
1176            },
1177            size: Size {
1178                width: sp(100.0),
1179                height: sp(100.0),
1180            },
1181        }
1182    }
1183
1184    fn mask() -> ContentMask<ScaledPixels> {
1185        ContentMask {
1186            bounds: full_bounds(),
1187        }
1188    }
1189
1190    fn quad() -> Quad {
1191        Quad {
1192            bounds: full_bounds(),
1193            content_mask: mask(),
1194            ..Default::default()
1195        }
1196    }
1197
1198    /// A 100x100 quad whose bounds don't overlap `full_bounds()` (used to exercise the
1199    /// order-reuse path: non-overlapping content reuses low draw-orders).
1200    fn detached_quad() -> Quad {
1201        let bounds = Bounds {
1202            origin: Point {
1203                x: sp(200.0),
1204                y: sp(200.0),
1205            },
1206            size: Size {
1207                width: sp(100.0),
1208                height: sp(100.0),
1209            },
1210        };
1211        Quad {
1212            bounds,
1213            content_mask: ContentMask { bounds },
1214            ..Default::default()
1215        }
1216    }
1217
1218    fn boundary(is_start: bool) -> FilterBoundary {
1219        FilterBoundary {
1220            order: 0,
1221            bounds: full_bounds(),
1222            content_mask: mask(),
1223            corner_radii: Corners::default(),
1224            filters: smallvec::smallvec![ScaledFilter::Blur(sp(8.0))],
1225            opacity: 1.0,
1226            is_start,
1227        }
1228    }
1229
1230    fn backdrop() -> BackdropFilter {
1231        BackdropFilter {
1232            bounds: full_bounds(),
1233            content_mask: mask(),
1234            corner_radii: Corners::default(),
1235            filters: smallvec::smallvec![ScaledFilter::Blur(sp(20.0))],
1236            opacity: 1.0,
1237            ..Default::default()
1238        }
1239    }
1240
1241    fn batch_kinds(scene: &mut Scene) -> Vec<&'static str> {
1242        scene.finish();
1243        scene
1244            .batches()
1245            .map(|batch| match batch {
1246                PrimitiveBatch::Quads(_) => "quad",
1247                PrimitiveBatch::BackdropFilters(_) => "backdrop",
1248                PrimitiveBatch::FilterBoundary(ix) => {
1249                    if scene.filter_boundaries[ix].is_start {
1250                        "start"
1251                    } else {
1252                        "end"
1253                    }
1254                }
1255                _ => "other",
1256            })
1257            .collect()
1258    }
1259
1260    #[test]
1261    fn content_filter_group_brackets_its_children() {
1262        let mut scene = Scene::default();
1263        // Background painted before the filtered element.
1264        scene.insert_primitive(quad());
1265        // A content-filtered element: start marker, its child, end marker.
1266        scene.insert_primitive(boundary(true));
1267        scene.insert_primitive(quad());
1268        scene.insert_primitive(boundary(false));
1269
1270        // The start must precede the group's child and the end must follow it, so the
1271        // renderer can redirect rendering for exactly the group's span.
1272        assert_eq!(
1273            batch_kinds(&mut scene),
1274            vec!["quad", "start", "quad", "end"]
1275        );
1276    }
1277
1278    // Note: this validates only the *scene ordering* of nested filter boundaries (start/child/
1279    // end interleaving), not that a renderer actually isolates both levels — that depends on the
1280    // backend's group-texture pool (see MAX_FILTER_DEPTH) and is exercised by the `blur` example.
1281    #[test]
1282    fn nested_content_filters_emit_well_nested_ordering() {
1283        let mut scene = Scene::default();
1284        scene.insert_primitive(boundary(true)); // outer start
1285        scene.insert_primitive(quad()); // outer child
1286        scene.insert_primitive(boundary(true)); // inner start
1287        scene.insert_primitive(quad()); // inner child
1288        scene.insert_primitive(boundary(false)); // inner end
1289        scene.insert_primitive(boundary(false)); // outer end
1290
1291        assert_eq!(
1292            batch_kinds(&mut scene),
1293            vec!["start", "quad", "start", "quad", "end", "end"]
1294        );
1295    }
1296
1297    #[test]
1298    fn content_after_a_filter_group_sorts_above_it() {
1299        let mut scene = Scene::default();
1300        // A content-filtered element: start marker, its child, end marker.
1301        scene.insert_primitive(boundary(true));
1302        scene.insert_primitive(quad());
1303        scene.insert_primitive(boundary(false));
1304        // A sibling painted after the group that does NOT overlap it. Without the close-time
1305        // order-floor it would reuse the lowest order, tie with the start marker, and be swept
1306        // into the group (start, quad, quad, end); it must instead sort after the end marker.
1307        scene.insert_primitive(detached_quad());
1308
1309        assert_eq!(
1310            batch_kinds(&mut scene),
1311            vec!["start", "quad", "end", "quad"]
1312        );
1313    }
1314
1315    #[test]
1316    fn backdrop_filter_sorts_before_a_later_overlapping_quad() {
1317        let mut scene = Scene::default();
1318        // Content behind the frosted panel.
1319        scene.insert_primitive(quad());
1320        // The panel: its backdrop snapshot, then its (translucent) background quad on top.
1321        scene.insert_primitive(backdrop());
1322        scene.insert_primitive(quad());
1323
1324        assert_eq!(batch_kinds(&mut scene), vec!["quad", "backdrop", "quad"]);
1325    }
1326}