Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sweep gradients #435

Merged
merged 6 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions crates/encoding/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ impl DrawTag {
/// Radial gradient fill.
pub const RADIAL_GRADIENT: Self = Self(0x29c);

/// Sweep gradient fill.
pub const SWEEP_GRADIENT: Self = Self(0x254);

/// Image fill.
pub const IMAGE: Self = Self(0x248);

Expand Down Expand Up @@ -99,6 +102,20 @@ pub struct DrawRadialGradient {
pub r1: f32,
}

/// Draw data for a sweep gradient.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawSweepGradient {
/// Ramp index.
pub index: u32,
/// Center point.
pub p0: [f32; 2],
/// Normalized start angle.
pub t0: f32,
/// Normalized end angle.
pub t1: f32,
}

/// Draw data for an image.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
Expand Down
50 changes: 47 additions & 3 deletions crates/encoding/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use peniko::{

#[cfg(feature = "full")]
use {
super::{DrawImage, DrawLinearGradient, DrawRadialGradient, Glyph, GlyphRun, Patch},
super::{
DrawImage, DrawLinearGradient, DrawRadialGradient, DrawSweepGradient, Glyph, GlyphRun,
Patch,
},
peniko::{ColorStop, Extend, GradientKind, Image},
skrifa::instance::NormalizedCoord,
};
Expand Down Expand Up @@ -298,8 +301,23 @@ impl Encoding {
gradient.extend,
);
}
GradientKind::Sweep { .. } => {
todo!("sweep gradients aren't supported yet!")
GradientKind::Sweep {
center,
start_angle,
end_angle,
} => {
use core::f32::consts::TAU;
self.encode_sweep_gradient(
DrawSweepGradient {
index: 0,
p0: point_to_f32(center),
t0: start_angle / TAU,
t1: end_angle / TAU,
},
gradient.stops.iter().copied(),
alpha,
gradient.extend,
);
}
},
#[cfg(feature = "full")]
Expand Down Expand Up @@ -351,6 +369,7 @@ impl Encoding {
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));
return;
}
match self.add_ramp(color_stops, alpha, extend) {
RampStops::Empty => self.encode_color(DrawColor::new(Color::TRANSPARENT)),
Expand All @@ -363,6 +382,31 @@ impl Encoding {
}
}

/// Encodes a radial gradient brush.
#[cfg(feature = "full")]
pub fn encode_sweep_gradient(
&mut self,
gradient: DrawSweepGradient,
color_stops: impl Iterator<Item = ColorStop>,
alpha: f32,
extend: Extend,
) {
const SKIA_DEGENERATE_THRESHOLD: f32 = 1.0 / (1 << 15) as f32;
if (gradient.t0 - gradient.t1).abs() < SKIA_DEGENERATE_THRESHOLD {
self.encode_color(DrawColor::new(Color::TRANSPARENT));
return;
}
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::SWEEP_GRADIENT);
self.draw_data
.extend_from_slice(bytemuck::bytes_of(&gradient));
}
}
}

/// Encodes an image brush.
#[cfg(feature = "full")]
pub fn encode_image(&mut self, image: &Image, _alpha: f32) {
Expand Down
2 changes: 1 addition & 1 deletion crates/encoding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub use config::{
};
pub use draw::{
DrawBbox, DrawBeginClip, DrawColor, DrawImage, DrawLinearGradient, DrawMonoid,
DrawRadialGradient, DrawTag, DRAW_INFO_FLAGS_FILL_RULE_BIT,
DrawRadialGradient, DrawSweepGradient, DrawTag, DRAW_INFO_FLAGS_FILL_RULE_BIT,
};
pub use encoding::{Encoding, StreamOffsets};
pub use mask::{make_mask_lut, make_mask_lut_16};
Expand Down
51 changes: 35 additions & 16 deletions examples/scenes/src/test_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -754,22 +754,38 @@ fn brush_transform(scene: &mut Scene, params: &mut SceneParams) {
}

fn gradient_extend(scene: &mut Scene, params: &mut SceneParams) {
fn square(scene: &mut Scene, is_radial: bool, transform: Affine, extend: Extend) {
enum Kind {
Linear,
Radial,
Sweep,
}
fn square(scene: &mut Scene, kind: Kind, 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()
let gradient: Brush = match kind {
Kind::Linear => {
Gradient::new_linear((width * 0.35, height * 0.5), (width * 0.65, height * 0.5))
.with_stops(colors)
.with_extend(extend)
.into()
}
Kind::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()
}
Kind::Sweep => Gradient::new_sweep(
(width * 0.5, height * 0.5),
30f32.to_radians(),
150f32.to_radians(),
)
.with_stops(colors)
.with_extend(extend)
.into(),
};
scene.fill(
Fill::NonZero,
Expand All @@ -781,10 +797,12 @@ fn gradient_extend(scene: &mut Scene, params: &mut SceneParams) {
}
let extend_modes = [Extend::Pad, Extend::Repeat, Extend::Reflect];
for (x, extend) in extend_modes.iter().enumerate() {
for y in 0..2 {
let is_radial = y & 1 != 0;
for (y, kind) in [Kind::Linear, Kind::Radial, Kind::Sweep]
.into_iter()
.enumerate()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These now go off-screen in the default resolution. I think the resolution field of params can be set to counteract this (see #453)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't aware this existed! Added to this scene and made sure it scales properly.

{
let transform = Affine::translate((x as f64 * 350.0 + 50.0, y as f64 * 350.0 + 100.0));
square(scene, is_radial, transform, *extend);
square(scene, kind, transform, *extend);
}
}
for (i, label) in ["Pad", "Repeat", "Reflect"].iter().enumerate() {
Expand All @@ -798,6 +816,7 @@ fn gradient_extend(scene: &mut Scene, params: &mut SceneParams) {
label,
);
}
params.resolution = Some((1200.0, 1200.0).into());
}

#[allow(clippy::too_many_arguments)]
Expand Down
7 changes: 7 additions & 0 deletions shader/coarse.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@ fn main(
let info_offset = di + 1u;
write_grad(CMD_RAD_GRAD, index, info_offset);
}
// DRAWTAG_FILL_SWEEP_GRADIENT
case 0x254u: {
write_path(tile, tile_ix, draw_flags);
let index = scene[dd];
let info_offset = di + 1u;
write_grad(CMD_SWEEP_GRAD, index, info_offset);
}
// DRAWTAG_FILL_IMAGE
case 0x248u: {
write_path(tile, tile_ix, draw_flags);
Expand Down
21 changes: 18 additions & 3 deletions shader/draw_leaf.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ fn main(
let dd = config.drawdata_base + m.scene_offset;
let di = m.info_offset;
if tag_word == DRAWTAG_FILL_COLOR || tag_word == DRAWTAG_FILL_LIN_GRADIENT ||
tag_word == DRAWTAG_FILL_RAD_GRADIENT || tag_word == DRAWTAG_FILL_IMAGE ||
tag_word == DRAWTAG_BEGIN_CLIP
tag_word == DRAWTAG_FILL_RAD_GRADIENT || tag_word == DRAWTAG_FILL_SWEEP_GRADIENT ||
tag_word == DRAWTAG_FILL_IMAGE || tag_word == DRAWTAG_BEGIN_CLIP
{
let bbox = path_bbox[m.path_ix];
// TODO: bbox is mostly yagni here, sort that out. Maybe clips?
Expand All @@ -112,7 +112,7 @@ fn main(
var transform = Transform();
let draw_flags = bbox.draw_flags;
if tag_word == DRAWTAG_FILL_LIN_GRADIENT || tag_word == DRAWTAG_FILL_RAD_GRADIENT ||
tag_word == DRAWTAG_FILL_IMAGE
tag_word == DRAWTAG_FILL_SWEEP_GRADIENT || tag_word == DRAWTAG_FILL_IMAGE
{
transform = read_transform(config.transform_base, bbox.trans_ix);
}
Expand Down Expand Up @@ -220,6 +220,21 @@ fn main(
info[di + 8u] = bitcast<u32>(radius);
info[di + 9u] = bitcast<u32>((flags << 3u) | kind);
}
// DRAWTAG_FILL_SWEEP_GRADIENT
case 0x254u: {
info[di] = draw_flags;
let p0 = bitcast<vec2<f32>>(vec2(scene[dd + 1u], scene[dd + 2u]));
let xform = transform_mul(transform, Transform(vec4(1.0, 0.0, 0.0, 1.0), p0));
let inv = transform_inverse(xform);
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 + 3u];
info[di + 8u] = scene[dd + 4u];
}
// DRAWTAG_FILL_IMAGE
case 0x248u: {
info[di] = draw_flags;
Expand Down
58 changes: 54 additions & 4 deletions shader/fine.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,22 @@ fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad {
return CmdRadGrad(index, extend_mode, matrx, xlat, focal_x, radius, kind, flags);
}

fn read_sweep_grad(cmd_ix: u32) -> CmdSweepGrad {
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]);
let m2 = bitcast<f32>(info[info_offset + 2u]);
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 t0 = bitcast<f32>(info[info_offset + 6u]);
let t1 = bitcast<f32>(info[info_offset + 7u]);
return CmdSweepGrad(index, extend_mode, matrx, xlat, t0, t1);
}

fn read_image(cmd_ix: u32) -> CmdImage {
let info_offset = ptcl[cmd_ix + 1u];
let m0 = bitcast<f32>(info[info_offset]);
Expand Down Expand Up @@ -971,8 +987,42 @@ fn main(
}
cmd_ix += 3u;
}
// CMD_IMAGE
// CMD_SWEEP_GRAD
case 8u: {
let sweep = read_sweep_grad(cmd_ix);
let scale = 1.0 / (sweep.t1 - sweep.t0);
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
let my_xy = vec2(xy.x + f32(i), xy.y);
let local_xy = sweep.matrx.xy * my_xy.x + sweep.matrx.zw * my_xy.y + sweep.xlat;
let x = local_xy.x;
let y = local_xy.y;
// xy_to_unit_angle from Skia:
// See <https://github.com/google/skia/blob/30bba741989865c157c7a997a0caebe94921276b/src/opts/SkRasterPipeline_opts.h#L5859>
let xabs = abs(x);
let yabs = abs(y);
let slope = min(xabs, yabs) / max(xabs, yabs);
let s = slope * slope;
// again, from Skia:
// Use a 7th degree polynomial to approximate atan.
// This was generated using sollya.gforge.inria.fr.
// A float optimized polynomial was generated using the following command.
// P1 = fpminimax((1/(2*Pi))*atan(x),[|1,3,5,7|],[|24...|],[2^(-40),1],relative);
var phi = slope * (0.15912117063999176025390625f + s * (-5.185396969318389892578125e-2f + s * (2.476101927459239959716796875e-2f + s * (-7.0547382347285747528076171875e-3f))));
phi = select(phi, 1.0 / 4.0 - phi, xabs < yabs);
phi = select(phi, 1.0 / 2.0 - phi, x < 0.0);
phi = select(phi, 1.0 - phi, y < 0.0);
phi = select(phi, 0.0, phi != phi); // check for NaN
phi = (phi - sweep.t0) * scale;
let t = extend_mode(phi, sweep.extend_mode);
let ramp_x = i32(round(t * f32(GRADIENT_WIDTH - 1)));
let fg_rgba = textureLoad(gradients, vec2(ramp_x, i32(sweep.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
case 9u: {
let image = read_image(cmd_ix);
let atlas_extents = image.atlas_offset + image.extents;
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
Expand All @@ -994,7 +1044,7 @@ fn main(
cmd_ix += 2u;
}
// CMD_BEGIN_CLIP
case 9u: {
case 10u: {
if clip_depth < BLEND_STACK_SPLIT {
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
blend_stack[clip_depth][i] = pack4x8unorm(rgba[i]);
Expand All @@ -1007,7 +1057,7 @@ fn main(
cmd_ix += 1u;
}
// CMD_END_CLIP
case 10u: {
case 11u: {
let end_clip = read_end_clip(cmd_ix);
clip_depth -= 1u;
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
Expand All @@ -1024,7 +1074,7 @@ fn main(
cmd_ix += 3u;
}
// CMD_JUMP
case 11u: {
case 12u: {
cmd_ix = ptcl[cmd_ix + 1u];
}
default: {}
Expand Down
1 change: 1 addition & 0 deletions shader/shared/drawtag.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ let DRAWTAG_NOP = 0u;
let DRAWTAG_FILL_COLOR = 0x44u;
let DRAWTAG_FILL_LIN_GRADIENT = 0x114u;
let DRAWTAG_FILL_RAD_GRADIENT = 0x29cu;
let DRAWTAG_FILL_SWEEP_GRADIENT = 0x254u;
let DRAWTAG_FILL_IMAGE = 0x248u;
let DRAWTAG_BEGIN_CLIP = 0x9u;
let DRAWTAG_END_CLIP = 0x21u;
Expand Down
18 changes: 14 additions & 4 deletions shader/shared/ptcl.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ let CMD_SOLID = 3u;
let CMD_COLOR = 5u;
let CMD_LIN_GRAD = 6u;
let CMD_RAD_GRAD = 7u;
let CMD_IMAGE = 8u;
let CMD_BEGIN_CLIP = 9u;
let CMD_END_CLIP = 10u;
let CMD_JUMP = 11u;
let CMD_SWEEP_GRAD = 8u;
let CMD_IMAGE = 9u;
let CMD_BEGIN_CLIP = 10u;
let CMD_END_CLIP = 11u;
let CMD_JUMP = 12u;

// The individual PTCL structs are written here, but read/write is by
// hand in the relevant shaders
Expand Down Expand Up @@ -63,6 +64,15 @@ struct CmdRadGrad {
flags: u32,
}

struct CmdSweepGrad {
index: u32,
extend_mode: u32,
matrx: vec4<f32>,
xlat: vec2<f32>,
t0: f32,
t1: f32,
}

struct CmdImage {
matrx: vec4<f32>,
xlat: vec2<f32>,
Expand Down
Loading