Skip to main content

frust_engine/gpu/
strips.rs

1//! The strip instance the vertex shader steps over, and the conversion into
2//! it from a rasterized [`Strip`].
3//!
4//! One [`GpuStrip`] is one instance of a four-vertex quad: there is no
5//! geometry buffer at all, the vertex shader builds the quad's corners from
6//! the instance's own fields (see [`GpuStrip::vertex_range`]).
7
8use bytemuck::{Pod, Zeroable};
9use core::ops::Range;
10use frust_gpu::VertexLayout;
11use vello_common::strip::Strip;
12use vello_common::tile::Tile;
13
14const _: () = assert!(
15    Tile::HEIGHT == 4,
16    "the strip shaders require `Tile::HEIGHT` to be 4",
17);
18
19/// Bit 31 of [`GpuStrip::paint_and_rect_flag`], marking an instance as a
20/// whole rectangle rather than a strip: the shader then reads
21/// [`GpuStrip::dense_width_or_rect_height`] as a height and
22/// [`GpuStrip::col_idx_or_rect_frac`] as a coverage fraction.
23pub const RECT_STRIP_FLAG: u32 = 1 << 31;
24
25/// Bit the colour source starts at in [`GpuStrip::paint_and_rect_flag`].
26const COLOR_SOURCE_SHIFT: u32 = 29;
27
28/// Mask of the two colour-source bits once shifted down.
29const COLOR_SOURCE_MASK: u32 = 0x3;
30
31/// Mask of the three paint-type bits once shifted down.
32const PAINT_TYPE_MASK: u32 = 0x7;
33
34/// The colour source naming a rendered layer page — what a composite instance
35/// carries in bits 29-30.
36const COLOR_SOURCE_LAYER: u32 = 1;
37
38/// The paint type a glyph quad carries in bits 26-28 ([`PaintType::Glyph`]).
39const PAINT_TYPE_GLYPH: u32 = PaintType::Glyph as u32;
40
41/// The shader's `IMAGE_QUALITY_MEDIUM` — bilinear, the highest quality the
42/// [`PaintClass::Image`] program samples at.
43const IMAGE_QUALITY_BILINEAR: u32 = 1;
44
45/// Bit the paint type starts at in [`GpuStrip::paint_and_rect_flag`].
46const PAINT_TYPE_SHIFT: u32 = 26;
47
48/// Mask covering the encoded-paint texel index in
49/// [`GpuStrip::paint_and_rect_flag`] — everything below the paint type.
50pub const PAINT_TEXTURE_INDEX_MASK: u32 = (1 << PAINT_TYPE_SHIFT) - 1;
51
52/// The colour source saying the fragment shader reads
53/// [`GpuStrip::payload`] rather than sampling a rendered layer.
54///
55/// The only source the engine emits: layer compositing is later work, and an
56/// instance that named the layer source would sample the placeholder view
57/// bound in its place.
58const COLOR_SOURCE_PAYLOAD: u32 = 0;
59
60/// How the fragment shader turns an instance's payload into colour.
61///
62/// The discriminants are the shader's own paint-type numbering, not an
63/// arbitrary ordering — they are written into
64/// [`GpuStrip::paint_and_rect_flag`] as-is.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66#[repr(u32)]
67pub enum PaintType {
68    /// The payload is a premultiplied RGBA8 colour, used directly.
69    Solid = 0,
70    /// The payload is a sample position; the record names an atlas image.
71    Image = 1,
72    /// The payload is a sample position; the record names a linear gradient.
73    LinearGradient = 2,
74    /// The payload is a sample position; the record names a radial gradient.
75    RadialGradient = 3,
76    /// The payload is a sample position; the record names a sweep gradient.
77    SweepGradient = 4,
78    /// The payload is a sample position; the record names a blurred rounded
79    /// rectangle.
80    BlurredRoundedRect = 5,
81    /// The payload is the atlas texel under the instance's top-left corner;
82    /// the record names the glyph atlas layer and tint of a whole glyph run.
83    ///
84    /// Only ever carried by a rect instance ([`RECT_STRIP_FLAG`]): the payload
85    /// addresses the atlas from the instance's own origin, which is the glyph's
86    /// corner only when the instance is the glyph's whole rectangle.
87    Glyph = 6,
88}
89
90/// The packed paint descriptor for a `paint_type` instance whose encoded-paint
91/// record starts at `texel_index`.
92///
93/// [`PaintType::Solid`] indexes no record and passes `0`; every other type
94/// carries the texel its record starts at, which is what
95/// `load_encoded_paint_texel` addresses the paint texture by. The index is
96/// masked rather than reported, because it is produced by this crate's own
97/// serialization — a paint table large enough to overflow 26 bits is refused
98/// by the paint texture's own capacity check long before it reaches here.
99#[must_use]
100pub const fn pack_paint_descriptor(paint_type: PaintType, texel_index: u32) -> u32 {
101    (COLOR_SOURCE_PAYLOAD << COLOR_SOURCE_SHIFT)
102        | ((paint_type as u32) << PAINT_TYPE_SHIFT)
103        | (texel_index & PAINT_TEXTURE_INDEX_MASK)
104}
105
106/// The alpha *column* the strip shader addresses coverage by, from the byte
107/// index a [`Strip`] carries.
108///
109/// The two are different units, and the conversion is the shader's own. A
110/// strip's `alpha_idx` counts coverage *bytes* from the start of the frame's
111/// buffer; [`GpuStrip::col_idx_or_rect_frac`] is read as a column ordinal,
112/// which the fragment stage turns into a texel with `col / 4` and a channel
113/// within it with `col % 4` — one channel holding one pixel column's
114/// `Tile::HEIGHT` coverage bytes. So a column is `Tile::HEIGHT` bytes wide,
115/// which is the same unit [`Strip::width_to`] already reports a strip's width
116/// in.
117///
118/// Getting this wrong is invisible on fully-covered geometry and total on
119/// anti-aliased geometry: every alpha-sampled span reads past its own
120/// coverage, lands on unwritten texels, and resolves to zero alpha.
121#[must_use]
122pub const fn alpha_column(alpha_idx: u32) -> u32 {
123    alpha_idx / Tile::HEIGHT as u32
124}
125
126/// One strip instance, matching the shaders' `StripInstance` byte for byte.
127///
128/// Three of the fields are overloaded by [`RECT_STRIP_FLAG`], which is why
129/// they are named for both readings.
130#[repr(C)]
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
132pub struct GpuStrip {
133    /// Left edge in pixels.
134    pub x: u16,
135    /// Top edge in pixels (a multiple of `Tile::HEIGHT`).
136    pub y: u16,
137    /// Width in pixels.
138    pub width: u16,
139    /// Width of the alpha-sampled (dense) part in pixels, or, for a rect
140    /// instance, the rect's height in pixels.
141    pub dense_width_or_rect_height: u16,
142    /// Index of the instance's first alpha column in the alpha texture, or,
143    /// for a rect instance, its packed coverage fraction.
144    pub col_idx_or_rect_frac: u32,
145    /// Paint-dependent payload — a packed color, or the coordinates the paint
146    /// is sampled at.
147    pub payload: u32,
148    /// Packed paint descriptor, with [`RECT_STRIP_FLAG`] in bit 31.
149    pub paint_and_rect_flag: u32,
150    /// Painter's-order index driving early-z rejection: the backmost draw is
151    /// 0 and each draw in front of it increments.
152    pub depth_index: u32,
153}
154
155const _: () = assert!(
156    size_of::<GpuStrip>() == 24,
157    "`GpuStrip` must stay 24 bytes — the shaders' `StripInstance` layout",
158);
159const _: () = assert!(
160    size_of::<GpuStrip>() == size_of::<u16>() * 4 + size_of::<u32>() * 4,
161    "`GpuStrip` must stay padding-free — six `Uint32` vertex attributes read it",
162);
163
164/// The per-draw values every strip instance of one draw shares.
165///
166/// Carried separately from the geometry because a draw resolves them once
167/// (its paint, its painter's-order index) and every strip it emits repeats
168/// them.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub struct StripDraw {
171    /// [`GpuStrip::payload`].
172    pub payload: u32,
173    /// The packed paint descriptor, without [`RECT_STRIP_FLAG`].
174    pub paint: u32,
175    /// [`GpuStrip::depth_index`].
176    pub depth_index: u32,
177}
178
179impl GpuStrip {
180    /// Vertices in the quad each instance expands to.
181    pub const QUAD_VERTICES: u32 = 4;
182
183    /// The six `Uint32` vertex attributes the 24-byte instance is read
184    /// through, at shader locations 0-5.
185    ///
186    /// The `u16` pairs are read as packed `u32`s and unpacked in the shader,
187    /// so the attribute list stays uniform.
188    #[must_use]
189    pub fn vertex_attributes() -> [wgpu::VertexAttribute; 6] {
190        wgpu::vertex_attr_array![
191            0 => Uint32,
192            1 => Uint32,
193            2 => Uint32,
194            3 => Uint32,
195            4 => Uint32,
196            5 => Uint32,
197        ]
198    }
199
200    /// The instance-stepped vertex buffer layout over [`GpuStrip`].
201    #[must_use]
202    pub fn vertex_layout() -> VertexLayout {
203        VertexLayout {
204            array_stride: size_of::<Self>() as wgpu::BufferAddress,
205            step_mode: wgpu::VertexStepMode::Instance,
206            attributes: Self::vertex_attributes().to_vec(),
207        }
208    }
209
210    /// The vertex range of a strip draw: one quad, built in the shader from
211    /// the vertex index, with no vertex buffer behind it.
212    #[must_use]
213    pub fn vertex_range() -> Range<u32> {
214        0..Self::QUAD_VERTICES
215    }
216
217    /// The instance range of a strip draw covering `count` instances from
218    /// `first_instance` — paired with [`Self::vertex_range`] for one
219    /// `draw` call.
220    #[must_use]
221    pub fn instance_range(first_instance: u32, count: u32) -> Range<u32> {
222        first_instance..first_instance.saturating_add(count)
223    }
224
225    /// The alpha-sampled instance for `strip`, `width` pixels wide.
226    ///
227    /// The strip's alpha index becomes the instance's first alpha *column*,
228    /// and the instance is dense across its whole width: every column samples
229    /// coverage.
230    #[must_use]
231    pub fn from_strip(strip: &Strip, width: u16, draw: StripDraw) -> Self {
232        Self {
233            x: strip.x,
234            y: strip.y,
235            width,
236            dense_width_or_rect_height: width,
237            col_idx_or_rect_frac: alpha_column(strip.alpha_idx()),
238            payload: draw.payload,
239            paint_and_rect_flag: draw.paint,
240            depth_index: draw.depth_index,
241        }
242    }
243
244    /// The alpha-sampled instance for `strip`, taking its width from `next` —
245    /// the strip immediately after it in the same run, which is where a
246    /// strip's extent is recorded.
247    #[must_use]
248    pub fn from_strip_pair(strip: &Strip, next: &Strip, draw: StripDraw) -> Self {
249        Self::from_strip(strip, strip.width_to(next), draw)
250    }
251
252    /// The solid instance filling the gap between `strip` and `next`, or
253    /// `None` when there is no gap to fill.
254    ///
255    /// A gap is filled only when `next` carries the fill-gap flag and sits on
256    /// the same strip row: the flag means "the winding between me and the
257    /// previous strip is non-zero", which says nothing about a strip on
258    /// another row. The instance samples no coverage, so its dense width and
259    /// column index are both zero.
260    #[must_use]
261    pub fn gap_fill(strip: &Strip, next: &Strip, draw: StripDraw) -> Option<Self> {
262        if !next.fill_gap() || next.y != strip.y {
263            return None;
264        }
265        let gap_x = strip.x.saturating_add(strip.width_to(next));
266        let gap_width = next.x.saturating_sub(gap_x);
267        if gap_width == 0 {
268            return None;
269        }
270        Some(Self::solid_fill(gap_x, strip.y, gap_width, draw))
271    }
272
273    /// A solid instance covering one strip row, sampling no coverage.
274    #[must_use]
275    pub fn solid_fill(x: u16, y: u16, width: u16, draw: StripDraw) -> Self {
276        Self {
277            x,
278            y,
279            width,
280            dense_width_or_rect_height: 0,
281            col_idx_or_rect_frac: 0,
282            payload: draw.payload,
283            paint_and_rect_flag: draw.paint,
284            depth_index: draw.depth_index,
285        }
286    }
287
288    /// A rectangle instance of `width` x `height` pixels with packed coverage
289    /// fraction `frac`, flagged with [`RECT_STRIP_FLAG`].
290    ///
291    /// Unlike a strip, a rect instance is not bound to one strip row: its
292    /// height is carried in the instance itself.
293    #[must_use]
294    pub fn from_rect(x: u16, y: u16, width: u16, height: u16, frac: u32, draw: StripDraw) -> Self {
295        Self {
296            x,
297            y,
298            width,
299            dense_width_or_rect_height: height,
300            col_idx_or_rect_frac: frac,
301            payload: draw.payload,
302            paint_and_rect_flag: draw.paint | RECT_STRIP_FLAG,
303            depth_index: draw.depth_index,
304        }
305    }
306
307    /// Whether this instance is a rectangle rather than a strip.
308    #[must_use]
309    pub const fn is_rect(&self) -> bool {
310        self.paint_and_rect_flag & RECT_STRIP_FLAG != 0
311    }
312
313    /// The packed paint descriptor with [`RECT_STRIP_FLAG`] masked off.
314    #[must_use]
315    pub const fn paint(&self) -> u32 {
316        self.paint_and_rect_flag & !RECT_STRIP_FLAG
317    }
318}
319
320/// Which specialised strip program an instance can be drawn with.
321///
322/// The strip fragment stage dispatches on the instance's colour source and
323/// paint type, and a mobile GPU charges every fragment for the branches that
324/// dispatch *could* take, not the one it does (the S1 ablation: a program
325/// keeping only coverage, solid and gradients ran S1's fragments at less than
326/// half the cost of the whole one). So the one `strip.wgsl` source is compiled
327/// once per class, each keeping only its own branches
328/// ([`crate::gpu::shader_src::strip_class_source`]), and a pass's instances are
329/// drawn in runs of one class each ([`split_class_runs`]).
330///
331/// Coverage (strip and rect) and the solid paint are in every class, which is
332/// what lets a short solid run be drawn by its non-solid neighbour's program
333/// (see [`Self::draws`]). [`Self::Uber`] is the whole program — the source
334/// unchanged — and draws everything.
335///
336/// Mostly derived from the instance alone ([`Self::of_flag`]): bits 29-30 of
337/// [`GpuStrip::paint_and_rect_flag`] name the colour source and bits 26-28 the
338/// paint type. The one split the flag cannot make is an image's: whether it
339/// samples nearest/bilinear from the atlas ([`Self::Image`]) or needs the
340/// bicubic or external-texture path ([`Self::ImageFiltered`]) is in its
341/// encoded record, so [`Self::of_flag`] answers the superset and the renderer
342/// refines it per draw from the resolved record ([`Self::of_image`]).
343#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
344pub enum PaintClass {
345    /// Coverage plus the inline solid colour — and nothing else.
346    SolidCoverage,
347    /// Adds the linear, radial and sweep gradients.
348    Gradient,
349    /// Adds atlas image sampling at nearest and bilinear quality — every
350    /// atlas glyph not drawn as a [glyph quad](Self::Glyph), and every image
351    /// not drawn bicubic.
352    Image,
353    /// Adds every image path: [`Self::Image`]'s, plus bicubic atlas sampling
354    /// and the externally bound texture. Kept apart because the bicubic and
355    /// external code is what an image fragment pays for most, taken or not.
356    ImageFiltered,
357    /// Adds the blurred rounded rectangle.
358    BlurredRect,
359    /// Adds the layer colour source a composite reads its finished page
360    /// through.
361    LayerComposite,
362    /// Adds the glyph quad: one nearest load from the atlas texel the
363    /// instance's payload names, with no paint transform and no extend mode —
364    /// what a cached glyph run draws with. Independent of the image program
365    /// family, since it never samples bilinearly.
366    Glyph,
367    /// The whole program, every branch: what a run too short to be worth its
368    /// own pipeline switch, and every run under `FRUST_ENGINE_UBER_PROGRAM`,
369    /// is drawn with.
370    Uber,
371}
372
373impl PaintClass {
374    /// Every class, in index order.
375    pub const ALL: [Self; 8] = [
376        Self::SolidCoverage,
377        Self::Gradient,
378        Self::Image,
379        Self::ImageFiltered,
380        Self::BlurredRect,
381        Self::LayerComposite,
382        Self::Glyph,
383        Self::Uber,
384    ];
385
386    /// The classes compiled from a specialisation of the source — every class
387    /// but [`Self::Uber`], which is the source itself.
388    pub const SPECIALISED: [Self; 7] = [
389        Self::SolidCoverage,
390        Self::Gradient,
391        Self::Image,
392        Self::ImageFiltered,
393        Self::BlurredRect,
394        Self::LayerComposite,
395        Self::Glyph,
396    ];
397
398    /// The class of an instance whose packed descriptor is
399    /// `paint_and_rect_flag` ([`RECT_STRIP_FLAG`] may be set or not).
400    ///
401    /// A colour source or paint type the shader does not name maps to
402    /// [`Self::Uber`] rather than to a guess: the whole program reads it
403    /// exactly as it always did. An image maps to [`Self::ImageFiltered`],
404    /// the class that draws every image — the flag does not say which path
405    /// the record takes (see [`Self::of_image`]).
406    ///
407    /// A glyph quad is [`Self::Glyph`] only on a rect instance: its payload
408    /// names the atlas texel under the instance's own corner, which is only
409    /// the glyph's corner when the instance is the glyph's whole rectangle. A
410    /// strip carrying the glyph paint type is not an encoding the engine emits,
411    /// and is read as reserved.
412    #[must_use]
413    pub const fn of_flag(paint_and_rect_flag: u32) -> Self {
414        let color_source = (paint_and_rect_flag >> COLOR_SOURCE_SHIFT) & COLOR_SOURCE_MASK;
415        if color_source == COLOR_SOURCE_LAYER {
416            return Self::LayerComposite;
417        }
418        if color_source != COLOR_SOURCE_PAYLOAD {
419            return Self::Uber;
420        }
421        match (paint_and_rect_flag >> PAINT_TYPE_SHIFT) & PAINT_TYPE_MASK {
422            0 => Self::SolidCoverage,
423            1 => Self::ImageFiltered,
424            2..=4 => Self::Gradient,
425            5 => Self::BlurredRect,
426            PAINT_TYPE_GLYPH if paint_and_rect_flag & RECT_STRIP_FLAG != 0 => Self::Glyph,
427            _ => Self::Uber,
428        }
429    }
430
431    /// The class of `strip`, as far as its flag says ([`Self::of_flag`]).
432    #[must_use]
433    pub const fn of_instance(strip: &GpuStrip) -> Self {
434        Self::of_flag(strip.paint_and_rect_flag)
435    }
436
437    /// The class of an image paint whose encoded record samples at `quality`
438    /// (the shader's `IMAGE_QUALITY_*` numbering: 0 nearest, 1 bilinear, 2
439    /// bicubic) from the external texture (`external`) or the atlas.
440    ///
441    /// Only an atlas image at nearest or bilinear quality is [`Self::Image`];
442    /// a bicubic or external one needs [`Self::ImageFiltered`]. The renderer
443    /// asks this once per resolved paint, from the record it lowered.
444    #[must_use]
445    pub const fn of_image(quality: u32, external: bool) -> Self {
446        if external || quality > IMAGE_QUALITY_BILINEAR {
447            Self::ImageFiltered
448        } else {
449            Self::Image
450        }
451    }
452
453    /// This class's position in [`Self::ALL`].
454    #[must_use]
455    pub const fn index(self) -> usize {
456        self as usize
457    }
458
459    /// A short name for logs, pipeline labels and shader-library names.
460    #[must_use]
461    pub const fn label(self) -> &'static str {
462        match self {
463            Self::SolidCoverage => "solid",
464            Self::Gradient => "gradient",
465            Self::Image => "image",
466            Self::ImageFiltered => "image-filtered",
467            Self::BlurredRect => "blurred-rect",
468            Self::LayerComposite => "layer",
469            Self::Glyph => "glyph",
470            Self::Uber => "uber",
471        }
472    }
473
474    /// Whether this class's program draws an instance of class `other`
475    /// exactly as [`Self::Uber`] would.
476    ///
477    /// A class draws itself and the solid paint (coverage and solid are in
478    /// every program); [`Self::ImageFiltered`] also draws [`Self::Image`];
479    /// [`Self::Uber`] draws everything.
480    #[must_use]
481    pub const fn draws(self, other: Self) -> bool {
482        matches!(self, Self::Uber)
483            || matches!(other, Self::SolidCoverage)
484            || matches!((self, other), (Self::ImageFiltered, Self::Image))
485            || self as usize == other as usize
486    }
487
488    /// Whether this class's program draws an instance whose flag is
489    /// `paint_and_rect_flag`, as far as the flag can tell: an image instance
490    /// is admitted by either image class, since which one its record needs is
491    /// not in the flag ([`Self::of_flag`]).
492    #[must_use]
493    pub const fn admits_flag(self, paint_and_rect_flag: u32) -> bool {
494        let class = Self::of_flag(paint_and_rect_flag);
495        self.draws(class) || (matches!(self, Self::Image) && matches!(class, Self::ImageFiltered))
496    }
497}
498
499/// A set of [`PaintClass`]es, as a bitmask — which class programs one pass
500/// needs, so a frame resolves exactly those pipelines and no others.
501#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
502pub struct ClassSet(u8);
503
504const _: () = assert!(
505    PaintClass::ALL.len() <= u8::BITS as usize,
506    "`ClassSet` holds one bit per paint class",
507);
508
509impl ClassSet {
510    /// The empty set.
511    pub const EMPTY: Self = Self(0);
512
513    /// The set holding `class` alone.
514    #[must_use]
515    pub const fn only(class: PaintClass) -> Self {
516        Self(1 << class as u8)
517    }
518
519    /// Adds `class`.
520    pub fn insert(&mut self, class: PaintClass) {
521        self.0 |= 1 << class as u8;
522    }
523
524    /// Whether `class` is in the set.
525    #[must_use]
526    pub const fn contains(self, class: PaintClass) -> bool {
527        self.0 & (1 << class as u8) != 0
528    }
529
530    /// Whether the set is empty.
531    #[must_use]
532    pub const fn is_empty(self) -> bool {
533        self.0 == 0
534    }
535
536    /// The classes in the set, in [`PaintClass::ALL`] order.
537    pub fn iter(self) -> impl Iterator<Item = PaintClass> {
538        PaintClass::ALL
539            .into_iter()
540            .filter(move |class| self.contains(*class))
541    }
542}
543
544/// One run of consecutive instances drawn with one class's program, as an
545/// offset into the slice [`split_class_runs`] was handed.
546#[derive(Debug, Clone, Copy, PartialEq, Eq)]
547pub struct ClassRun {
548    /// Index of the run's first instance within the split slice.
549    pub first: u32,
550    /// How many instances the run holds; never zero.
551    pub count: u32,
552    /// The program the run is drawn with.
553    pub class: PaintClass,
554}
555
556/// The default [`split_class_runs`] threshold: a run of fewer instances than
557/// this is not worth a pipeline switch of its own and is drawn with
558/// [`PaintClass::Uber`] instead.
559///
560/// Measured on a Pixel 4a (Adreno 618) S1 capture, `gpu_main` p50 in ms:
561/// 16 → 13.52 (repeat 13.69), 64 → 13.68, 256 → 13.43 — no GPU difference
562/// within noise, so the default keeps the most runs specialised at about
563/// +13 µs of `record_us`. Overridable at build or launch time with
564/// `FRUST_ENGINE_RUN_SPLIT_MIN` ([`crate::config::run_split_min`]).
565pub const RUN_SPLIT_MIN: u32 = 16;
566
567/// Splits `instances` — one pass segment, in painter order — into consecutive
568/// runs, each drawn with one class's program, appending them to `out`.
569///
570/// Painter order is preserved by construction: the runs are contiguous,
571/// non-overlapping and cover the slice front to back, so drawing them in the
572/// order returned draws exactly the instances the unsplit segment did, in the
573/// same order. Only the program each instance is shaded by changes, and every
574/// program shades the instances it is handed exactly as the whole one would.
575///
576/// Three steps, each linear:
577///
578/// 1. maximal runs of equal [`PaintClass::of_instance`];
579/// 2. a solid run shorter than `min_run` is folded into a neighbouring
580///    non-solid run (the previous one, else the next) — every program draws
581///    solid, so this costs that neighbour nothing and saves a switch; runs of
582///    one class left adjacent by a fold are joined;
583/// 3. a remaining non-solid run shorter than `min_run` is drawn with
584///    [`PaintClass::Uber`], and adjacent Uber runs are joined — the short run
585///    is merged into its neighbours rather than paying for a pipeline switch
586///    of its own.
587///
588/// A solid run is never promoted to Uber: it only survives step 2 short when
589/// it has no non-solid neighbour at all, and then the solid program is the
590/// cheapest one that draws it. `min_run` 0 or 1 disables both merges.
591///
592/// `uber_only` is the kill switch (`FRUST_ENGINE_UBER_PROGRAM`): the whole
593/// slice becomes ONE Uber run, which is the unsplit segment exactly.
594pub fn split_class_runs(
595    instances: &[GpuStrip],
596    min_run: u32,
597    uber_only: bool,
598    out: &mut Vec<ClassRun>,
599) {
600    let total = u32::try_from(instances.len()).unwrap_or(u32::MAX);
601    let mut index = 0_u32;
602    let maximal = std::iter::from_fn(|| {
603        if index >= total {
604            return None;
605        }
606        let class = PaintClass::of_instance(&instances[index as usize]);
607        let mut end = index + 1;
608        while end < total && PaintClass::of_instance(&instances[end as usize]) == class {
609            end += 1;
610        }
611        let run = ClassRun {
612            first: index,
613            count: end - index,
614            class,
615        };
616        index = end;
617        Some(run)
618    });
619    merge_class_runs(maximal, min_run, uber_only, out);
620}
621
622/// [`split_class_runs`] over runs already known to share a class, rather than
623/// over the instances themselves — steps 2 and 3 alone.
624///
625/// `runs` must tile one span front to back (each run's `first` is the end of
626/// the one before it); an empty run is skipped, and adjacent runs sharing a
627/// class are joined before anything else, so the result is exactly what
628/// scanning the instances would give. This is how the renderer splits a segment without
629/// re-reading every instance: every instance of one draw carries the same
630/// paint, so the class changes only at draw boundaries, and it records those
631/// as it emits the draw.
632pub fn merge_class_runs(
633    runs: impl IntoIterator<Item = ClassRun>,
634    min_run: u32,
635    uber_only: bool,
636    out: &mut Vec<ClassRun>,
637) {
638    let start = out.len();
639    if uber_only {
640        let mut runs = runs.into_iter().filter(|run| run.count > 0);
641        if let Some(first) = runs.next() {
642            let count = runs.fold(first.count, |count, run| count.saturating_add(run.count));
643            out.push(ClassRun {
644                first: first.first,
645                count,
646                class: PaintClass::Uber,
647            });
648        }
649        return;
650    }
651
652    // Step 1's maximality first — adjacent runs of one class are joined before
653    // any fold decision looks at a length — then step 2, over a stack of closed
654    // runs: each maximal run is pushed, folding a short solid run into a
655    // non-solid neighbour as soon as both are known.
656    let mut pending: Option<ClassRun> = None;
657    for run in runs {
658        if run.count == 0 {
659            continue;
660        }
661        match pending.as_mut() {
662            Some(open) if open.class == run.class => {
663                open.count = open.count.saturating_add(run.count);
664            }
665            _ => {
666                if let Some(closed) = pending.replace(run) {
667                    push_folding(out, start, closed, min_run);
668                }
669            }
670        }
671    }
672    if let Some(closed) = pending {
673        push_folding(out, start, closed, min_run);
674    }
675
676    // Step 3, in place: a short non-solid run is drawn with Uber, and Uber
677    // runs left adjacent are joined.
678    let mut write = start;
679    for read in start..out.len() {
680        let mut run = out[read];
681        if run.class != PaintClass::SolidCoverage && run.count < min_run {
682            run.class = PaintClass::Uber;
683        }
684        if write > start && out[write - 1].class == run.class {
685            out[write - 1].count += run.count;
686        } else {
687            out[write] = run;
688            write += 1;
689        }
690    }
691    out.truncate(write);
692}
693
694/// Pushes `run` onto `out[start..]`, folding a short solid run into a
695/// non-solid neighbour and joining equal neighbours (step 2 of
696/// [`split_class_runs`]).
697fn push_folding(out: &mut Vec<ClassRun>, start: usize, mut run: ClassRun, min_run: u32) {
698    let short_solid =
699        |run: &ClassRun| run.class == PaintClass::SolidCoverage && run.count < min_run;
700    if let Some(top) = out.get_mut(start..).and_then(<[ClassRun]>::last_mut) {
701        if top.class == run.class || (short_solid(&run) && top.class != PaintClass::SolidCoverage) {
702            top.count += run.count;
703            return;
704        }
705        if short_solid(top) && run.class != PaintClass::SolidCoverage {
706            // The solid run before this one had no non-solid run to its left
707            // to fold into; it folds into this one instead.
708            run.first = top.first;
709            run.count += top.count;
710            out.pop();
711            // The fold can leave this run beside one of its own class.
712            if let Some(below) = out.get_mut(start..).and_then(<[ClassRun]>::last_mut)
713                && below.class == run.class
714            {
715                below.count += run.count;
716                return;
717            }
718        }
719    }
720    out.push(run);
721}
722
723#[cfg(test)]
724mod tests {
725    use super::*;
726
727    fn strip(paint: u32) -> GpuStrip {
728        GpuStrip::solid_fill(
729            0,
730            0,
731            1,
732            StripDraw {
733                payload: 0,
734                paint,
735                depth_index: 0,
736            },
737        )
738    }
739
740    fn of(classes: &[(PaintType, u32)]) -> Vec<GpuStrip> {
741        classes
742            .iter()
743            .flat_map(|(paint, count)| {
744                std::iter::repeat_n(strip(pack_paint_descriptor(*paint, 7)), *count as usize)
745            })
746            .collect()
747    }
748
749    fn runs(instances: &[GpuStrip], min_run: u32) -> Vec<(u32, u32, PaintClass)> {
750        let mut out = Vec::new();
751        split_class_runs(instances, min_run, false, &mut out);
752        out.iter().map(|r| (r.first, r.count, r.class)).collect()
753    }
754
755    #[test]
756    fn every_paint_type_and_source_maps_to_its_class() {
757        use PaintClass as C;
758        assert_eq!(
759            C::of_flag(pack_paint_descriptor(PaintType::Solid, 0)),
760            C::SolidCoverage
761        );
762        assert_eq!(
763            C::of_flag(pack_paint_descriptor(PaintType::Image, 3)),
764            C::ImageFiltered,
765            "the flag alone answers the class that draws every image"
766        );
767        for gradient in [
768            PaintType::LinearGradient,
769            PaintType::RadialGradient,
770            PaintType::SweepGradient,
771        ] {
772            assert_eq!(C::of_flag(pack_paint_descriptor(gradient, 9)), C::Gradient);
773        }
774        assert_eq!(
775            C::of_flag(pack_paint_descriptor(PaintType::BlurredRoundedRect, 1)),
776            C::BlurredRect
777        );
778        // A composite: layer source, opacity in the low byte, rect flag set.
779        assert_eq!(
780            C::of_flag((1 << 29) | 0x80 | RECT_STRIP_FLAG),
781            C::LayerComposite
782        );
783        // The rect flag never changes a class.
784        assert_eq!(
785            C::of_flag(pack_paint_descriptor(PaintType::Image, 3) | RECT_STRIP_FLAG),
786            C::ImageFiltered
787        );
788        // The record refines an image: nearest and bilinear atlas images take
789        // the narrow program, bicubic and external ones the filtered one.
790        assert_eq!(C::of_image(0, false), C::Image);
791        assert_eq!(C::of_image(1, false), C::Image);
792        assert_eq!(C::of_image(2, false), C::ImageFiltered);
793        assert_eq!(C::of_image(0, true), C::ImageFiltered);
794        assert_eq!(C::of_image(1, true), C::ImageFiltered);
795        let image = pack_paint_descriptor(PaintType::Image, 3);
796        assert!(C::Image.admits_flag(image));
797        assert!(C::ImageFiltered.admits_flag(image));
798        assert!(!C::Gradient.admits_flag(image));
799        // Reserved sources and paint types fall back to the whole program.
800        assert_eq!(C::of_flag(2 << 29), C::Uber);
801        assert_eq!(C::of_flag(3 << 29), C::Uber);
802        assert_eq!(C::of_flag(6 << 26), C::Uber);
803        assert_eq!(C::of_flag(7 << 26), C::Uber);
804    }
805
806    #[test]
807    fn a_glyph_quad_is_its_own_class_only_on_a_rect_instance() {
808        use PaintClass as C;
809        let glyph = pack_paint_descriptor(PaintType::Glyph, 12);
810        assert_eq!(C::of_flag(glyph | RECT_STRIP_FLAG), C::Glyph);
811        assert_eq!(
812            C::of_flag(glyph),
813            C::Uber,
814            "a strip carrying the glyph paint type is reserved"
815        );
816        assert!(C::Glyph.admits_flag(glyph | RECT_STRIP_FLAG));
817        assert!(C::Glyph.admits_flag(pack_paint_descriptor(PaintType::Solid, 0)));
818        assert!(!C::Glyph.admits_flag(pack_paint_descriptor(PaintType::Image, 3)));
819        assert!(!C::Image.admits_flag(glyph | RECT_STRIP_FLAG));
820        assert!(!C::ImageFiltered.draws(C::Glyph));
821        assert!(C::Uber.admits_flag(glyph | RECT_STRIP_FLAG));
822        assert_eq!(C::Glyph.label(), "glyph");
823        // Every class still fits the set's one bit each.
824        let mut set = ClassSet::EMPTY;
825        for class in C::ALL {
826            set.insert(class);
827        }
828        assert_eq!(set.iter().count(), C::ALL.len());
829    }
830
831    #[test]
832    fn the_class_lattice_is_solid_below_everything_and_uber_above() {
833        for class in PaintClass::ALL {
834            assert!(class.draws(PaintClass::SolidCoverage));
835            assert!(class.draws(class));
836            assert!(PaintClass::Uber.draws(class));
837        }
838        assert!(!PaintClass::Gradient.draws(PaintClass::Image));
839        assert!(PaintClass::ImageFiltered.draws(PaintClass::Image));
840        assert!(!PaintClass::Image.draws(PaintClass::ImageFiltered));
841        assert!(!PaintClass::SolidCoverage.draws(PaintClass::Gradient));
842        assert!(!PaintClass::Image.draws(PaintClass::Uber));
843        for (index, class) in PaintClass::ALL.iter().enumerate() {
844            assert_eq!(class.index(), index);
845        }
846        assert_eq!(PaintClass::SPECIALISED.len() + 1, PaintClass::ALL.len());
847        assert!(!PaintClass::SPECIALISED.contains(&PaintClass::Uber));
848    }
849
850    #[test]
851    fn a_class_set_round_trips() {
852        let mut set = ClassSet::EMPTY;
853        assert!(set.is_empty());
854        set.insert(PaintClass::Image);
855        set.insert(PaintClass::Uber);
856        set.insert(PaintClass::Image);
857        assert!(set.contains(PaintClass::Image));
858        assert!(!set.contains(PaintClass::Gradient));
859        assert_eq!(
860            set.iter().collect::<Vec<_>>(),
861            vec![PaintClass::Image, PaintClass::Uber]
862        );
863        assert_eq!(ClassSet::only(PaintClass::Gradient).iter().count(), 1);
864    }
865
866    #[test]
867    fn long_runs_keep_their_class_and_cover_the_slice_in_order() {
868        use PaintType as P;
869        let instances = of(&[
870            (P::LinearGradient, 20),
871            (P::Image, 30),
872            (P::RadialGradient, 17),
873        ]);
874        assert_eq!(
875            runs(&instances, 16),
876            vec![
877                (0, 20, PaintClass::Gradient),
878                (20, 30, PaintClass::ImageFiltered),
879                (50, 17, PaintClass::Gradient),
880            ]
881        );
882    }
883
884    #[test]
885    fn a_short_solid_run_folds_into_its_non_solid_neighbour() {
886        use PaintType as P;
887        // Behind a gradient: folds backwards.
888        let instances = of(&[
889            (P::LinearGradient, 20),
890            (P::Solid, 3),
891            (P::LinearGradient, 20),
892        ]);
893        assert_eq!(runs(&instances, 16), vec![(0, 43, PaintClass::Gradient)]);
894        // At the front: folds forwards into the first non-solid run.
895        let instances = of(&[(P::Solid, 3), (P::Image, 20)]);
896        assert_eq!(
897            runs(&instances, 16),
898            vec![(0, 23, PaintClass::ImageFiltered)]
899        );
900        // A long solid run stays a run of its own.
901        let instances = of(&[(P::Image, 20), (P::Solid, 40), (P::Image, 20)]);
902        assert_eq!(
903            runs(&instances, 16),
904            vec![
905                (0, 20, PaintClass::ImageFiltered),
906                (20, 40, PaintClass::SolidCoverage),
907                (60, 20, PaintClass::ImageFiltered),
908            ]
909        );
910        // An all-solid segment is one solid run however short.
911        assert_eq!(
912            runs(&of(&[(P::Solid, 2)]), 16),
913            vec![(0, 2, PaintClass::SolidCoverage)]
914        );
915    }
916
917    #[test]
918    fn a_run_below_the_threshold_is_merged_and_drawn_with_uber() {
919        use PaintType as P;
920        // S1's shape: a long gradient run, then a short text (image) run.
921        let instances = of(&[
922            (P::RadialGradient, 40),
923            (P::Image, 5),
924            (P::BlurredRoundedRect, 3),
925            (P::RadialGradient, 40),
926        ]);
927        assert_eq!(
928            runs(&instances, 16),
929            vec![
930                (0, 40, PaintClass::Gradient),
931                (40, 8, PaintClass::Uber),
932                (48, 40, PaintClass::Gradient),
933            ]
934        );
935        // The same slice under a higher threshold: the gradient runs are now
936        // short too, and the whole segment collapses into one Uber run.
937        assert_eq!(runs(&instances, 64), vec![(0, 88, PaintClass::Uber)]);
938        // Threshold 0 disables merging altogether.
939        assert_eq!(runs(&instances, 0).len(), 4);
940    }
941
942    #[test]
943    fn the_kill_switch_yields_one_uber_run() {
944        use PaintType as P;
945        let instances = of(&[(P::RadialGradient, 40), (P::Image, 40), (P::Solid, 40)]);
946        let mut out = Vec::new();
947        split_class_runs(&instances, 16, true, &mut out);
948        assert_eq!(
949            out,
950            vec![ClassRun {
951                first: 0,
952                count: 120,
953                class: PaintClass::Uber
954            }]
955        );
956        out.clear();
957        split_class_runs(&[], 16, false, &mut out);
958        assert!(out.is_empty());
959    }
960
961    #[test]
962    fn appending_to_a_non_empty_vec_never_merges_across_calls() {
963        use PaintType as P;
964        let mut out = Vec::new();
965        split_class_runs(&of(&[(P::Image, 20)]), 16, false, &mut out);
966        split_class_runs(&of(&[(P::Image, 20)]), 16, false, &mut out);
967        assert_eq!(out.len(), 2);
968        assert_eq!(out[1].first, 0);
969    }
970
971    /// The renderer's draw-granular path agrees with the instance scan: the
972    /// same slice, handed over as per-draw runs that may repeat a class, splits
973    /// into exactly the runs the scan produces.
974    #[test]
975    fn merging_draw_runs_equals_scanning_their_instances() {
976        use PaintType as P;
977        let draws = [
978            (P::Solid, 3),
979            (P::RadialGradient, 20),
980            (P::Solid, 10),
981            (P::Solid, 10),
982            (P::LinearGradient, 9),
983            (P::Image, 5),
984            (P::Image, 4),
985            (P::Solid, 2),
986            (P::BlurredRoundedRect, 30),
987            (P::Solid, 40),
988        ];
989        let instances = of(&draws);
990        let mut first = 0;
991        let per_draw: Vec<ClassRun> = draws
992            .iter()
993            .map(|(paint, count)| {
994                let run = ClassRun {
995                    first,
996                    count: *count,
997                    class: PaintClass::of_flag(pack_paint_descriptor(*paint, 7)),
998                };
999                first += count;
1000                run
1001            })
1002            .collect();
1003        for min_run in [0, 1, 4, 16, 64] {
1004            for uber in [false, true] {
1005                let mut scanned = Vec::new();
1006                split_class_runs(&instances, min_run, uber, &mut scanned);
1007                let mut merged = Vec::new();
1008                merge_class_runs(per_draw.iter().copied(), min_run, uber, &mut merged);
1009                assert_eq!(scanned, merged, "threshold {min_run}, uber {uber}");
1010            }
1011        }
1012    }
1013
1014    /// Exhaustive over short sequences: the runs always tile the slice, never
1015    /// hand an instance to a program that cannot draw it, and never leave two
1016    /// adjacent runs of one class.
1017    #[test]
1018    fn the_split_always_tiles_and_every_run_draws_its_instances() {
1019        let kinds = [
1020            PaintType::Solid,
1021            PaintType::Image,
1022            PaintType::LinearGradient,
1023            PaintType::BlurredRoundedRect,
1024        ];
1025        for len in 1..=6_u32 {
1026            for code in 0..(kinds.len() as u32).pow(len) {
1027                let mut instances = Vec::new();
1028                let mut c = code;
1029                for _ in 0..len {
1030                    let kind = kinds[(c % kinds.len() as u32) as usize];
1031                    c /= kinds.len() as u32;
1032                    instances.push(strip(pack_paint_descriptor(kind, 1)));
1033                }
1034                for min_run in [0, 1, 2, 3, 16] {
1035                    let mut out = Vec::new();
1036                    split_class_runs(&instances, min_run, false, &mut out);
1037                    let mut next = 0;
1038                    for (i, run) in out.iter().enumerate() {
1039                        assert_eq!(run.first, next, "{out:?}");
1040                        assert!(run.count > 0);
1041                        for instance in
1042                            &instances[run.first as usize..(run.first + run.count) as usize]
1043                        {
1044                            assert!(
1045                                run.class.draws(PaintClass::of_instance(instance)),
1046                                "{out:?}"
1047                            );
1048                        }
1049                        if i > 0 {
1050                            assert_ne!(out[i - 1].class, run.class, "{out:?}");
1051                        }
1052                        next += run.count;
1053                    }
1054                    assert_eq!(next, len);
1055                }
1056            }
1057        }
1058    }
1059}