From f61e8936f1b49b69ac19ca49a9a19de96efc29f8 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Tue, 15 Sep 2026 02:16:56 -0400 Subject: [PATCH] Restore abs() in the rect shader, and validate every shader without a device `7c50a3e` renamed a length's `abs` component to `px` and took the WGSL `abs()` builtin in the rounded-rect distance with it, so every window failed shader validation on the first frame while `cargo test` stayed green. `naga` is reachable through `wgpu`, so a unit test now composes each shader file with the prelude the way the renderer does and parses and validates it; it reads the shader directory rather than naming primitives, so a new one is covered by adding its file. Co-Authored-By: Claude Fable 5.1 --- core/src/render/mod.rs | 38 +++++++++++++++++++++++++++++++- core/src/render/shader/rect.wgsl | 2 +- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/core/src/render/mod.rs b/core/src/render/mod.rs index d368ffb..834cc8c 100644 --- a/core/src/render/mod.rs +++ b/core/src/render/mod.rs @@ -22,6 +22,10 @@ pub use primitive::*; const PRELUDE: &str = include_str!("./shader/prelude.wgsl"); +fn module_source(wgsl: &str) -> String { + format!("{PRELUDE}\n{wgsl}") +} + pub struct UiRenderNode { shared_layout: BindGroupLayout, shared_group: BindGroup, @@ -222,7 +226,7 @@ impl UiRenderNode { ) -> RenderPipeline { let module = device.create_shader_module(ShaderModuleDescriptor { label: Some(label), - source: ShaderSource::Wgsl(format!("{PRELUDE}\n{wgsl}").into()), + source: ShaderSource::Wgsl(module_source(wgsl).into()), }); device.create_render_pipeline(&RenderPipelineDescriptor { label: Some(label), @@ -401,3 +405,35 @@ impl ListBuffers { } } } + +#[cfg(test)] +mod tests { + use super::module_source; + use wgpu::naga::{ + front::wgsl, + valid::{Capabilities, ValidationFlags, Validator}, + }; + + /// Every shader file, composed as the renderer composes it, parses and + /// validates with no device -- so an edit that breaks one fails here and + /// not in the first window opened. + #[test] + fn every_shader_validates() { + let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/src/render/shader"); + let mut checked = 0; + for entry in std::fs::read_dir(dir).unwrap() { + let path = entry.unwrap().path(); + if path.extension().is_none_or(|e| e != "wgsl") || path.ends_with("prelude.wgsl") { + continue; + } + let source = module_source(&std::fs::read_to_string(&path).unwrap()); + let module = wgsl::parse_str(&source) + .unwrap_or_else(|e| panic!("{}: {}", path.display(), e.emit_to_string(&source))); + Validator::new(ValidationFlags::all(), Capabilities::all()) + .validate(&module) + .unwrap_or_else(|e| panic!("{}: {e:?}", path.display())); + checked += 1; + } + assert!(checked > 0, "no shaders found in {dir}"); + } +} diff --git a/core/src/render/shader/rect.wgsl b/core/src/render/shader/rect.wgsl index 5a01564..6d8694d 100644 --- a/core/src/render/shader/rect.wgsl +++ b/core/src/render/shader/rect.wgsl @@ -34,6 +34,6 @@ fn distance_from_rect(pixel_pos: vec2, rect_center: vec2, rect_corner: // vec from center to pixel let p = pixel_pos - rect_center; // vec from inner rect corner to pixel - let q = px(p) - (rect_corner - radius); + let q = abs(p) - (rect_corner - radius); return length(max(q, vec2(0.0))) - radius; }