.
diff --git a/sparse_strips/vello_sparse_shaders/shaders/filters.wgsl b/sparse_strips/vello_sparse_shaders/shaders/filters.wgsl index 166a338..9093bda 100644 --- a/sparse_strips/vello_sparse_shaders/shaders/filters.wgsl +++ b/sparse_strips/vello_sparse_shaders/shaders/filters.wgsl
@@ -11,6 +11,11 @@ // for the drop shadow filter, we need to composite the original content on top of the shadow. @group(2) @binding(0) var original_tex: texture_2d<f32>; +// VERY IMPORTANT NOTE: Whenever making use of structs in the fragment shader, +// make sure that any field could be interpreted using 16-bit precision and still work +// correctly!! See <ISSUE> for more information. +// If that's not possible, avoid using structs and make use of native types (like vectors) instead. + // Keep these variables and structs in sync with the ones in `filter.rs`! const FILTER_SIZE_BYTES: u32 = 48; @@ -33,20 +38,6 @@ const MAX_TAPS_PER_SIDE: u32 = 3u; -// A type erased instance of a filter containing the values of all parameters. -struct GpuFilterData { - data: array<u32, FILTER_SIZE_U32> -} - -struct OffsetFilter { - dx: f32, - dy: f32, -} - -struct FloodFilter { - color: u32, -} - struct BlurParams { n_linear_taps: u32, center_weight: f32, @@ -55,12 +46,6 @@ linear_offsets: vec3<f32>, } -struct DropShadowFilter { - dx: f32, - dy: f32, - color: u32, -} - // The layout of the header: // bits [0:4] = filter_type (5 bits) // bits [5:6] = edge_mode (2 bits, only for blur filters), currently ignored. @@ -68,53 +53,57 @@ // bits [11:12] = n_linear_taps (2 bits, only for blur filters) // bits [13:32] = reserved for future use -fn unpack_filter_type(data: GpuFilterData) -> u32 { return data.data[0] & 0x1Fu; } +fn unpack_filter_type(data: array<u32, FILTER_SIZE_U32>) -> u32 { return data[0] & 0x1Fu; } fn unpack_header_n_linear_taps(header: u32) -> u32 { return (header >> 11u) & 0x3u; } -fn unpack_offset_filter(data: GpuFilterData) -> OffsetFilter { - return OffsetFilter( - bitcast<f32>(data.data[1]), - bitcast<f32>(data.data[2]) +// Returns the filter offset as `(dx, dy)`. +fn unpack_offset_filter(data: array<u32, FILTER_SIZE_U32>) -> vec2<f32> { + return vec2<f32>( + bitcast<f32>(data[1]), + bitcast<f32>(data[2]) ); } -fn unpack_flood_filter(data: GpuFilterData) -> FloodFilter { - return FloodFilter(data.data[1]); +// Returns the packed flood color as a packed u32. +fn unpack_flood_filter(data: array<u32, FILTER_SIZE_U32>) -> u32 { + return data[1]; } // Note that this assumes that the data is stored directly after the header, // which currently is the case for gaussian blur and drop shadow. -fn unpack_blur_params(data: GpuFilterData) -> BlurParams { - let n_linear_taps = unpack_header_n_linear_taps(data.data[0]); - let center_weight = bitcast<f32>(data.data[1]); +fn unpack_blur_params(data: array<u32, FILTER_SIZE_U32>) -> BlurParams { + let n_linear_taps = unpack_header_n_linear_taps(data[0]); + let center_weight = bitcast<f32>(data[1]); let weights = vec3<f32>( - bitcast<f32>(data.data[2u]), - bitcast<f32>(data.data[3u]), - bitcast<f32>(data.data[4u]), + bitcast<f32>(data[2u]), + bitcast<f32>(data[3u]), + bitcast<f32>(data[4u]), ); let offsets = vec3<f32>( - bitcast<f32>(data.data[2u + MAX_TAPS_PER_SIDE]), - bitcast<f32>(data.data[3u + MAX_TAPS_PER_SIDE]), - bitcast<f32>(data.data[4u + MAX_TAPS_PER_SIDE]), + bitcast<f32>(data[2u + MAX_TAPS_PER_SIDE]), + bitcast<f32>(data[3u + MAX_TAPS_PER_SIDE]), + bitcast<f32>(data[4u + MAX_TAPS_PER_SIDE]), ); return BlurParams(n_linear_taps, center_weight, weights, offsets); } -fn unpack_drop_shadow_filter(data: GpuFilterData) -> DropShadowFilter { - return DropShadowFilter( - bitcast<f32>(data.data[8]), - bitcast<f32>(data.data[9]), - data.data[10], +// Returns the drop shadow data as `(dx, dy, packed_color_bits_as_f32)`. +// In order to extract the color, you need to bitcast the value back to u32. +fn unpack_drop_shadow_filter(data: array<u32, FILTER_SIZE_U32>) -> vec3<f32> { + return vec3<f32>( + bitcast<f32>(data[8]), + bitcast<f32>(data[9]), + bitcast<f32>(data[10]), ); } -fn load_filter_data(texel_offset: u32) -> GpuFilterData { +fn load_filter_data(texel_offset: u32) -> array<u32, FILTER_SIZE_U32> { let w = textureDimensions(filter_data).x; let t0 = textureLoad(filter_data, vec2((texel_offset ) % w, (texel_offset ) / w), 0); let t1 = textureLoad(filter_data, vec2((texel_offset + 1u) % w, (texel_offset + 1u) / w), 0); let t2 = textureLoad(filter_data, vec2((texel_offset + 2u) % w, (texel_offset + 2u) / w), 0); - return GpuFilterData(array(t0.x, t0.y, t0.z, t0.w, t1.x, t1.y, t1.z, t1.w, t2.x, t2.y, t2.z, t2.w)); + return array(t0.x, t0.y, t0.z, t0.w, t1.x, t1.y, t1.z, t1.w, t2.x, t2.y, t2.z, t2.w); } struct FilterInstanceData { @@ -183,27 +172,27 @@ // Sample a pixel from the original texture. // Note: `rel_cord` needs to be positive and must not exceed the width/height of the image // that is to be sampled. -fn sample_original(in: FilterVertexOutput, rel_coord: vec2<f32>) -> vec4<f32> { - let src_coord = vec2<u32>(vec2<i32>(in.original_offset) + vec2<i32>(rel_coord)); +fn sample_original(original_offset: vec2<u32>, rel_coord: vec2<f32>) -> vec4<f32> { + let src_coord = vec2<u32>(vec2<i32>(original_offset) + vec2<i32>(rel_coord)); return textureLoad(original_tex, src_coord, 0); } // Sample a pixel from the input texture. // Note: `rel_cord` needs to be positive and must not exceed the width/height of the image // that is to be sampled. -fn sample_input(in: FilterVertexOutput, rel_coord: vec2<f32>) -> vec4<f32> { - let src_coord = vec2<u32>(vec2<i32>(in.src_offset) + vec2<i32>(rel_coord)); +fn sample_input(src_offset: vec2<u32>, rel_coord: vec2<f32>) -> vec4<f32> { + let src_coord = vec2<u32>(vec2<i32>(src_offset) + vec2<i32>(rel_coord)); return textureLoad(in_tex, src_coord, 0); } // Same as `sample_input`, but with bounds checking. -fn sample_input_checked(in: FilterVertexOutput, rel_coord: vec2<f32>) -> vec4<f32> { - if rel_coord.x < 0.0 || rel_coord.x >= f32(in.src_size.x) || - rel_coord.y < 0.0 || rel_coord.y >= f32(in.src_size.y) { +fn sample_input_checked(src_offset: vec2<u32>, src_size: vec2<u32>, rel_coord: vec2<f32>) -> vec4<f32> { + if rel_coord.x < 0.0 || rel_coord.x >= f32(src_size.x) || + rel_coord.y < 0.0 || rel_coord.y >= f32(src_size.y) { return vec4<f32>(0.0); } - return sample_input(in, rel_coord); + return sample_input(src_offset, rel_coord); } // TODO: Add support for edge modes when blurring. This unfortunately will make it harder/impossible to perform @@ -216,11 +205,11 @@ // We need to use `textureSampleLevel` instead of `textureSample` for loops with dynamic // iteration count so that it works properly in the Direct3D backend. -fn downscale(in: FilterVertexOutput) -> vec4<f32> { - let frag_coord = vec2<u32>(in.position.xy); - let rel = vec2<i32>(frag_coord - in.dest_offset); +fn downscale(position: vec4<f32>, src_offset: vec2<u32>, dest_offset: vec2<u32>) -> vec4<f32> { + let frag_coord = vec2<u32>(position.xy); + let rel = vec2<i32>(frag_coord - dest_offset); let src_rel = vec2<f32>(rel * 2); - let src_texel = vec2<f32>(in.src_offset) + src_rel; + let src_texel = vec2<f32>(src_offset) + src_rel; let tex_size = vec2<f32>(textureDimensions(in_tex)); // Overall, this follows the same approach that is used by the CPU, where a [1,3,3,1]/8 filter @@ -244,11 +233,11 @@ return (s00 + s01 + s10 + s11) * 0.25; } -fn upscale(in: FilterVertexOutput) -> vec4<f32> { +fn upscale(position: vec4<f32>, src_offset: vec2<u32>, dest_offset: vec2<u32>) -> vec4<f32> { // Same story as for downscaling, but this time even simpler and we can get away with a single texture sample. - let frag_coord = vec2<u32>(in.position.xy); - let rel = vec2<i32>(frag_coord - in.dest_offset); + let frag_coord = vec2<u32>(position.xy); + let rel = vec2<i32>(frag_coord - dest_offset); let src_base = vec2<f32>(rel / 2); let phase = vec2<f32>(rel % 2); let tex_size = vec2<f32>(textureDimensions(in_tex)); @@ -256,14 +245,14 @@ // For even phases: 75% of current, 25% of top/left. // For odd phases: 75% of current, 25% of bottom/right. let sample_offset = select(vec2(-0.25), vec2(0.25), phase == vec2(1.0)); - let src_texel = vec2<f32>(in.src_offset) + src_base + sample_offset; + let src_texel = vec2<f32>(src_offset) + src_base + sample_offset; // Yay, just a single sample! return textureSampleLevel(in_tex, linear_sampler, (src_texel + 0.5) / tex_size, 0.0); } fn convolve( - in: FilterVertexOutput, + src_offset: vec2<u32>, src_rel: vec2<f32>, dir: vec2<f32>, n_linear_taps: u32, @@ -280,7 +269,7 @@ // TODO: Explore whether combining horizontal and vertical filtering is worth it. Likely not worth doing // since downscaling/upscaling forms the bottleneck for now. - let src_texel = vec2<f32>(in.src_offset) + src_rel; + let src_texel = vec2<f32>(src_offset) + src_rel; let tex_size = vec2<f32>(textureDimensions(in_tex)); // First compute the color contribution of the center pixel. @@ -306,65 +295,73 @@ const VERTICAL: vec2<f32> = vec2<f32>(0.0, 1.0); @fragment -fn fs_main(in: FilterVertexOutput) -> @location(0) vec4<f32> { - let frag_coord = vec2<u32>(in.position.xy); - let rel_coord = vec2<f32>(frag_coord - in.dest_offset); +fn fs_main( + @builtin(position) position: vec4<f32>, + @location(0) @interpolate(flat) filter_offset: u32, + @location(1) @interpolate(flat) src_offset: vec2<u32>, + @location(2) @interpolate(flat) src_size: vec2<u32>, + @location(3) @interpolate(flat) dest_offset: vec2<u32>, + @location(4) @interpolate(flat) dest_size: vec2<u32>, + @location(6) @interpolate(flat) original_offset: vec2<u32>, + @location(7) @interpolate(flat) original_size: vec2<u32>, + @location(8) @interpolate(flat) pass_kind: u32, +) -> @location(0) vec4<f32> { + let frag_coord = vec2<u32>(position.xy); + let rel_coord = vec2<f32>(frag_coord - dest_offset); // See the comment in `vs_main`. - if rel_coord.x >= f32(in.dest_size.x) || rel_coord.y >= f32(in.dest_size.y) { + if rel_coord.x >= f32(dest_size.x) || rel_coord.y >= f32(dest_size.y) { return vec4<f32>(0.0); } - switch in.pass_kind { + switch pass_kind { case PASS_COPY: { - return sample_input(in, rel_coord); + return sample_input(src_offset, rel_coord); } case PASS_FLOOD: { - let data = load_filter_data(in.filter_offset); - let flood = unpack_flood_filter(data); - - return unpack4x8unorm(flood.color); + let data = load_filter_data(filter_offset); + let packed_color = unpack_flood_filter(data); + return unpack4x8unorm(packed_color); } case PASS_OFFSET: { - let data = load_filter_data(in.filter_offset); + let data = load_filter_data(filter_offset); let filter_type = unpack_filter_type(data); var dxdy: vec2<f32>; if filter_type == FILTER_TYPE_DROP_SHADOW { let shadow = unpack_drop_shadow_filter(data); - dxdy = vec2<f32>(shadow.dx, shadow.dy); + dxdy = shadow.xy; } else { - let offset = unpack_offset_filter(data); - dxdy = vec2<f32>(offset.dx, offset.dy); + dxdy = unpack_offset_filter(data); } // CPU version uses normal round but WGSL round with ties even, so we use floor + 0.5 instead. - return sample_input_checked(in, rel_coord - floor(dxdy + 0.5)); + return sample_input_checked(src_offset, src_size, rel_coord - floor(dxdy + 0.5)); } case PASS_DOWNSCALE: { - return downscale(in); + return downscale(position, src_offset, dest_offset); } case PASS_BLUR_H: { - let data = load_filter_data(in.filter_offset); + let data = load_filter_data(filter_offset); let blur = unpack_blur_params(data); - return convolve(in, rel_coord, HORIZONTAL, blur.n_linear_taps, blur.center_weight, blur.linear_weights, blur.linear_offsets); + return convolve(src_offset, rel_coord, HORIZONTAL, blur.n_linear_taps, blur.center_weight, blur.linear_weights, blur.linear_offsets); } case PASS_BLUR_V: { - let data = load_filter_data(in.filter_offset); + let data = load_filter_data(filter_offset); let blur = unpack_blur_params(data); - return convolve(in, rel_coord, VERTICAL, blur.n_linear_taps, blur.center_weight, blur.linear_weights, blur.linear_offsets); + return convolve(src_offset, rel_coord, VERTICAL, blur.n_linear_taps, blur.center_weight, blur.linear_weights, blur.linear_offsets); } case PASS_UPSCALE: { - return upscale(in); + return upscale(position, src_offset, dest_offset); } case PASS_COMPOSITE_DROP_SHADOW: { - let data = load_filter_data(in.filter_offset); + let data = load_filter_data(filter_offset); // Drop shadow composite: colorize blurred result, composite original on top. let shadow = unpack_drop_shadow_filter(data); - let blurred = sample_input(in, rel_coord); - let shadow_color = unpack4x8unorm(shadow.color); + let blurred = sample_input(src_offset, rel_coord); + let shadow_color = unpack4x8unorm(bitcast<u32>(shadow.z)); let shadow_result = shadow_color * blurred.a; - let original = sample_original(in, rel_coord); + let original = sample_original(original_offset, rel_coord); // Simple source-over compositing. return original + shadow_result * (1.0 - original.a);
diff --git a/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl b/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl index 6c39f54..6be6000 100644 --- a/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl +++ b/sparse_strips/vello_sparse_shaders/shaders/render_strips.wgsl
@@ -23,6 +23,10 @@ // - Otherwise, the fragment shader samples the source clip texture using the given slot index. // More details in the `StripInstance` documentation below. +// VERY IMPORTANT NOTE: Whenever making use of structs in the fragment shader, +// make sure that any field could be interpreted using 16-bit precision and still work +// correctly!! See <ISSUE> for more information. +// If that's not possible, avoid using structs and make use of native types (like vectors) instead. // Color source modes - where the fragment shader gets color data from. // Use payload (color or image coordinates) @@ -327,28 +331,36 @@ var clip_input_texture: texture_2d<f32>; @fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { +fn fs_main( + @location(0) @interpolate(flat) paint_and_rect_flag: u32, + @location(1) tex_coord: vec2<f32>, + @location(2) sample_xy: vec2<f32>, + @location(3) @interpolate(flat) dense_end_or_rect_size: u32, + @location(4) @interpolate(flat) payload: u32, + @location(5) @interpolate(flat) rect_frac: u32, + @builtin(position) position: vec4<f32>, +) -> @location(0) vec4<f32> { var alpha = 1.0; - let is_rect = (in.paint_and_rect_flag & RECT_STRIP_FLAG) != 0u; + let is_rect = (paint_and_rect_flag & RECT_STRIP_FLAG) != 0u; // TODO: Explore doing these calculations only for rectangle parts that actually need anti-aliasing. See // https://github.com/linebender/vello/pull/1482#discussion_r2861311034 - if is_rect && in.rect_frac != 0u { - let frac = unpack4x8unorm(in.rect_frac); + if is_rect && rect_frac != 0u { + let frac = unpack4x8unorm(rect_frac); // Calculate how much of the pixel is actually covered by the rect. // We do this by simply calculating the fractions in the x and y direction, and // then multiplying them. // For (maybe?) better performance, we calculate the x and y dimension in a single // pass by packing everything into a vec2. - let rect_size = vec2<f32>(f32(in.dense_end_or_rect_size & 0xFFFFu), f32(in.dense_end_or_rect_size >> 16u)); - let tc = in.tex_coord; + let rect_size = vec2<f32>(f32(dense_end_or_rect_size & 0xFFFFu), f32(dense_end_or_rect_size >> 16u)); + let tc = tex_coord; // + 0.5 and -0.5 since the fragment shader positions the coordinates in the center of the pixel. let bottom_and_right = min(tc + 0.5, rect_size - frac.zw); let top_and_left = max(tc - 0.5, frac.xy); let a = clamp(bottom_and_right - top_and_left, vec2(0.0), vec2(1.0)); alpha = a.x * a.y; - } else if !is_rect && in.dense_end_or_rect_size != 0u { - let x = u32(floor(in.tex_coord.x)); - let y = u32(floor(in.tex_coord.y)); + } else if !is_rect && dense_end_or_rect_size != 0u { + let x = u32(floor(tex_coord.x)); + let y = u32(floor(tex_coord.y)); // Retrieve alpha value from the texture. We store 16 1-byte alpha // values per texel, with each color channel packing 4 alpha values. // The code here assumes the strip height is 4, i.e., each color @@ -374,21 +386,21 @@ alpha = f32((alphas_u32 >> (y * 8u)) & 0xffu) * (1.0 / 255.0); } // Apply the alpha value to the unpacked RGBA color or slot index - let color_source = (in.paint_and_rect_flag >> 29u) & 0x3u; + let color_source = (paint_and_rect_flag >> 29u) & 0x3u; var final_color: vec4<f32>; if color_source == COLOR_SOURCE_PAYLOAD { - let paint_type = (in.paint_and_rect_flag >> 26u) & 0x7u; + let paint_type = (paint_and_rect_flag >> 26u) & 0x7u; - // in.payload encodes a color for PAINT_TYPE_SOLID or sample_xy for PAINT_TYPE_IMAGE + // payload encodes a color for PAINT_TYPE_SOLID or sample_xy for PAINT_TYPE_IMAGE if paint_type == PAINT_TYPE_SOLID { - final_color = alpha * unpack4x8unorm(in.payload); + final_color = alpha * unpack4x8unorm(payload); } else if paint_type == PAINT_TYPE_IMAGE { - let paint_tex_idx = in.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let encoded_image = unpack_encoded_image(paint_tex_idx); let image_offset = encoded_image.image_offset; let image_size = encoded_image.image_size; - let local_xy = in.sample_xy - image_offset; + let local_xy = sample_xy - image_offset; // This offset doesn't exist in vello_cpu, and we use it because 45 degree skewing seems to cause // artifacts on the GPU. We have something similar in place for gradients. It might be worth revisiting // this to see whether a better approach is possible. @@ -441,11 +453,11 @@ is_multiply ); } else if paint_type == PAINT_TYPE_LINEAR_GRADIENT { - let paint_tex_idx = in.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let linear_gradient = unpack_linear_gradient(paint_tex_idx); // Calculate fragment position and apply affine transform - let fragment_pos = in.sample_xy; + let fragment_pos = sample_xy; let grad_pos = linear_gradient.transform * fragment_pos + linear_gradient.translate; // For linear gradient, t-value is just the x coordinate in gradient space @@ -458,11 +470,11 @@ ); final_color = alpha * gradient_color; } else if paint_type == PAINT_TYPE_RADIAL_GRADIENT { - let paint_tex_idx = in.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let radial_gradient = unpack_radial_gradient(paint_tex_idx); // Calculate fragment position and apply affine transform - let fragment_pos = in.sample_xy; + let fragment_pos = sample_xy; let grad_pos = radial_gradient.transform * fragment_pos + radial_gradient.translate; // For radial gradient, calculate distance from center @@ -479,11 +491,11 @@ gradient_result.is_valid ); } else if paint_type == PAINT_TYPE_SWEEP_GRADIENT { - let paint_tex_idx = in.paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; + let paint_tex_idx = paint_and_rect_flag & PAINT_TEXTURE_INDEX_MASK; let sweep_gradient = unpack_sweep_gradient(paint_tex_idx); // Calculate fragment position and apply affine transform - let fragment_pos = in.sample_xy; + let fragment_pos = sample_xy; var grad_pos = sweep_gradient.transform * fragment_pos + sweep_gradient.translate; // Before passing the position to the angle calculation, we bias @@ -515,8 +527,8 @@ // assumes a `y-up` or `y-down` coordinate system. However, for slot textures, we need the original // coordinate in the `y-down` system. Therefore, we invert the y-position _again_ in case we are // currently rendering to a y-up system, to get the original coordinate. - let sample_y = select(in.position.y, f32(config.height) - in.position.y, config.ndc_y_negate != 0u); - // in.payload encodes a slot in the source clip texture. + let sample_y = select(position.y, f32(config.height) - position.y, config.ndc_y_negate != 0u); + // payload encodes a slot in the source clip texture. // This is a bit finicky: When copying from a texture slot, we already // know which slot to choose and where that slot is located. Therefore, we now // only need to determine the pixel within the slot to sample. However, this @@ -524,26 +536,26 @@ // strip offset is (2, 2), then the pixel (2, 2) should still map to (0, 0) // within the wide tile slot! Therefore, we need to subtract the strip // offset here. - let clip_x = u32(i32(in.position.x) - config.strip_offset_x) & 0xFFu; - let clip_y = (u32(i32(sample_y) - config.strip_offset_y) & 3u) + in.payload * config.strip_height; + let clip_x = u32(i32(position.x) - config.strip_offset_x) & 0xFFu; + let clip_y = (u32(i32(sample_y) - config.strip_offset_y) & 3u) + payload * config.strip_height; let clip_in_color = textureLoad(clip_input_texture, vec2(clip_x, clip_y), 0); // Extract opacity from first 8 bits (quantized from [0, 255]) - let opacity = f32(in.paint_and_rect_flag & 0xFFu) * (1.0 / 255.0); + let opacity = f32(paint_and_rect_flag & 0xFFu) * (1.0 / 255.0); final_color = alpha * opacity * clip_in_color; } else if color_source == COLOR_SOURCE_BLEND { // See the comment above. - let sample_y = select(in.position.y, f32(config.height) - in.position.y, config.ndc_y_negate != 0u); - let opacity = f32((in.paint_and_rect_flag >> 16u) & 0xFFu) * (1.0 / 255.0); - let mix_mode = (in.paint_and_rect_flag >> 8u) & 0xFFu; - let compose_mode = in.paint_and_rect_flag & 0xFFu; + let sample_y = select(position.y, f32(config.height) - position.y, config.ndc_y_negate != 0u); + let opacity = f32((paint_and_rect_flag >> 16u) & 0xFFu) * (1.0 / 255.0); + let mix_mode = (paint_and_rect_flag >> 8u) & 0xFFu; + let compose_mode = paint_and_rect_flag & 0xFFu; // Read source color from slot - let src_slot = in.payload & 0xFFFFu; - let dest_slot = (in.payload >> 16u) & 0xFFFFu; + let src_slot = payload & 0xFFFFu; + let dest_slot = (payload >> 16u) & 0xFFFFu; // See the comment above for why we need to subtract the strip offset. - let clip_x = u32(i32(in.position.x) - config.strip_offset_x) & 0xFFu; + let clip_x = u32(i32(position.x) - config.strip_offset_x) & 0xFFu; let clip_y_in_strip = u32(i32(sample_y) - config.strip_offset_y) & 3u; let src_y = clip_y_in_strip + src_slot * config.strip_height; let src_color = textureLoad(clip_input_texture, vec2(clip_x, src_y), 0);