@@ -892,6 +892,110 @@ fn extend_mode(t: f32, mode: u32, max: f32) -> f32 {
892892 }
893893}
894894
895+ // Cubic resampler logic borrowed from Skia (same as CPU cubic_resampler function)
896+ // Mitchell-Netravali cubic filter coefficients with parameters B=1/3 and C=1/3
897+ const MF : array <vec4 <f32 >, 4 > = array <vec4 <f32 >, 4 >(
898+ vec4 <f32 >(
899+ (1 .0 / 6 .0 ) / 3 .0 ,
900+ - (3 .0 / 6 .0 ) / 3 .0 - 1 .0 / 3 .0 ,
901+ (3 .0 / 6 .0 ) / 3 .0 + 2 .0 * 1 .0 / 3 .0 ,
902+ - (1 .0 / 6 .0 ) / 3 .0 - 1 .0 / 3 .0
903+ ),
904+ vec4 <f32 >(
905+ 1 .0 - (2 .0 / 6 .0 ) / 3 .0 ,
906+ 0 .0 ,
907+ - 3 .0 + (12 .0 / 6 .0 ) / 3 .0 + 1 .0 / 3 .0 ,
908+ 2 .0 - (9 .0 / 6 .0 ) / 3 .0 - 1 .0 / 3 .0
909+ ),
910+ vec4 <f32 >(
911+ (1 .0 / 6 .0 ) / 3 .0 ,
912+ (3 .0 / 6 .0 ) / 3 .0 + 1 .0 / 3 .0 ,
913+ 3 .0 - (15 .0 / 6 .0 ) / 3 .0 - 2 .0 * 1 .0 / 3 .0 ,
914+ - 2 .0 + (9 .0 / 6 .0 ) / 3 .0 + 1 .0 / 3 .0
915+ ),
916+ vec4 <f32 >(
917+ 0 .0 ,
918+ 0 .0 ,
919+ - 1 .0 / 3 .0 ,
920+ (1 .0 / 6 .0 ) / 3 .0 + 1 .0 / 3 .0
921+ )
922+ );
923+
924+ // Calculate the weights for a single fractional value (same as CPU weights function)
925+ fn cubic_weights (fract : f32 ) -> vec4 <f32 > {
926+ return vec4 <f32 >(
927+ single_weight (fract , MF [0 ][0 ], MF [0 ][1 ], MF [0 ][2 ], MF [0 ][3 ]),
928+ single_weight (fract , MF [1 ][0 ], MF [1 ][1 ], MF [1 ][2 ], MF [1 ][3 ]),
929+ single_weight (fract , MF [2 ][0 ], MF [2 ][1 ], MF [2 ][2 ], MF [2 ][3 ]),
930+ single_weight (fract , MF [3 ][0 ], MF [3 ][1 ], MF [3 ][2 ], MF [3 ][3 ])
931+ );
932+ }
933+
934+ // Calculate a weight based on the fractional value t and the cubic coefficients
935+ // This matches the CPU implementation exactly
936+ fn single_weight (t : f32 , a : f32 , b : f32 , c : f32 , d : f32 ) -> f32 {
937+ return t * (t * (t * d + c ) + b ) + a ;
938+ }
939+
940+ // Bicubic filtering using Mitchell filter with B=1/3, C=1/3
941+ //
942+ // Cubic resampling consists of sampling the 16 surrounding pixels of the target point and
943+ // interpolating them with a cubic filter. The generated matrix is 4x4 and represent the coefficients
944+ // of the cubic function used to calculate weights based on the `x_fract` and `y_fract` of the
945+ // location we are looking at.
946+ //
947+ // This is adapted from the sparse-strips shader for the main Vello image path:
948+ // - the atlas is a single `texture_2d`, not a texture array
949+ // - each tap is premultiplied before filtering, matching the existing bilinear path
950+ fn bicubic_sample (
951+ coords : vec2 <f32 >,
952+ atlas_offset : vec2 <f32 >,
953+ atlas_max : vec2 <f32 >,
954+ alpha_type : u32 ,
955+ ) -> vec4 <f32 > {
956+ let frac_coords = fract (coords + vec2 (0 .5 ));
957+ // Get cubic weights for x and y directions
958+ let cx = cubic_weights (frac_coords . x );
959+ let cy = cubic_weights (frac_coords . y );
960+
961+ // Sample 4x4 grid around coords
962+ let s00 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (- 1 .5 , - 1 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
963+ let s10 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (- 0 .5 , - 1 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
964+ let s20 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (0 .5 , - 1 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
965+ let s30 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (1 .5 , - 1 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
966+
967+ let s01 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (- 1 .5 , - 0 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
968+ let s11 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (- 0 .5 , - 0 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
969+ let s21 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (0 .5 , - 0 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
970+ let s31 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (1 .5 , - 0 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
971+
972+ let s02 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (- 1 .5 , 0 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
973+ let s12 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (- 0 .5 , 0 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
974+ let s22 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (0 .5 , 0 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
975+ let s32 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (1 .5 , 0 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
976+
977+ let s03 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (- 1 .5 , 1 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
978+ let s13 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (- 0 .5 , 1 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
979+ let s23 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (0 .5 , 1 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
980+ let s33 = maybe_premul_alpha (textureLoad (image_atlas , vec2 <i32 >(clamp (coords + vec2 (1 .5 , 1 .5 ), atlas_offset , atlas_max )), 0 ), alpha_type );
981+
982+ // Interpolate in x direction for each row
983+ let row0 = cx . x * s00 + cx . y * s10 + cx . z * s20 + cx . w * s30 ;
984+ let row1 = cx . x * s01 + cx . y * s11 + cx . z * s21 + cx . w * s31 ;
985+ let row2 = cx . x * s02 + cx . y * s12 + cx . z * s22 + cx . w * s32 ;
986+ let row3 = cx . x * s03 + cx . y * s13 + cx . z * s23 + cx . w * s33 ;
987+ // Interpolate in y direction
988+ let result = cy . x * row0 + cy . y * row1 + cy . z * row2 + cy . w * row3 ;
989+
990+ // Clamp each component to [0,1] and ensure color components don't exceed alpha
991+ return vec4 <f32 >(
992+ min (clamp (result . r , 0 .0 , 1 .0 ), result . a ),
993+ min (clamp (result . g , 0 .0 , 1 .0 ), result . a ),
994+ min (clamp (result . b , 0 .0 , 1 .0 ), result . a ),
995+ clamp (result . a , 0 .0 , 1 .0 )
996+ );
997+ }
998+
895999const PIXELS_PER_THREAD = 4u ;
8961000
8971001#ifndef msaa
@@ -1235,7 +1339,6 @@ fn main(
12351339 }
12361340 }
12371341 case IMAGE_QUALITY_MEDIUM , default : {
1238- // We don't have an implementation for `IMAGE_QUALITY_HIGH` yet, just use the same as medium
12391342 for (var i = 0u ; i < PIXELS_PER_THREAD ; i += 1u ) {
12401343 // We only need to load from the textures if the value will be used.
12411344 if area [i ] != 0 .0 {
@@ -1261,6 +1364,20 @@ fn main(
12611364 }
12621365 }
12631366 }
1367+ case IMAGE_QUALITY_HIGH : {
1368+ for (var i = 0u ; i < PIXELS_PER_THREAD ; i += 1u ) {
1369+ if area [i ] != 0 .0 {
1370+ let my_xy = vec2 (xy . x + f32 (i ), xy . y );
1371+ var atlas_uv = image . matrx . xy * my_xy . x + image . matrx . zw * my_xy . y + image . xlat ;
1372+ atlas_uv . x = extend_mode (atlas_uv . x , image . x_extend_mode , image . extents . x );
1373+ atlas_uv . y = extend_mode (atlas_uv . y , image . y_extend_mode , image . extents . y );
1374+ atlas_uv = atlas_uv + image . atlas_offset ;
1375+ let fg_rgba = bicubic_sample (atlas_uv , image . atlas_offset , atlas_max , image . alpha_type );
1376+ let fg_i = pixel_format (fg_rgba * area [i ] * image . alpha , image . format );
1377+ rgba [i ] = rgba [i ] * (1 .0 - fg_i . a ) + fg_i ;
1378+ }
1379+ }
1380+ }
12641381 }
12651382 cmd_ix += 2u ;
12661383 }
0 commit comments