.
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);