From 20e1f74b2f6d4204585482f77aad7f8e8bb5d36b Mon Sep 17 00:00:00 2001 From: Eric Van Albert Date: Fri, 28 Nov 2025 14:10:47 -0500 Subject: [PATCH] Add compilation of all built-in effect node shaders to build.rs --- build.rs | 160 +++++++++--------- library/fractalzoom.wgsl | 2 +- src/lib/{ => effect_node}/effect_footer.wgsl | 0 src/lib/{ => effect_node}/effect_header.wgsl | 0 .../{effect_node.rs => effect_node/mod.rs} | 50 +----- src/lib/effect_node/preprocess_shader.rs | 46 +++++ 6 files changed, 129 insertions(+), 129 deletions(-) rename src/lib/{ => effect_node}/effect_footer.wgsl (100%) rename src/lib/{ => effect_node}/effect_header.wgsl (100%) rename src/lib/{effect_node.rs => effect_node/mod.rs} (92%) create mode 100644 src/lib/effect_node/preprocess_shader.rs diff --git a/build.rs b/build.rs index d718e785..003de91f 100644 --- a/build.rs +++ b/build.rs @@ -4,9 +4,13 @@ use std::io::Write; use std::path; use std::path::Path; +#[path = "src/lib/effect_node/preprocess_shader.rs"] +mod preprocess_shader; +use preprocess_shader::preprocess_shader; + fn main() { check_builtin_shaders(); - //check_library_shaders(); + check_library_shaders(); embed_default_library(); } @@ -31,10 +35,12 @@ fn check_builtin_shaders() { )); } - println!("cargo:rerun-if-changed=src/lib/effect_header.wgsl"); - println!("cargo:rerun-if-changed=src/lib/effect_footer.wgsl"); - let effect_header = fs::read_to_string(Path::new("src/lib/effect_header.wgsl")).unwrap(); - let effect_footer = fs::read_to_string(Path::new("src/lib/effect_footer.wgsl")).unwrap(); + println!("cargo:rerun-if-changed=src/lib/effect_node/effect_header.wgsl"); + println!("cargo:rerun-if-changed=src/lib/effect_node/effect_footer.wgsl"); + let effect_header = + fs::read_to_string(Path::new("src/lib/effect_node/effect_header.wgsl")).unwrap(); + let effect_footer = + fs::read_to_string(Path::new("src/lib/effect_node/effect_footer.wgsl")).unwrap(); let effect_noop = "fn main(uv: vec2) -> vec4 { return vec4(0., 0., 0., 0.); }"; shader_sources.push(( @@ -73,7 +79,6 @@ fn check_builtin_shaders() { } } -/* fn check_library_shaders() { let mut had_errors = false; @@ -81,95 +86,88 @@ fn check_library_shaders() { let library_dir = Path::new("library"); println!("cargo:rerun-if-changed=library/"); - if library_dir.exists() && library_dir.is_dir() { - let library_shaders = fs::read_dir(library_dir) - .expect("Failed to read library directory") - .filter_map(|entry| { - let entry = entry.ok()?; - let path = entry.path(); - if path.extension()?.to_str()? == "wgsl" { - Some(path) - } else { - None + let library_shaders = fs::read_dir(library_dir) + .expect("Failed to read library directory") + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.extension()?.to_str()? == "wgsl" { + Some(path) + } else { + None + } + }); + + println!("cargo:rerun-if-changed=src/lib/effect_node/effect_header.wgsl"); + println!("cargo:rerun-if-changed=src/lib/effect_node/effect_footer.wgsl"); + let effect_header = + fs::read_to_string(Path::new("src/lib/effect_node/effect_header.wgsl")).unwrap(); + let effect_footer = + fs::read_to_string(Path::new("src/lib/effect_node/effect_footer.wgsl")).unwrap(); + + for shader_path in library_shaders { + println!("cargo:rerun-if-changed={}", shader_path.display()); + + let shader_source = match fs::read_to_string(&shader_path) { + Ok(source) => source, + Err(e) => { + eprintln!( + "Failed to read effect node file {}: {}", + shader_path.display(), + e + ); + had_errors = true; + continue; + } + }; + + let (buffer_shader_sources, _input_count, _frequency) = + match preprocess_shader(&shader_source) { + Ok(results) => results, + Err(e) => { + eprintln!( + "Failed to parse effect node file {}: {}", + shader_path.display(), + e + ); + had_errors = true; + continue; } - }); + }; - let header_source = match fs::read_to_string("src/lib/effect_header.wgsl") { - Ok(source) => Some(source), - Err(e) => { - eprintln!("Failed to read shader file {}: {}", shader_path, e); - had_errors = true; - None - } - }; + for buffer_shader_source in buffer_shader_sources { + // Parse and validate the WGSL shader using naga + match naga::front::wgsl::parse_str(&format!( + "{}\n{}\n{}\n", + effect_header, buffer_shader_source, effect_footer + )) { + Ok(module) => { + // Validate the module + let mut validator = naga::valid::Validator::new( + naga::valid::ValidationFlags::all(), + naga::valid::Capabilities::all(), + ); - let footer_source = match fs::read_to_string("src/lib/effect_footer.wgsl") { - Ok(source) => Some(source), - Err(e) => { - eprintln!("Failed to read shader file {}: {}", shader_path, e); - had_errors = true; - None - } - }; - - if let (Some(header_source), Some(footer_source)) = (header_source, footer_source) { - for shader_path in library_shaders { - println!("cargo:rerun-if-changed={}", shader_path.display()); - - let shader_source = match fs::read_to_string(&shader_path) { - Ok(source) => source, - Err(e) => { - eprintln!( - "Failed to read shader file {}: {}", - shader_path.display(), - e - ); - had_errors = true; - continue; - } - }; - - // Parse and validate the WGSL shader using naga - match naga::front::wgsl::parse_str(&format!( - "{}\n{}\n{}\n", - header_source, shader_source, footer_source - )) { - Ok(module) => { - // Validate the module - let mut validator = naga::valid::Validator::new( - naga::valid::ValidationFlags::all(), - naga::valid::Capabilities::all(), - ); - - match validator.validate(&module) { - Ok(_) => { - println!( - "cargo:warning=✓ Validated shader: {}", - shader_path.display() - ); - } - Err(e) => { - eprintln!("Validation error in {}: {}", shader_path.display(), e); - had_errors = true; - } + match validator.validate(&module) { + Ok(_) => {} + Err(e) => { + eprintln!("Validation error in {}: {}", shader_path.display(), e); + had_errors = true; } } - Err(e) => { - eprintln!("Parse error in {}: {}", shader_path.display(), e); - had_errors = true; - } + } + Err(e) => { + eprintln!("WGSL parse error in {}: {}", shader_path.display(), e); + had_errors = true; } } } - } else { - println!("cargo:warning=Library directory 'library/' not found, skipping library shaders"); } if had_errors { panic!("Shader validation failed!"); } } -*/ fn embed_default_library() { let out_dir = env::var("OUT_DIR").unwrap(); diff --git a/library/fractalzoom.wgsl b/library/fractalzoom.wgsl index 8ba3e899..0d6487f1 100644 --- a/library/fractalzoom.wgsl +++ b/library/fractalzoom.wgsl @@ -1,7 +1,7 @@ #property description Overlays a smaller pattern and zoom in on it #property frequency 0.5 -let N_COPIES = 8; +const N_COPIES = 8; fn lookup(uv: vec2, scale: f32) -> vec4{ let newUV = (uv - 0.5) * scale + 0.5; diff --git a/src/lib/effect_footer.wgsl b/src/lib/effect_node/effect_footer.wgsl similarity index 100% rename from src/lib/effect_footer.wgsl rename to src/lib/effect_node/effect_footer.wgsl diff --git a/src/lib/effect_header.wgsl b/src/lib/effect_node/effect_header.wgsl similarity index 100% rename from src/lib/effect_header.wgsl rename to src/lib/effect_node/effect_header.wgsl diff --git a/src/lib/effect_node.rs b/src/lib/effect_node/mod.rs similarity index 92% rename from src/lib/effect_node.rs rename to src/lib/effect_node/mod.rs index a1ca9bd6..ff6d8307 100644 --- a/src/lib/effect_node.rs +++ b/src/lib/effect_node/mod.rs @@ -6,6 +6,9 @@ use std::collections::HashMap; use std::num::NonZeroU32; use std::string::String; +mod preprocess_shader; +use preprocess_shader::preprocess_shader; + const EFFECT_HEADER: &str = include_str!("effect_header.wgsl"); const EFFECT_FOOTER: &str = include_str!("effect_footer.wgsl"); const INTENSITY_INTEGRAL_PERIOD: f32 = 1024.; @@ -86,53 +89,6 @@ fn handle_shader_error(error: wgpu::Error) { eprintln!("wgpu error: {}\n", error); } -fn preprocess_shader(effect_source: &str) -> Result<(Vec, u32, f32), String> { - let mut processed_sources = Vec::::new(); - let mut processed_source = String::new(); - let mut input_count = 1; - let mut frequency = 0.; - - for l in effect_source.lines() { - let line_parts: Vec<&str> = l.split_whitespace().collect(); - if !line_parts.is_empty() && line_parts[0] == "#property" { - if line_parts.len() >= 2 { - if line_parts[1] == "inputCount" { - if line_parts.len() >= 3 { - input_count = line_parts[2].parse::().map_err(|e| e.to_string())?; - } else { - return Err(String::from("inputCount missing argument")); - } - } else if line_parts[1] == "frequency" { - if line_parts.len() >= 3 { - frequency = line_parts[2].parse::().map_err(|e| e.to_string())?; - } else { - return Err(String::from("frequency missing argument")); - } - } else if line_parts[1] == "description" { - if line_parts.len() >= 3 { - // TODO parse description and do something with it - } else { - return Err(String::from("description missing argument")); - } - } else { - return Err(format!("Unrecognized property: {}", line_parts[1])); - } - } else { - return Err(String::from("Missing property name")); - } - } else if !line_parts.is_empty() && line_parts[0] == "#buffershader" { - processed_sources.push(std::mem::take(&mut processed_source)); - } else { - processed_source.push_str(l); - processed_source.push('\n'); - } - } - - processed_sources.push(processed_source); - - Ok((processed_sources, input_count, frequency)) -} - // This is a state machine, it's more natural to use `match` than `if let` #[allow(clippy::single_match)] impl EffectNodeState { diff --git a/src/lib/effect_node/preprocess_shader.rs b/src/lib/effect_node/preprocess_shader.rs new file mode 100644 index 00000000..a1d07739 --- /dev/null +++ b/src/lib/effect_node/preprocess_shader.rs @@ -0,0 +1,46 @@ +pub fn preprocess_shader(effect_source: &str) -> Result<(Vec, u32, f32), String> { + let mut processed_sources = Vec::::new(); + let mut processed_source = String::new(); + let mut input_count = 1; + let mut frequency = 0.; + + for l in effect_source.lines() { + let line_parts: Vec<&str> = l.split_whitespace().collect(); + if !line_parts.is_empty() && line_parts[0] == "#property" { + if line_parts.len() >= 2 { + if line_parts[1] == "inputCount" { + if line_parts.len() >= 3 { + input_count = line_parts[2].parse::().map_err(|e| e.to_string())?; + } else { + return Err(String::from("inputCount missing argument")); + } + } else if line_parts[1] == "frequency" { + if line_parts.len() >= 3 { + frequency = line_parts[2].parse::().map_err(|e| e.to_string())?; + } else { + return Err(String::from("frequency missing argument")); + } + } else if line_parts[1] == "description" { + if line_parts.len() >= 3 { + // TODO parse description and do something with it + } else { + return Err(String::from("description missing argument")); + } + } else { + return Err(format!("Unrecognized property: {}", line_parts[1])); + } + } else { + return Err(String::from("Missing property name")); + } + } else if !line_parts.is_empty() && line_parts[0] == "#buffershader" { + processed_sources.push(std::mem::take(&mut processed_source)); + } else { + processed_source.push_str(l); + processed_source.push('\n'); + } + } + + processed_sources.push(processed_source); + + Ok((processed_sources, input_count, frequency)) +}