dont support aliasing threshold for now
diff --git a/sparse_strips/vello_common/src/strip.rs b/sparse_strips/vello_common/src/strip.rs
index 5849f4f..e146e73 100644
--- a/sparse_strips/vello_common/src/strip.rs
+++ b/sparse_strips/vello_common/src/strip.rs
@@ -91,6 +91,7 @@
 }
 
 /// Render the tiles stored in `tiles` into the strip and alpha buffer.
+#[inline(never)]
 pub fn render(
     level: Level,
     tiles: &Tiles,
diff --git a/sparse_strips/vello_common/src/strip_generator.rs b/sparse_strips/vello_common/src/strip_generator.rs
index b4d9a70..0b35a3c 100644
--- a/sparse_strips/vello_common/src/strip_generator.rs
+++ b/sparse_strips/vello_common/src/strip_generator.rs
@@ -208,6 +208,55 @@
         );
     }
 
+    /// Flatten a filled path and generate sorted tiles, without computing strips or alpha.
+    ///
+    /// After this call, [`tiles()`](Self::tiles) and [`lines()`](Self::lines) contain the
+    /// tile and line data for the path.
+    pub fn prepare_tiles_for_fill(
+        &mut self,
+        path: impl IntoIterator<Item = PathEl>,
+        transform: Affine,
+    ) {
+        flatten::fill(
+            self.level,
+            path,
+            transform,
+            &mut self.line_buf,
+            &mut self.flatten_ctx,
+            self.width,
+            self.height,
+        );
+        self.tiles
+            .make_tiles_analytic_aa(&self.line_buf, self.width, self.height);
+        self.tiles.sort_tiles();
+    }
+
+    /// Flatten a stroked path and generate sorted tiles, without computing strips or alpha.
+    ///
+    /// After this call, [`tiles()`](Self::tiles) and [`lines()`](Self::lines) contain the
+    /// tile and line data for the path.
+    pub fn prepare_tiles_for_stroke(
+        &mut self,
+        path: impl IntoIterator<Item = PathEl>,
+        stroke: &Stroke,
+        transform: Affine,
+    ) {
+        flatten::stroke(
+            self.level,
+            path,
+            stroke,
+            transform,
+            &mut self.line_buf,
+            &mut self.flatten_ctx,
+            &mut self.stroke_ctx,
+            self.width,
+            self.height,
+        );
+        self.tiles
+            .make_tiles_analytic_aa(&self.line_buf, self.width, self.height);
+        self.tiles.sort_tiles();
+    }
+
     /// Access the tiles from the last generation call.
     pub fn tiles(&self) -> &Tiles {
         &self.tiles
diff --git a/sparse_strips/vello_example_scenes/src/lib.rs b/sparse_strips/vello_example_scenes/src/lib.rs
index d4756b2..7b68b09 100644
--- a/sparse_strips/vello_example_scenes/src/lib.rs
+++ b/sparse_strips/vello_example_scenes/src/lib.rs
@@ -444,6 +444,7 @@
     scenes.push(AnyScene::new(path::TrickyStrokesScene::new()));
     scenes.push(AnyScene::new(path::FunkyPathsScene::new()));
     scenes.push(AnyScene::new(path::RobustPathsScene::new()));
+    scenes.push(AnyScene::new(path::RandomPolylineScene::new()));
 
     scenes.into_boxed_slice()
 }
@@ -472,6 +473,7 @@
         AnyScene::new(path::TrickyStrokesScene::new()),
         AnyScene::new(path::FunkyPathsScene::new()),
         AnyScene::new(path::RobustPathsScene::new()),
+        AnyScene::new(path::RandomPolylineScene::new()),
     ];
     scenes.into_boxed_slice()
 }
diff --git a/sparse_strips/vello_example_scenes/src/path.rs b/sparse_strips/vello_example_scenes/src/path.rs
index 54e124d..afe71b3 100644
--- a/sparse_strips/vello_example_scenes/src/path.rs
+++ b/sparse_strips/vello_example_scenes/src/path.rs
@@ -11,7 +11,7 @@
 //! - `robust_paths` method
 
 use crate::{ExampleScene, RenderingContext};
-use vello_common::color::palette::css::{AQUA, BLUE, GRAY, LIME, YELLOW};
+use vello_common::color::palette::css::{AQUA, BLUE, GRAY, LIME, REBECCA_PURPLE, YELLOW};
 use vello_common::kurbo::{Affine, BezPath, Cap, Join, Point, Rect, Shape, Stroke};
 use vello_common::peniko::{Color, Fill};
 
@@ -547,3 +547,29 @@
         ctx.fill_path(&path);
     }
 }
+
+/// Scene that draws a filled polyline with pseudo-random vertices.
+#[derive(Debug, Default)]
+pub struct RandomPolylineScene;
+
+impl RandomPolylineScene {
+    /// Create a new random polyline scene.
+    pub fn new() -> Self {
+        Self
+    }
+}
+
+impl ExampleScene for RandomPolylineScene {
+    fn render(&mut self, ctx: &mut impl RenderingContext, root_transform: Affine) {
+        ctx.set_transform(root_transform);
+
+        let mut path = BezPath::new();
+        path.move_to((100.0, 50.0));
+        path.line_to((300.0, 250.0));
+        path.line_to((50.0, 200.0));
+        path.close_path();
+
+        ctx.set_paint(REBECCA_PURPLE);
+        ctx.fill_path(&path);
+    }
+}
diff --git a/sparse_strips/vello_example_scenes/src/svg.rs b/sparse_strips/vello_example_scenes/src/svg.rs
index 9165ebb..ba957aa 100644
--- a/sparse_strips/vello_example_scenes/src/svg.rs
+++ b/sparse_strips/vello_example_scenes/src/svg.rs
@@ -182,7 +182,7 @@
         Self {
             transform: Affine::scale(3.0),
             svg,
-            recording_enabled: true,
+            recording_enabled: false,
             recording: CachedRecording::new(),
         }
     }
@@ -197,7 +197,7 @@
             transform: Affine::scale(3.0),
             svg,
             recording: CachedRecording::new(),
-            recording_enabled: true,
+            recording_enabled: false,
         })
     }
 
diff --git a/sparse_strips/vello_example_scenes/src/text.rs b/sparse_strips/vello_example_scenes/src/text.rs
index 3c31cc8..7326e14 100644
--- a/sparse_strips/vello_example_scenes/src/text.rs
+++ b/sparse_strips/vello_example_scenes/src/text.rs
@@ -118,7 +118,7 @@
 
         Self {
             layout,
-            recording_enabled: true,
+            recording_enabled: false,
             recording: CachedRecording::new(),
         }
     }
diff --git a/sparse_strips/vello_hybrid/src/render/wgpu.rs b/sparse_strips/vello_hybrid/src/render/wgpu.rs
index f1305c8..bf8ed82 100644
--- a/sparse_strips/vello_hybrid/src/render/wgpu.rs
+++ b/sparse_strips/vello_hybrid/src/render/wgpu.rs
@@ -1504,11 +1504,12 @@
         max_texture_dimension_2d: u32,
         winding_value_count: usize,
     ) {
-        // R16Float: 1 pixel per winding value.
-        let required_height = u32::try_from(winding_value_count)
-            .unwrap()
-            .div_ceil(max_texture_dimension_2d)
-            .max(u32::from(Tile::HEIGHT)); // at least one band
+        // R16Float: 1 pixel per winding value. Columns are TILE_HEIGHT pixels tall
+        // and wrap into bands of `max_texture_dimension_2d` columns each.
+        let tile_height = u32::from(Tile::HEIGHT);
+        let num_columns = u32::try_from(winding_value_count).unwrap().div_ceil(tile_height);
+        let num_bands = num_columns.div_ceil(max_texture_dimension_2d).max(1);
+        let required_height = num_bands * tile_height;
         let current_height = self.resources.winding_texture.height();
         if required_height > current_height {
             assert!(
diff --git a/sparse_strips/vello_hybrid/src/scene.rs b/sparse_strips/vello_hybrid/src/scene.rs
index 3c45c90..c5fff72 100644
--- a/sparse_strips/vello_hybrid/src/scene.rs
+++ b/sparse_strips/vello_hybrid/src/scene.rs
@@ -371,9 +371,28 @@
     ) {
         let strip_storage = &mut self.strip_storage.borrow_mut();
         let strip_start = strip_storage.strips.len();
-        #[cfg(feature = "wgpu")]
-        let alpha_offset = strip_storage.alphas.len() as u32;
 
+        #[cfg(feature = "wgpu")]
+        {
+            // GPU path: flatten + tile, then produce strips and tile-lines together.
+            // This replaces the CPU per-pixel alpha computation.
+            let alpha_offset = strip_storage.alphas.len() as u32;
+            self.strip_generator
+                .prepare_tiles_for_fill(path, transform);
+            let tiles = self.strip_generator.tiles();
+            let lines = self.strip_generator.lines();
+            let output =
+                gpu_winding::render_strips_and_tile_lines(tiles, fill_rule, lines, alpha_offset);
+            strip_storage.strips.extend_from_slice(&output.strips);
+            // Alphas buffer must match in length for winding texture sizing, but
+            // values are unused — the GPU winding pass fills the winding texture.
+            strip_storage
+                .alphas
+                .resize(output.winding_value_count as usize, 0);
+            self.tile_lines.extend(output.tile_lines);
+        }
+
+        #[cfg(not(feature = "wgpu"))]
         self.strip_generator.generate_filled_path(
             path,
             fill_rule,
@@ -383,24 +402,6 @@
             self.clip_context.get(),
         );
 
-        // Generate GPU tile-line instances from the tiles that are still available
-        // in the strip generator after the generate call.
-        #[cfg(feature = "wgpu")]
-        {
-            let tiles = self.strip_generator.tiles();
-            let lines = self.strip_generator.lines();
-            let output =
-                gpu_winding::render_strips_and_tile_lines(tiles, fill_rule, lines, alpha_offset);
-            debug_assert_eq!(
-                output.winding_value_count,
-                strip_storage.alphas.len() as u32,
-                "winding_value_count ({}) != alphas.len() ({})",
-                output.winding_value_count,
-                strip_storage.alphas.len(),
-            );
-            self.tile_lines.extend(output.tile_lines);
-        }
-
         submit_strips!(self, strip_storage, strip_start, paint);
     }
 
@@ -456,9 +457,27 @@
     ) {
         let strip_storage = &mut self.strip_storage.borrow_mut();
         let strip_start = strip_storage.strips.len();
-        #[cfg(feature = "wgpu")]
-        let alpha_offset = strip_storage.alphas.len() as u32;
 
+        #[cfg(feature = "wgpu")]
+        {
+            let alpha_offset = strip_storage.alphas.len() as u32;
+            self.strip_generator.prepare_tiles_for_stroke(
+                path,
+                &self.render_state.stroke,
+                transform,
+            );
+            let tiles = self.strip_generator.tiles();
+            let lines = self.strip_generator.lines();
+            let output =
+                gpu_winding::render_strips_and_tile_lines(tiles, Fill::NonZero, lines, alpha_offset);
+            strip_storage.strips.extend_from_slice(&output.strips);
+            strip_storage
+                .alphas
+                .resize(output.winding_value_count as usize, 0);
+            self.tile_lines.extend(output.tile_lines);
+        }
+
+        #[cfg(not(feature = "wgpu"))]
         self.strip_generator.generate_stroked_path(
             path,
             &self.render_state.stroke,
@@ -468,22 +487,6 @@
             self.clip_context.get(),
         );
 
-        #[cfg(feature = "wgpu")]
-        {
-            let tiles = self.strip_generator.tiles();
-            let lines = self.strip_generator.lines();
-            let output =
-                gpu_winding::render_strips_and_tile_lines(tiles, Fill::NonZero, lines, alpha_offset);
-            debug_assert_eq!(
-                output.winding_value_count,
-                strip_storage.alphas.len() as u32,
-                "winding_value_count ({}) != alphas.len() ({})",
-                output.winding_value_count,
-                strip_storage.alphas.len(),
-            );
-            self.tile_lines.extend(output.tile_lines);
-        }
-
         submit_strips!(self, strip_storage, strip_start, paint);
     }
 
@@ -842,7 +845,9 @@
 
     /// Set the fill rule for subsequent fill operations.
     pub fn set_fill_rule(&mut self, fill_rule: Fill) {
-        unimplemented!();
+        if fill_rule != Fill::NonZero {
+            unimplemented!();
+        }
         self.render_state.fill_rule = fill_rule;
     }