Merge branch 'main' into failure
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 087c986..4f31e5e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,9 +20,21 @@
       - uses: dtolnay/rust-toolchain@stable
       - name: Install native dependencies
         run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
-      - run: cargo check --workspace 
+      - run: cargo check --workspace
+      # Check vello (the default crate) without the features used by `with_winit` for debugging
+      - run: cargo check 
       # --exclude with_bevy # for when bevy has an outdated wgpu version
       # -Dwarnings # for when we have fixed unused code warnings
+
+  clippy:
+    runs-on: ubuntu-latest
+    name: Enforce clippy lints
+    steps:
+      - uses: actions/checkout@v2
+      - uses: dtolnay/rust-toolchain@stable
+      - name: Install native dependencies
+        run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
+      - run: cargo clippy --all-targets --workspace -- -D warnings
   
   wasm:
     runs-on: ubuntu-latest
diff --git a/crates/shaders/build.rs b/crates/shaders/build.rs
index 614bacb..073d138 100644
--- a/crates/shaders/build.rs
+++ b/crates/shaders/build.rs
@@ -26,7 +26,7 @@
         .and_then(|p| Path::new(&p).parent().map(|p| p.to_owned()))
         .unwrap_or(PathBuf::from("../../"));
     let shader_dir = Path::new(&workspace_dir).join("shader");
-    let mut shaders = compile::ShaderInfo::from_dir(&shader_dir);
+    let mut shaders = compile::ShaderInfo::from_dir(shader_dir);
 
     // Drop the HashMap and sort by name so that we get deterministic order.
     let mut shaders = shaders.drain().collect::<Vec<_>>();
@@ -34,7 +34,7 @@
     let mut buf = String::default();
     write_types(&mut buf, &shaders).unwrap();
     write_shaders(&mut buf, &shaders).unwrap();
-    std::fs::write(&dest_path, &buf).unwrap();
+    std::fs::write(dest_path, &buf).unwrap();
     println!("cargo:rerun-if-changed=../shader");
 }
 
@@ -93,12 +93,12 @@
             wg_bufs
         )?;
         if cfg!(feature = "wgsl") {
-            writeln!(buf, "            wgsl: Cow::Borrowed(&{:?}),", info.source)?;
+            writeln!(buf, "            wgsl: Cow::Borrowed({:?}),", info.source)?;
         }
         if cfg!(feature = "msl") {
             writeln!(
                 buf,
-                "            msl: Cow::Borrowed(&{:?}),",
+                "            msl: Cow::Borrowed({:?}),",
                 compile::msl::translate(info).unwrap()
             )?;
         }
diff --git a/examples/headless/src/main.rs b/examples/headless/src/main.rs
index 43d33aa..5a49d05 100644
--- a/examples/headless/src/main.rs
+++ b/examples/headless/src/main.rs
@@ -21,7 +21,7 @@
     #[cfg(not(target_arch = "wasm32"))]
     env_logger::init();
     let args = Args::parse();
-    let scenes = args.args.select_scene_set(|| Args::command())?;
+    let scenes = args.args.select_scene_set(Args::command)?;
     if let Some(scenes) = scenes {
         let mut scene_idx = None;
         for (idx, scene) in scenes.scenes.iter().enumerate() {
@@ -41,8 +41,8 @@
                     args.scene
                 ))?;
 
-                if !(parsed < scenes.scenes.len()) {
-                    if scenes.scenes.len() == 0 {
+                if parsed >= scenes.scenes.len() {
+                    if scenes.scenes.is_empty() {
                         bail!("Cannot select a scene, as there are no scenes")
                     }
                     bail!(
@@ -86,7 +86,7 @@
     let device = &device_handle.device;
     let queue = &device_handle.queue;
     let mut renderer = vello::Renderer::new(
-        &device,
+        device,
         &RendererOptions {
             surface_format: None,
         },
@@ -120,20 +120,14 @@
         };
         let factor = Vec2::new(new_width as f64, new_height as f64);
         let scale_factor = (factor.x / resolution.x).min(factor.y / resolution.y);
-        transform = transform * Affine::scale(scale_factor);
+        transform *= Affine::scale(scale_factor);
         (new_width, new_height)
     } else {
         match (args.x_resolution, args.y_resolution) {
             (None, None) => (1000, 1000),
-            (None, Some(y)) => {
-                let y = y.try_into()?;
-                (y, y)
-            }
-            (Some(x), None) => {
-                let x = x.try_into()?;
-                (x, x)
-            }
-            (Some(x), Some(y)) => (x.try_into()?, y.try_into()?),
+            (None, Some(y)) => (y, y),
+            (Some(x), None) => (x, x),
+            (Some(x), Some(y)) => (x, y),
         }
     };
     let render_params = vello::RenderParams {
@@ -165,11 +159,11 @@
     });
     let view = target.create_view(&wgpu::TextureViewDescriptor::default());
     renderer
-        .render_to_texture(&device, &queue, &scene, &view, &render_params)
+        .render_to_texture(device, queue, &scene, &view, &render_params)
         .or_else(|_| bail!("Got non-Send/Sync error from rendering"))?;
     // (width * 4).next_multiple_of(256)
     let padded_byte_width = {
-        let w = width as u32 * 4;
+        let w = width * 4;
         match w % 256 {
             0 => w,
             r => w + (256 - r),
@@ -202,7 +196,7 @@
 
     let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
     buf_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
-    if let Some(recv_result) = block_on_wgpu(&device, receiver.receive()) {
+    if let Some(recv_result) = block_on_wgpu(device, receiver.receive()) {
         recv_result?;
     } else {
         bail!("channel was closed");
diff --git a/examples/scenes/src/download.rs b/examples/scenes/src/download.rs
index 805770d..3f3eb51 100644
--- a/examples/scenes/src/download.rs
+++ b/examples/scenes/src/download.rs
@@ -40,7 +40,7 @@
         if let Some(downloads) = &self.downloads {
             to_download = downloads
                 .iter()
-                .map(|it| Self::parse_download(&it))
+                .map(|it| Self::parse_download(it))
                 .collect();
         } else {
             let mut accepted = self.auto;
@@ -52,7 +52,7 @@
                 })
                 .collect::<Vec<_>>();
             if !accepted {
-                if downloads.len() != 0 {
+                if !downloads.is_empty() {
                     println!(
                         "Would you like to download a set of default svg files? These files are:"
                     );
@@ -140,7 +140,7 @@
         let mut file = std::fs::OpenOptions::new()
             .create_new(true)
             .write(true)
-            .open(&self.file_path(directory))
+            .open(self.file_path(directory))
             .context("Creating file")?;
         let mut reader = ureq::get(&self.url).call()?.into_reader();
 
@@ -152,14 +152,8 @@
         if reader.read_exact(&mut [0]).is_ok() {
             bail!("Size limit exceeded");
         }
-        if limit_exact {
-            if file
-                .seek(std::io::SeekFrom::Current(0))
-                .context("Checking file limit")?
-                != size_limit
-            {
-                bail!("Builtin downloaded file was not as expected");
-            }
+        if limit_exact && file.stream_position().context("Checking file limit")? != size_limit {
+            bail!("Builtin downloaded file was not as expected");
         }
         Ok(())
     }
diff --git a/examples/scenes/src/lib.rs b/examples/scenes/src/lib.rs
index 7fee5cd..6cea26d 100644
--- a/examples/scenes/src/lib.rs
+++ b/examples/scenes/src/lib.rs
@@ -99,7 +99,7 @@
             if self.test_scenes {
                 Ok(test_scenes())
             } else if let Some(svgs) = &self.svgs {
-                scene_from_files(&svgs)
+                scene_from_files(svgs)
             } else {
                 default_scene(command)
             }
diff --git a/examples/scenes/src/mmark.rs b/examples/scenes/src/mmark.rs
index dc5ce32..98be5fa 100644
--- a/examples/scenes/src/mmark.rs
+++ b/examples/scenes/src/mmark.rs
@@ -1,5 +1,7 @@
 //! A benchmark based on MotionMark 1.2's path benchmark.
 
+use std::cmp::Ordering;
+
 use rand::{seq::SliceRandom, Rng};
 use vello::peniko::Color;
 use vello::{
@@ -40,19 +42,21 @@
 
     fn resize(&mut self, n: usize) {
         let old_n = self.elements.len();
-        if n < old_n {
-            self.elements.truncate(n)
-        } else if n > old_n {
-            let mut last = self
-                .elements
-                .last()
-                .map(|e| e.grid_point)
-                .unwrap_or(GridPoint((GRID_WIDTH / 2, GRID_HEIGHT / 2)));
-            self.elements.extend((old_n..n).map(|_| {
-                let element = Element::new_rand(last);
-                last = element.grid_point;
-                element
-            }));
+        match n.cmp(&old_n) {
+            Ordering::Less => self.elements.truncate(n),
+            Ordering::Greater => {
+                let mut last = self
+                    .elements
+                    .last()
+                    .map(|e| e.grid_point)
+                    .unwrap_or(GridPoint((GRID_WIDTH / 2, GRID_HEIGHT / 2)));
+                self.elements.extend((old_n..n).map(|_| {
+                    let element = Element::new_rand(last);
+                    last = element.grid_point;
+                    element
+                }));
+            }
+            _ => (),
         }
     }
 }
@@ -149,7 +153,7 @@
                 )),
             )
         };
-        let color = COLORS.choose(&mut rng).unwrap().clone();
+        let color = *COLORS.choose(&mut rng).unwrap();
         let width = rng.gen::<f64>().powi(5) * 20.0 + 1.0;
         let is_split = rng.gen();
         Element {
@@ -170,11 +174,11 @@
 
         let offset = OFFSETS.choose(&mut rng).unwrap();
         let mut x = last.0 .0 + offset.0;
-        if x < 0 || x > GRID_WIDTH {
+        if !(0..=GRID_WIDTH).contains(&x) {
             x -= offset.0 * 2;
         }
         let mut y = last.0 .1 + offset.1;
-        if y < 0 || y > GRID_HEIGHT {
+        if !(0..=GRID_HEIGHT).contains(&y) {
             y -= offset.1 * 2;
         }
         GridPoint((x, y))
diff --git a/examples/scenes/src/simple_text.rs b/examples/scenes/src/simple_text.rs
index 717d0f1..9e81198 100644
--- a/examples/scenes/src/simple_text.rs
+++ b/examples/scenes/src/simple_text.rs
@@ -37,6 +37,7 @@
 }
 
 impl SimpleText {
+    #[allow(clippy::new_without_default)]
     pub fn new() -> Self {
         Self {
             gcx: GlyphContext::new(),
@@ -45,6 +46,7 @@
         }
     }
 
+    #[allow(clippy::too_many_arguments)]
     pub fn add_run<'a>(
         &mut self,
         builder: &mut SceneBuilder,
@@ -69,6 +71,7 @@
         );
     }
 
+    #[allow(clippy::too_many_arguments)]
     pub fn add_var_run<'a>(
         &mut self,
         builder: &mut SceneBuilder,
@@ -118,7 +121,7 @@
                     }
                     let gid = charmap.map(ch).unwrap_or_default();
                     let advance = glyph_metrics.advance_width(gid).unwrap_or_default();
-                    let x = pen_x as f32;
+                    let x = pen_x;
                     pen_x += advance;
                     Some(Glyph {
                         id: gid.to_u16() as u32,
@@ -139,10 +142,7 @@
         text: &str,
     ) {
         let default_font = FontRef::new(ROBOTO_FONT).unwrap();
-        let font = font
-            .map(|font| to_font_ref(font))
-            .flatten()
-            .unwrap_or(default_font);
+        let font = font.and_then(to_font_ref).unwrap_or(default_font);
         let fello_size = vello::fello::Size::new(size);
         let charmap = font.charmap();
         let metrics = font.metrics(fello_size, Default::default());
@@ -171,7 +171,7 @@
     }
 }
 
-fn to_font_ref<'a>(font: &'a Font) -> Option<FontRef<'a>> {
+fn to_font_ref(font: &Font) -> Option<FontRef<'_>> {
     use vello::fello::raw::FileRef;
     let file_ref = FileRef::new(font.data.as_ref()).ok()?;
     match file_ref {
diff --git a/examples/scenes/src/svg.rs b/examples/scenes/src/svg.rs
index 708be50..122bd79 100644
--- a/examples/scenes/src/svg.rs
+++ b/examples/scenes/src/svg.rs
@@ -75,8 +75,7 @@
         .unwrap_or_else(|| "unknown".to_string());
     ExampleScene {
         function: Box::new(svg_function_of(name.clone(), move || {
-            let contents = std::fs::read_to_string(&file).expect("failed to read svg file");
-            contents
+            std::fs::read_to_string(file).expect("failed to read svg file")
         })),
         config: crate::SceneConfig {
             animated: false,
@@ -91,7 +90,7 @@
 ) -> impl FnMut(&mut SceneBuilder, &mut SceneParams) {
     fn render_svg_contents(name: &str, contents: &str) -> (SceneFragment, Vec2) {
         let start = Instant::now();
-        let svg = usvg::Tree::from_str(&contents, &usvg::Options::default())
+        let svg = usvg::Tree::from_str(contents, &usvg::Options::default())
             .expect("failed to parse svg file");
         eprintln!("Parsed svg {name} in {:?}", start.elapsed());
         let start = Instant::now();
@@ -112,7 +111,7 @@
     let mut contents = Some(contents);
     move |builder, params| {
         if let Some((scene_frag, resolution)) = cached_scene.as_mut() {
-            builder.append(&scene_frag, None);
+            builder.append(scene_frag, None);
             params.resolution = Some(*resolution);
             return;
         }
diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs
index ef9676d..ab3ee1e 100644
--- a/examples/scenes/src/test_scenes.rs
+++ b/examples/scenes/src/test_scenes.rs
@@ -167,7 +167,7 @@
         Fill::NonZero,
         "And some vello\ntext with a newline",
     );
-    let th = params.time as f64;
+    let th = params.time;
     let center = Point::new(500.0, 500.0);
     let mut p1 = center;
     p1.x += 400.0 * th.cos();
@@ -186,7 +186,7 @@
         None,
         &rect,
     );
-    let alpha = (params.time as f64).sin() as f32 * 0.5 + 0.5;
+    let alpha = params.time.sin() as f32 * 0.5 + 0.5;
     sb.push_layer(Mix::Normal, alpha, Affine::IDENTITY, &rect);
     sb.fill(
         Fill::NonZero,
@@ -284,12 +284,11 @@
         );
     }
     let extend_modes = [Extend::Pad, Extend::Repeat, Extend::Reflect];
-    for x in 0..3 {
-        let extend = extend_modes[x];
+    for (x, extend) in extend_modes.iter().enumerate() {
         for y in 0..2 {
             let is_radial = y & 1 != 0;
             let transform = Affine::translate((x as f64 * 350.0 + 50.0, y as f64 * 350.0 + 100.0));
-            square(sb, is_radial, transform, extend);
+            square(sb, is_radial, transform, *extend);
         }
     }
     for (i, label) in ["Pad", "Repeat", "Reflect"].iter().enumerate() {
@@ -305,6 +304,7 @@
     }
 }
 
+#[allow(clippy::too_many_arguments)]
 fn two_point_radial(sb: &mut SceneBuilder, _params: &mut SceneParams) {
     fn make(
         sb: &mut SceneBuilder,
@@ -604,7 +604,7 @@
         (125., 200., Color::rgb8(64, 192, 255)),
     ];
     for (x, y, c) in GRADIENTS {
-        let mut color2 = c.clone();
+        let mut color2 = *c;
         color2.a = 0;
         let radial = Gradient::new_radial((*x, *y), 100.0).with_stops([*c, color2]);
         sb.fill(Fill::NonZero, transform, &radial, None, &rect);
diff --git a/examples/with_bevy/src/main.rs b/examples/with_bevy/src/main.rs
index eb88c48..d4d57d6 100644
--- a/examples/with_bevy/src/main.rs
+++ b/examples/with_bevy/src/main.rs
@@ -63,7 +63,7 @@
             .0
             .render_to_texture(
                 device.wgpu_device(),
-                &*queue,
+                &queue,
                 &scene.0,
                 &gpu_image.texture_view,
                 &params,
diff --git a/examples/with_winit/src/lib.rs b/examples/with_winit/src/lib.rs
index 94cd791..9ae4576 100644
--- a/examples/with_winit/src/lib.rs
+++ b/examples/with_winit/src/lib.rs
@@ -100,6 +100,8 @@
     let mut images = ImageCache::new();
     let mut stats = stats::Stats::new();
     let mut stats_shown = true;
+    // Currently not updated in wasm builds
+    #[allow(unused_mut)]
     let mut scene_complexity: Option<BumpAllocators> = None;
     let mut complexity_shown = false;
     let mut vsync_on = true;
@@ -327,13 +329,13 @@
                 // TODO: Apply svg view_box, somehow
                 let factor = Vec2::new(width as f64, height as f64);
                 let scale_factor = (factor.x / resolution.x).min(factor.y / resolution.y);
-                transform = transform * Affine::scale(scale_factor);
+                transform *= Affine::scale(scale_factor);
             }
             builder.append(&fragment, Some(transform));
             if stats_shown {
                 snapshot.draw_layer(
                     &mut builder,
-                    &mut scene_params.text,
+                    scene_params.text,
                     width as f64,
                     height as f64,
                     stats.samples(),
@@ -455,7 +457,7 @@
         .with_inner_size(LogicalSize::new(1044, 800))
         .with_resizable(true)
         .with_title("Vello demo")
-        .build(&event_loop)
+        .build(event_loop)
         .unwrap()
 }
 
@@ -492,7 +494,7 @@
     #[cfg(not(target_arch = "wasm32"))]
     env_logger::init();
     let args = Args::parse();
-    let scenes = args.args.select_scene_set(|| Args::command())?;
+    let scenes = args.args.select_scene_set(Args::command)?;
     if let Some(scenes) = scenes {
         let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
         #[allow(unused_mut)]
diff --git a/examples/with_winit/src/stats.rs b/examples/with_winit/src/stats.rs
index 81dda86..694ecdf 100644
--- a/examples/with_winit/src/stats.rs
+++ b/examples/with_winit/src/stats.rs
@@ -33,6 +33,7 @@
 }
 
 impl Snapshot {
+    #[allow(clippy::too_many_arguments)]
     pub fn draw_layer<'a, T>(
         &self,
         sb: &mut SceneBuilder,
@@ -69,7 +70,7 @@
         ];
         if let Some(bump) = &bump {
             if bump.failed >= 1 {
-                labels.push(format!("Allocation Failed!"));
+                labels.push("Allocation Failed!".into());
             }
             labels.push(format!("binning: {}", bump.binning));
             labels.push(format!("ptcl: {}", bump.ptcl));
@@ -89,7 +90,7 @@
                 text_size,
                 Some(&Brush::Solid(Color::WHITE)),
                 offset * Affine::translate((left_margin, (i + 1) as f64 * text_height)),
-                &label,
+                label,
             );
         }
         text.add(
@@ -132,6 +133,7 @@
             let sample_ms = ((*sample as f64) * 0.001).min(display_max);
             let h = sample_ms / display_max;
             let s = Affine::scale_non_uniform(1., -h);
+            #[allow(clippy::match_overlapping_arm)]
             let color = match *sample {
                 ..=16_667 => Color::rgb8(100, 143, 255),
                 ..=33_334 => Color::rgb8(255, 176, 0),
diff --git a/src/engine.rs b/src/engine.rs
index 85a5208..4a910e6 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -63,6 +63,7 @@
 #[derive(Copy, Clone, PartialEq, Eq)]
 pub enum ImageFormat {
     Rgba8,
+    #[allow(unused)]
     Bgra8,
 }
 
@@ -81,6 +82,7 @@
 }
 
 pub enum ExternalResource<'a> {
+    #[allow(unused)]
     Buf(BufProxy, &'a Buffer),
     Image(ImageProxy, &'a TextureView),
 }
@@ -800,7 +802,7 @@
     fn get_buf(
         &mut self,
         size: u64,
-        name: &'static str,
+        #[allow(unused)] name: &'static str,
         usage: BufferUsages,
         device: &Device,
     ) -> Buffer {
@@ -809,7 +811,7 @@
             size: rounded_size,
             usages: usage,
             #[cfg(feature = "buffer_labels")]
-            name: name,
+            name,
         };
         if let Some(buf_vec) = self.bufs.get_mut(&props) {
             if let Some(buf) = buf_vec.pop() {
diff --git a/src/lib.rs b/src/lib.rs
index a1e3b03..43be77a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -230,7 +230,7 @@
                 return Err("channel was closed".into());
             }
             let mapped = buf_slice.get_mapped_range();
-            bump = Some(bytemuck::pod_read_unaligned(&*mapped));
+            bump = Some(bytemuck::pod_read_unaligned(&mapped));
         }
         // TODO: apply logic to determine whether we need to rerun coarse, and also
         // allocate the blend stack as needed.
diff --git a/src/render.rs b/src/render.rs
index 600aded..24ec41a 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -2,7 +2,7 @@
 
 use crate::{
     engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy},
-    shaders::{self, FullShaders},
+    shaders::FullShaders,
     RenderParams, Scene,
 };
 use vello_encoding::{Encoding, WorkgroupSize};
diff --git a/src/shaders/preprocess.rs b/src/shaders/preprocess.rs
index bb9ed68..97367b8 100644
--- a/src/shaders/preprocess.rs
+++ b/src/shaders/preprocess.rs
@@ -5,6 +5,7 @@
     vec,
 };
 
+#[allow(unused)]
 pub fn get_imports(shader_dir: &Path) -> HashMap<String, String> {
     let mut imports = HashMap::new();
     let imports_dir = shader_dir.join("shared");