pub const STRIP: &str = "// Copyright 2024 the Vello Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// Derived from vello_sparse_shaders 0.2.0 (`shaders/helpers/*.wesl`).\n//\n// Shared, binding-free helper functions: packing, quad geometry, extend\n// modes, encoded-paint accessors, gradient/blur evaluation and atlas\n// sampling. Every entry-point module (`strip.wgsl`, `clear.wgsl`,\n// `copy.wgsl`) is compiled with this file prepended, so the WESL reference\'s\n// `import package::helpers::...` lines are resolved by concatenation rather\n// than by a resolver at build time.\n//\n// Two consequences of that flattening are visible below:\n//\n// 1. Nothing here declares a `@group`/`@binding` global. A texture a helper\n// reads is always a function parameter, so prepending this file never\n// changes an entry-point module\'s derived bind-group layout.\n// 2. Parameters that named a texture or an extend mode in the reference are\n// renamed (`tex`, `mode`) where the reference\'s own module boundary was\n// the only thing keeping them from shadowing an entry-point global or a\n// function declared here.\n\n// Mathematical constants.\nconst PI: f32 = 3.1415926535897932384626433832795028;\nconst TWO_PI: f32 = 2.0 * PI;\n// Tolerance for nearly-zero comparisons. Must match `SCALAR_NEARLY_ZERO` in\n// `vello_common::math`, which the CPU-side rasterizer compares against.\nconst NEARLY_ZERO_TOLERANCE: f32 = 1.0 / 4096.0;\n\n// Extend modes, shared by images and gradients.\nconst EXTEND_PAD: u32 = 0u;\nconst EXTEND_REPEAT: u32 = 1u;\nconst EXTEND_REFLECT: u32 = 2u;\n\n// Image rendering quality.\nconst IMAGE_QUALITY_LOW: u32 = 0u;\nconst IMAGE_QUALITY_MEDIUM: u32 = 1u;\nconst IMAGE_QUALITY_HIGH: u32 = 2u;\n\n// Image source kinds.\nconst IMAGE_SOURCE_ATLAS: u32 = 0u;\nconst IMAGE_SOURCE_EXTERNAL: u32 = 1u;\n\n// Tint modes.\nconst TINT_MODE_ALPHA_MASK: u32 = 0u;\nconst TINT_MODE_MULTIPLY: u32 = 1u;\n\n// Gradient types.\nconst GRADIENT_TYPE_LINEAR: u32 = 0u;\nconst GRADIENT_TYPE_RADIAL: u32 = 1u;\nconst GRADIENT_TYPE_SWEEP: u32 = 2u;\n\n// Radial gradient types.\nconst RADIAL_GRADIENT_TYPE_STANDARD: u32 = 0u;\nconst RADIAL_GRADIENT_TYPE_STRIP: u32 = 1u;\nconst RADIAL_GRADIENT_TYPE_FOCAL: u32 = 2u;\n\n// -----------------------------------------------------------------------------\n// Packing\n// -----------------------------------------------------------------------------\n\nfn unpack_u16_pair(value: u32) -> vec2<u32> {\n return vec2<u32>(value & 0xffffu, value >> 16u);\n}\n\n// -----------------------------------------------------------------------------\n// Texture addressing\n// -----------------------------------------------------------------------------\n\nfn flat_index_to_texture_coord(index: u32, width: u32) -> vec2<u32> {\n return vec2<u32>(index % width, index / width);\n}\n\n// -----------------------------------------------------------------------------\n// Quad geometry\n// -----------------------------------------------------------------------------\n\nfn quad_corner(vertex_index: u32) -> vec2<f32> {\n return vec2<f32>(\n f32(vertex_index & 1u),\n f32(vertex_index >> 1u),\n );\n}\n\nfn pixel_to_ndc(pixel: vec2<f32>, target_size: vec2<f32>) -> vec2<f32> {\n return vec2<f32>(\n pixel.x * 2.0 / target_size.x - 1.0,\n 1.0 - pixel.y * 2.0 / target_size.y,\n );\n}\n\n// -----------------------------------------------------------------------------\n// Strip alpha unpacking\n// -----------------------------------------------------------------------------\n\n// Alpha textures store 16 1-byte alpha values per texel, with each color\n// channel packing the 4 alpha values of a single strip column.\nfn unpack_alphas_from_channel(rgba: vec4<u32>, channel_index: u32) -> u32 {\n switch channel_index {\n case 0u: { return rgba.x; }\n case 1u: { return rgba.y; }\n case 2u: { return rgba.z; }\n case 3u: { return rgba.w; }\n // Fallback, should never happen.\n default: { return rgba.x; }\n }\n}\n\n// -----------------------------------------------------------------------------\n// Extend modes\n// -----------------------------------------------------------------------------\n\nfn extend_mode(t: f32, mode: u32, max: f32) -> f32 {\n switch mode {\n case EXTEND_PAD: {\n return clamp(t, 0.0, max - 1.0);\n }\n case EXTEND_REPEAT: {\n return extend_mode_normalized(t / max, mode) * max;\n }\n case EXTEND_REFLECT, default: {\n return extend_mode_normalized(t / max, mode) * max;\n }\n }\n}\n\nfn extend_mode_normalized(t: f32, mode: u32) -> f32 {\n switch mode {\n case EXTEND_PAD: {\n return clamp(t, 0.0, 1.0);\n }\n case EXTEND_REPEAT: {\n return fract(t);\n }\n case EXTEND_REFLECT, default: {\n return abs(t - 2.0 * round(0.5 * t));\n }\n }\n}\n\n// -----------------------------------------------------------------------------\n// Encoded gradient accessors and evaluation\n// -----------------------------------------------------------------------------\n\n// Sample from the gradient LUT texture at the calculated position.\nfn sample_gradient_lut(\n tex: texture_2d<f32>,\n t_value: f32,\n mode: u32,\n gradient_start: u32,\n texture_width: u32,\n) -> vec4<f32> {\n // Apply the extend mode to t_value.\n let clamped_t = extend_mode_normalized(t_value, mode);\n // Convert t_value to a texture coordinate.\n let t_offset = u32(clamped_t * f32(texture_width - 1u));\n // Absolute position in the flat gradient texture.\n let flat_coord = gradient_start + t_offset;\n let gradient_tex_width = textureDimensions(tex).x;\n let texture_coord = flat_index_to_texture_coord(flat_coord, gradient_tex_width);\n return textureLoad(tex, texture_coord, 0);\n}\n\n// Width of the gradient\'s own ramp, in texels.\nfn get_gradient_texture_width(texel0: vec4<u32>) -> u32 { return texel0.x & 0x0FFFFFFFu; }\n\n// The extend mode for the gradient.\nfn get_gradient_extend_mode(texel0: vec4<u32>) -> u32 { return (texel0.x >> 30u) & 3u; }\n\n// Start coordinate in the flat gradient texture.\nfn get_gradient_start(texel0: vec4<u32>) -> u32 { return texel0.y; }\n\n// 2x2 linear part of the affine transform (columns [a,b] and [c,d]).\nfn get_gradient_transform(texel0: vec4<u32>, texel1: vec4<u32>) -> mat2x2<f32> {\n return mat2x2<f32>(\n vec2<f32>(bitcast<f32>(texel0.z), bitcast<f32>(texel0.w)),\n vec2<f32>(bitcast<f32>(texel1.x), bitcast<f32>(texel1.y))\n );\n}\n\n// Translation part of the affine transform [tx, ty].\nfn get_gradient_translate(texel1: vec4<u32>) -> vec2<f32> {\n return vec2<f32>(bitcast<f32>(texel1.z), bitcast<f32>(texel1.w));\n}\n\nfn apply_gradient_transform(\n texel0: vec4<u32>,\n texel1: vec4<u32>,\n fragment_pos: vec2<f32>,\n) -> vec2<f32> {\n return get_gradient_transform(texel0, texel1) * fragment_pos + get_gradient_translate(texel1);\n}\n\n// Kind of radial gradient (0=Radial, 1=Strip, 2=Focal).\nfn get_radial_kind(texel2: vec4<u32>) -> u32 { return texel2.x & 0x3u; }\n\n// Whether the focal point is swapped for the radial gradient (0=false, 1=true).\nfn get_radial_f_is_swapped(texel2: vec4<u32>) -> u32 { return (texel2.x >> 2u) & 1u; }\n\n// Bias value for radial gradient calculation.\nfn get_radial_bias(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.y); }\n\n// Scale factor for radial gradient calculation.\nfn get_radial_scale(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.z); }\n\n// Focal point 0 parameter for radial gradient.\nfn get_radial_fp0(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.w); }\n\n// Focal point 1 parameter for radial gradient.\nfn get_radial_fp1(texel3: vec4<u32>) -> f32 { return bitcast<f32>(texel3.x); }\n\n// Focal radius 1 parameter for radial gradient.\nfn get_radial_fr1(texel3: vec4<u32>) -> f32 { return bitcast<f32>(texel3.y); }\n\n// Focal X coordinate for radial gradient.\nfn get_radial_f_focal_x(texel3: vec4<u32>) -> f32 { return bitcast<f32>(texel3.z); }\n\n// Scaled radius 0 squared parameter for the radial gradient strip kind.\nfn get_radial_scaled_r0_squared(texel3: vec4<u32>) -> f32 { return bitcast<f32>(texel3.w); }\n\n// Starting angle for sweep gradient (in radians).\nfn get_sweep_start_angle(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.x); }\n\n// Inverse of angle delta for sweep gradient.\nfn get_sweep_inv_angle_delta(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.y); }\n\n// Fast polynomial approximation for xy_to_unit_angle from Skia.\n// Returns an angle in the [0, 1) range representing [0, 2*PI).\n// See: https://github.com/google/skia/blob/30bba741989865c157c7a997a0caebe94921276b/src/opts/SkRasterPipeline_opts.h#L5859\nfn xy_to_unit_angle(x: f32, y: f32) -> f32 {\n let xabs = abs(x);\n let yabs = abs(y);\n let slope = min(xabs, yabs) / max(xabs, yabs);\n let s = slope * slope;\n // A 7th degree polynomial approximating atan, generated with\n // sollya.gforge.inria.fr via\n // P1 = fpminimax((1/(2*Pi))*atan(x),[|1,3,5,7|],[|24...|],[2^(-40),1],relative);\n var phi = slope * (0.15912117063999176025390625 + s * (-5.185396969318389892578125e-2 + s * (2.476101927459239959716796875e-2 + s * (-7.0547382347285747528076171875e-3))));\n // Map from the first octant to the full circle using quadrant information.\n // Handle the [0, 90] degree range.\n phi = select(phi, 0.25 - phi, xabs < yabs);\n // Handle the [90, 180] degree range.\n phi = select(phi, 0.5 - phi, x < 0.0);\n // Handle the [180, 360] degree range.\n phi = select(phi, 1.0 - phi, y < 0.0);\n // Handle NaN cases (using the property that NaN != NaN).\n phi = select(phi, 0.0, phi != phi);\n return phi;\n}\n\n// Calculate a radial gradient; matches the CPU rasterizer\'s implementation.\n// Returns [t_value, validity], where validity is 0.0 for a sample that has no\n// gradient coverage at all.\nfn calculate_radial_gradient(\n grad_pos: vec2<f32>,\n texel2: vec4<u32>,\n texel3: vec4<u32>,\n) -> vec2<f32> {\n let x_pos = grad_pos.x;\n let y_pos = grad_pos.y;\n\n var t_value: f32;\n var is_valid: bool;\n let kind = get_radial_kind(texel2);\n\n switch kind {\n case RADIAL_GRADIENT_TYPE_STANDARD: {\n // Standard radial gradient: bias + scale * sqrt(x^2 + y^2).\n let radius = sqrt(x_pos * x_pos + y_pos * y_pos);\n t_value = get_radial_bias(texel2) + get_radial_scale(texel2) * radius;\n // Radial gradients are always valid.\n is_valid = true;\n }\n case RADIAL_GRADIENT_TYPE_STRIP: {\n // Strip gradient: x + sqrt(scaled_r0_squared - y^2).\n let p1 = get_radial_scaled_r0_squared(texel3) - y_pos * y_pos;\n // Invalid if negative under the square root.\n is_valid = p1 >= 0.0;\n if is_valid {\n t_value = x_pos + sqrt(p1);\n } else {\n // Value doesn\'t matter when invalid.\n t_value = 0.0;\n }\n }\n case RADIAL_GRADIENT_TYPE_FOCAL, default: {\n var t = 0.0;\n let fp0 = get_radial_fp0(texel2);\n let fp1 = get_radial_fp1(texel3);\n let fr1 = get_radial_fr1(texel3);\n let f_focal_x = get_radial_f_focal_x(texel3);\n let is_swapped = get_radial_f_is_swapped(texel2);\n\n // Focal flags, derived from the encoded field values.\n let is_focal_on_circle = abs(1.0 - fr1) <= NEARLY_ZERO_TOLERANCE;\n let is_well_behaved = !is_focal_on_circle && fr1 > 1.0;\n let is_natively_focal = abs(f_focal_x) <= NEARLY_ZERO_TOLERANCE;\n\n // Start with the valid assumption.\n is_valid = true;\n\n if is_focal_on_circle {\n t = x_pos + y_pos * y_pos / x_pos;\n // Check for division by zero and negative t.\n is_valid = t >= 0.0 && x_pos != 0.0;\n } else if is_well_behaved {\n t = sqrt(x_pos * x_pos + y_pos * y_pos) - x_pos * fp0;\n } else {\n // For non-well-behaved gradients, check whether the\n // calculation is valid.\n let xx = x_pos * x_pos;\n let yy = y_pos * y_pos;\n let discriminant = xx - yy;\n\n if is_swapped != 0u || (1.0 - f_focal_x < 0.0) {\n t = -sqrt(discriminant) - x_pos * fp0;\n } else {\n t = sqrt(discriminant) - x_pos * fp0;\n }\n\n // Invalid if the discriminant is negative or t is negative.\n is_valid = discriminant >= 0.0 && t >= 0.0;\n }\n\n // Apply the additional focal transforms only if still valid.\n if is_valid {\n if 1.0 - f_focal_x < 0.0 {\n t = -t;\n }\n\n if !is_natively_focal {\n t = t + fp1;\n }\n\n if is_swapped != 0u {\n t = 1.0 - t;\n }\n }\n\n t_value = t;\n }\n }\n\n return vec2<f32>(t_value, select(0.0, 1.0, is_valid));\n}\n\n// -----------------------------------------------------------------------------\n// Encoded blurred-rounded-rect accessors and evaluation\n// -----------------------------------------------------------------------------\n\n// 2x2 linear part of the affine transform (columns [a,b] and [c,d]).\nfn get_blurred_rounded_rect_transform(texel0: vec4<u32>) -> mat2x2<f32> {\n return mat2x2<f32>(\n vec2<f32>(bitcast<f32>(texel0.x), bitcast<f32>(texel0.y)),\n vec2<f32>(bitcast<f32>(texel0.z), bitcast<f32>(texel0.w))\n );\n}\n\n// Translation part of the affine transform [tx, ty].\nfn get_blurred_rounded_rect_translate(texel1: vec4<u32>) -> vec2<f32> {\n return vec2<f32>(bitcast<f32>(texel1.x), bitcast<f32>(texel1.y));\n}\n\n// Premultiplied rectangle color.\nfn get_blurred_rounded_rect_color(texel1: vec4<u32>) -> vec4<f32> { return unpack4x8unorm(texel1.z); }\n\n// Whether to paint the inverse (`1 - alpha`) of the blur coverage.\nfn get_blurred_rounded_rect_invert(texel1: vec4<u32>) -> u32 { return texel1.w; }\n\nfn get_blurred_rounded_rect_exponent(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.x); }\n\nfn get_blurred_rounded_rect_recip_exponent(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.y); }\n\nfn get_blurred_rounded_rect_scale(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.z); }\n\nfn get_blurred_rounded_rect_std_dev_inv(texel2: vec4<u32>) -> f32 { return bitcast<f32>(texel2.w); }\n\nfn get_blurred_rounded_rect_min_edge(texel3: vec4<u32>) -> f32 { return bitcast<f32>(texel3.x); }\n\nfn get_blurred_rounded_rect_w(texel3: vec4<u32>) -> f32 { return bitcast<f32>(texel3.y); }\n\nfn get_blurred_rounded_rect_h(texel3: vec4<u32>) -> f32 { return bitcast<f32>(texel3.z); }\n\nfn get_blurred_rounded_rect_r1(texel3: vec4<u32>) -> f32 { return bitcast<f32>(texel3.w); }\n\nfn get_blurred_rounded_rect_width(texel4: vec4<u32>) -> f32 { return bitcast<f32>(texel4.x); }\n\nfn get_blurred_rounded_rect_height(texel4: vec4<u32>) -> f32 { return bitcast<f32>(texel4.y); }\n\n// Approximation to erf, matching the CPU rasterizer\'s blur painter.\nfn erf7(x: f32) -> f32 {\n let y = clamp(x * 1.1283791671, -100.0, 100.0);\n let yy = y * y;\n let z = y + (0.24295 + (0.03395 + 0.0104 * yy) * yy) * (y * yy);\n return z / sqrt(1.0 + z * z);\n}\n\n// Approximation for the convolution of a gaussian filter with a rounded\n// rectangle, modelled after the CPU rasterizer\'s blurred-rounded-rect painter.\nfn calculate_blurred_rounded_rect(\n fragment_pos: vec2<f32>,\n texel0: vec4<u32>,\n texel1: vec4<u32>,\n texel2: vec4<u32>,\n texel3: vec4<u32>,\n texel4: vec4<u32>,\n) -> vec4<f32> {\n let transform = get_blurred_rounded_rect_transform(texel0);\n let translate = get_blurred_rounded_rect_translate(texel1);\n let color = get_blurred_rounded_rect_color(texel1);\n let invert = get_blurred_rounded_rect_invert(texel1);\n let exponent = get_blurred_rounded_rect_exponent(texel2);\n let recip_exponent = get_blurred_rounded_rect_recip_exponent(texel2);\n let scale = get_blurred_rounded_rect_scale(texel2);\n let std_dev_inv = get_blurred_rounded_rect_std_dev_inv(texel2);\n let min_edge = get_blurred_rounded_rect_min_edge(texel3);\n let w = get_blurred_rounded_rect_w(texel3);\n let h = get_blurred_rounded_rect_h(texel3);\n let r1 = get_blurred_rounded_rect_r1(texel3);\n let width = get_blurred_rounded_rect_width(texel4);\n let height = get_blurred_rounded_rect_height(texel4);\n\n let local_xy = transform * fragment_pos + translate;\n // The 0.5 and 0.0 constants correspond to the CPU painter\'s v1 and v0.\n let y = local_xy.y - 0.5 * height;\n let y0 = r1 + abs(y) - 0.5 * h;\n let y1 = max(y0, 0.0);\n\n let x = local_xy.x - 0.5 * width;\n let x0 = r1 + abs(x) - 0.5 * w;\n let x1 = max(x0, 0.0);\n\n let d_pos = pow(\n pow(x1, exponent) + pow(y1, exponent),\n recip_exponent,\n );\n let d_neg = min(max(x0, y0), 0.0);\n let d = d_pos + d_neg - r1;\n let blur_coverage = scale * (\n erf7(std_dev_inv * (min_edge + d)) -\n erf7(std_dev_inv * d)\n );\n\n // Invert alpha when the `invert` flag is set.\n let blur_alpha = select(blur_coverage, 1.0 - blur_coverage, invert != 0u);\n\n return color * blur_alpha;\n}\n\n// -----------------------------------------------------------------------------\n// Encoded image accessors\n// -----------------------------------------------------------------------------\n\n// Encoded image layout. Must match `GpuEncodedImage` in `gpu::paint_texture`.\n//\n// texel0.x: image_params\n// bits 0-1: quality\n// bits 2-3: extend_x\n// bits 4-5: extend_y\n// bits 6-13: atlas_index\n// bit 14: source_kind (0=atlas, 1=external texture)\n// texel0.y: image_size, packed as [width:16, height:16]\n// texel0.z: image_offset, packed as [x:16, y:16]\n// texel0.w/texel1.x/texel1.y/texel1.z: transform matrix [a, b, c, d]\n// texel1.w/texel2.x: translation [tx, ty]\n// texel2.y: premultiplied tint color packed as RGBA8 unorm\n// texel2.z: tint mode\n// texel2.w: transparent padding pixels around the image in the atlas\n\n// The rendering quality of the image.\nfn get_image_quality(texel0: vec4<u32>) -> u32 { return texel0.x & 0x3u; }\n\n// The extend modes in the horizontal and vertical direction.\nfn get_image_extend_modes(texel0: vec4<u32>) -> vec2<u32> {\n return vec2<u32>((texel0.x >> 2u) & 0x3u, (texel0.x >> 4u) & 0x3u);\n}\n\n// The size of the image in pixels.\nfn get_image_size(texel0: vec4<u32>) -> vec2<f32> {\n return vec2<f32>(f32(texel0.y >> 16u), f32(texel0.y & 0xFFFFu));\n}\n\n// The offset of the image in pixels.\nfn get_image_offset(texel0: vec4<u32>) -> vec2<f32> {\n return vec2<f32>(f32(texel0.z >> 16u), f32(texel0.z & 0xFFFFu));\n}\n\n// The atlas index containing this image.\nfn get_image_atlas_index(texel0: vec4<u32>) -> u32 { return (texel0.x >> 6u) & 0xFFu; }\n\n// Whether the image is sourced from the atlas or the externally bound texture.\nfn get_image_source_kind(texel0: vec4<u32>) -> u32 { return (texel0.x >> 14u) & 0x1u; }\n\n// 2x2 linear part of the affine transform (columns [a,b] and [c,d]).\nfn get_image_transform(texel0: vec4<u32>, texel1: vec4<u32>) -> mat2x2<f32> {\n return mat2x2<f32>(\n vec2<f32>(bitcast<f32>(texel0.w), bitcast<f32>(texel1.x)),\n vec2<f32>(bitcast<f32>(texel1.y), bitcast<f32>(texel1.z))\n );\n}\n\n// Translation part of the affine transform [tx, ty].\nfn get_image_translate(texel1: vec4<u32>, texel2: vec4<u32>) -> vec2<f32> {\n return vec2<f32>(bitcast<f32>(texel1.w), bitcast<f32>(texel2.x));\n}\n\n// Number of transparent padding pixels around the image in the atlas.\nfn get_image_padding(texel2: vec4<u32>) -> f32 { return f32(texel2.w); }\n\n// -----------------------------------------------------------------------------\n// Atlas-array sampling\n// -----------------------------------------------------------------------------\n\n// Bilinear filtering: sample the 4 surrounding texels of the target point and\n// interpolate them with a bilinear filter.\nfn bilinear_sample(\n tex: texture_2d_array<f32>,\n coords: vec2<f32>,\n atlas_idx: i32,\n image_offset: vec2<f32>,\n image_size: vec2<f32>,\n _extend_modes: vec2<u32>,\n _image_padding: f32,\n) -> vec4<f32> {\n let atlas_max = image_offset + image_size - vec2(1.0);\n let atlas_uv_clamped = clamp(coords, image_offset, atlas_max);\n let uv_quad = vec4(floor(atlas_uv_clamped), ceil(atlas_uv_clamped));\n let uv_frac = fract(coords);\n let a = textureLoad(tex, vec2<i32>(uv_quad.xy), atlas_idx, 0);\n let b = textureLoad(tex, vec2<i32>(uv_quad.xw), atlas_idx, 0);\n let c = textureLoad(tex, vec2<i32>(uv_quad.zy), atlas_idx, 0);\n let d = textureLoad(tex, vec2<i32>(uv_quad.zw), atlas_idx, 0);\n return mix(mix(a, b, uv_frac.y), mix(c, d, uv_frac.y), uv_frac.x);\n}\n\n// Bicubic filtering with a Mitchell filter (B=1/3, C=1/3): sample the 16\n// surrounding texels of the target point and interpolate them with a cubic\n// filter. The 4x4 matrix holds the coefficients of the cubic function used to\n// derive the weights from the fractional part of the sample location.\nfn bicubic_sample(\n tex: texture_2d_array<f32>,\n coords: vec2<f32>,\n atlas_idx: i32,\n image_offset: vec2<f32>,\n image_size: vec2<f32>,\n _extend_modes: vec2<u32>,\n _image_padding: f32,\n) -> vec4<f32> {\n let atlas_max = image_offset + image_size - vec2(1.0);\n let frac_coords = fract(coords + 0.5);\n // Cubic weights for the x and y directions.\n let cx = cubic_weights(frac_coords.x);\n let cy = cubic_weights(frac_coords.y);\n\n // Sample the 4x4 grid around `coords`.\n let s00 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-1.5, -1.5), image_offset, atlas_max)), atlas_idx, 0);\n let s10 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-0.5, -1.5), image_offset, atlas_max)), atlas_idx, 0);\n let s20 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(0.5, -1.5), image_offset, atlas_max)), atlas_idx, 0);\n let s30 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(1.5, -1.5), image_offset, atlas_max)), atlas_idx, 0);\n\n let s01 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-1.5, -0.5), image_offset, atlas_max)), atlas_idx, 0);\n let s11 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-0.5, -0.5), image_offset, atlas_max)), atlas_idx, 0);\n let s21 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(0.5, -0.5), image_offset, atlas_max)), atlas_idx, 0);\n let s31 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(1.5, -0.5), image_offset, atlas_max)), atlas_idx, 0);\n\n let s02 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-1.5, 0.5), image_offset, atlas_max)), atlas_idx, 0);\n let s12 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-0.5, 0.5), image_offset, atlas_max)), atlas_idx, 0);\n let s22 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(0.5, 0.5), image_offset, atlas_max)), atlas_idx, 0);\n let s32 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(1.5, 0.5), image_offset, atlas_max)), atlas_idx, 0);\n\n let s03 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-1.5, 1.5), image_offset, atlas_max)), atlas_idx, 0);\n let s13 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-0.5, 1.5), image_offset, atlas_max)), atlas_idx, 0);\n let s23 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(0.5, 1.5), image_offset, atlas_max)), atlas_idx, 0);\n let s33 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(1.5, 1.5), image_offset, atlas_max)), atlas_idx, 0);\n\n // Interpolate in the x direction for each row.\n let row0 = cx.x * s00 + cx.y * s10 + cx.z * s20 + cx.w * s30;\n let row1 = cx.x * s01 + cx.y * s11 + cx.z * s21 + cx.w * s31;\n let row2 = cx.x * s02 + cx.y * s12 + cx.z * s22 + cx.w * s32;\n let row3 = cx.x * s03 + cx.y * s13 + cx.z * s23 + cx.w * s33;\n // Interpolate in the y direction.\n let result = cy.x * row0 + cy.y * row1 + cy.z * row2 + cy.w * row3;\n\n // Clamp alpha first, then clamp the premultiplied color channels against it.\n let a = clamp(result.a, 0.0, 1.0);\n return vec4<f32>(clamp(result.rgb, vec3(0.0), vec3(a)), a);\n}\n\n// Mitchell-Netravali cubic filter coefficients with B=1/3 and C=1/3, matching\n// the CPU rasterizer\'s cubic resampler.\nconst MF: array<vec4<f32>, 4> = array<vec4<f32>, 4>(\n vec4<f32>(\n (1.0 / 6.0) / 3.0,\n -(3.0 / 6.0) / 3.0 - 1.0 / 3.0,\n (3.0 / 6.0) / 3.0 + 2.0 * 1.0 / 3.0,\n -(1.0 / 6.0) / 3.0 - 1.0 / 3.0\n ),\n vec4<f32>(\n 1.0 - (2.0 / 6.0) / 3.0,\n 0.0,\n -3.0 + (12.0 / 6.0) / 3.0 + 1.0 / 3.0,\n 2.0 - (9.0 / 6.0) / 3.0 - 1.0 / 3.0\n ),\n vec4<f32>(\n (1.0 / 6.0) / 3.0,\n (3.0 / 6.0) / 3.0 + 1.0 / 3.0,\n 3.0 - (15.0 / 6.0) / 3.0 - 2.0 * 1.0 / 3.0,\n -2.0 + (9.0 / 6.0) / 3.0 + 1.0 / 3.0\n ),\n vec4<f32>(\n 0.0,\n 0.0,\n -1.0 / 3.0,\n (1.0 / 6.0) / 3.0 + 1.0 / 3.0\n )\n);\n\n// The four cubic weights for a single fractional value.\nfn cubic_weights(fract: f32) -> vec4<f32> {\n return vec4<f32>(\n single_weight(fract, MF[0][0], MF[0][1], MF[0][2], MF[0][3]),\n single_weight(fract, MF[1][0], MF[1][1], MF[1][2], MF[1][3]),\n single_weight(fract, MF[2][0], MF[2][1], MF[2][2], MF[2][3]),\n single_weight(fract, MF[3][0], MF[3][1], MF[3][2], MF[3][3])\n );\n}\n\n// One weight from the fractional value t and the cubic coefficients.\nfn single_weight(t: f32, a: f32, b: f32, c: f32, d: f32) -> f32 {\n return t * (t * (t * d + c) + b) + a;\n}\n\n// -----------------------------------------------------------------------------\n// External-texture sampling\n// -----------------------------------------------------------------------------\n\n// The atlas-array samplers above, restated for a plain 2D texture: an\n// externally bound image is a whole texture rather than a page of the array.\n\nfn external_bilinear_sample(\n tex: texture_2d<f32>,\n coords: vec2<f32>,\n image_offset: vec2<f32>,\n image_size: vec2<f32>,\n) -> vec4<f32> {\n let image_max = image_offset + image_size - vec2(1.0);\n let clamped_coords = clamp(coords, image_offset, image_max);\n let coord_quad = vec4(floor(clamped_coords), ceil(clamped_coords));\n let coord_frac = fract(coords);\n let a = textureLoad(tex, vec2<i32>(coord_quad.xy), 0);\n let b = textureLoad(tex, vec2<i32>(coord_quad.xw), 0);\n let c = textureLoad(tex, vec2<i32>(coord_quad.zy), 0);\n let d = textureLoad(tex, vec2<i32>(coord_quad.zw), 0);\n return mix(mix(a, b, coord_frac.y), mix(c, d, coord_frac.y), coord_frac.x);\n}\n\nfn external_bicubic_sample(\n tex: texture_2d<f32>,\n coords: vec2<f32>,\n image_offset: vec2<f32>,\n image_size: vec2<f32>,\n) -> vec4<f32> {\n let image_max = image_offset + image_size - vec2(1.0);\n let frac_coords = fract(coords + 0.5);\n let cx = cubic_weights(frac_coords.x);\n let cy = cubic_weights(frac_coords.y);\n\n let s00 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-1.5, -1.5), image_offset, image_max)), 0);\n let s10 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-0.5, -1.5), image_offset, image_max)), 0);\n let s20 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(0.5, -1.5), image_offset, image_max)), 0);\n let s30 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(1.5, -1.5), image_offset, image_max)), 0);\n\n let s01 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-1.5, -0.5), image_offset, image_max)), 0);\n let s11 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-0.5, -0.5), image_offset, image_max)), 0);\n let s21 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(0.5, -0.5), image_offset, image_max)), 0);\n let s31 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(1.5, -0.5), image_offset, image_max)), 0);\n\n let s02 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-1.5, 0.5), image_offset, image_max)), 0);\n let s12 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-0.5, 0.5), image_offset, image_max)), 0);\n let s22 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(0.5, 0.5), image_offset, image_max)), 0);\n let s32 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(1.5, 0.5), image_offset, image_max)), 0);\n\n let s03 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-1.5, 1.5), image_offset, image_max)), 0);\n let s13 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(-0.5, 1.5), image_offset, image_max)), 0);\n let s23 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(0.5, 1.5), image_offset, image_max)), 0);\n let s33 = textureLoad(tex, vec2<i32>(clamp(coords + vec2(1.5, 1.5), image_offset, image_max)), 0);\n\n let row0 = cx.x * s00 + cx.y * s10 + cx.z * s20 + cx.w * s30;\n let row1 = cx.x * s01 + cx.y * s11 + cx.z * s21 + cx.w * s31;\n let row2 = cx.x * s02 + cx.y * s12 + cx.z * s22 + cx.w * s32;\n let row3 = cx.x * s03 + cx.y * s13 + cx.z * s23 + cx.w * s33;\n let result = cy.x * row0 + cy.y * row1 + cy.z * row2 + cy.w * row3;\n\n // Clamp alpha first, then clamp the premultiplied color channels against it.\n let a = clamp(result.a, 0.0, 1.0);\n return vec4<f32>(clamp(result.rgb, vec3(0.0), vec3(a)), a);\n}\n\nfn sample_external_image(\n tex: texture_2d<f32>,\n quality: u32,\n coords: vec2<f32>,\n image_offset: vec2<f32>,\n image_size: vec2<f32>,\n) -> vec4<f32> {\n if quality == IMAGE_QUALITY_HIGH {\n return external_bicubic_sample(tex, coords, image_offset, image_size);\n }\n if quality == IMAGE_QUALITY_MEDIUM {\n return external_bilinear_sample(\n tex,\n coords - vec2(0.5),\n image_offset,\n image_size,\n );\n }\n return textureLoad(tex, vec2<u32>(coords), 0);\n}\n// Copyright 2024 the Vello Authors\n// SPDX-License-Identifier: Apache-2.0 OR MIT\n\n// Derived from vello_sparse_shaders 0.2.0 (`shaders/render.wesl`); its\n// `import package::helpers::...` lines are resolved by prepending\n// `helpers.wgsl` at load time (`gpu::shader_src`).\n//\n// Renders sparse strips with alpha blending. Each strip instance is a\n// horizontal slice of the output made of:\n//\n// 1. a variable-width region of alpha values for semi-transparent rendering, and\n// 2. a solid region for fully opaque areas.\n//\n// The alpha values live in a texture and are sampled during fragment shading,\n// so coverage is stored only where it is actually needed.\n//\n// `StripInstance::paint_and_rect_flag` encodes a color source, a paint type\n// and a paint texture id. The color source says where the fragment shader\n// reads color data from, the paint type says how it uses that data, and the\n// paint texture id locates the encoded paint record in\n// `encoded_paints_texture`. `StripInstance::payload` then carries either a\n// packed color, an [x, y] sample coordinate, or a layer texture origin. See\n// the `StripInstance` comment below for the full bit layout.\n//\n// This module has four pipeline variants over the same two entry points\n// (`gpu::pipelines`): an intermediate-target variant, an alpha-blended\n// variant with and without a depth attachment, and an opaque variant that\n// writes depth.\n\n// Color source modes: where the fragment shader gets color data from.\n// Use the payload (a color or image coordinates).\nconst COLOR_SOURCE_PAYLOAD: u32 = 0u;\n// Sample from a rendered layer texture.\nconst COLOR_SOURCE_LAYER: u32 = 1u;\n\n// Paint types.\nconst PAINT_TYPE_SOLID: u32 = 0u;\nconst PAINT_TYPE_IMAGE: u32 = 1u;\nconst PAINT_TYPE_LINEAR_GRADIENT: u32 = 2u;\nconst PAINT_TYPE_RADIAL_GRADIENT: u32 = 3u;\nconst PAINT_TYPE_SWEEP_GRADIENT: u32 = 4u;\nconst PAINT_TYPE_BLURRED_ROUNDED_RECT: u32 = 5u;\n\n// Paint texture index mask (the low 26 bits of the paint field).\nconst PAINT_TEXTURE_INDEX_MASK: u32 = 0x03FFFFFFu;\n\nconst RECT_STRIP_FLAG: u32 = 0x80000000u;\n\n// Must stay byte-identical to `GpuConfig` in `gpu::config`.\nstruct Config {\n // Width of the rendering target.\n width: u32,\n // Height of the rendering target.\n height: u32,\n // Height of a strip in pixels.\n // CAUTION: changing this value also requires changing the fragment\n // shader\'s alpha unpacking, which assumes one channel per strip column.\n strip_height: u32,\n // Number of trailing zeros in the alpha texture\'s width (its log2),\n // pre-computed on the CPU because a downlevel (GLES 3.0 / WebGL2) target\n // has no bit-scan intrinsic to derive it here.\n alphas_tex_width_bits: u32,\n // Number of trailing zeros in the encoded-paint texture\'s width, for the\n // same reason as `alphas_tex_width_bits`.\n encoded_paints_tex_width_bits: u32,\n // An offset applied to every strip.\n //\n // Usually zero. Rendering a filter layer needs it to account for both the\n // shift caused by rendering only the tight bounding box of that layer and\n // the offset of the layer\'s destination within an atlas.\n strip_offset_x: i32,\n strip_offset_y: i32,\n // Whether to flip the y component of the NDC coordinates.\n negate_ndc: u32,\n}\n\n// A `StripInstance` is either a **normal strip** (a sparse fill or alpha fill\n// of height `Config::strip_height`) or a **rect strip** (a whole rectangle\n// drawn as one quad, with anti-aliasing). The two are distinguished by\n// RECT_STRIP_FLAG (bit 31 of `paint_and_rect_flag`).\n//\n// The fields are read differently in each mode:\n//\n// Field | Normal strip | Rect strip\n// ----------------------+-----------------------------------+-----------------------------------\n// xy | Strip position | Rect top-left (snapped outward)\n// widths_or_rect_height | [width, dense_width] | [width, height] (both snapped)\n// col_idx_or_rect_frac | Alpha column index | Packed AA edge fractions (4 x u8)\n// payload | Color / scene coords / layer xy | Color / scene coords / layer xy\n// paint_and_rect_flag | Paint encoding | Paint encoding | RECT_STRIP_FLAG\n//\n// `paint_and_rect_flag` bit layout:\n// - Bit 31: `RECT_STRIP_FLAG` 0 = normal strip, 1 = rect strip\n// - Bits 29-30: `color_source` 0 = use payload, 1 = use layer texture\n// - Bits 0-28: Usage depends on color_source:\n//\n// When color_source = 0 (COLOR_SOURCE_PAYLOAD):\n// - Bits 26-28: `paint_type` (0 = solid, 1 = image, 2 = linear gradient,\n// 3 = radial gradient, 4 = sweep gradient, 5 = blurred rounded rect)\n// - Bits 0-25:\n// - If paint_type = 0: unused\n// - If paint_type >= 1: `paint_texture_idx`\n//\n// When color_source = 1 (COLOR_SOURCE_LAYER):\n// - Bits 0-7: opacity (0-255)\n// - Bits 8-28: unused\n//\n// Decision tree for paint/payload interpretation:\n//\n// color_source = 0 (COLOR_SOURCE_PAYLOAD) - use the payload directly\n// |-- paint_type = 0 (PAINT_TYPE_SOLID)\n// | \\-- payload = [r, g, b, a] RGBA (packed as u8s)\n// |\n// |-- paint_type = 1 (PAINT_TYPE_IMAGE)\n// | \\-- payload = packed image parameters\n// |\n// |-- paint_type = 2/3/4 (LINEAR / RADIAL / SWEEP gradient)\n// | |-- payload = [x, y] scene coordinates (packed as u16s)\n// | \\-- bits 0-25 = paint_texture_idx\n// \\-- paint_type = 5 (PAINT_TYPE_BLURRED_ROUNDED_RECT)\n// |-- payload = [x, y] scene coordinates (packed as u16s)\n// \\-- bits 0-25 = paint_texture_idx\n//\n// color_source = 1 (COLOR_SOURCE_LAYER) - use the rendered layer texture\n// |-- payload = [x, y] source layer texture origin (packed as u16s)\n// \\-- bits 0-7 = opacity\n//\n// Must stay byte-identical to `GpuStrip` in `gpu::strips`.\nstruct StripInstance {\n // [x, y] packed as u16s: the coordinates of the strip or rect.\n @location(0)\n xy: u32,\n // [width, dense_width] packed as u16s.\n // width \u{2014} width of the strip or rect.\n // dense_width \u{2014} width of the portion alpha blending applies to. For an\n // anti-aliased strip width = dense_width; for a sparse fill region\n // dense_width = 0. For a rect strip, dense_width holds the rect height\n // instead.\n @location(1)\n widths_or_rect_height: u32,\n // For normal strips: the alpha texture column index this strip\'s alpha\n // values begin at. There are `Config::strip_height` alpha values per\n // column. For rect strips: packed fractional edge offsets for AA.\n @location(2)\n col_idx_or_rect_frac: u32,\n // See the StripInstance comment above.\n @location(3)\n payload: u32,\n // See the StripInstance comment above.\n @location(4)\n paint_and_rect_flag: u32,\n // Painter\'s-order index driving the z-depth computation.\n @location(5)\n depth_index: u32,\n}\n\nstruct VertexOutput {\n // Paint encoding plus the rect flag for this strip.\n @location(0) @interpolate(flat)\n paint_and_rect_flag: u32,\n // Texture coordinates for the current fragment.\n @location(1)\n tex_coord: vec2<f32>,\n // Coordinates the paint is sampled at, used for images and gradients.\n @location(2)\n sample_xy: vec2<f32>,\n // For normal strips: the ending x position of the dense (alpha) region.\n // For rect strips: packed dimensions (width | height << 16).\n @location(3) @interpolate(flat)\n dense_end_or_rect_size: u32,\n // Packed paint payload or layer sample coordinate.\n @location(4) @interpolate(flat)\n payload: u32,\n // Packed fractional edge offsets for rectangles.\n // Bits 0-7: x0, 8-15: y0, 16-23: x1, 24-31: y1. Zero for normal strips.\n @location(5) @interpolate(flat)\n rect_frac: u32,\n // Normalized device coordinates (NDC) for the current vertex.\n @builtin(position)\n position: vec4<f32>,\n};\n\n@group(0) @binding(0)\nvar alphas_texture: texture_2d<u32>;\n\n@group(0) @binding(1)\nvar<uniform> config: Config;\n\n@group(0) @binding(2)\nvar layer_input_texture: texture_2d<f32>;\n\n@group(1) @binding(0)\nvar atlas_texture_array: texture_2d_array<f32>;\n\n@group(1) @binding(1)\nvar external_texture: texture_2d<f32>;\n\n@group(2) @binding(0)\nvar encoded_paints_texture: texture_2d<u32>;\n\n@group(3) @binding(0)\nvar gradient_texture: texture_2d<f32>;\n\n// Convert a flat texel index to 2D coordinates in the encoded-paints texture.\nfn encoded_paint_coord(flat_idx: u32) -> vec2<u32> {\n return vec2<u32>(\n flat_idx & ((1u << config.encoded_paints_tex_width_bits) - 1u),\n flat_idx >> config.encoded_paints_tex_width_bits\n );\n}\n\nfn load_encoded_paint_texel(paint_tex_idx: u32, texel_offset: u32) -> vec4<u32> {\n return textureLoad(\n encoded_paints_texture,\n encoded_paint_coord(paint_tex_idx + texel_offset),\n 0,\n );\n}\n\n@vertex\nfn vs_main(\n @builtin(vertex_index) in_vertex_index: u32,\n instance: StripInstance,\n) -> VertexOutput {\n var out: VertexOutput;\n out.sample_xy = vec2(0.0);\n // Map vertex_index (0-3) to quad corners:\n // 0 -> (0,0), 1 -> (1,0), 2 -> (0,1), 3 -> (1,1)\n let corner = quad_corner(in_vertex_index);\n let x = corner.x;\n let y = corner.y;\n // Unpack the x and y coordinates from the packed u32 instance.xy.\n let strip_position = unpack_u16_pair(instance.xy);\n let widths = unpack_u16_pair(instance.widths_or_rect_height);\n let x0 = strip_position.x;\n let y0 = strip_position.y;\n let width = widths.x;\n let dense_width = widths.y;\n\n let is_rect = (instance.paint_and_rect_flag & RECT_STRIP_FLAG) != 0u;\n var height = config.strip_height;\n if is_rect {\n height = dense_width;\n out.dense_end_or_rect_size = width | (dense_width << 16u);\n out.rect_frac = instance.col_idx_or_rect_frac;\n } else {\n out.dense_end_or_rect_size = instance.col_idx_or_rect_frac + dense_width;\n out.rect_frac = 0u;\n }\n // Pixel coordinates of this vertex within the strip, with the strip\n // offset applied.\n let pixel = vec2<f32>(\n f32(i32(x0) + config.strip_offset_x) + x * f32(width),\n f32(i32(y0) + config.strip_offset_y) + y * f32(height),\n );\n // Convert pixel coordinates to normalized device coordinates, which range\n // from -1 to 1 with (0,0) at the center of the viewport.\n let ndc = pixel_to_ndc(pixel, vec2<f32>(f32(config.width), f32(config.height)));\n\n let color_source = (instance.paint_and_rect_flag >> 29u) & 0x3u;\n if color_source == COLOR_SOURCE_PAYLOAD {\n let paint_type = (instance.paint_and_rect_flag >> 26u) & 0x7u;\n // Unpack the view coordinates used for image sampling and gradients.\n let scene_strip = unpack_u16_pair(instance.payload);\n\n if paint_type == PAINT_TYPE_IMAGE {\n let paint_tex_idx = instance.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK;\n let image_texel0 = load_encoded_paint_texel(paint_tex_idx, 0u);\n let image_texel1 = load_encoded_paint_texel(paint_tex_idx, 1u);\n let image_texel2 = load_encoded_paint_texel(paint_tex_idx, 2u);\n // Image sampling always happens in global view space.\n let pos = vec2<f32>(\n f32(scene_strip.x) + x * f32(width),\n f32(scene_strip.y) + y * f32(height),\n );\n out.sample_xy = get_image_translate(image_texel1, image_texel2)\n + get_image_offset(image_texel0)\n + get_image_transform(image_texel0, image_texel1) * pos;\n } else if paint_type == PAINT_TYPE_LINEAR_GRADIENT || paint_type == PAINT_TYPE_RADIAL_GRADIENT || paint_type == PAINT_TYPE_SWEEP_GRADIENT || paint_type == PAINT_TYPE_BLURRED_ROUNDED_RECT {\n // The gradient transform is likewise applied in global view space.\n out.sample_xy = vec2<f32>(\n f32(scene_strip.x) + x * f32(width),\n f32(scene_strip.y) + y * f32(height)\n );\n }\n } else if color_source == COLOR_SOURCE_LAYER {\n let source = unpack_u16_pair(instance.payload);\n out.sample_xy = vec2<f32>(\n f32(source.x) + x * f32(width),\n f32(source.y) + y * f32(height),\n );\n }\n\n let col_offset = select(f32(instance.col_idx_or_rect_frac), 0.0, is_rect);\n out.tex_coord = vec2<f32>(col_offset + x * f32(width), y * f32(height));\n\n // Divide by a power of two so the arithmetic is exact in f32, and by the\n // expected 24 bits of depth-buffer precision.\n let z = 1.0 - f32(instance.depth_index) / f32(1u << 24u);\n // Flip y based on the config flag.\n let final_ndc_y = select(ndc.y, -ndc.y, config.negate_ndc != 0u);\n out.position = vec4<f32>(ndc.x, final_ndc_y, z, 1.0);\n out.payload = instance.payload;\n out.paint_and_rect_flag = instance.paint_and_rect_flag;\n\n return out;\n}\n\n@fragment\nfn fs_main(\n @location(0) @interpolate(flat) paint_and_rect_flag: u32,\n @location(1) tex_coord: vec2<f32>,\n @location(2) sample_xy: vec2<f32>,\n @location(3) @interpolate(flat) dense_end_or_rect_size: u32,\n @location(4) @interpolate(flat) payload: u32,\n @location(5) @interpolate(flat) rect_frac: u32,\n @builtin(position) position: vec4<f32>,\n) -> @location(0) vec4<f32> {\n var alpha = 1.0;\n let is_rect = (paint_and_rect_flag & RECT_STRIP_FLAG) != 0u;\n if is_rect && rect_frac != 0u {\n let frac = unpack4x8unorm(rect_frac);\n // How much of the pixel the rect actually covers: the fractions in\n // the x and y direction, multiplied. Both directions are computed in\n // one pass by packing them into a vec2.\n let rect_size = vec2<f32>(unpack_u16_pair(dense_end_or_rect_size));\n let tc = tex_coord;\n // +0.5 / -0.5 because the fragment shader positions coordinates at\n // the center of the pixel.\n let bottom_and_right = min(tc + 0.5, rect_size - frac.zw);\n let top_and_left = max(tc - 0.5, frac.xy);\n let a = clamp(bottom_and_right - top_and_left, vec2(0.0), vec2(1.0));\n alpha = a.x * a.y;\n } else if !is_rect && dense_end_or_rect_size != 0u {\n let x = u32(floor(tex_coord.x));\n let y = u32(floor(tex_coord.y));\n // Retrieve the alpha value from the texture. 16 1-byte alpha values\n // live in one texel, with each color channel packing 4 of them. This\n // assumes a strip height of 4, i.e. each channel encodes the alpha\n // values of a single column within a strip.\n let alphas_index = x;\n let tex_dimensions = textureDimensions(alphas_texture);\n let alphas_tex_width = tex_dimensions.x;\n // Which texel holds the alpha values for this column.\n let texel_index = alphas_index / 4u;\n // Which channel (R,G,B,A) of that texel holds them.\n let channel_index = alphas_index % 4u;\n let tex_x = texel_index & (alphas_tex_width - 1u);\n let tex_y = texel_index >> config.alphas_tex_width_bits;\n\n // Load all 4 channels from the texture.\n let rgba_values = textureLoad(alphas_texture, vec2<u32>(tex_x, tex_y), 0);\n\n // Take the column\'s alphas from the channel the index selects.\n let alphas_u32 = unpack_alphas_from_channel(rgba_values, channel_index);\n // Extract the alpha for the current y position from the packed u32.\n alpha = f32((alphas_u32 >> (y * 8u)) & 0xffu) * (1.0 / 255.0);\n }\n // Apply the alpha value to the unpacked RGBA color or the sampled paint.\n let color_source = (paint_and_rect_flag >> 29u) & 0x3u;\n var final_color: vec4<f32>;\n\n if color_source == COLOR_SOURCE_PAYLOAD {\n let paint_type = (paint_and_rect_flag >> 26u) & 0x7u;\n\n // `payload` encodes a color for PAINT_TYPE_SOLID, or sample\n // coordinates for the other paint types.\n if paint_type == PAINT_TYPE_SOLID {\n final_color = alpha * unpack4x8unorm(payload);\n } else if paint_type == PAINT_TYPE_IMAGE {\n let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK;\n let image_texel0 = load_encoded_paint_texel(paint_tex_idx, 0u);\n let image_texel1 = load_encoded_paint_texel(paint_tex_idx, 1u);\n let image_texel2 = load_encoded_paint_texel(paint_tex_idx, 2u);\n let image_offset = get_image_offset(image_texel0);\n let image_size = get_image_size(image_texel0);\n let image_extend_modes = get_image_extend_modes(image_texel0);\n let image_atlas_index = get_image_atlas_index(image_texel0);\n let image_quality = get_image_quality(image_texel0);\n let image_source_kind = get_image_source_kind(image_texel0);\n let image_padding = get_image_padding(image_texel2);\n let packed_tint = image_texel2.y;\n let image_tint = unpack4x8unorm(packed_tint);\n let is_multiply = image_texel2.z == TINT_MODE_MULTIPLY;\n let local_xy = sample_xy - image_offset;\n // A small offset the CPU rasterizer does not need: 45-degree\n // skewing produces artifacts on the GPU without it. Gradients\n // carry an equivalent bias below.\n let offset = 0.00001;\n let extended_xy = vec2<f32>(\n extend_mode(local_xy.x + offset, image_extend_modes.x, image_size.x),\n extend_mode(local_xy.y + offset, image_extend_modes.y, image_size.y)\n );\n\n var sample_color: vec4<f32>;\n if image_source_kind == IMAGE_SOURCE_EXTERNAL {\n let final_xy = image_offset + extended_xy;\n sample_color = sample_external_image(\n external_texture,\n image_quality,\n final_xy,\n image_offset,\n image_size,\n );\n } else if image_quality == IMAGE_QUALITY_HIGH {\n let final_xy = image_offset + extended_xy;\n sample_color = bicubic_sample(\n atlas_texture_array,\n final_xy,\n i32(image_atlas_index),\n image_offset,\n image_size,\n image_extend_modes,\n image_padding,\n );\n } else if image_quality == IMAGE_QUALITY_MEDIUM {\n let final_xy = image_offset + extended_xy - vec2(0.5);\n sample_color = bilinear_sample(\n atlas_texture_array,\n final_xy,\n i32(image_atlas_index),\n image_offset,\n image_size,\n image_extend_modes,\n image_padding,\n );\n } else {\n let final_xy = image_offset + extended_xy;\n sample_color = textureLoad(\n atlas_texture_array,\n vec2<u32>(final_xy),\n i32(image_atlas_index),\n 0,\n );\n }\n\n final_color = alpha * select(\n image_tint * sample_color.a,\n sample_color * image_tint,\n is_multiply\n );\n } else if paint_type == PAINT_TYPE_LINEAR_GRADIENT {\n let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK;\n let gradient_texel0 = load_encoded_paint_texel(paint_tex_idx, 0u);\n let gradient_texel1 = load_encoded_paint_texel(paint_tex_idx, 1u);\n\n // Fragment position with the gradient\'s affine transform applied.\n let fragment_pos = sample_xy;\n let grad_pos = apply_gradient_transform(gradient_texel0, gradient_texel1, fragment_pos);\n\n // For a linear gradient the t value is just the x coordinate in\n // gradient space.\n let t_value = grad_pos.x + 0.00001;\n let gradient_color = sample_gradient_lut(\n gradient_texture,\n t_value,\n get_gradient_extend_mode(gradient_texel0),\n get_gradient_start(gradient_texel0),\n get_gradient_texture_width(gradient_texel0)\n );\n final_color = alpha * gradient_color;\n } else if paint_type == PAINT_TYPE_RADIAL_GRADIENT {\n let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK;\n let gradient_texel0 = load_encoded_paint_texel(paint_tex_idx, 0u);\n let gradient_texel1 = load_encoded_paint_texel(paint_tex_idx, 1u);\n let gradient_texel2 = load_encoded_paint_texel(paint_tex_idx, 2u);\n let gradient_texel3 = load_encoded_paint_texel(paint_tex_idx, 3u);\n\n let fragment_pos = sample_xy;\n let grad_pos = apply_gradient_transform(gradient_texel0, gradient_texel1, fragment_pos);\n\n // For a radial gradient, evaluate the distance from the center.\n let gradient_result = calculate_radial_gradient(grad_pos, gradient_texel2, gradient_texel3);\n let gradient_color = sample_gradient_lut(\n gradient_texture,\n gradient_result.x,\n get_gradient_extend_mode(gradient_texel0),\n get_gradient_start(gradient_texel0),\n get_gradient_texture_width(gradient_texel0)\n );\n final_color = select(\n vec4<f32>(0.0, 0.0, 0.0, 0.0),\n alpha * gradient_color,\n gradient_result.y != 0.0\n );\n } else if paint_type == PAINT_TYPE_SWEEP_GRADIENT {\n let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK;\n let gradient_texel0 = load_encoded_paint_texel(paint_tex_idx, 0u);\n let gradient_texel1 = load_encoded_paint_texel(paint_tex_idx, 1u);\n let gradient_texel2 = load_encoded_paint_texel(paint_tex_idx, 2u);\n\n let fragment_pos = sample_xy;\n var grad_pos = apply_gradient_transform(gradient_texel0, gradient_texel1, fragment_pos);\n\n // Bias very small coordinates to zero before the angle\n // calculation. The angle calculation picks a quadrant from the\n // coordinates\' signs, so for coordinates around zero slight noise\n // lands it in different quadrants from frame to frame; the\n // resulting flicker is very visible because the sweep gradient\'s\n // seam is not anti-aliased, and it varies across machines.\n grad_pos = select(grad_pos, vec2(0.0), abs(grad_pos) < vec2(NEARLY_ZERO_TOLERANCE));\n\n // For a sweep gradient, take the angle from the center using the\n // fast polynomial approximation.\n let unit_angle = xy_to_unit_angle(grad_pos.x, grad_pos.y);\n // Convert the unit angle [0, 1) to radians [0, 2*PI).\n let angle = unit_angle * TWO_PI;\n let t_value = (angle - get_sweep_start_angle(gradient_texel2)) * get_sweep_inv_angle_delta(gradient_texel2);\n let gradient_color = sample_gradient_lut(\n gradient_texture,\n t_value,\n get_gradient_extend_mode(gradient_texel0),\n get_gradient_start(gradient_texel0),\n get_gradient_texture_width(gradient_texel0)\n );\n final_color = alpha * gradient_color;\n } else if paint_type == PAINT_TYPE_BLURRED_ROUNDED_RECT {\n let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK;\n let blurred_texel0 = load_encoded_paint_texel(paint_tex_idx, 0u);\n let blurred_texel1 = load_encoded_paint_texel(paint_tex_idx, 1u);\n let blurred_texel2 = load_encoded_paint_texel(paint_tex_idx, 2u);\n let blurred_texel3 = load_encoded_paint_texel(paint_tex_idx, 3u);\n let blurred_texel4 = load_encoded_paint_texel(paint_tex_idx, 4u);\n final_color = alpha * calculate_blurred_rounded_rect(\n sample_xy,\n blurred_texel0,\n blurred_texel1,\n blurred_texel2,\n blurred_texel3,\n blurred_texel4,\n );\n }\n } else if color_source == COLOR_SOURCE_LAYER {\n let layer_opacity = f32(paint_and_rect_flag & 0xffu) * (1.0 / 255.0);\n final_color = alpha * layer_opacity * textureLoad(layer_input_texture, vec2<i32>(sample_xy), 0);\n } else {\n final_color = vec4<f32>(0.0);\n }\n\n return final_color;\n}\n";Expand description
The sparse-strip rasterizer: vs_main + fs_main, four pipeline variants
(see crate::gpu::pipelines).