use bytemuck::{Pod, Zeroable};
use core::ops::Range;
use frust_gpu::VertexLayout;
use vello_common::strip::Strip;
use vello_common::tile::Tile;
const _: () = assert!(
Tile::HEIGHT == 4,
"the strip shaders require `Tile::HEIGHT` to be 4",
);
pub const RECT_STRIP_FLAG: u32 = 1 << 31;
const COLOR_SOURCE_SHIFT: u32 = 29;
const COLOR_SOURCE_MASK: u32 = 0x3;
const PAINT_TYPE_MASK: u32 = 0x7;
const COLOR_SOURCE_LAYER: u32 = 1;
const PAINT_TYPE_GLYPH: u32 = PaintType::Glyph as u32;
const IMAGE_QUALITY_BILINEAR: u32 = 1;
const PAINT_TYPE_SHIFT: u32 = 26;
pub const PAINT_TEXTURE_INDEX_MASK: u32 = (1 << PAINT_TYPE_SHIFT) - 1;
const COLOR_SOURCE_PAYLOAD: u32 = 0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum PaintType {
Solid = 0,
Image = 1,
LinearGradient = 2,
RadialGradient = 3,
SweepGradient = 4,
BlurredRoundedRect = 5,
Glyph = 6,
}
#[must_use]
pub const fn pack_paint_descriptor(paint_type: PaintType, texel_index: u32) -> u32 {
(COLOR_SOURCE_PAYLOAD << COLOR_SOURCE_SHIFT)
| ((paint_type as u32) << PAINT_TYPE_SHIFT)
| (texel_index & PAINT_TEXTURE_INDEX_MASK)
}
#[must_use]
pub const fn alpha_column(alpha_idx: u32) -> u32 {
alpha_idx / Tile::HEIGHT as u32
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
pub struct GpuStrip {
pub x: u16,
pub y: u16,
pub width: u16,
pub dense_width_or_rect_height: u16,
pub col_idx_or_rect_frac: u32,
pub payload: u32,
pub paint_and_rect_flag: u32,
pub depth_index: u32,
}
const _: () = assert!(
size_of::<GpuStrip>() == 24,
"`GpuStrip` must stay 24 bytes — the shaders' `StripInstance` layout",
);
const _: () = assert!(
size_of::<GpuStrip>() == size_of::<u16>() * 4 + size_of::<u32>() * 4,
"`GpuStrip` must stay padding-free — six `Uint32` vertex attributes read it",
);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StripDraw {
pub payload: u32,
pub paint: u32,
pub depth_index: u32,
}
impl GpuStrip {
pub const QUAD_VERTICES: u32 = 4;
#[must_use]
pub fn vertex_attributes() -> [wgpu::VertexAttribute; 6] {
wgpu::vertex_attr_array![
0 => Uint32,
1 => Uint32,
2 => Uint32,
3 => Uint32,
4 => Uint32,
5 => Uint32,
]
}
#[must_use]
pub fn vertex_layout() -> VertexLayout {
VertexLayout {
array_stride: size_of::<Self>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: Self::vertex_attributes().to_vec(),
}
}
#[must_use]
pub fn vertex_range() -> Range<u32> {
0..Self::QUAD_VERTICES
}
#[must_use]
pub fn instance_range(first_instance: u32, count: u32) -> Range<u32> {
first_instance..first_instance.saturating_add(count)
}
#[must_use]
pub fn from_strip(strip: &Strip, width: u16, draw: StripDraw) -> Self {
Self {
x: strip.x,
y: strip.y,
width,
dense_width_or_rect_height: width,
col_idx_or_rect_frac: alpha_column(strip.alpha_idx()),
payload: draw.payload,
paint_and_rect_flag: draw.paint,
depth_index: draw.depth_index,
}
}
#[must_use]
pub fn from_strip_pair(strip: &Strip, next: &Strip, draw: StripDraw) -> Self {
Self::from_strip(strip, strip.width_to(next), draw)
}
#[must_use]
pub fn gap_fill(strip: &Strip, next: &Strip, draw: StripDraw) -> Option<Self> {
if !next.fill_gap() || next.y != strip.y {
return None;
}
let gap_x = strip.x.saturating_add(strip.width_to(next));
let gap_width = next.x.saturating_sub(gap_x);
if gap_width == 0 {
return None;
}
Some(Self::solid_fill(gap_x, strip.y, gap_width, draw))
}
#[must_use]
pub fn solid_fill(x: u16, y: u16, width: u16, draw: StripDraw) -> Self {
Self {
x,
y,
width,
dense_width_or_rect_height: 0,
col_idx_or_rect_frac: 0,
payload: draw.payload,
paint_and_rect_flag: draw.paint,
depth_index: draw.depth_index,
}
}
#[must_use]
pub fn from_rect(x: u16, y: u16, width: u16, height: u16, frac: u32, draw: StripDraw) -> Self {
Self {
x,
y,
width,
dense_width_or_rect_height: height,
col_idx_or_rect_frac: frac,
payload: draw.payload,
paint_and_rect_flag: draw.paint | RECT_STRIP_FLAG,
depth_index: draw.depth_index,
}
}
#[must_use]
pub const fn is_rect(&self) -> bool {
self.paint_and_rect_flag & RECT_STRIP_FLAG != 0
}
#[must_use]
pub const fn paint(&self) -> u32 {
self.paint_and_rect_flag & !RECT_STRIP_FLAG
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PaintClass {
SolidCoverage,
Gradient,
Image,
ImageFiltered,
BlurredRect,
LayerComposite,
Glyph,
Uber,
}
impl PaintClass {
pub const ALL: [Self; 8] = [
Self::SolidCoverage,
Self::Gradient,
Self::Image,
Self::ImageFiltered,
Self::BlurredRect,
Self::LayerComposite,
Self::Glyph,
Self::Uber,
];
pub const SPECIALISED: [Self; 7] = [
Self::SolidCoverage,
Self::Gradient,
Self::Image,
Self::ImageFiltered,
Self::BlurredRect,
Self::LayerComposite,
Self::Glyph,
];
#[must_use]
pub const fn of_flag(paint_and_rect_flag: u32) -> Self {
let color_source = (paint_and_rect_flag >> COLOR_SOURCE_SHIFT) & COLOR_SOURCE_MASK;
if color_source == COLOR_SOURCE_LAYER {
return Self::LayerComposite;
}
if color_source != COLOR_SOURCE_PAYLOAD {
return Self::Uber;
}
match (paint_and_rect_flag >> PAINT_TYPE_SHIFT) & PAINT_TYPE_MASK {
0 => Self::SolidCoverage,
1 => Self::ImageFiltered,
2..=4 => Self::Gradient,
5 => Self::BlurredRect,
PAINT_TYPE_GLYPH if paint_and_rect_flag & RECT_STRIP_FLAG != 0 => Self::Glyph,
_ => Self::Uber,
}
}
#[must_use]
pub const fn of_instance(strip: &GpuStrip) -> Self {
Self::of_flag(strip.paint_and_rect_flag)
}
#[must_use]
pub const fn of_image(quality: u32, external: bool) -> Self {
if external || quality > IMAGE_QUALITY_BILINEAR {
Self::ImageFiltered
} else {
Self::Image
}
}
#[must_use]
pub const fn index(self) -> usize {
self as usize
}
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::SolidCoverage => "solid",
Self::Gradient => "gradient",
Self::Image => "image",
Self::ImageFiltered => "image-filtered",
Self::BlurredRect => "blurred-rect",
Self::LayerComposite => "layer",
Self::Glyph => "glyph",
Self::Uber => "uber",
}
}
#[must_use]
pub const fn draws(self, other: Self) -> bool {
matches!(self, Self::Uber)
|| matches!(other, Self::SolidCoverage)
|| matches!((self, other), (Self::ImageFiltered, Self::Image))
|| self as usize == other as usize
}
#[must_use]
pub const fn admits_flag(self, paint_and_rect_flag: u32) -> bool {
let class = Self::of_flag(paint_and_rect_flag);
self.draws(class) || (matches!(self, Self::Image) && matches!(class, Self::ImageFiltered))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct ClassSet(u8);
const _: () = assert!(
PaintClass::ALL.len() <= u8::BITS as usize,
"`ClassSet` holds one bit per paint class",
);
impl ClassSet {
pub const EMPTY: Self = Self(0);
#[must_use]
pub const fn only(class: PaintClass) -> Self {
Self(1 << class as u8)
}
pub fn insert(&mut self, class: PaintClass) {
self.0 |= 1 << class as u8;
}
#[must_use]
pub const fn contains(self, class: PaintClass) -> bool {
self.0 & (1 << class as u8) != 0
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub fn iter(self) -> impl Iterator<Item = PaintClass> {
PaintClass::ALL
.into_iter()
.filter(move |class| self.contains(*class))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClassRun {
pub first: u32,
pub count: u32,
pub class: PaintClass,
}
pub const RUN_SPLIT_MIN: u32 = 16;
pub fn split_class_runs(
instances: &[GpuStrip],
min_run: u32,
uber_only: bool,
out: &mut Vec<ClassRun>,
) {
let total = u32::try_from(instances.len()).unwrap_or(u32::MAX);
let mut index = 0_u32;
let maximal = std::iter::from_fn(|| {
if index >= total {
return None;
}
let class = PaintClass::of_instance(&instances[index as usize]);
let mut end = index + 1;
while end < total && PaintClass::of_instance(&instances[end as usize]) == class {
end += 1;
}
let run = ClassRun {
first: index,
count: end - index,
class,
};
index = end;
Some(run)
});
merge_class_runs(maximal, min_run, uber_only, out);
}
pub fn merge_class_runs(
runs: impl IntoIterator<Item = ClassRun>,
min_run: u32,
uber_only: bool,
out: &mut Vec<ClassRun>,
) {
let start = out.len();
if uber_only {
let mut runs = runs.into_iter().filter(|run| run.count > 0);
if let Some(first) = runs.next() {
let count = runs.fold(first.count, |count, run| count.saturating_add(run.count));
out.push(ClassRun {
first: first.first,
count,
class: PaintClass::Uber,
});
}
return;
}
let mut pending: Option<ClassRun> = None;
for run in runs {
if run.count == 0 {
continue;
}
match pending.as_mut() {
Some(open) if open.class == run.class => {
open.count = open.count.saturating_add(run.count);
}
_ => {
if let Some(closed) = pending.replace(run) {
push_folding(out, start, closed, min_run);
}
}
}
}
if let Some(closed) = pending {
push_folding(out, start, closed, min_run);
}
let mut write = start;
for read in start..out.len() {
let mut run = out[read];
if run.class != PaintClass::SolidCoverage && run.count < min_run {
run.class = PaintClass::Uber;
}
if write > start && out[write - 1].class == run.class {
out[write - 1].count += run.count;
} else {
out[write] = run;
write += 1;
}
}
out.truncate(write);
}
fn push_folding(out: &mut Vec<ClassRun>, start: usize, mut run: ClassRun, min_run: u32) {
let short_solid =
|run: &ClassRun| run.class == PaintClass::SolidCoverage && run.count < min_run;
if let Some(top) = out.get_mut(start..).and_then(<[ClassRun]>::last_mut) {
if top.class == run.class || (short_solid(&run) && top.class != PaintClass::SolidCoverage) {
top.count += run.count;
return;
}
if short_solid(top) && run.class != PaintClass::SolidCoverage {
run.first = top.first;
run.count += top.count;
out.pop();
if let Some(below) = out.get_mut(start..).and_then(<[ClassRun]>::last_mut)
&& below.class == run.class
{
below.count += run.count;
return;
}
}
}
out.push(run);
}
#[cfg(test)]
mod tests {
use super::*;
fn strip(paint: u32) -> GpuStrip {
GpuStrip::solid_fill(
0,
0,
1,
StripDraw {
payload: 0,
paint,
depth_index: 0,
},
)
}
fn of(classes: &[(PaintType, u32)]) -> Vec<GpuStrip> {
classes
.iter()
.flat_map(|(paint, count)| {
std::iter::repeat_n(strip(pack_paint_descriptor(*paint, 7)), *count as usize)
})
.collect()
}
fn runs(instances: &[GpuStrip], min_run: u32) -> Vec<(u32, u32, PaintClass)> {
let mut out = Vec::new();
split_class_runs(instances, min_run, false, &mut out);
out.iter().map(|r| (r.first, r.count, r.class)).collect()
}
#[test]
fn every_paint_type_and_source_maps_to_its_class() {
use PaintClass as C;
assert_eq!(
C::of_flag(pack_paint_descriptor(PaintType::Solid, 0)),
C::SolidCoverage
);
assert_eq!(
C::of_flag(pack_paint_descriptor(PaintType::Image, 3)),
C::ImageFiltered,
"the flag alone answers the class that draws every image"
);
for gradient in [
PaintType::LinearGradient,
PaintType::RadialGradient,
PaintType::SweepGradient,
] {
assert_eq!(C::of_flag(pack_paint_descriptor(gradient, 9)), C::Gradient);
}
assert_eq!(
C::of_flag(pack_paint_descriptor(PaintType::BlurredRoundedRect, 1)),
C::BlurredRect
);
assert_eq!(
C::of_flag((1 << 29) | 0x80 | RECT_STRIP_FLAG),
C::LayerComposite
);
assert_eq!(
C::of_flag(pack_paint_descriptor(PaintType::Image, 3) | RECT_STRIP_FLAG),
C::ImageFiltered
);
assert_eq!(C::of_image(0, false), C::Image);
assert_eq!(C::of_image(1, false), C::Image);
assert_eq!(C::of_image(2, false), C::ImageFiltered);
assert_eq!(C::of_image(0, true), C::ImageFiltered);
assert_eq!(C::of_image(1, true), C::ImageFiltered);
let image = pack_paint_descriptor(PaintType::Image, 3);
assert!(C::Image.admits_flag(image));
assert!(C::ImageFiltered.admits_flag(image));
assert!(!C::Gradient.admits_flag(image));
assert_eq!(C::of_flag(2 << 29), C::Uber);
assert_eq!(C::of_flag(3 << 29), C::Uber);
assert_eq!(C::of_flag(6 << 26), C::Uber);
assert_eq!(C::of_flag(7 << 26), C::Uber);
}
#[test]
fn a_glyph_quad_is_its_own_class_only_on_a_rect_instance() {
use PaintClass as C;
let glyph = pack_paint_descriptor(PaintType::Glyph, 12);
assert_eq!(C::of_flag(glyph | RECT_STRIP_FLAG), C::Glyph);
assert_eq!(
C::of_flag(glyph),
C::Uber,
"a strip carrying the glyph paint type is reserved"
);
assert!(C::Glyph.admits_flag(glyph | RECT_STRIP_FLAG));
assert!(C::Glyph.admits_flag(pack_paint_descriptor(PaintType::Solid, 0)));
assert!(!C::Glyph.admits_flag(pack_paint_descriptor(PaintType::Image, 3)));
assert!(!C::Image.admits_flag(glyph | RECT_STRIP_FLAG));
assert!(!C::ImageFiltered.draws(C::Glyph));
assert!(C::Uber.admits_flag(glyph | RECT_STRIP_FLAG));
assert_eq!(C::Glyph.label(), "glyph");
let mut set = ClassSet::EMPTY;
for class in C::ALL {
set.insert(class);
}
assert_eq!(set.iter().count(), C::ALL.len());
}
#[test]
fn the_class_lattice_is_solid_below_everything_and_uber_above() {
for class in PaintClass::ALL {
assert!(class.draws(PaintClass::SolidCoverage));
assert!(class.draws(class));
assert!(PaintClass::Uber.draws(class));
}
assert!(!PaintClass::Gradient.draws(PaintClass::Image));
assert!(PaintClass::ImageFiltered.draws(PaintClass::Image));
assert!(!PaintClass::Image.draws(PaintClass::ImageFiltered));
assert!(!PaintClass::SolidCoverage.draws(PaintClass::Gradient));
assert!(!PaintClass::Image.draws(PaintClass::Uber));
for (index, class) in PaintClass::ALL.iter().enumerate() {
assert_eq!(class.index(), index);
}
assert_eq!(PaintClass::SPECIALISED.len() + 1, PaintClass::ALL.len());
assert!(!PaintClass::SPECIALISED.contains(&PaintClass::Uber));
}
#[test]
fn a_class_set_round_trips() {
let mut set = ClassSet::EMPTY;
assert!(set.is_empty());
set.insert(PaintClass::Image);
set.insert(PaintClass::Uber);
set.insert(PaintClass::Image);
assert!(set.contains(PaintClass::Image));
assert!(!set.contains(PaintClass::Gradient));
assert_eq!(
set.iter().collect::<Vec<_>>(),
vec![PaintClass::Image, PaintClass::Uber]
);
assert_eq!(ClassSet::only(PaintClass::Gradient).iter().count(), 1);
}
#[test]
fn long_runs_keep_their_class_and_cover_the_slice_in_order() {
use PaintType as P;
let instances = of(&[
(P::LinearGradient, 20),
(P::Image, 30),
(P::RadialGradient, 17),
]);
assert_eq!(
runs(&instances, 16),
vec![
(0, 20, PaintClass::Gradient),
(20, 30, PaintClass::ImageFiltered),
(50, 17, PaintClass::Gradient),
]
);
}
#[test]
fn a_short_solid_run_folds_into_its_non_solid_neighbour() {
use PaintType as P;
let instances = of(&[
(P::LinearGradient, 20),
(P::Solid, 3),
(P::LinearGradient, 20),
]);
assert_eq!(runs(&instances, 16), vec![(0, 43, PaintClass::Gradient)]);
let instances = of(&[(P::Solid, 3), (P::Image, 20)]);
assert_eq!(
runs(&instances, 16),
vec![(0, 23, PaintClass::ImageFiltered)]
);
let instances = of(&[(P::Image, 20), (P::Solid, 40), (P::Image, 20)]);
assert_eq!(
runs(&instances, 16),
vec![
(0, 20, PaintClass::ImageFiltered),
(20, 40, PaintClass::SolidCoverage),
(60, 20, PaintClass::ImageFiltered),
]
);
assert_eq!(
runs(&of(&[(P::Solid, 2)]), 16),
vec![(0, 2, PaintClass::SolidCoverage)]
);
}
#[test]
fn a_run_below_the_threshold_is_merged_and_drawn_with_uber() {
use PaintType as P;
let instances = of(&[
(P::RadialGradient, 40),
(P::Image, 5),
(P::BlurredRoundedRect, 3),
(P::RadialGradient, 40),
]);
assert_eq!(
runs(&instances, 16),
vec![
(0, 40, PaintClass::Gradient),
(40, 8, PaintClass::Uber),
(48, 40, PaintClass::Gradient),
]
);
assert_eq!(runs(&instances, 64), vec![(0, 88, PaintClass::Uber)]);
assert_eq!(runs(&instances, 0).len(), 4);
}
#[test]
fn the_kill_switch_yields_one_uber_run() {
use PaintType as P;
let instances = of(&[(P::RadialGradient, 40), (P::Image, 40), (P::Solid, 40)]);
let mut out = Vec::new();
split_class_runs(&instances, 16, true, &mut out);
assert_eq!(
out,
vec![ClassRun {
first: 0,
count: 120,
class: PaintClass::Uber
}]
);
out.clear();
split_class_runs(&[], 16, false, &mut out);
assert!(out.is_empty());
}
#[test]
fn appending_to_a_non_empty_vec_never_merges_across_calls() {
use PaintType as P;
let mut out = Vec::new();
split_class_runs(&of(&[(P::Image, 20)]), 16, false, &mut out);
split_class_runs(&of(&[(P::Image, 20)]), 16, false, &mut out);
assert_eq!(out.len(), 2);
assert_eq!(out[1].first, 0);
}
#[test]
fn merging_draw_runs_equals_scanning_their_instances() {
use PaintType as P;
let draws = [
(P::Solid, 3),
(P::RadialGradient, 20),
(P::Solid, 10),
(P::Solid, 10),
(P::LinearGradient, 9),
(P::Image, 5),
(P::Image, 4),
(P::Solid, 2),
(P::BlurredRoundedRect, 30),
(P::Solid, 40),
];
let instances = of(&draws);
let mut first = 0;
let per_draw: Vec<ClassRun> = draws
.iter()
.map(|(paint, count)| {
let run = ClassRun {
first,
count: *count,
class: PaintClass::of_flag(pack_paint_descriptor(*paint, 7)),
};
first += count;
run
})
.collect();
for min_run in [0, 1, 4, 16, 64] {
for uber in [false, true] {
let mut scanned = Vec::new();
split_class_runs(&instances, min_run, uber, &mut scanned);
let mut merged = Vec::new();
merge_class_runs(per_draw.iter().copied(), min_run, uber, &mut merged);
assert_eq!(scanned, merged, "threshold {min_run}, uber {uber}");
}
}
}
#[test]
fn the_split_always_tiles_and_every_run_draws_its_instances() {
let kinds = [
PaintType::Solid,
PaintType::Image,
PaintType::LinearGradient,
PaintType::BlurredRoundedRect,
];
for len in 1..=6_u32 {
for code in 0..(kinds.len() as u32).pow(len) {
let mut instances = Vec::new();
let mut c = code;
for _ in 0..len {
let kind = kinds[(c % kinds.len() as u32) as usize];
c /= kinds.len() as u32;
instances.push(strip(pack_paint_descriptor(kind, 1)));
}
for min_run in [0, 1, 2, 3, 16] {
let mut out = Vec::new();
split_class_runs(&instances, min_run, false, &mut out);
let mut next = 0;
for (i, run) in out.iter().enumerate() {
assert_eq!(run.first, next, "{out:?}");
assert!(run.count > 0);
for instance in
&instances[run.first as usize..(run.first + run.count) as usize]
{
assert!(
run.class.draws(PaintClass::of_instance(instance)),
"{out:?}"
);
}
if i > 0 {
assert_ne!(out[i - 1].class, run.class, "{out:?}");
}
next += run.count;
}
assert_eq!(next, len);
}
}
}
}
}