blob: 13d67fa0f8acd02361654fd9ab62eb2fb39484f7 [file] [log] [blame]
// Copyright 2023 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
use vello_encoding::{Clip, ConfigUniform, DrawMonoid, DrawTag, Monoid, PathBbox};
use super::{
CpuBinding, RAD_GRAD_KIND_CIRCULAR, RAD_GRAD_KIND_CONE, RAD_GRAD_KIND_FOCAL_ON_CIRCLE,
RAD_GRAD_KIND_STRIP, RAD_GRAD_SWAPPED,
util::{Transform, Vec2, read_draw_tag_from_scene},
};
const WG_SIZE: usize = 256;
fn draw_leaf_main(
n_wg: u32,
config: &ConfigUniform,
scene: &[u32],
reduced: &[DrawMonoid],
path_bbox: &[PathBbox],
draw_monoid: &mut [DrawMonoid],
info: &mut [u32],
clip_inp: &mut [Clip],
) {
let num_blocks_total = (config.layout.n_draw_objects as usize).div_ceil(WG_SIZE);
let n_blocks_base = num_blocks_total / WG_SIZE;
let remainder = num_blocks_total % WG_SIZE;
let mut prefix = DrawMonoid::default();
for i in 0..n_wg as usize {
let first_block = n_blocks_base * i + i.min(remainder);
let n_blocks = n_blocks_base + (i < remainder) as usize;
let mut m = prefix;
for j in 0..WG_SIZE * n_blocks {
let ix = (first_block * WG_SIZE) as u32 + j as u32;
let tag_raw = read_draw_tag_from_scene(config, scene, ix);
let tag_word = DrawTag(tag_raw);
// store exclusive prefix sum
if ix < config.layout.n_draw_objects {
draw_monoid[ix as usize] = m;
}
let m_next = m.combine(&DrawMonoid::new(tag_word));
let dd = config.layout.draw_data_base + m.scene_offset;
let di = m.info_offset as usize;
if tag_word == DrawTag::COLOR
|| tag_word == DrawTag::LINEAR_GRADIENT
|| tag_word == DrawTag::RADIAL_GRADIENT
|| tag_word == DrawTag::SWEEP_GRADIENT
|| tag_word == DrawTag::IMAGE
|| tag_word == DrawTag::BEGIN_CLIP
|| tag_word == DrawTag::BLUR_RECT
{
let bbox = path_bbox[m.path_ix as usize];
let transform = Transform::read(config.layout.transform_base, bbox.trans_ix, scene);
let draw_flags = bbox.draw_flags;
match tag_word {
DrawTag::COLOR => {
info[di] = draw_flags;
}
DrawTag::LINEAR_GRADIENT => {
info[di] = draw_flags;
let p0 = Vec2::new(
f32::from_bits(scene[dd as usize + 1]),
f32::from_bits(scene[dd as usize + 2]),
);
let p1 = Vec2::new(
f32::from_bits(scene[dd as usize + 3]),
f32::from_bits(scene[dd as usize + 4]),
);
let p0 = transform.apply(p0);
let p1 = transform.apply(p1);
let dxy = p1 - p0;
let scale = 1.0 / dxy.dot(dxy);
let line_xy = dxy * scale;
let line_c = -p0.dot(line_xy);
info[di + 1] = f32::to_bits(line_xy.x);
info[di + 2] = f32::to_bits(line_xy.y);
info[di + 3] = f32::to_bits(line_c);
}
DrawTag::RADIAL_GRADIENT => {
const GRADIENT_EPSILON: f32 = 1.0_f32 / (1 << 12) as f32;
info[di] = draw_flags;
let mut p0 = Vec2::new(
f32::from_bits(scene[dd as usize + 1]),
f32::from_bits(scene[dd as usize + 2]),
);
let mut p1 = Vec2::new(
f32::from_bits(scene[dd as usize + 3]),
f32::from_bits(scene[dd as usize + 4]),
);
let mut r0 = f32::from_bits(scene[dd as usize + 5]);
let mut r1 = f32::from_bits(scene[dd as usize + 6]);
let user_to_gradient = transform.inverse();
let xform;
let mut focal_x = 0.0;
let radius;
let mut kind;
let mut flags = 0;
if (r0 - r1).abs() < GRADIENT_EPSILON {
// When the radii are the same, emit a strip gradient
kind = RAD_GRAD_KIND_STRIP;
let scaled = r0 / p0.distance(p1);
xform = two_point_to_unit_line(p0, p1) * user_to_gradient;
radius = scaled * scaled;
} else {
// Assume a two point conical gradient unless the centers
// are equal.
kind = RAD_GRAD_KIND_CONE;
if p0 == p1 {
kind = RAD_GRAD_KIND_CIRCULAR;
// Nudge p0 a bit to avoid denormals.
p0.x += GRADIENT_EPSILON;
}
if r1 == 0.0 {
// If r1 == 0.0, swap the points and radii
flags |= RAD_GRAD_SWAPPED;
core::mem::swap(&mut p0, &mut p1);
core::mem::swap(&mut r0, &mut r1);
}
focal_x = r0 / (r0 - r1);
let cf = (1.0 - focal_x) * p0 + focal_x * p1;
radius = r1 / cf.distance(p1);
let user_to_unit_line =
two_point_to_unit_line(cf, p1) * user_to_gradient;
let user_to_scaled;
// When r == 1.0, focal point is on circle
if (radius - 1.0).abs() <= GRADIENT_EPSILON {
kind = RAD_GRAD_KIND_FOCAL_ON_CIRCLE;
let scale = 0.5 * (1.0 - focal_x).abs();
user_to_scaled = Transform([scale, 0.0, 0.0, scale, 0.0, 0.0])
* user_to_unit_line;
} else {
let a = radius * radius - 1.0;
let scale_ratio = (1.0 - focal_x).abs() / a;
let scale_x = radius * scale_ratio;
let scale_y = a.abs().sqrt() * scale_ratio;
user_to_scaled = Transform([scale_x, 0.0, 0.0, scale_y, 0.0, 0.0])
* user_to_unit_line;
}
xform = user_to_scaled;
}
info[di + 1] = f32::to_bits(xform.0[0]);
info[di + 2] = f32::to_bits(xform.0[1]);
info[di + 3] = f32::to_bits(xform.0[2]);
info[di + 4] = f32::to_bits(xform.0[3]);
info[di + 5] = f32::to_bits(xform.0[4]);
info[di + 6] = f32::to_bits(xform.0[5]);
info[di + 7] = f32::to_bits(focal_x);
info[di + 8] = f32::to_bits(radius);
info[di + 9] = (flags << 3) | kind;
}
DrawTag::SWEEP_GRADIENT => {
info[di] = draw_flags;
let p0 = Vec2::new(
f32::from_bits(scene[dd as usize + 1]),
f32::from_bits(scene[dd as usize + 2]),
);
let xform =
(transform * Transform([1.0, 0.0, 0.0, 1.0, p0.x, p0.y])).inverse();
info[di + 1] = f32::to_bits(xform.0[0]);
info[di + 2] = f32::to_bits(xform.0[1]);
info[di + 3] = f32::to_bits(xform.0[2]);
info[di + 4] = f32::to_bits(xform.0[3]);
info[di + 5] = f32::to_bits(xform.0[4]);
info[di + 6] = f32::to_bits(xform.0[5]);
info[di + 7] = scene[dd as usize + 3];
info[di + 8] = scene[dd as usize + 4];
}
DrawTag::IMAGE => {
info[di] = draw_flags;
let xform = transform.inverse();
info[di + 1] = f32::to_bits(xform.0[0]);
info[di + 2] = f32::to_bits(xform.0[1]);
info[di + 3] = f32::to_bits(xform.0[2]);
info[di + 4] = f32::to_bits(xform.0[3]);
info[di + 5] = f32::to_bits(xform.0[4]);
info[di + 6] = f32::to_bits(xform.0[5]);
info[di + 7] = scene[dd as usize];
info[di + 8] = scene[dd as usize + 1];
info[di + 9] = scene[dd as usize + 2];
}
DrawTag::BLUR_RECT => {
info[di] = draw_flags;
let xform = transform.inverse();
info[di + 1] = f32::to_bits(xform.0[0]);
info[di + 2] = f32::to_bits(xform.0[1]);
info[di + 3] = f32::to_bits(xform.0[2]);
info[di + 4] = f32::to_bits(xform.0[3]);
info[di + 5] = f32::to_bits(xform.0[4]);
info[di + 6] = f32::to_bits(xform.0[5]);
info[di + 7] = scene[dd as usize + 1];
info[di + 8] = scene[dd as usize + 2];
info[di + 9] = scene[dd as usize + 3];
info[di + 10] = scene[dd as usize + 4];
}
DrawTag::BEGIN_CLIP => (),
_ => todo!("unhandled draw tag {:x}", tag_word.0),
}
}
if tag_word == DrawTag::BEGIN_CLIP {
let path_ix = m.path_ix as i32;
clip_inp[m.clip_ix as usize] = Clip { ix, path_ix };
} else if tag_word == DrawTag::END_CLIP {
let path_ix = !ix as i32;
clip_inp[m.clip_ix as usize] = Clip { ix, path_ix };
}
m = m_next;
}
prefix = prefix.combine(&reduced[i]);
}
}
pub fn draw_leaf(n_wg: u32, resources: &[CpuBinding<'_>]) {
let config = resources[0].as_typed();
let scene = resources[1].as_slice();
let reduced = resources[2].as_slice();
let path_bbox = resources[3].as_slice();
let mut draw_monoid = resources[4].as_slice_mut();
let mut info = resources[5].as_slice_mut();
let mut clip_inp = resources[6].as_slice_mut();
draw_leaf_main(
n_wg,
&config,
&scene,
&reduced,
&path_bbox,
&mut draw_monoid,
&mut info,
&mut clip_inp,
);
}
fn two_point_to_unit_line(p0: Vec2, p1: Vec2) -> Transform {
let tmp1 = from_poly2(p0, p1);
let inv = tmp1.inverse();
let tmp2 = from_poly2(Vec2::default(), Vec2::new(1.0, 0.0));
tmp2 * inv
}
fn from_poly2(p0: Vec2, p1: Vec2) -> Transform {
Transform([
p1.y - p0.y,
p0.x - p1.x,
p1.x - p0.x,
p1.y - p0.y,
p0.x,
p0.y,
])
}