Skip to content

Commit 7b32dc2

Browse files
authored
extend_mode_normalized and extend_mode (#974)
Current `extend_mode` is renamed to `extend_mode_normalized` (because it require normalized coordinates). To avoid rounding errors we introduce `extend_mode` that relaxes normalization requirement. Currently only EXTEND_PAD is implemented differently, others fallback to `extend_mode_normalized` for now. partial fix #972 --------- Signed-off-by: sagudev <[email protected]>
1 parent 90bcf18 commit 7b32dc2

File tree

3 files changed

+71
-16
lines changed

3 files changed

+71
-16
lines changed

vello_shaders/shader/fine.wgsl

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,7 @@ fn read_end_clip(cmd_ix: u32) -> CmdEndClip {
831831
const EXTEND_PAD: u32 = 0u;
832832
const EXTEND_REPEAT: u32 = 1u;
833833
const EXTEND_REFLECT: u32 = 2u;
834-
fn extend_mode(t: f32, mode: u32) -> f32 {
834+
fn extend_mode_normalized(t: f32, mode: u32) -> f32 {
835835
switch mode {
836836
case EXTEND_PAD: {
837837
return clamp(t, 0.0, 1.0);
@@ -845,6 +845,20 @@ fn extend_mode(t: f32, mode: u32) -> f32 {
845845
}
846846
}
847847

848+
fn extend_mode(t: f32, mode: u32, max: f32) -> f32 {
849+
switch mode {
850+
case EXTEND_PAD: {
851+
return clamp(t, 0.0, max);
852+
}
853+
case EXTEND_REPEAT: {
854+
return extend_mode_normalized(t / max, mode) * max;
855+
}
856+
case EXTEND_REFLECT, default: {
857+
return extend_mode_normalized(t / max, mode) * max;
858+
}
859+
}
860+
}
861+
848862
const PIXELS_PER_THREAD = 4u;
849863

850864
#ifndef msaa
@@ -1067,7 +1081,7 @@ fn main(
10671081
let d = lin.line_x * xy.x + lin.line_y * xy.y + lin.line_c;
10681082
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
10691083
let my_d = d + lin.line_x * f32(i);
1070-
let x = i32(round(extend_mode(my_d, lin.extend_mode) * f32(GRADIENT_WIDTH - 1)));
1084+
let x = i32(round(extend_mode_normalized(my_d, lin.extend_mode) * f32(GRADIENT_WIDTH - 1)));
10711085
let fg_rgba = textureLoad(gradients, vec2(x, i32(lin.index)), 0);
10721086
let fg_i = fg_rgba * area[i];
10731087
rgba[i] = rgba[i] * (1.0 - fg_i.a) + fg_i;
@@ -1109,7 +1123,7 @@ fn main(
11091123
is_valid = a >= 0.0 && t >= 0.0;
11101124
}
11111125
if is_valid {
1112-
t = extend_mode(focal_x + t_sign * t, rad.extend_mode);
1126+
t = extend_mode_normalized(focal_x + t_sign * t, rad.extend_mode);
11131127
t = select(t, 1.0 - t, is_swapped);
11141128
let x = i32(round(t * f32(GRADIENT_WIDTH - 1)));
11151129
let fg_rgba = textureLoad(gradients, vec2(x, i32(rad.index)), 0);
@@ -1144,7 +1158,7 @@ fn main(
11441158
phi = select(phi, 1.0 - phi, y < 0.0);
11451159
phi = select(phi, 0.0, phi != phi); // check for NaN
11461160
phi = (phi - sweep.t0) * scale;
1147-
let t = extend_mode(phi, sweep.extend_mode);
1161+
let t = extend_mode_normalized(phi, sweep.extend_mode);
11481162
let ramp_x = i32(round(t * f32(GRADIENT_WIDTH - 1)));
11491163
let fg_rgba = textureLoad(gradients, vec2(ramp_x, i32(sweep.index)), 0);
11501164
let fg_i = fg_rgba * area[i];
@@ -1155,16 +1169,15 @@ fn main(
11551169
case CMD_IMAGE: {
11561170
let image = read_image(cmd_ix);
11571171
let atlas_max = image.atlas_offset + image.extents - vec2(1.0);
1158-
let extents_inv = vec2(1.0) / image.extents;
11591172
switch image.quality {
11601173
case IMAGE_QUALITY_LOW: {
11611174
for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) {
11621175
// We only need to load from the textures if the value will be used.
11631176
if area[i] != 0.0 {
11641177
let my_xy = vec2(xy.x + f32(i), xy.y);
11651178
var atlas_uv = image.matrx.xy * my_xy.x + image.matrx.zw * my_xy.y + image.xlat;
1166-
atlas_uv.x = extend_mode(atlas_uv.x * extents_inv.x, image.x_extend_mode) * image.extents.x;
1167-
atlas_uv.y = extend_mode(atlas_uv.y * extents_inv.y, image.y_extend_mode) * image.extents.y;
1179+
atlas_uv.x = extend_mode(atlas_uv.x, image.x_extend_mode, image.extents.x);
1180+
atlas_uv.y = extend_mode(atlas_uv.y, image.y_extend_mode, image.extents.y);
11681181
atlas_uv = atlas_uv + image.atlas_offset;
11691182
// TODO: If the image couldn't be added to the atlas (i.e. was too big), this isn't robust
11701183
let atlas_uv_clamped = clamp(atlas_uv, image.atlas_offset, atlas_max);
@@ -1182,8 +1195,8 @@ fn main(
11821195
if area[i] != 0.0 {
11831196
let my_xy = vec2(xy.x + f32(i), xy.y);
11841197
var atlas_uv = image.matrx.xy * my_xy.x + image.matrx.zw * my_xy.y + image.xlat;
1185-
atlas_uv.x = extend_mode(atlas_uv.x * extents_inv.x, image.x_extend_mode) * image.extents.x;
1186-
atlas_uv.y = extend_mode(atlas_uv.y * extents_inv.y, image.y_extend_mode) * image.extents.y;
1198+
atlas_uv.x = extend_mode(atlas_uv.x, image.x_extend_mode, image.extents.x);
1199+
atlas_uv.y = extend_mode(atlas_uv.y, image.y_extend_mode, image.extents.y);
11871200
atlas_uv = atlas_uv + image.atlas_offset - vec2(0.5);
11881201
// TODO: If the image couldn't be added to the atlas (i.e. was too big), this isn't robust
11891202
let atlas_uv_clamped = clamp(atlas_uv, image.atlas_offset, atlas_max);

vello_tests/tests/known_issues.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use scenes::ImageCache;
1616
use vello::{
1717
AaConfig, Scene,
1818
kurbo::{Affine, Rect},
19-
peniko::{ImageFormat, ImageQuality, color::palette},
19+
peniko::{Extend, ImageFormat, ImageQuality, color::palette},
2020
};
2121
use vello_tests::{TestParams, smoke_snapshot_test_sync};
2222

@@ -82,15 +82,36 @@ const DATA_IMAGE_PNG: &[u8] = include_bytes!("../snapshots/smoke/data_image_roun
8282

8383
/// Test for <https://github.com/linebender/vello/issues/972>
8484
#[test]
85-
// Note that this isn't exactly 0.3, it's a number starting with "0.3"
86-
#[should_panic(expected = "Expected mean to be less than 0.001, got 0.3")]
87-
fn test_data_image_roundtrip() {
85+
#[ignore = "CI runs these tests on a CPU, leading to them having unrealistic precision"]
86+
#[should_panic]
87+
fn test_data_image_roundtrip_extend_reflect() {
88+
let mut scene = Scene::new();
89+
let mut images = ImageCache::new();
90+
let image = images
91+
.from_bytes(0, DATA_IMAGE_PNG)
92+
.unwrap()
93+
.with_quality(ImageQuality::Low)
94+
.with_extend(Extend::Reflect);
95+
scene.draw_image(&image, Affine::IDENTITY);
96+
let mut params = TestParams::new("data_image_roundtrip", image.width, image.height);
97+
params.anti_aliasing = AaConfig::Area;
98+
smoke_snapshot_test_sync(scene, &params)
99+
.unwrap()
100+
.assert_mean_less_than(0.001);
101+
}
102+
103+
/// Test for <https://github.com/linebender/vello/issues/972>
104+
#[test]
105+
#[ignore = "CI runs these tests on a CPU, leading to them having unrealistic precision"]
106+
#[should_panic]
107+
fn test_data_image_roundtrip_extend_repeat() {
88108
let mut scene = Scene::new();
89109
let mut images = ImageCache::new();
90110
let image = images
91111
.from_bytes(0, DATA_IMAGE_PNG)
92112
.unwrap()
93-
.with_quality(ImageQuality::Low);
113+
.with_quality(ImageQuality::Low)
114+
.with_extend(Extend::Repeat);
94115
scene.draw_image(&image, Affine::IDENTITY);
95116
let mut params = TestParams::new("data_image_roundtrip", image.width, image.height);
96117
params.anti_aliasing = AaConfig::Area;

vello_tests/tests/regression.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
//! Tests to ensure that certain issues which don't deserve a test scene don't regress
55
6+
use scenes::ImageCache;
67
use vello::{
78
AaConfig, Scene,
89
kurbo::{Affine, RoundedRect, Stroke},
9-
peniko::color::palette,
10+
peniko::{Extend, ImageQuality, color::palette},
1011
};
11-
use vello_tests::{TestParams, snapshot_test_sync};
12+
use vello_tests::{TestParams, smoke_snapshot_test_sync, snapshot_test_sync};
1213

1314
/// Test created from <https://github.com/linebender/vello/issues/616>
1415
#[test]
@@ -24,3 +25,23 @@ fn rounded_rectangle_watertight() {
2425
.unwrap()
2526
.assert_mean_less_than(0.001);
2627
}
28+
29+
const DATA_IMAGE_PNG: &[u8] = include_bytes!("../snapshots/smoke/data_image_roundtrip.png");
30+
31+
/// Test for <https://github.com/linebender/vello/issues/972>
32+
#[test]
33+
fn test_data_image_roundtrip_extend_pad() {
34+
let mut scene = Scene::new();
35+
let mut images = ImageCache::new();
36+
let image = images
37+
.from_bytes(0, DATA_IMAGE_PNG)
38+
.unwrap()
39+
.with_quality(ImageQuality::Low)
40+
.with_extend(Extend::Pad);
41+
scene.draw_image(&image, Affine::IDENTITY);
42+
let mut params = TestParams::new("data_image_roundtrip", image.width, image.height);
43+
params.anti_aliasing = AaConfig::Area;
44+
smoke_snapshot_test_sync(scene, &params)
45+
.unwrap()
46+
.assert_mean_less_than(0.001);
47+
}

0 commit comments

Comments
 (0)