vello_cpu: Make `RenderContext` resizable (#1705)

This is based on top of #1701. Now we can make the render context fully
resizable, which means that it can be reused even if the window size
changes!



https://github.com/user-attachments/assets/13f00b04-5a4d-4bd0-924f-1bc921d0d388
diff --git a/sparse_strips/vello_bench/src/data.rs b/sparse_strips/vello_bench/src/data.rs
index eda3b7a..5a2622a 100644
--- a/sparse_strips/vello_bench/src/data.rs
+++ b/sparse_strips/vello_bench/src/data.rs
@@ -132,7 +132,7 @@
 
     /// Get the unsorted tiles.
     pub fn unsorted_tiles(&self) -> Tiles {
-        let mut tiles = Tiles::new(Level::new(), self.height);
+        let mut tiles = Tiles::new(Level::new(), self.width, self.height);
         let lines = self.lines();
         tiles.make_tiles_analytic_aa(Level::new(), &lines, self.width, self.height);
 
diff --git a/sparse_strips/vello_bench/src/strip.rs b/sparse_strips/vello_bench/src/strip.rs
index 0ec6cbf..c57afd6 100644
--- a/sparse_strips/vello_bench/src/strip.rs
+++ b/sparse_strips/vello_bench/src/strip.rs
@@ -87,7 +87,7 @@
 
         let shifted_lines = shift_lines_50_percent(&item.lines());
 
-        let mut tiler = Tiles::new(simd_level, item.height);
+        let mut tiler = Tiles::new(simd_level, item.width, item.height);
         tiler.make_tiles_analytic_aa(simd_level, &shifted_lines, item.width, item.height);
         tiler.sort_tiles();
 
@@ -139,7 +139,7 @@
                 &mut storage,
                 None,
             );
-            generator.reset();
+            generator.reset(width, height);
             std::hint::black_box(&storage);
         });
     });
@@ -152,7 +152,7 @@
         b.iter(|| {
             storage.clear();
             generator.generate_filled_rect_fast(&rect, &mut storage, None);
-            generator.reset();
+            generator.reset(width, height);
             std::hint::black_box(&storage);
         });
     });
diff --git a/sparse_strips/vello_bench/src/tile.rs b/sparse_strips/vello_bench/src/tile.rs
index f685329..8dd1bfe 100644
--- a/sparse_strips/vello_bench/src/tile.rs
+++ b/sparse_strips/vello_bench/src/tile.rs
@@ -24,7 +24,7 @@
 
         g.bench_with_input(BenchmarkId::from_parameter(&item.name), &item, |b, item| {
             b.iter(|| {
-                let mut tiler = Tiles::new(Level::new(), item.height);
+                let mut tiler = Tiles::new(Level::new(), item.width, item.height);
                 op(&mut tiler, &lines, item.width, item.height);
             });
         });
diff --git a/sparse_strips/vello_common/src/strip_generator.rs b/sparse_strips/vello_common/src/strip_generator.rs
index 9a8bfc2..f06b15c 100644
--- a/sparse_strips/vello_common/src/strip_generator.rs
+++ b/sparse_strips/vello_common/src/strip_generator.rs
@@ -95,7 +95,7 @@
         Self {
             level,
             line_buf: Vec::new(),
-            tiles: Tiles::new(level, height),
+            tiles: Tiles::new(level, width, height),
             flatten_ctx: FlattenCtx::default(),
             stroke_ctx: StrokeCtx::default(),
             temp_storage: StripStorage::default(),
@@ -238,10 +238,12 @@
         );
     }
 
-    /// Reset the strip generator.
-    pub fn reset(&mut self) {
+    /// Reset the strip generator for a viewport size, resizing only when needed.
+    pub fn reset(&mut self, width: u16, height: u16) {
+        self.width = width;
+        self.height = height;
         self.line_buf.clear();
-        self.tiles.reset();
+        self.tiles.reset(width, height);
         self.temp_storage.clear();
     }
 }
@@ -307,7 +309,7 @@
         assert!(!generator.line_buf.is_empty());
         assert!(!storage.is_empty());
 
-        generator.reset();
+        generator.reset(100, 100);
         storage.clear();
 
         assert!(generator.line_buf.is_empty());
@@ -329,7 +331,7 @@
             &mut storage_path,
             None,
         );
-        generator.reset();
+        generator.reset(100, 100);
 
         generator.generate_filled_rect_fast(&rect, &mut storage_rect, None);
 
diff --git a/sparse_strips/vello_common/src/tile.rs b/sparse_strips/vello_common/src/tile.rs
index 9c32a89..a516a35 100644
--- a/sparse_strips/vello_common/src/tile.rs
+++ b/sparse_strips/vello_common/src/tile.rs
@@ -53,6 +53,7 @@
     pub active: Vec<u32>,
     /// Flag indicating if any geometry was early-culled outside the viewport.
     pub culled: bool,
+    height: u16,
 }
 
 impl CulledWindings {
@@ -63,25 +64,36 @@
     /// Bitmask equivalent to modulo `WORD_BITS` (32 - 1 = 31).
     const WORD_MASK: usize = 31;
 
-    /// Constructor chained to `Tiles`' constructor and matches its lifetime. Since `Tiles` itself
-    /// matches the lifetime of `StripGenerator`, we know that the viewport dimensions will never
-    /// change, and thus the backing vecs never need to be resized. (For now).
+    /// Constructor chained to `Tiles`' constructor and matching its initial viewport height.
     pub fn new(height: u16) -> Self {
-        let height_usize = height as usize;
-        let tile_height = Tile::HEIGHT as usize;
-        let num_rows = height_usize.div_ceil(tile_height);
-        let num_bits = num_rows.div_ceil(Self::WORD_BITS);
+        let (num_rows, num_bits) = Self::sizes(height);
 
         Self {
             partial: vec![[0.0; Tile::HEIGHT as usize]; num_rows],
             coarse: vec![0; num_rows],
             active: vec![0; num_bits],
             culled: false,
+            height,
         }
     }
 
-    /// Clears but does not resize
-    pub fn reset(&mut self) {
+    fn sizes(height: u16) -> (usize, usize) {
+        let num_rows = usize::from(height).div_ceil(Tile::HEIGHT as usize);
+        let num_bits = num_rows.div_ceil(Self::WORD_BITS);
+
+        (num_rows, num_bits)
+    }
+
+    /// Reset the winding buffers.
+    pub fn reset(&mut self, height: u16) {
+        if self.height != height {
+            let (num_rows, num_bits) = Self::sizes(height);
+            self.partial.resize(num_rows, [0.0; Tile::HEIGHT as usize]);
+            self.coarse.resize(num_rows, 0);
+            self.active.resize(num_bits, 0);
+            self.height = height;
+        }
+
         // TODO: Maybe consider tracking touched regions and only resetting those
         // instead of always the full array?
         if self.culled {
@@ -414,17 +426,21 @@
     tile_buf: Vec<Tile>,
     level: Level,
     sorted: bool,
+    width: u16,
+    height: u16,
     /// Auxiliary data tracking row windings and active rows for early culling.
     pub windings: CulledWindings,
 }
 
 impl Tiles {
     /// Create a new tiles container.
-    pub fn new(level: Level, height: u16) -> Self {
+    pub fn new(level: Level, width: u16, height: u16) -> Self {
         Self {
             tile_buf: vec![],
             level,
             sorted: false,
+            width,
+            height,
             windings: CulledWindings::new(height),
         }
     }
@@ -444,9 +460,11 @@
         self.windings.culled
     }
 
-    /// Reset the tiles' container.
-    pub fn reset(&mut self) {
-        self.windings.reset();
+    /// Reset the tiles' container and resize to the given dimensions.
+    pub fn reset(&mut self, width: u16, height: u16) {
+        self.windings.reset(height);
+        self.width = width;
+        self.height = height;
         self.tile_buf.clear();
         self.sorted = false;
     }
@@ -511,7 +529,7 @@
         width: u16,
         height: u16,
     ) -> bool {
-        self.reset();
+        self.reset(width, height);
 
         if width == 0 || height == 0 {
             return self.windings.culled;
@@ -939,7 +957,7 @@
     /// - W (Winding): Tracks whether the line touched the top edge of the tile.
     /// - R/L/B/T: Right, Left, Bottom, and Top edge intersections.
     pub fn make_tiles_msaa(&mut self, lines: &[Line], width: u16, height: u16) {
-        self.reset();
+        self.reset(width, height);
 
         if width == 0 || height == 0 {
             return;
@@ -1288,6 +1306,14 @@
     const VIEW_DIM: u16 = 100;
     const F_V_DIM: f32 = VIEW_DIM as f32;
 
+    fn new_tiles() -> Tiles {
+        Tiles::new(
+            Level::try_detect().unwrap_or(Level::baseline()),
+            VIEW_DIM,
+            VIEW_DIM,
+        )
+    }
+
     impl Tiles {
         fn assert_tiles_match(
             &mut self,
@@ -1377,7 +1403,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
     }
 
@@ -1402,7 +1428,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, W | T),
             Tile::new(1, 0, 1, W | T),
@@ -1448,7 +1474,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(1, 24, 0, B),
             Tile::new(2, 24, 1, B),
@@ -1471,7 +1497,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, W | T | R),
             Tile::new(1, 0, 0, L | B),
@@ -1502,7 +1528,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 23, 0, B),
             Tile::new(0, 24, 0, W | T | R),
@@ -1533,7 +1559,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(24, 0, 0, R),
             Tile::new(23, 0, 1, R),
@@ -1560,7 +1586,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, L),
             Tile::new(0, 0, 1, L | R),
@@ -1583,7 +1609,7 @@
             p1: Point { x: 90.0, y: -5.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
     }
 
@@ -1600,7 +1626,7 @@
             },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
     }
 
@@ -1611,7 +1637,7 @@
             p1: Point { x: 10.0, y: 10.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 2, 0, L | R),
             Tile::new(1, 2, 0, L | R),
@@ -1634,7 +1660,7 @@
             },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(23, 2, 0, R), Tile::new(24, 2, 0, L | R)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -1659,7 +1685,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &[]);
     }
 
@@ -1679,7 +1705,7 @@
             RectU16::new(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT),
         );
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         tiles.assert_tiles_match(&line_buf, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, &[]);
     }
 
@@ -1700,7 +1726,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, W | T),
             Tile::new(0, 0, 1, W | B | T),
@@ -1737,7 +1763,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 24, 0, B),
             Tile::new(0, 23, 1, B),
@@ -1754,7 +1780,7 @@
             p1: Point { x: 2.0, y: -1.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, W | L | T)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -1773,7 +1799,7 @@
             },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(24, 24, 0, R | B)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -1789,7 +1815,7 @@
             p1: Point { x: 8.5, y: 1.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, R),
             Tile::new(1, 0, 0, R | L),
@@ -1800,13 +1826,45 @@
     }
 
     #[test]
+    fn resize_works_correctly() {
+        let lines = [
+            Line {
+                p0: Point { x: 1.5, y: 1.0 },
+                p1: Point { x: 8.5, y: 1.0 },
+            },
+            Line {
+                p0: Point { x: 1.5, y: 13.0 },
+                p1: Point { x: 8.5, y: 13.0 },
+            },
+        ];
+        let small_expected = [
+            Tile::new(0, 0, 0, R),
+            Tile::new(1, 0, 0, R | L),
+            Tile::new(2, 0, 0, L),
+        ];
+        let large_expected = [
+            Tile::new(0, 0, 0, R),
+            Tile::new(1, 0, 0, R | L),
+            Tile::new(2, 0, 0, L),
+            Tile::new(0, 3, 1, R),
+            Tile::new(1, 3, 1, R | L),
+            Tile::new(2, 3, 1, L),
+        ];
+
+        let mut tiles = Tiles::new(Level::baseline(), 12, 8);
+        tiles.assert_tiles_match(&lines, 12, 8, &small_expected);
+        tiles.assert_tiles_match(&lines, 12, 16, &large_expected);
+        tiles.assert_tiles_match(&lines, 12, 8, &small_expected);
+    }
+
+    #[test]
     fn horizontal_line_right_to_left_three_tile() {
         let lines = [Line {
             p0: Point { x: 8.5, y: 1.0 },
             p1: Point { x: 1.5, y: 1.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, R),
             Tile::new(1, 0, 0, R | L),
@@ -1823,7 +1881,7 @@
             p1: Point { x: 12.5, y: 1.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, R),
             Tile::new(1, 0, 0, R | L),
@@ -1841,7 +1899,7 @@
             p1: Point { x: 1.0, y: 8.5 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, B),
             Tile::new(0, 1, 0, W | T | B),
@@ -1858,7 +1916,7 @@
             p1: Point { x: 1.0, y: 13.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, B),
             Tile::new(0, 1, 0, W | T | B),
@@ -1876,7 +1934,7 @@
             p1: Point { x: 1.0, y: 1.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, B),
             Tile::new(0, 1, 0, W | T | B),
@@ -1894,7 +1952,7 @@
             p1: Point { x: 1.0, y: 1.5 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, B),
             Tile::new(0, 1, 0, W | T | B),
@@ -1912,7 +1970,7 @@
             p1: Point { x: 1.0, y: 8.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, B), Tile::new(0, 1, 0, W | T)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -1925,7 +1983,7 @@
             p1: Point { x: 1.0, y: 7.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, W | B), Tile::new(0, 1, 0, W | T)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -1941,7 +1999,7 @@
             p1: Point { x: 11.0, y: 9.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, R),
             Tile::new(1, 0, 0, L | B),
@@ -1960,7 +2018,7 @@
             p1: Point { x: 1.0, y: 1.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, R),
             Tile::new(1, 0, 0, L | B),
@@ -1979,7 +2037,7 @@
             p1: Point { x: 14.0, y: 6.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(2, 1, 0, R | B),
             Tile::new(3, 1, 0, L),
@@ -1998,7 +2056,7 @@
             p1: Point { x: 2.0, y: 11.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(2, 1, 0, R | B),
             Tile::new(3, 1, 0, L),
@@ -2023,7 +2081,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, 0), Tile::new(0, 0, 1, 0)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2036,7 +2094,7 @@
             p1: Point { x: 5.0, y: 3.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(1, 0, 0, L),
             Tile::new(0, 1, 0, R),
@@ -2053,7 +2111,7 @@
             p1: Point { x: 0.1, y: 0.1 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, R),
             Tile::new(1, 0, 0, L),
@@ -2070,7 +2128,7 @@
             p1: Point { x: 9.0, y: 9.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(1, 1, 0, R),
             Tile::new(2, 1, 0, L),
@@ -2087,7 +2145,7 @@
             p1: Point { x: 9.0, y: 5.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(1, 1, 0, R | B),
             Tile::new(2, 1, 0, L),
@@ -2104,7 +2162,7 @@
             p1: Point { x: 4.0, y: 4.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, W | R), Tile::new(1, 0, 0, L)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2117,7 +2175,7 @@
             p1: Point { x: 4.0, y: 0.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, R), Tile::new(1, 0, 0, W | L)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2130,7 +2188,7 @@
             p1: Point { x: 8.0, y: 8.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, W | R),
             Tile::new(1, 0, 0, L),
@@ -2148,7 +2206,7 @@
             p1: Point { x: 8.0, y: 0.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(1, 0, 0, R | L),
             Tile::new(2, 0, 0, W | L),
@@ -2166,7 +2224,7 @@
             p1: Point { x: 8.0, y: 2.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, R),
             Tile::new(1, 0, 0, R | L),
@@ -2197,7 +2255,7 @@
             },
         }];
 
-        let mut tiles = Tiles::new(Level::baseline(), HEIGHT);
+        let mut tiles = Tiles::new(Level::baseline(), HEIGHT, HEIGHT);
         tiles.make_tiles_analytic_aa(Level::baseline(), &lines, WIDTH, HEIGHT);
 
         let row_tiles: Vec<Tile> = tiles
@@ -2219,7 +2277,7 @@
             p1: Point { x: 4.0, y: 0.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, R | B),
             Tile::new(1, 0, 0, W | L),
@@ -2236,7 +2294,7 @@
             p1: Point { x: 4.0, y: 8.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [
             Tile::new(0, 0, 0, W | B),
             Tile::new(0, 1, 0, W | R | T),
@@ -2256,7 +2314,7 @@
             p1: Point { x: 3.0, y: 3.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, 0)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2269,7 +2327,7 @@
             p1: Point { x: 3.0, y: 1.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, 0)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2282,7 +2340,7 @@
             p1: Point { x: 1.0, y: 3.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, W)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2295,7 +2353,7 @@
             p1: Point { x: 4.0, y: 1.0 },
         }];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, R), Tile::new(1, 0, 0, L)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2314,7 +2372,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, 0), Tile::new(0, 0, 1, 0)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2333,7 +2391,7 @@
             },
         ];
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         let expected = [Tile::new(0, 0, 0, W), Tile::new(0, 0, 1, W)];
 
         tiles.assert_tiles_match(&lines, VIEW_DIM, VIEW_DIM, &expected);
@@ -2344,16 +2402,16 @@
     //==============================================================================================
     #[test]
     fn test_culled_windings_new_and_reset() {
-        let mut windings = CulledWindings::new(100);
-        assert_eq!(windings.partial.len(), 25);
-        assert_eq!(windings.coarse.len(), 25);
+        let mut windings = CulledWindings::new(8);
+        assert_eq!(windings.partial.len(), 2);
+        assert_eq!(windings.coarse.len(), 2);
         assert_eq!(windings.active.len(), 1);
 
         windings.coarse[0] = 1;
         windings.active[0] = 0xFF;
         windings.culled = true;
 
-        windings.reset();
+        windings.reset(8);
         assert_eq!(windings.coarse[0], 0);
         assert_eq!(windings.active[0], 0);
 
@@ -2361,9 +2419,14 @@
         windings.active[0] = 0xFF;
         windings.culled = false;
 
-        windings.reset();
+        windings.reset(8);
         assert_eq!(windings.coarse[0], 1);
         assert_eq!(windings.active[0], 0xFF);
+
+        windings.culled = true;
+        windings.reset(12);
+        assert_eq!(windings.coarse[0], 0);
+        assert_eq!(windings.active[0], 0);
     }
 
     #[test]
@@ -2432,7 +2495,7 @@
             p1: Point { x: 224.0, y: 388.0 },
         };
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         tiles.make_tiles_msaa(&[line], 600, 600);
         tiles.make_tiles_analytic_aa(Level::baseline(), &[line], 600, 600);
     }
@@ -2451,7 +2514,7 @@
             },
         };
 
-        let mut tiles = Tiles::new(Level::try_detect().unwrap_or(Level::baseline()), VIEW_DIM);
+        let mut tiles = new_tiles();
         tiles.make_tiles_analytic_aa(Level::baseline(), &[line], 200, 100);
         tiles.make_tiles_msaa(&[line], 200, 100);
     }
@@ -2459,7 +2522,7 @@
     #[test]
     fn sort_test() {
         let mut lines: Vec<Line> = Vec::new();
-        let mut tiles = Tiles::new(Level::baseline(), VIEW_DIM);
+        let mut tiles = Tiles::new(Level::baseline(), VIEW_DIM, VIEW_DIM);
 
         let step = 4.0;
         let mut y = F_V_DIM - 10.0;
diff --git a/sparse_strips/vello_cpu/examples/winit/src/main.rs b/sparse_strips/vello_cpu/examples/winit/src/main.rs
index b53f609..cef0c18 100644
--- a/sparse_strips/vello_cpu/examples/winit/src/main.rs
+++ b/sparse_strips/vello_cpu/examples/winit/src/main.rs
@@ -211,14 +211,7 @@
                     .unwrap();
 
                 self.pixmap.resize(width as u16, height as u16);
-                self.renderer = RenderContext::new_with(
-                    width as u16,
-                    height as u16,
-                    RenderSettings {
-                        num_threads: 0,
-                        ..Default::default()
-                    },
-                );
+                self.renderer.reset_and_resize(width as u16, height as u16);
 
                 window.request_redraw();
             }
diff --git a/sparse_strips/vello_cpu/src/dispatch/mod.rs b/sparse_strips/vello_cpu/src/dispatch/mod.rs
index 8c1fde1..b0ece11 100644
--- a/sparse_strips/vello_cpu/src/dispatch/mod.rs
+++ b/sparse_strips/vello_cpu/src/dispatch/mod.rs
@@ -65,7 +65,7 @@
         filter_data: Option<FilterData>,
     );
     fn pop_layer(&mut self);
-    fn reset(&mut self);
+    fn reset(&mut self, width: u16, height: u16);
     fn flush(&mut self);
     fn rasterize(
         &self,
diff --git a/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs b/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs
index 39e26b6..7d78792 100644
--- a/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs
+++ b/sparse_strips/vello_cpu/src/dispatch/multi_threaded.rs
@@ -587,8 +587,8 @@
             .expect("layer stack underflow");
     }
 
-    fn reset(&mut self) {
-        // Bucketer will be reset on demand.
+    fn reset(&mut self, width: u16, height: u16) {
+        // Bucketer will be reset lazily during rasterization with the active viewport.
         self.clip_context.reset();
         self.recorder.reset();
         self.strip_storage.clear();
@@ -599,7 +599,7 @@
         self.layer_depth = 0;
         self.task_sender = None;
         self.recorded_command_receiver = None;
-        self.strip_generator.reset();
+        self.strip_generator.reset(width, height);
         self.alpha_storage.with_inner(|alphas| {
             for alpha in alphas {
                 alpha.clear();
@@ -614,7 +614,7 @@
         self.thread_pool.spawn_broadcast(move |_| {
             let worker = workers.get().unwrap();
             let mut borrowed = worker.borrow_mut();
-            borrowed.reset();
+            borrowed.reset(width, height);
             t_barrier.wait();
         });
 
diff --git a/sparse_strips/vello_cpu/src/dispatch/multi_threaded/worker.rs b/sparse_strips/vello_cpu/src/dispatch/multi_threaded/worker.rs
index ea2a9d4..3aa63a7 100644
--- a/sparse_strips/vello_cpu/src/dispatch/multi_threaded/worker.rs
+++ b/sparse_strips/vello_cpu/src/dispatch/multi_threaded/worker.rs
@@ -36,8 +36,8 @@
         self.thread_id
     }
 
-    pub(crate) fn reset(&mut self) {
-        self.strip_generator.reset();
+    pub(crate) fn reset(&mut self, width: u16, height: u16) {
+        self.strip_generator.reset(width, height);
     }
 
     pub(crate) fn run_render_task(
diff --git a/sparse_strips/vello_cpu/src/dispatch/single_threaded.rs b/sparse_strips/vello_cpu/src/dispatch/single_threaded.rs
index f98e578..784a12b 100644
--- a/sparse_strips/vello_cpu/src/dispatch/single_threaded.rs
+++ b/sparse_strips/vello_cpu/src/dispatch/single_threaded.rs
@@ -292,8 +292,7 @@
             .height()
             .saturating_add(padding.y0)
             .saturating_add(padding.y1);
-        // TODO: Once `StripGenerator`s (in particular `Tiles`) can be resized,
-        // we can use a pool of strip generators.
+        // TODO: Use a pool of strip generators.
         let filter_generator = StripGenerator::new(width, height, self.level);
         let parent_generator = core::mem::replace(&mut self.strip_generator, filter_generator);
         self.strip_generator_stack.push(parent_generator);
@@ -441,13 +440,13 @@
         }
     }
 
-    fn reset(&mut self) {
+    fn reset(&mut self, width: u16, height: u16) {
         // Bucketer will be reset on demand, so no need to reset it here.
         self.clip_state.reset();
         self.recorder.reset();
         self.strip_generator_stack.clear();
-        self.strip_generator.reset();
         self.strip_storage.clear();
+        self.strip_generator.reset(width, height);
     }
 
     fn flush(&mut self) {
@@ -605,7 +604,7 @@
         assert!(!dispatcher.strip_storage.strips.is_empty());
         assert!(!dispatcher.recorder.root_cmds.is_empty());
 
-        dispatcher.reset();
+        dispatcher.reset(100, 100);
 
         // Verify all buffers are cleared.
         assert!(dispatcher.strip_storage.strips.is_empty());
diff --git a/sparse_strips/vello_cpu/src/render.rs b/sparse_strips/vello_cpu/src/render.rs
index fdec33f..ee044ea 100644
--- a/sparse_strips/vello_cpu/src/render.rs
+++ b/sparse_strips/vello_cpu/src/render.rs
@@ -692,9 +692,17 @@
         self.filter = None;
     }
 
+    /// Reset the render context and update the scene size.
+    pub fn reset_and_resize(&mut self, width: u16, height: u16) {
+        self.width = width;
+        self.height = height;
+
+        self.reset();
+    }
+
     /// Reset the render context.
     pub fn reset(&mut self) {
-        self.dispatcher.reset();
+        self.dispatcher.reset(self.width, self.height);
         self.encoded_paints.clear();
         self.mask = None;
         self.root_transforms.clear();
@@ -1081,6 +1089,28 @@
     }
 
     #[test]
+    fn reset_and_resize_updates_scene_size() {
+        let mut ctx = RenderContext::new(8, 4);
+        let mut resources = Resources::new();
+        let mut pixmap = Pixmap::new(4, 8);
+
+        ctx.reset_and_resize(4, 8);
+        assert_eq!(ctx.width(), 4);
+        assert_eq!(ctx.height(), 8);
+
+        ctx.set_paint(BLUE);
+        ctx.fill_rect(&Rect::new(0.0, 0.0, 4.0, 8.0));
+        ctx.flush();
+        ctx.render(&mut pixmap, &mut resources);
+
+        for y in 0..8 {
+            for x in 0..4 {
+                assert_eq!(pixmap.sample(x, y), blue_pixel(), "pixel at ({x}, {y})");
+            }
+        }
+    }
+
+    #[test]
     fn render_into_raw_buffer() {
         let ctx = red_rect_context(2, 1, Rect::new(0.0, 0.0, 2.0, 1.0));
         let mut resources = Resources::new();
diff --git a/sparse_strips/vello_hybrid/src/scene.rs b/sparse_strips/vello_hybrid/src/scene.rs
index c21c7da..0546893 100644
--- a/sparse_strips/vello_hybrid/src/scene.rs
+++ b/sparse_strips/vello_hybrid/src/scene.rs
@@ -1096,7 +1096,7 @@
     /// Reset scene to default values.
     pub fn reset(&mut self) {
         self.wide.reset();
-        self.strip_generator.reset();
+        self.strip_generator.reset(self.width, self.height);
         self.clip_context.reset();
         // Set the strip storage back to `Append` mode since the fast path is re-enabled on reset.
         {
diff --git a/sparse_strips/vello_toy/src/debug.rs b/sparse_strips/vello_toy/src/debug.rs
index f6dd158..4684253 100644
--- a/sparse_strips/vello_toy/src/debug.rs
+++ b/sparse_strips/vello_toy/src/debug.rs
@@ -33,7 +33,7 @@
         Document::new().set("viewBox", (-10, -10, args.width + 20, args.height + 20));
 
     let mut line_buf = vec![];
-    let mut tiles = Tiles::new(Level::new(), args.height);
+    let mut tiles = Tiles::new(Level::new(), args.width, args.height);
     let mut strip_buf = vec![];
     let mut alpha_buf = vec![];
     let mut wide = Wide::<MODE_CPU>::new(args.width, args.height);