Add compilation of all built-in effect node shaders to build.rs

This commit is contained in:
Eric Van Albert
2025-11-28 14:10:47 -05:00
parent b5bd7c8736
commit 20e1f74b2f
6 changed files with 129 additions and 129 deletions
+79 -81
View File
@@ -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<f32>) -> vec4<f32> { return vec4<f32>(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();
+1 -1
View File
@@ -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<f32>, scale: f32) -> vec4<f32>{
let newUV = (uv - 0.5) * scale + 0.5;
@@ -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<String>, u32, f32), String> {
let mut processed_sources = Vec::<String>::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::<u32>().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::<f32>().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 {
+46
View File
@@ -0,0 +1,46 @@
pub fn preprocess_shader(effect_source: &str) -> Result<(Vec<String>, u32, f32), String> {
let mut processed_sources = Vec::<String>::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::<u32>().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::<f32>().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))
}