Skip to content
Open
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
3 changes: 1 addition & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ bytemuck = { version = "1.25.0", features = ["derive"] }
skrifa = { version = "0.44.0", default-features = false, features = ["autohint_shaping"] }
# The version of kurbo used below should be kept in sync
# with the version of kurbo used by peniko.
peniko = { version = "0.6.1", default-features = false }
peniko = { git = "https://github.com/DioxusLabs/peniko", rev = "44095211acdfb4024ecf0a4c620cd91e82440df3", default-features = false }
# FIXME: This can be removed once peniko supports the schemars feature.
kurbo = { version = "0.13.1", default-features = false }
futures-intrusive = "0.5.0"
Expand Down
5 changes: 4 additions & 1 deletion sparse_strips/vello_common/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,10 @@ impl EncodeExt for Image {
// even if the source pixels are all opaque.
let has_opacity = tint.as_ref().is_some_and(|t| t.color.components[3] < 1.0)
// Not supported yet, but just to future-proof.
|| sampler.alpha != 1.0;
|| sampler.alpha != 1.0
// `Extend::None` produces transparent samples outside of the image.
|| sampler.x_extend == Extend::None
|| sampler.y_extend == Extend::None;

let encoded = EncodedImage {
may_have_transparency: self.image.may_have_transparency() || has_opacity,
Expand Down
3 changes: 2 additions & 1 deletion sparse_strips/vello_cpu/src/fine/common/gradient/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ fn invalid_f32_mask<S: Simd>(simd: S, indices: u32x4<S>) -> mask32x16<S> {
#[inline(always)]
pub(crate) fn apply_extend<S: Simd>(val: f32x8<S>, extend: peniko::Extend) -> f32x8<S> {
match extend {
peniko::Extend::Pad => val.max(0.0).min(1.0),
// `None` is currently only supported for images and falls back to `Pad` for gradients.
peniko::Extend::Pad | peniko::Extend::None => val.max(0.0).min(1.0),
peniko::Extend::Repeat => (val - val.floor()).fract(),
// See <https://github.com/google/skia/blob/220738774f7a0ce4a6c7bd17519a336e5e5dea5b/src/opts/SkRasterPipeline_opts.h#L6472-L6475>
peniko::Extend::Reflect => ((val - 1.0) - 2.0 * ((val - 1.0) * 0.5).floor() - 1.0)
Expand Down
169 changes: 146 additions & 23 deletions sparse_strips/vello_cpu/src/fine/common/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use crate::fine::macros::{f32x16_painter, u8x16_painter};
use crate::fine::{PosExt, Splat4thExt, u8_to_f32};
use crate::kurbo::Point;
use vello_common::encode::EncodedImage;
use vello_common::fearless_simd::{Bytes, Simd, SimdBase, SimdFloat, f32x4, f32x16, u8x16, u32x4};
use vello_common::fearless_simd::{
Bytes, Simd, SimdBase, SimdFloat, f32x4, f32x16, mask32x4, u8x16, u32x4,
};
use vello_common::pixmap::Pixmap;
use vello_common::simd::element_wise_splat;

Expand All @@ -14,6 +16,7 @@ use vello_common::simd::element_wise_splat;
pub(crate) struct PlainNNImagePainter<'a, S: Simd> {
data: ImagePainterData<'a, S>,
y_positions: f32x4<S>,
y_mask: mask32x4<S>,
cur_x_pos: f32x4<S>,
advance: f32,
simd: S,
Expand All @@ -32,14 +35,17 @@ impl<'a, S: Simd> PlainNNImagePainter<'a, S> {
simd.vectorize(
#[inline(always)]
|| {
let raw_y_positions = f32x4::splat_pos(
simd,
data.cur_pos.y as f32,
data.x_advances.1,
data.y_advances.1,
);
let y_mask =
in_bounds_mask(simd, raw_y_positions, image.sampler.y_extend, data.height);
let y_positions = extend(
simd,
f32x4::splat_pos(
simd,
data.cur_pos.y as f32,
data.x_advances.1,
data.y_advances.1,
),
raw_y_positions,
image.sampler.y_extend,
data.height,
data.height_inv,
Expand All @@ -56,6 +62,7 @@ impl<'a, S: Simd> PlainNNImagePainter<'a, S> {
data,
advance: image.x_advance.x as f32,
y_positions,
y_mask,
cur_x_pos,
simd,
}
Expand All @@ -69,6 +76,12 @@ impl<S: Simd> Iterator for PlainNNImagePainter<'_, S> {

#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
let x_mask = in_bounds_mask(
self.simd,
self.cur_x_pos,
self.data.image.sampler.x_extend,
self.data.width,
);
let x_pos = extend(
self.simd,
self.cur_x_pos,
Expand All @@ -78,6 +91,11 @@ impl<S: Simd> Iterator for PlainNNImagePainter<'_, S> {
);

let samples = sample(self.simd, &self.data, x_pos, self.y_positions);
let samples = apply_mask(
self.simd,
samples,
self.simd.and_mask32x4(x_mask, self.y_mask),
);

self.cur_x_pos += self.advance;

Expand Down Expand Up @@ -113,33 +131,50 @@ impl<S: Simd> Iterator for NNImagePainter<'_, S> {

#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
let raw_x_positions = f32x4::splat_pos(
self.simd,
self.data.cur_pos.x as f32,
self.data.x_advances.0,
self.data.y_advances.0,
);
let raw_y_positions = f32x4::splat_pos(
self.simd,
self.data.cur_pos.y as f32,
self.data.x_advances.1,
self.data.y_advances.1,
);

let x_mask = in_bounds_mask(
self.simd,
raw_x_positions,
self.data.image.sampler.x_extend,
self.data.width,
);
let y_mask = in_bounds_mask(
self.simd,
raw_y_positions,
self.data.image.sampler.y_extend,
self.data.height,
);

let x_positions = extend(
self.simd,
f32x4::splat_pos(
self.simd,
self.data.cur_pos.x as f32,
self.data.x_advances.0,
self.data.y_advances.0,
),
raw_x_positions,
self.data.image.sampler.x_extend,
self.data.width,
self.data.width_inv,
);

let y_positions = extend(
self.simd,
f32x4::splat_pos(
self.simd,
self.data.cur_pos.y as f32,
self.data.x_advances.1,
self.data.y_advances.1,
),
raw_y_positions,
self.data.image.sampler.y_extend,
self.data.height,
self.data.height_inv,
);

let samples = sample(self.simd, &self.data, x_positions, y_positions);
let samples = apply_mask(self.simd, samples, self.simd.and_mask32x4(x_mask, y_mask));

self.data.cur_pos += self.data.image.x_advance;

Expand Down Expand Up @@ -221,7 +256,7 @@ impl<S: Simd, const QUALITY: u8> Iterator for FilteredImagePainter<'_, S, QUALIT
extend(
self.simd,
x_positions + $offsets[$idx],
self.data.image.sampler.y_extend,
self.data.image.sampler.x_extend,
self.data.width,
self.data.width_inv,
)
Expand All @@ -240,6 +275,34 @@ impl<S: Simd, const QUALITY: u8> Iterator for FilteredImagePainter<'_, S, QUALIT
};
}

macro_rules! mask_x {
($idx:expr,$offsets:expr) => {
mask_to_weights(
self.simd,
in_bounds_mask(
self.simd,
x_positions + $offsets[$idx],
self.data.image.sampler.x_extend,
self.data.width,
),
)
};
}

macro_rules! mask_y {
($idx:expr,$offsets:expr) => {
mask_to_weights(
self.simd,
in_bounds_mask(
self.simd,
y_positions + $offsets[$idx],
self.data.image.sampler.y_extend,
self.data.height,
),
)
};
}

match QUALITY {
// medium quality: bilinear
1 => {
Expand All @@ -253,6 +316,9 @@ impl<S: Simd, const QUALITY: u8> Iterator for FilteredImagePainter<'_, S, QUALIT

const OFFSETS: [f32; 2] = [-0.5, 0.5];

let x_masks = [mask_x!(0, OFFSETS), mask_x!(1, OFFSETS)];
let y_masks = [mask_y!(0, OFFSETS), mask_y!(1, OFFSETS)];

let x_positions = [extend_x!(0, OFFSETS), extend_x!(1, OFFSETS)];

let y_positions = [extend_y!(0, OFFSETS), extend_y!(1, OFFSETS)];
Expand All @@ -264,7 +330,10 @@ impl<S: Simd, const QUALITY: u8> Iterator for FilteredImagePainter<'_, S, QUALIT
for y_idx in 0..2 {
let y_positions = y_positions[y_idx];
let color_sample = sample(x_positions, y_positions);
let w = element_wise_splat(self.simd, cx[x_idx] * cy[y_idx]);
let w = element_wise_splat(
self.simd,
cx[x_idx] * cy[y_idx] * x_masks[x_idx] * y_masks[y_idx],
);

interpolated_color = w.mul_add(color_sample, interpolated_color);
}
Expand All @@ -280,6 +349,19 @@ impl<S: Simd, const QUALITY: u8> Iterator for FilteredImagePainter<'_, S, QUALIT

const OFFSETS: [f32; 4] = [-1.5, -0.5, 0.5, 1.5];

let x_masks = [
mask_x!(0, OFFSETS),
mask_x!(1, OFFSETS),
mask_x!(2, OFFSETS),
mask_x!(3, OFFSETS),
];
let y_masks = [
mask_y!(0, OFFSETS),
mask_y!(1, OFFSETS),
mask_y!(2, OFFSETS),
mask_y!(3, OFFSETS),
];

let x_positions = [
extend_x!(0, OFFSETS),
extend_x!(1, OFFSETS),
Expand All @@ -304,7 +386,10 @@ impl<S: Simd, const QUALITY: u8> Iterator for FilteredImagePainter<'_, S, QUALIT
let y_positions = y_positions[y_idx];

let color_sample = sample(x_positions, y_positions);
let w = element_wise_splat(self.simd, cx[x_idx] * cy[y_idx]);
let w = element_wise_splat(
self.simd,
cx[x_idx] * cy[y_idx] * x_masks[x_idx] * y_masks[y_idx],
);

interpolated_color = w.mul_add(color_sample, interpolated_color);
}
Expand Down Expand Up @@ -439,7 +524,12 @@ pub(crate) fn extend<S: Simd>(
// Note that max should be exclusive, so subtract one to enforce that.
// Since the maximum image dimensions we support is u16::MAX, subtracting 1 in f32
// is enough to ensure that all numbers are subtracted correctly.
crate::peniko::Extend::Pad => val.min(max - 1.0).max(f32x4::splat(simd, 0.0)),
//
// For `None`, out-of-bounds positions are clamped like `Pad` so that they can be
// sampled safely; the resulting samples are zeroed via `in_bounds_mask`.
crate::peniko::Extend::Pad | crate::peniko::Extend::None => {
val.min(max - 1.0).max(f32x4::splat(simd, 0.0))
}
crate::peniko::Extend::Repeat => {
// floor := (val * inv_max).floor() * max is the nearest multiple of `max` below val.
max.mul_add(-(val * inv_max).floor(), val)
Expand Down Expand Up @@ -468,6 +558,39 @@ pub(crate) fn extend<S: Simd>(
}
}

/// A mask that is all-ones for lanes where `val` is inside `[0, max)` and all-zeroes
/// otherwise. For extend modes other than [`None`](crate::peniko::Extend::None), all
/// positions are considered in-bounds.
#[inline(always)]
pub(crate) fn in_bounds_mask<S: Simd>(
simd: S,
val: f32x4<S>,
extend: crate::peniko::Extend,
max: f32x4<S>,
) -> mask32x4<S> {
match extend {
crate::peniko::Extend::None => simd.and_mask32x4(
simd.simd_ge_f32x4(val, f32x4::splat(simd, 0.0)),
simd.simd_lt_f32x4(val, max),
),
_ => mask32x4::splat(simd, -1),
}
}

/// Converts an in-bounds mask to per-lane multiplicative weights of `1.0`/`0.0`.
#[inline(always)]
pub(crate) fn mask_to_weights<S: Simd>(simd: S, mask: mask32x4<S>) -> f32x4<S> {
simd.select_f32x4(mask, f32x4::splat(simd, 1.0), f32x4::splat(simd, 0.0))
}

/// Zeroes out the pixels of `samples` for lanes that are masked out.
#[inline(always)]
pub(crate) fn apply_mask<S: Simd>(simd: S, samples: u8x16<S>, mask: mask32x4<S>) -> u8x16<S> {
let as_u32 = u32x4::from_bytes(samples.to_bytes());
simd.select_u32x4(mask, as_u32, u32x4::splat(simd, 0))
.to_bytes()
}

/// Calculate the weights for a single fractional value.
fn weights<S: Simd>(simd: S, fract: f32x4<S>) -> [f32x4<S>; 4] {
simd.vectorize(
Expand Down
3 changes: 2 additions & 1 deletion sparse_strips/vello_cpu/src/fine/lowp/gradient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ impl<S: Simd> crate::fine::Painter for GradientPainter<'_, S> {
#[inline(always)]
pub(crate) fn apply_extend<S: Simd>(val: f32x16<S>, extend: peniko::Extend) -> f32x16<S> {
match extend {
peniko::Extend::Pad => val.max(0.0).min(1.0),
// `None` is currently only supported for images and falls back to `Pad` for gradients.
peniko::Extend::Pad | peniko::Extend::None => val.max(0.0).min(1.0),
peniko::Extend::Repeat => (val - val.floor()).fract(),
// See <https://github.com/google/skia/blob/220738774f7a0ce4a6c7bd17519a336e5e5dea5b/src/opts/SkRasterPipeline_opts.h#L6472-L6475>
peniko::Extend::Reflect => ((val - 1.0) - 2.0 * ((val - 1.0) * 0.5).floor() - 1.0)
Expand Down
Loading
Loading