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/// Bit the paint type starts at in [`GpuStrip::paint_and_rect_flag`].
29const PAINT_TYPE_SHIFT: u32 = 26;
30
31/// Mask covering the encoded-paint texel index in
32/// [`GpuStrip::paint_and_rect_flag`] — everything below the paint type.
33pub const PAINT_TEXTURE_INDEX_MASK: u32 = (1 << PAINT_TYPE_SHIFT) - 1;
34
35/// The colour source saying the fragment shader reads
36/// [`GpuStrip::payload`] rather than sampling a rendered layer.
37///
38/// The only source the engine emits: layer compositing is later work, and an
39/// instance that named the layer source would sample the placeholder view
40/// bound in its place.
41const COLOR_SOURCE_PAYLOAD: u32 = 0;
42
43/// How the fragment shader turns an instance's payload into colour.
44///
45/// The discriminants are the shader's own paint-type numbering, not an
46/// arbitrary ordering — they are written into
47/// [`GpuStrip::paint_and_rect_flag`] as-is.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[repr(u32)]
50pub enum PaintType {
51    /// The payload is a premultiplied RGBA8 colour, used directly.
52    Solid = 0,
53    /// The payload is a sample position; the record names an atlas image.
54    Image = 1,
55    /// The payload is a sample position; the record names a linear gradient.
56    LinearGradient = 2,
57    /// The payload is a sample position; the record names a radial gradient.
58    RadialGradient = 3,
59    /// The payload is a sample position; the record names a sweep gradient.
60    SweepGradient = 4,
61    /// The payload is a sample position; the record names a blurred rounded
62    /// rectangle.
63    BlurredRoundedRect = 5,
64}
65
66/// The packed paint descriptor for a `paint_type` instance whose encoded-paint
67/// record starts at `texel_index`.
68///
69/// [`PaintType::Solid`] indexes no record and passes `0`; every other type
70/// carries the texel its record starts at, which is what
71/// `load_encoded_paint_texel` addresses the paint texture by. The index is
72/// masked rather than reported, because it is produced by this crate's own
73/// serialization — a paint table large enough to overflow 26 bits is refused
74/// by the paint texture's own capacity check long before it reaches here.
75#[must_use]
76pub const fn pack_paint_descriptor(paint_type: PaintType, texel_index: u32) -> u32 {
77    (COLOR_SOURCE_PAYLOAD << COLOR_SOURCE_SHIFT)
78        | ((paint_type as u32) << PAINT_TYPE_SHIFT)
79        | (texel_index & PAINT_TEXTURE_INDEX_MASK)
80}
81
82/// The alpha *column* the strip shader addresses coverage by, from the byte
83/// index a [`Strip`] carries.
84///
85/// The two are different units, and the conversion is the shader's own. A
86/// strip's `alpha_idx` counts coverage *bytes* from the start of the frame's
87/// buffer; [`GpuStrip::col_idx_or_rect_frac`] is read as a column ordinal,
88/// which the fragment stage turns into a texel with `col / 4` and a channel
89/// within it with `col % 4` — one channel holding one pixel column's
90/// `Tile::HEIGHT` coverage bytes. So a column is `Tile::HEIGHT` bytes wide,
91/// which is the same unit [`Strip::width_to`] already reports a strip's width
92/// in.
93///
94/// Getting this wrong is invisible on fully-covered geometry and total on
95/// anti-aliased geometry: every alpha-sampled span reads past its own
96/// coverage, lands on unwritten texels, and resolves to zero alpha.
97#[must_use]
98pub const fn alpha_column(alpha_idx: u32) -> u32 {
99    alpha_idx / Tile::HEIGHT as u32
100}
101
102/// One strip instance, matching the shaders' `StripInstance` byte for byte.
103///
104/// Three of the fields are overloaded by [`RECT_STRIP_FLAG`], which is why
105/// they are named for both readings.
106#[repr(C)]
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
108pub struct GpuStrip {
109    /// Left edge in pixels.
110    pub x: u16,
111    /// Top edge in pixels (a multiple of `Tile::HEIGHT`).
112    pub y: u16,
113    /// Width in pixels.
114    pub width: u16,
115    /// Width of the alpha-sampled (dense) part in pixels, or, for a rect
116    /// instance, the rect's height in pixels.
117    pub dense_width_or_rect_height: u16,
118    /// Index of the instance's first alpha column in the alpha texture, or,
119    /// for a rect instance, its packed coverage fraction.
120    pub col_idx_or_rect_frac: u32,
121    /// Paint-dependent payload — a packed color, or the coordinates the paint
122    /// is sampled at.
123    pub payload: u32,
124    /// Packed paint descriptor, with [`RECT_STRIP_FLAG`] in bit 31.
125    pub paint_and_rect_flag: u32,
126    /// Painter's-order index driving early-z rejection: the backmost draw is
127    /// 0 and each draw in front of it increments.
128    pub depth_index: u32,
129}
130
131const _: () = assert!(
132    size_of::<GpuStrip>() == 24,
133    "`GpuStrip` must stay 24 bytes — the shaders' `StripInstance` layout",
134);
135const _: () = assert!(
136    size_of::<GpuStrip>() == size_of::<u16>() * 4 + size_of::<u32>() * 4,
137    "`GpuStrip` must stay padding-free — six `Uint32` vertex attributes read it",
138);
139
140/// The per-draw values every strip instance of one draw shares.
141///
142/// Carried separately from the geometry because a draw resolves them once
143/// (its paint, its painter's-order index) and every strip it emits repeats
144/// them.
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub struct StripDraw {
147    /// [`GpuStrip::payload`].
148    pub payload: u32,
149    /// The packed paint descriptor, without [`RECT_STRIP_FLAG`].
150    pub paint: u32,
151    /// [`GpuStrip::depth_index`].
152    pub depth_index: u32,
153}
154
155impl GpuStrip {
156    /// Vertices in the quad each instance expands to.
157    pub const QUAD_VERTICES: u32 = 4;
158
159    /// The six `Uint32` vertex attributes the 24-byte instance is read
160    /// through, at shader locations 0-5.
161    ///
162    /// The `u16` pairs are read as packed `u32`s and unpacked in the shader,
163    /// so the attribute list stays uniform.
164    #[must_use]
165    pub fn vertex_attributes() -> [wgpu::VertexAttribute; 6] {
166        wgpu::vertex_attr_array![
167            0 => Uint32,
168            1 => Uint32,
169            2 => Uint32,
170            3 => Uint32,
171            4 => Uint32,
172            5 => Uint32,
173        ]
174    }
175
176    /// The instance-stepped vertex buffer layout over [`GpuStrip`].
177    #[must_use]
178    pub fn vertex_layout() -> VertexLayout {
179        VertexLayout {
180            array_stride: size_of::<Self>() as wgpu::BufferAddress,
181            step_mode: wgpu::VertexStepMode::Instance,
182            attributes: Self::vertex_attributes().to_vec(),
183        }
184    }
185
186    /// The vertex range of a strip draw: one quad, built in the shader from
187    /// the vertex index, with no vertex buffer behind it.
188    #[must_use]
189    pub fn vertex_range() -> Range<u32> {
190        0..Self::QUAD_VERTICES
191    }
192
193    /// The instance range of a strip draw covering `count` instances from
194    /// `first_instance` — paired with [`Self::vertex_range`] for one
195    /// `draw` call.
196    #[must_use]
197    pub fn instance_range(first_instance: u32, count: u32) -> Range<u32> {
198        first_instance..first_instance.saturating_add(count)
199    }
200
201    /// The alpha-sampled instance for `strip`, `width` pixels wide.
202    ///
203    /// The strip's alpha index becomes the instance's first alpha *column*,
204    /// and the instance is dense across its whole width: every column samples
205    /// coverage.
206    #[must_use]
207    pub fn from_strip(strip: &Strip, width: u16, draw: StripDraw) -> Self {
208        Self {
209            x: strip.x,
210            y: strip.y,
211            width,
212            dense_width_or_rect_height: width,
213            col_idx_or_rect_frac: alpha_column(strip.alpha_idx()),
214            payload: draw.payload,
215            paint_and_rect_flag: draw.paint,
216            depth_index: draw.depth_index,
217        }
218    }
219
220    /// The alpha-sampled instance for `strip`, taking its width from `next` —
221    /// the strip immediately after it in the same run, which is where a
222    /// strip's extent is recorded.
223    #[must_use]
224    pub fn from_strip_pair(strip: &Strip, next: &Strip, draw: StripDraw) -> Self {
225        Self::from_strip(strip, strip.width_to(next), draw)
226    }
227
228    /// The solid instance filling the gap between `strip` and `next`, or
229    /// `None` when there is no gap to fill.
230    ///
231    /// A gap is filled only when `next` carries the fill-gap flag and sits on
232    /// the same strip row: the flag means "the winding between me and the
233    /// previous strip is non-zero", which says nothing about a strip on
234    /// another row. The instance samples no coverage, so its dense width and
235    /// column index are both zero.
236    #[must_use]
237    pub fn gap_fill(strip: &Strip, next: &Strip, draw: StripDraw) -> Option<Self> {
238        if !next.fill_gap() || next.y != strip.y {
239            return None;
240        }
241        let gap_x = strip.x.saturating_add(strip.width_to(next));
242        let gap_width = next.x.saturating_sub(gap_x);
243        if gap_width == 0 {
244            return None;
245        }
246        Some(Self::solid_fill(gap_x, strip.y, gap_width, draw))
247    }
248
249    /// A solid instance covering one strip row, sampling no coverage.
250    #[must_use]
251    pub fn solid_fill(x: u16, y: u16, width: u16, draw: StripDraw) -> Self {
252        Self {
253            x,
254            y,
255            width,
256            dense_width_or_rect_height: 0,
257            col_idx_or_rect_frac: 0,
258            payload: draw.payload,
259            paint_and_rect_flag: draw.paint,
260            depth_index: draw.depth_index,
261        }
262    }
263
264    /// A rectangle instance of `width` x `height` pixels with packed coverage
265    /// fraction `frac`, flagged with [`RECT_STRIP_FLAG`].
266    ///
267    /// Unlike a strip, a rect instance is not bound to one strip row: its
268    /// height is carried in the instance itself.
269    #[must_use]
270    pub fn from_rect(x: u16, y: u16, width: u16, height: u16, frac: u32, draw: StripDraw) -> Self {
271        Self {
272            x,
273            y,
274            width,
275            dense_width_or_rect_height: height,
276            col_idx_or_rect_frac: frac,
277            payload: draw.payload,
278            paint_and_rect_flag: draw.paint | RECT_STRIP_FLAG,
279            depth_index: draw.depth_index,
280        }
281    }
282
283    /// Whether this instance is a rectangle rather than a strip.
284    #[must_use]
285    pub const fn is_rect(&self) -> bool {
286        self.paint_and_rect_flag & RECT_STRIP_FLAG != 0
287    }
288
289    /// The packed paint descriptor with [`RECT_STRIP_FLAG`] masked off.
290    #[must_use]
291    pub const fn paint(&self) -> u32 {
292        self.paint_and_rect_flag & !RECT_STRIP_FLAG
293    }
294}