mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-23 18:06:34 +11:00
Merge pull request #316 from linebender/gradient-fixes
General gradient improvements
This commit is contained in:
commit
14eef3fafb
15 changed files with 514 additions and 98 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -10,7 +10,8 @@
|
|||
"pathtag": "${workspaceFolder}/shader/shared/pathtag.wgsl",
|
||||
"ptcl": "${workspaceFolder}/shader/shared/ptcl.wgsl",
|
||||
"segment": "${workspaceFolder}/shader/shared/segment.wgsl",
|
||||
"tile": "${workspaceFolder}/shader/shared/tile.wgsl"
|
||||
"tile": "${workspaceFolder}/shader/shared/tile.wgsl",
|
||||
"transform": "${workspaceFolder}/shader/shared/transform.wgsl"
|
||||
},
|
||||
"wgsl-analyzer.diagnostics.nagaVersion": "main",
|
||||
"wgsl-analyzer.preprocessor.shaderDefs": [
|
||||
|
|
|
@ -22,7 +22,7 @@ impl DrawTag {
|
|||
pub const LINEAR_GRADIENT: Self = Self(0x114);
|
||||
|
||||
/// Radial gradient fill.
|
||||
pub const RADIAL_GRADIENT: Self = Self(0x2dc);
|
||||
pub const RADIAL_GRADIENT: Self = Self(0x29c);
|
||||
|
||||
/// Image fill.
|
||||
pub const IMAGE: Self = Self(0x248);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use super::{DrawColor, DrawTag, PathEncoder, PathTag, Transform};
|
||||
|
||||
use peniko::{kurbo::Shape, BlendMode, BrushRef};
|
||||
use peniko::{kurbo::Shape, BlendMode, BrushRef, Color};
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
use {
|
||||
|
@ -105,11 +105,13 @@ impl Encoding {
|
|||
Patch::Ramp {
|
||||
draw_data_offset: offset,
|
||||
stops,
|
||||
extend,
|
||||
} => {
|
||||
let stops = stops.start + stops_base..stops.end + stops_base;
|
||||
Patch::Ramp {
|
||||
draw_data_offset: offset + offsets.draw_data,
|
||||
stops,
|
||||
extend: *extend,
|
||||
}
|
||||
}
|
||||
Patch::GlyphRun { index } => Patch::GlyphRun {
|
||||
|
@ -277,13 +279,18 @@ impl Encoding {
|
|||
gradient: DrawLinearGradient,
|
||||
color_stops: impl Iterator<Item = ColorStop>,
|
||||
alpha: f32,
|
||||
_extend: Extend,
|
||||
extend: Extend,
|
||||
) {
|
||||
self.add_ramp(color_stops, alpha);
|
||||
match self.add_ramp(color_stops, alpha, extend) {
|
||||
RampStops::Empty => self.encode_color(DrawColor::new(Color::TRANSPARENT)),
|
||||
RampStops::One(color) => self.encode_color(DrawColor::new(color)),
|
||||
_ => {
|
||||
self.draw_tags.push(DrawTag::LINEAR_GRADIENT);
|
||||
self.draw_data
|
||||
.extend_from_slice(bytemuck::bytes_of(&gradient));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a radial gradient brush.
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -292,13 +299,23 @@ impl Encoding {
|
|||
gradient: DrawRadialGradient,
|
||||
color_stops: impl Iterator<Item = ColorStop>,
|
||||
alpha: f32,
|
||||
_extend: Extend,
|
||||
extend: Extend,
|
||||
) {
|
||||
self.add_ramp(color_stops, alpha);
|
||||
// Match Skia's epsilon for radii comparison
|
||||
const SKIA_EPSILON: f32 = 1.0 / (1 << 12) as f32;
|
||||
if gradient.p0 == gradient.p1 && (gradient.r0 - gradient.r1).abs() < SKIA_EPSILON {
|
||||
self.encode_color(DrawColor::new(Color::TRANSPARENT));
|
||||
}
|
||||
match self.add_ramp(color_stops, alpha, extend) {
|
||||
RampStops::Empty => self.encode_color(DrawColor::new(Color::TRANSPARENT)),
|
||||
RampStops::One(color) => self.encode_color(DrawColor::new(color)),
|
||||
_ => {
|
||||
self.draw_tags.push(DrawTag::RADIAL_GRADIENT);
|
||||
self.draw_data
|
||||
.extend_from_slice(bytemuck::bytes_of(&gradient));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes an image brush.
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -347,7 +364,12 @@ impl Encoding {
|
|||
}
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
fn add_ramp(&mut self, color_stops: impl Iterator<Item = ColorStop>, alpha: f32) {
|
||||
fn add_ramp(
|
||||
&mut self,
|
||||
color_stops: impl Iterator<Item = ColorStop>,
|
||||
alpha: f32,
|
||||
extend: Extend,
|
||||
) -> RampStops {
|
||||
let offset = self.draw_data.len();
|
||||
let stops_start = self.resources.color_stops.len();
|
||||
if alpha != 1.0 {
|
||||
|
@ -357,12 +379,31 @@ impl Encoding {
|
|||
} else {
|
||||
self.resources.color_stops.extend(color_stops);
|
||||
}
|
||||
let stops_end = self.resources.color_stops.len();
|
||||
match stops_end - stops_start {
|
||||
0 => RampStops::Empty,
|
||||
1 => RampStops::One(self.resources.color_stops.pop().unwrap().color),
|
||||
_ => {
|
||||
self.resources.patches.push(Patch::Ramp {
|
||||
draw_data_offset: offset,
|
||||
stops: stops_start..self.resources.color_stops.len(),
|
||||
stops: stops_start..stops_end,
|
||||
extend,
|
||||
});
|
||||
RampStops::Many
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result for adding a sequence of color stops.
|
||||
enum RampStops {
|
||||
/// Color stop sequence was empty.
|
||||
Empty,
|
||||
/// Contained a single color stop.
|
||||
One(Color),
|
||||
/// More than one color stop.
|
||||
Many,
|
||||
}
|
||||
|
||||
/// Encoded data for late bound resources.
|
||||
#[cfg(feature = "full")]
|
||||
|
|
|
@ -12,7 +12,7 @@ use {
|
|||
image_cache::{ImageCache, Images},
|
||||
ramp_cache::{RampCache, Ramps},
|
||||
},
|
||||
peniko::Image,
|
||||
peniko::{Extend, Image},
|
||||
std::ops::Range,
|
||||
};
|
||||
|
||||
|
@ -279,11 +279,13 @@ impl Resolver {
|
|||
ResolvedPatch::Ramp {
|
||||
draw_data_offset,
|
||||
ramp_id,
|
||||
extend,
|
||||
} => {
|
||||
if pos < *draw_data_offset {
|
||||
data.extend_from_slice(&encoding.draw_data[pos..*draw_data_offset]);
|
||||
}
|
||||
data.extend_from_slice(bytemuck::bytes_of(ramp_id));
|
||||
let index_mode = (ramp_id << 2) | *extend as u32;
|
||||
data.extend_from_slice(bytemuck::bytes_of(&index_mode));
|
||||
pos = *draw_data_offset + 4;
|
||||
}
|
||||
ResolvedPatch::GlyphRun { .. } => {}
|
||||
|
@ -399,11 +401,13 @@ impl Resolver {
|
|||
Patch::Ramp {
|
||||
draw_data_offset,
|
||||
stops,
|
||||
extend,
|
||||
} => {
|
||||
let ramp_id = self.ramp_cache.add(&resources.color_stops[stops.clone()]);
|
||||
self.patches.push(ResolvedPatch::Ramp {
|
||||
draw_data_offset: *draw_data_offset + sizes.draw_data,
|
||||
ramp_id,
|
||||
extend: *extend,
|
||||
});
|
||||
}
|
||||
Patch::GlyphRun { index } => {
|
||||
|
@ -532,6 +536,8 @@ pub enum Patch {
|
|||
draw_data_offset: usize,
|
||||
/// Range of the gradient stops in the resource set.
|
||||
stops: Range<usize>,
|
||||
/// Extend mode for the gradient.
|
||||
extend: Extend,
|
||||
},
|
||||
/// Glyph run resource.
|
||||
GlyphRun {
|
||||
|
@ -563,6 +569,8 @@ enum ResolvedPatch {
|
|||
draw_data_offset: usize,
|
||||
/// Resolved ramp index.
|
||||
ramp_id: u32,
|
||||
/// Extend mode for the gradient.
|
||||
extend: Extend,
|
||||
},
|
||||
GlyphRun {
|
||||
/// Index of the original glyph run in the encoding.
|
||||
|
|
|
@ -30,6 +30,8 @@ pub fn test_scenes() -> SceneSet {
|
|||
scene!(funky_paths),
|
||||
scene!(cardioid_and_friends),
|
||||
scene!(animated_text: animated),
|
||||
scene!(gradient_extend),
|
||||
scene!(two_point_radial),
|
||||
scene!(brush_transform: animated),
|
||||
scene!(blend_grid),
|
||||
scene!(conflation_artifacts),
|
||||
|
@ -249,6 +251,218 @@ fn brush_transform(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
|||
);
|
||||
}
|
||||
|
||||
fn gradient_extend(sb: &mut SceneBuilder, params: &mut SceneParams) {
|
||||
fn square(sb: &mut SceneBuilder, is_radial: bool, transform: Affine, extend: Extend) {
|
||||
let colors = [Color::RED, Color::rgb8(0, 255, 0), Color::BLUE];
|
||||
let width = 300f64;
|
||||
let height = 300f64;
|
||||
let gradient: Brush = if is_radial {
|
||||
let center = (width * 0.5, height * 0.5);
|
||||
let radius = (width * 0.25) as f32;
|
||||
Gradient::new_two_point_radial(center, radius * 0.25, center, radius)
|
||||
.with_stops(colors)
|
||||
.with_extend(extend)
|
||||
.into()
|
||||
} else {
|
||||
Gradient::new_linear((width * 0.35, height * 0.5), (width * 0.65, height * 0.5))
|
||||
.with_stops(colors)
|
||||
.with_extend(extend)
|
||||
.into()
|
||||
};
|
||||
sb.fill(
|
||||
Fill::NonZero,
|
||||
transform,
|
||||
&gradient,
|
||||
None,
|
||||
&Rect::new(0.0, 0.0, width, height),
|
||||
);
|
||||
}
|
||||
let extend_modes = [Extend::Pad, Extend::Repeat, Extend::Reflect];
|
||||
for x in 0..3 {
|
||||
let extend = extend_modes[x];
|
||||
for y in 0..2 {
|
||||
let is_radial = y & 1 != 0;
|
||||
let transform = Affine::translate((x as f64 * 350.0 + 50.0, y as f64 * 350.0 + 100.0));
|
||||
square(sb, is_radial, transform, extend);
|
||||
}
|
||||
}
|
||||
for (i, label) in ["Pad", "Repeat", "Reflect"].iter().enumerate() {
|
||||
let x = i as f64 * 350.0 + 50.0;
|
||||
params.text.add(
|
||||
sb,
|
||||
None,
|
||||
32.0,
|
||||
Some(&Color::WHITE.into()),
|
||||
Affine::translate((x, 70.0)),
|
||||
label,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn two_point_radial(sb: &mut SceneBuilder, _params: &mut SceneParams) {
|
||||
fn make(
|
||||
sb: &mut SceneBuilder,
|
||||
x0: f64,
|
||||
y0: f64,
|
||||
r0: f32,
|
||||
x1: f64,
|
||||
y1: f64,
|
||||
r1: f32,
|
||||
transform: Affine,
|
||||
extend: Extend,
|
||||
) {
|
||||
let colors = [Color::RED, Color::YELLOW, Color::rgb8(6, 85, 186)];
|
||||
let width = 400f64;
|
||||
let height = 200f64;
|
||||
let rect = Rect::new(0.0, 0.0, width, height);
|
||||
sb.fill(Fill::NonZero, transform, Color::WHITE, None, &rect);
|
||||
sb.fill(
|
||||
Fill::NonZero,
|
||||
transform,
|
||||
&Gradient::new_two_point_radial((x0, y0), r0, (x1, y1), r1)
|
||||
.with_stops(colors)
|
||||
.with_extend(extend),
|
||||
None,
|
||||
&Rect::new(0.0, 0.0, width, height),
|
||||
);
|
||||
let r0 = r0 as f64 - 1.0;
|
||||
let r1 = r1 as f64 - 1.0;
|
||||
let stroke_width = 1.0;
|
||||
sb.stroke(
|
||||
&Stroke::new(stroke_width),
|
||||
transform,
|
||||
Color::BLACK,
|
||||
None,
|
||||
&Ellipse::new((x0, y0), (r0, r0), 0.0),
|
||||
);
|
||||
sb.stroke(
|
||||
&Stroke::new(stroke_width),
|
||||
transform,
|
||||
Color::BLACK,
|
||||
None,
|
||||
&Ellipse::new((x1, y1), (r1, r1), 0.0),
|
||||
);
|
||||
}
|
||||
|
||||
// These demonstrate radial gradient patterns similar to the examples shown
|
||||
// at <https://learn.microsoft.com/en-us/typography/opentype/spec/colr#radial-gradients>
|
||||
|
||||
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let y = 100.0;
|
||||
let x0 = 140.0;
|
||||
let x1 = x0 + 140.0;
|
||||
let r0 = 20.0;
|
||||
let r1 = 50.0;
|
||||
make(
|
||||
sb,
|
||||
x0,
|
||||
y,
|
||||
r0,
|
||||
x1,
|
||||
y,
|
||||
r1,
|
||||
Affine::translate((i as f64 * 420.0 + 20.0, 20.0)),
|
||||
*mode,
|
||||
);
|
||||
}
|
||||
|
||||
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let y = 100.0;
|
||||
let x0 = 140.0;
|
||||
let x1 = x0 + 140.0;
|
||||
let r0 = 20.0;
|
||||
let r1 = 50.0;
|
||||
make(
|
||||
sb,
|
||||
x1,
|
||||
y,
|
||||
r1,
|
||||
x0,
|
||||
y,
|
||||
r0,
|
||||
Affine::translate((i as f64 * 420.0 + 20.0, 240.0)),
|
||||
*mode,
|
||||
);
|
||||
}
|
||||
|
||||
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let y = 100.0;
|
||||
let x0 = 140.0;
|
||||
let x1 = x0 + 140.0;
|
||||
let r0 = 50.0;
|
||||
let r1 = 50.0;
|
||||
make(
|
||||
sb,
|
||||
x0,
|
||||
y,
|
||||
r0,
|
||||
x1,
|
||||
y,
|
||||
r1,
|
||||
Affine::translate((i as f64 * 420.0 + 20.0, 460.0)),
|
||||
*mode,
|
||||
);
|
||||
}
|
||||
|
||||
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let x0 = 140.0;
|
||||
let y0 = 125.0;
|
||||
let r0 = 20.0;
|
||||
let x1 = 190.0;
|
||||
let y1 = 100.0;
|
||||
let r1 = 95.0;
|
||||
make(
|
||||
sb,
|
||||
x0,
|
||||
y0,
|
||||
r0,
|
||||
x1,
|
||||
y1,
|
||||
r1,
|
||||
Affine::translate((i as f64 * 420.0 + 20.0, 680.0)),
|
||||
*mode,
|
||||
);
|
||||
}
|
||||
|
||||
for (i, mode) in [Extend::Pad, Extend::Repeat, Extend::Reflect]
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let x0 = 140.0;
|
||||
let y0 = 125.0;
|
||||
let r0 = 20.0;
|
||||
let x1 = 190.0;
|
||||
let y1 = 100.0;
|
||||
let r1 = 96.0;
|
||||
// Shift p0 so the outer edges of both circles touch
|
||||
let p0 = Point::new(x1, y1)
|
||||
+ ((Point::new(x0, y0) - Point::new(x1, y1)).normalize() * (r1 - r0));
|
||||
make(
|
||||
sb,
|
||||
p0.x,
|
||||
p0.y,
|
||||
r0 as f32,
|
||||
x1,
|
||||
y1,
|
||||
r1 as f32,
|
||||
Affine::translate((i as f64 * 420.0 + 20.0, 900.0)),
|
||||
*mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn blend_grid(sb: &mut SceneBuilder, _: &mut SceneParams) {
|
||||
const BLEND_MODES: &[Mix] = &[
|
||||
Mix::Normal,
|
||||
|
|
|
@ -376,7 +376,7 @@ fn main(
|
|||
}
|
||||
}
|
||||
// DRAWTAG_FILL_RAD_GRADIENT
|
||||
case 0x2dcu: {
|
||||
case 0x29cu: {
|
||||
let linewidth = bitcast<f32>(info_bin_data[di]);
|
||||
if write_path(tile, linewidth) {
|
||||
let index = scene[dd];
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#import clip
|
||||
#import drawtag
|
||||
#import bbox
|
||||
#import transform
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> config: Config;
|
||||
|
@ -30,12 +31,6 @@ var<storage, read_write> clip_inp: array<ClipInp>;
|
|||
|
||||
let WG_SIZE = 256u;
|
||||
|
||||
// Possibly dedup?
|
||||
struct Transform {
|
||||
matrx: vec4<f32>,
|
||||
translate: vec2<f32>,
|
||||
}
|
||||
|
||||
fn read_transform(transform_base: u32, ix: u32) -> Transform {
|
||||
let base = transform_base + ix * 6u;
|
||||
let c0 = bitcast<f32>(scene[base]);
|
||||
|
@ -110,18 +105,16 @@ fn main(
|
|||
// let y1 = f32(bbox.y1);
|
||||
// let bbox_f = vec4(x0, y0, x1, y1);
|
||||
let fill_mode = u32(bbox.linewidth >= 0.0);
|
||||
var matrx: vec4<f32>;
|
||||
var translate: vec2<f32>;
|
||||
var transform = Transform();
|
||||
var linewidth = bbox.linewidth;
|
||||
if linewidth >= 0.0 || tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT ||
|
||||
tag_word == DRAWTAG_FILL_IMAGE
|
||||
{
|
||||
let transform = read_transform(config.transform_base, bbox.trans_ix);
|
||||
matrx = transform.matrx;
|
||||
translate = transform.translate;
|
||||
transform = read_transform(config.transform_base, bbox.trans_ix);
|
||||
}
|
||||
if linewidth >= 0.0 {
|
||||
// Note: doesn't deal with anisotropic case
|
||||
let matrx = transform.matrx;
|
||||
linewidth *= sqrt(abs(matrx.x * matrx.w - matrx.y * matrx.z));
|
||||
}
|
||||
switch tag_word {
|
||||
|
@ -134,8 +127,8 @@ fn main(
|
|||
info[di] = bitcast<u32>(linewidth);
|
||||
var p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
|
||||
var p1 = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u]));
|
||||
p0 = matrx.xy * p0.x + matrx.zw * p0.y + translate;
|
||||
p1 = matrx.xy * p1.x + matrx.zw * p1.y + translate;
|
||||
p0 = transform_apply(transform, p0);
|
||||
p1 = transform_apply(transform, p1);
|
||||
let dxy = p1 - p0;
|
||||
let scale = 1.0 / dot(dxy, dxy);
|
||||
let line_xy = dxy * scale;
|
||||
|
@ -145,44 +138,99 @@ fn main(
|
|||
info[di + 3u] = bitcast<u32>(line_c);
|
||||
}
|
||||
// DRAWTAG_FILL_RAD_GRADIENT
|
||||
case 0x2dcu: {
|
||||
case 0x29cu: {
|
||||
// Two-point conical gradient implementation based
|
||||
// on the algorithm at <https://skia.org/docs/dev/design/conical/>
|
||||
// This epsilon matches what Skia uses
|
||||
let GRADIENT_EPSILON = 1.0 / f32(1 << 12u);
|
||||
info[di] = bitcast<u32>(linewidth);
|
||||
var p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
|
||||
var p1 = bitcast<vec2<f32>>(vec2(scene[dd + 3u], scene[dd + 4u]));
|
||||
let r0 = bitcast<f32>(scene[dd + 5u]);
|
||||
let r1 = bitcast<f32>(scene[dd + 6u]);
|
||||
let inv_det = 1.0 / (matrx.x * matrx.w - matrx.y * matrx.z);
|
||||
let inv_mat = inv_det * vec4(matrx.w, -matrx.y, -matrx.z, matrx.x);
|
||||
let inv_tr = mat2x2(inv_mat.xy, inv_mat.zw) * -translate - p0;
|
||||
let center1 = p1 - p0;
|
||||
let rr = r1 / (r1 - r0);
|
||||
let ra_inv = rr / (r1 * r1 - dot(center1, center1));
|
||||
let c1 = center1 * ra_inv;
|
||||
let ra = rr * ra_inv;
|
||||
let roff = rr - 1.0;
|
||||
info[di + 1u] = bitcast<u32>(inv_mat.x);
|
||||
info[di + 2u] = bitcast<u32>(inv_mat.y);
|
||||
info[di + 3u] = bitcast<u32>(inv_mat.z);
|
||||
info[di + 4u] = bitcast<u32>(inv_mat.w);
|
||||
info[di + 5u] = bitcast<u32>(inv_tr.x);
|
||||
info[di + 6u] = bitcast<u32>(inv_tr.y);
|
||||
info[di + 7u] = bitcast<u32>(c1.x);
|
||||
info[di + 8u] = bitcast<u32>(c1.y);
|
||||
info[di + 9u] = bitcast<u32>(ra);
|
||||
info[di + 10u] = bitcast<u32>(roff);
|
||||
var r0 = bitcast<f32>(scene[dd + 5u]);
|
||||
var r1 = bitcast<f32>(scene[dd + 6u]);
|
||||
let user_to_gradient = transform_inverse(transform);
|
||||
// Output variables
|
||||
var xform = Transform();
|
||||
var focal_x = 0.0;
|
||||
var radius = 0.0;
|
||||
var kind = 0u;
|
||||
var flags = 0u;
|
||||
if abs(r0 - r1) <= GRADIENT_EPSILON {
|
||||
// When the radii are the same, emit a strip gradient
|
||||
kind = RAD_GRAD_KIND_STRIP;
|
||||
let scaled = r0 / distance(p0, p1);
|
||||
xform = transform_mul(
|
||||
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 all(p0 == p1) {
|
||||
kind = RAD_GRAD_KIND_CIRCULAR;
|
||||
// Nudge p0 a bit to avoid denormals.
|
||||
p0 += GRADIENT_EPSILON;
|
||||
}
|
||||
if r1 == 0.0 {
|
||||
// If r1 == 0.0, swap the points and radii
|
||||
flags |= RAD_GRAD_SWAPPED;
|
||||
let tmp_p = p0;
|
||||
p0 = p1;
|
||||
p1 = tmp_p;
|
||||
let tmp_r = r0;
|
||||
r0 = r1;
|
||||
r1 = tmp_r;
|
||||
}
|
||||
focal_x = r0 / (r0 - r1);
|
||||
let cf = (1.0 - focal_x) * p0 + focal_x * p1;
|
||||
radius = r1 / (distance(cf, p1));
|
||||
let user_to_unit_line = transform_mul(
|
||||
two_point_to_unit_line(cf, p1),
|
||||
user_to_gradient
|
||||
);
|
||||
var user_to_scaled = user_to_unit_line;
|
||||
// When r == 1.0, focal point is on circle
|
||||
if abs(radius - 1.0) <= GRADIENT_EPSILON {
|
||||
kind = RAD_GRAD_KIND_FOCAL_ON_CIRCLE;
|
||||
let scale = 0.5 * abs(1.0 - focal_x);
|
||||
user_to_scaled = transform_mul(
|
||||
Transform(vec4(scale, 0.0, 0.0, scale), vec2(0.0)),
|
||||
user_to_unit_line
|
||||
);
|
||||
} else {
|
||||
let a = radius * radius - 1.0;
|
||||
let scale_ratio = abs(1.0 - focal_x) / a;
|
||||
let scale_x = radius * scale_ratio;
|
||||
let scale_y = sqrt(abs(a)) * scale_ratio;
|
||||
user_to_scaled = transform_mul(
|
||||
Transform(vec4(scale_x, 0.0, 0.0, scale_y), vec2(0.0)),
|
||||
user_to_unit_line
|
||||
);
|
||||
}
|
||||
xform = user_to_scaled;
|
||||
}
|
||||
info[di + 1u] = bitcast<u32>(xform.matrx.x);
|
||||
info[di + 2u] = bitcast<u32>(xform.matrx.y);
|
||||
info[di + 3u] = bitcast<u32>(xform.matrx.z);
|
||||
info[di + 4u] = bitcast<u32>(xform.matrx.w);
|
||||
info[di + 5u] = bitcast<u32>(xform.translate.x);
|
||||
info[di + 6u] = bitcast<u32>(xform.translate.y);
|
||||
info[di + 7u] = bitcast<u32>(focal_x);
|
||||
info[di + 8u] = bitcast<u32>(radius);
|
||||
info[di + 9u] = bitcast<u32>((flags << 3u) | kind);
|
||||
}
|
||||
// DRAWTAG_FILL_IMAGE
|
||||
case 0x248u: {
|
||||
info[di] = bitcast<u32>(linewidth);
|
||||
let inv_det = 1.0 / (matrx.x * matrx.w - matrx.y * matrx.z);
|
||||
let inv_mat = inv_det * vec4(matrx.w, -matrx.y, -matrx.z, matrx.x);
|
||||
let inv_tr = mat2x2(inv_mat.xy, inv_mat.zw) * -translate;
|
||||
info[di + 1u] = bitcast<u32>(inv_mat.x);
|
||||
info[di + 2u] = bitcast<u32>(inv_mat.y);
|
||||
info[di + 3u] = bitcast<u32>(inv_mat.z);
|
||||
info[di + 4u] = bitcast<u32>(inv_mat.w);
|
||||
info[di + 5u] = bitcast<u32>(inv_tr.x);
|
||||
info[di + 6u] = bitcast<u32>(inv_tr.y);
|
||||
let inv = transform_inverse(transform);
|
||||
info[di + 1u] = bitcast<u32>(inv.matrx.x);
|
||||
info[di + 2u] = bitcast<u32>(inv.matrx.y);
|
||||
info[di + 3u] = bitcast<u32>(inv.matrx.z);
|
||||
info[di + 4u] = bitcast<u32>(inv.matrx.w);
|
||||
info[di + 5u] = bitcast<u32>(inv.translate.x);
|
||||
info[di + 6u] = bitcast<u32>(inv.translate.y);
|
||||
info[di + 7u] = scene[dd];
|
||||
info[di + 8u] = scene[dd + 1u];
|
||||
}
|
||||
|
@ -197,3 +245,17 @@ fn main(
|
|||
clip_inp[m.clip_ix] = ClipInp(ix, i32(path_ix));
|
||||
}
|
||||
}
|
||||
|
||||
fn two_point_to_unit_line(p0: vec2<f32>, p1: vec2<f32>) -> Transform {
|
||||
let tmp1 = from_poly2(p0, p1);
|
||||
let inv = transform_inverse(tmp1);
|
||||
let tmp2 = from_poly2(vec2(0.0), vec2(1.0, 0.0));
|
||||
return transform_mul(tmp2, inv);
|
||||
}
|
||||
|
||||
fn from_poly2(p0: vec2<f32>, p1: vec2<f32>) -> Transform {
|
||||
return Transform(
|
||||
vec4(p1.y - p0.y, p0.x - p1.x, p1.x - p0.x, p1.y - p0.y),
|
||||
vec2(p0.x, p0.y)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -61,16 +61,20 @@ fn read_color(cmd_ix: u32) -> CmdColor {
|
|||
}
|
||||
|
||||
fn read_lin_grad(cmd_ix: u32) -> CmdLinGrad {
|
||||
let index = ptcl[cmd_ix + 1u];
|
||||
let index_mode = ptcl[cmd_ix + 1u];
|
||||
let index = index_mode >> 2u;
|
||||
let extend_mode = index_mode & 0x3u;
|
||||
let info_offset = ptcl[cmd_ix + 2u];
|
||||
let line_x = bitcast<f32>(info[info_offset]);
|
||||
let line_y = bitcast<f32>(info[info_offset + 1u]);
|
||||
let line_c = bitcast<f32>(info[info_offset + 2u]);
|
||||
return CmdLinGrad(index, line_x, line_y, line_c);
|
||||
return CmdLinGrad(index, extend_mode, line_x, line_y, line_c);
|
||||
}
|
||||
|
||||
fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad {
|
||||
let index = ptcl[cmd_ix + 1u];
|
||||
let index_mode = ptcl[cmd_ix + 1u];
|
||||
let index = index_mode >> 2u;
|
||||
let extend_mode = index_mode & 0x3u;
|
||||
let info_offset = ptcl[cmd_ix + 2u];
|
||||
let m0 = bitcast<f32>(info[info_offset]);
|
||||
let m1 = bitcast<f32>(info[info_offset + 1u]);
|
||||
|
@ -78,10 +82,12 @@ fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad {
|
|||
let m3 = bitcast<f32>(info[info_offset + 3u]);
|
||||
let matrx = vec4(m0, m1, m2, m3);
|
||||
let xlat = vec2(bitcast<f32>(info[info_offset + 4u]), bitcast<f32>(info[info_offset + 5u]));
|
||||
let c1 = vec2(bitcast<f32>(info[info_offset + 6u]), bitcast<f32>(info[info_offset + 7u]));
|
||||
let ra = bitcast<f32>(info[info_offset + 8u]);
|
||||
let roff = bitcast<f32>(info[info_offset + 9u]);
|
||||
return CmdRadGrad(index, matrx, xlat, c1, ra, roff);
|
||||
let focal_x = bitcast<f32>(info[info_offset + 6u]);
|
||||
let radius = bitcast<f32>(info[info_offset + 7u]);
|
||||
let flags_kind = info[info_offset + 8u];
|
||||
let flags = flags_kind >> 3u;
|
||||
let kind = flags_kind & 0x7u;
|
||||
return CmdRadGrad(index, extend_mode, matrx, xlat, focal_x, radius, kind, flags);
|
||||
}
|
||||
|
||||
fn read_image(cmd_ix: u32) -> CmdImage {
|
||||
|
@ -108,6 +114,26 @@ fn read_end_clip(cmd_ix: u32) -> CmdEndClip {
|
|||
return CmdEndClip(blend, alpha);
|
||||
}
|
||||
|
||||
fn extend_mode(t: f32, mode: u32) -> f32 {
|
||||
let EXTEND_PAD = 0u;
|
||||
let EXTEND_REPEAT = 1u;
|
||||
let EXTEND_REFLECT = 2u;
|
||||
switch mode {
|
||||
// EXTEND_PAD
|
||||
case 0u: {
|
||||
return clamp(t, 0.0, 1.0);
|
||||
}
|
||||
// EXTEND_REPEAT
|
||||
case 1u: {
|
||||
return fract(t);
|
||||
}
|
||||
// EXTEND_REFLECT
|
||||
default: {
|
||||
return abs(t - 2.0 * round(0.5 * t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@group(0) @binding(3)
|
||||
|
@ -262,7 +288,7 @@ fn main(
|
|||
let d = lin.line_x * xy.x + lin.line_y * xy.y + lin.line_c;
|
||||
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
|
||||
let my_d = d + lin.line_x * f32(i);
|
||||
let x = i32(round(clamp(my_d, 0.0, 1.0) * f32(GRADIENT_WIDTH - 1)));
|
||||
let x = i32(round(extend_mode(my_d, lin.extend_mode) * f32(GRADIENT_WIDTH - 1)));
|
||||
let fg_rgba = textureLoad(gradients, vec2(x, i32(lin.index)), 0);
|
||||
let fg_i = fg_rgba * area[i];
|
||||
rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i;
|
||||
|
@ -272,18 +298,47 @@ fn main(
|
|||
// CMD_RAD_GRAD
|
||||
case 7u: {
|
||||
let rad = read_rad_grad(cmd_ix);
|
||||
let focal_x = rad.focal_x;
|
||||
let radius = rad.radius;
|
||||
let is_strip = rad.kind == RAD_GRAD_KIND_STRIP;
|
||||
let is_circular = rad.kind == RAD_GRAD_KIND_CIRCULAR;
|
||||
let is_focal_on_circle = rad.kind == RAD_GRAD_KIND_FOCAL_ON_CIRCLE;
|
||||
let is_swapped = (rad.flags & RAD_GRAD_SWAPPED) != 0u;
|
||||
let r1_recip = select(1.0 / radius, 0.0, is_circular);
|
||||
let less_scale = select(1.0, -1.0, is_swapped || (1.0 - focal_x) < 0.0);
|
||||
let t_sign = sign(1.0 - focal_x);
|
||||
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
|
||||
let my_xy = vec2(xy.x + f32(i), xy.y);
|
||||
// TODO: can hoist y, but for now stick to the GLSL version
|
||||
let xy_xformed = rad.matrx.xy * my_xy.x + rad.matrx.zw * my_xy.y + rad.xlat;
|
||||
let ba = dot(xy_xformed, rad.c1);
|
||||
let ca = rad.ra * dot(xy_xformed, xy_xformed);
|
||||
let t = sqrt(ba * ba + ca) - ba - rad.roff;
|
||||
let x = i32(round(clamp(t, 0.0, 1.0) * f32(GRADIENT_WIDTH - 1)));
|
||||
let local_xy = rad.matrx.xy * my_xy.x + rad.matrx.zw * my_xy.y + rad.xlat;
|
||||
let x = local_xy.x;
|
||||
let y = local_xy.y;
|
||||
let xx = x * x;
|
||||
let yy = y * y;
|
||||
var t = 0.0;
|
||||
var is_valid = true;
|
||||
if is_strip {
|
||||
let a = radius - yy;
|
||||
t = sqrt(a) + x;
|
||||
is_valid = a >= 0.0;
|
||||
} else if is_focal_on_circle {
|
||||
t = (xx + yy) / x;
|
||||
is_valid = t >= 0.0 && x != 0.0;
|
||||
} else if radius > 1.0 {
|
||||
t = sqrt(xx + yy) - x * r1_recip;
|
||||
} else { // radius < 1.0
|
||||
let a = xx - yy;
|
||||
t = less_scale * sqrt(a) - x * r1_recip;
|
||||
is_valid = a >= 0.0 && t >= 0.0;
|
||||
}
|
||||
if is_valid {
|
||||
t = extend_mode(focal_x + t_sign * t, rad.extend_mode);
|
||||
t = select(t, 1.0 - t, is_swapped);
|
||||
let x = i32(round(t * f32(GRADIENT_WIDTH - 1)));
|
||||
let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0);
|
||||
let fg_i = fg_rgba * area[i];
|
||||
rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i;
|
||||
}
|
||||
}
|
||||
cmd_ix += 3u;
|
||||
}
|
||||
// CMD_IMAGE
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#import config
|
||||
#import pathtag
|
||||
#import cubic
|
||||
#import transform
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> config: Config;
|
||||
|
@ -36,7 +37,6 @@ struct AtomicPathBbox {
|
|||
@group(0) @binding(3)
|
||||
var<storage, read_write> path_bboxes: array<AtomicPathBbox>;
|
||||
|
||||
|
||||
@group(0) @binding(4)
|
||||
var<storage, read_write> cubics: array<Cubic>;
|
||||
|
||||
|
@ -85,11 +85,6 @@ fn read_i16_point(ix: u32) -> vec2<f32> {
|
|||
return vec2(x, y);
|
||||
}
|
||||
|
||||
struct Transform {
|
||||
matrx: vec4<f32>,
|
||||
translate: vec2<f32>,
|
||||
}
|
||||
|
||||
fn read_transform(transform_base: u32, ix: u32) -> Transform {
|
||||
let base = transform_base + ix * 6u;
|
||||
let c0 = bitcast<f32>(scene[base]);
|
||||
|
@ -103,10 +98,6 @@ fn read_transform(transform_base: u32, ix: u32) -> Transform {
|
|||
return Transform(matrx, translate);
|
||||
}
|
||||
|
||||
fn transform_apply(transform: Transform, p: vec2<f32>) -> vec2<f32> {
|
||||
return transform.matrx.xy * p.x + transform.matrx.zw * p.y + transform.translate;
|
||||
}
|
||||
|
||||
fn round_down(x: f32) -> i32 {
|
||||
return i32(floor(x));
|
||||
}
|
||||
|
|
|
@ -306,7 +306,8 @@ fn blend_compose(
|
|||
let as_fa = as_ * fa;
|
||||
let ab_fb = ab * fb;
|
||||
let co = as_fa * cs + ab_fb * cb;
|
||||
return vec4(co, as_fa + ab_fb);
|
||||
// Modes like COMPOSE_PLUS can generate alpha > 1.0, so clamp.
|
||||
return vec4(co, min(as_fa + ab_fb, 1.0));
|
||||
}
|
||||
|
||||
// Apply color mixing and composition. Both input and output colors are
|
||||
|
|
|
@ -49,3 +49,16 @@ let N_TILE_Y = 16u;
|
|||
let N_TILE = 256u;
|
||||
|
||||
let BLEND_STACK_SPLIT = 4u;
|
||||
|
||||
// The following are computed in draw_leaf from the generic gradient parameters
|
||||
// encoded in the scene, and stored in the gradient's info struct, for
|
||||
// consumption during fine rasterization.
|
||||
|
||||
// Radial gradient kinds
|
||||
let RAD_GRAD_KIND_CIRCULAR = 1u;
|
||||
let RAD_GRAD_KIND_STRIP = 2u;
|
||||
let RAD_GRAD_KIND_FOCAL_ON_CIRCLE = 3u;
|
||||
let RAD_GRAD_KIND_CONE = 4u;
|
||||
|
||||
// Radial gradient flags
|
||||
let RAD_GRAD_SWAPPED = 1u;
|
||||
|
|
|
@ -18,7 +18,7 @@ struct DrawMonoid {
|
|||
let DRAWTAG_NOP = 0u;
|
||||
let DRAWTAG_FILL_COLOR = 0x44u;
|
||||
let DRAWTAG_FILL_LIN_GRADIENT = 0x114u;
|
||||
let DRAWTAG_FILL_RAD_GRADIENT = 0x2dcu;
|
||||
let DRAWTAG_FILL_RAD_GRADIENT = 0x29cu;
|
||||
let DRAWTAG_FILL_IMAGE = 0x248u;
|
||||
let DRAWTAG_BEGIN_CLIP = 0x9u;
|
||||
let DRAWTAG_END_CLIP = 0x21u;
|
||||
|
|
|
@ -44,6 +44,7 @@ struct CmdColor {
|
|||
|
||||
struct CmdLinGrad {
|
||||
index: u32,
|
||||
extend_mode: u32,
|
||||
line_x: f32,
|
||||
line_y: f32,
|
||||
line_c: f32,
|
||||
|
@ -51,11 +52,13 @@ struct CmdLinGrad {
|
|||
|
||||
struct CmdRadGrad {
|
||||
index: u32,
|
||||
extend_mode: u32,
|
||||
matrx: vec4<f32>,
|
||||
xlat: vec2<f32>,
|
||||
c1: vec2<f32>,
|
||||
ra: f32,
|
||||
roff: f32,
|
||||
focal_x: f32,
|
||||
radius: f32,
|
||||
kind: u32,
|
||||
flags: u32,
|
||||
}
|
||||
|
||||
struct CmdImage {
|
||||
|
|
26
shader/shared/transform.wgsl
Normal file
26
shader/shared/transform.wgsl
Normal file
|
@ -0,0 +1,26 @@
|
|||
// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
|
||||
|
||||
// Helpers for working with transforms.
|
||||
|
||||
struct Transform {
|
||||
matrx: vec4<f32>,
|
||||
translate: vec2<f32>,
|
||||
}
|
||||
|
||||
fn transform_apply(transform: Transform, p: vec2<f32>) -> vec2<f32> {
|
||||
return transform.matrx.xy * p.x + transform.matrx.zw * p.y + transform.translate;
|
||||
}
|
||||
|
||||
fn transform_inverse(transform: Transform) -> Transform {
|
||||
let inv_det = 1.0 / (transform.matrx.x * transform.matrx.w - transform.matrx.y * transform.matrx.z);
|
||||
let inv_mat = inv_det * vec4(transform.matrx.w, -transform.matrx.y, -transform.matrx.z, transform.matrx.x);
|
||||
let inv_tr = mat2x2(inv_mat.xy, inv_mat.zw) * -transform.translate;
|
||||
return Transform(inv_mat, inv_tr);
|
||||
}
|
||||
|
||||
fn transform_mul(a: Transform, b: Transform) -> Transform {
|
||||
return Transform(
|
||||
a.matrx.xyxy * b.matrx.xxzz + a.matrx.zwzw * b.matrx.yyww,
|
||||
a.matrx.xy * b.translate.x + a.matrx.zw * b.translate.y + a.translate
|
||||
);
|
||||
}
|
|
@ -318,4 +318,5 @@ const SHARED_SHADERS: &[(&str, &str)] = &[
|
|||
shared_shader!("ptcl"),
|
||||
shared_shader!("segment"),
|
||||
shared_shader!("tile"),
|
||||
shared_shader!("transform"),
|
||||
];
|
||||
|
|
Loading…
Add table
Reference in a new issue