geng_shader/
lib.rs

1use std::collections::HashMap;
2use ugli::Ugli;
3
4pub struct Library {
5    ugli: Ugli,
6    files: HashMap<String, String>,
7    prefix: Option<(String, String)>, // TODO remove?
8}
9
10impl Library {
11    pub fn empty(ugli: &Ugli) -> Self {
12        Self {
13            ugli: ugli.clone(),
14            files: HashMap::new(),
15            prefix: None,
16        }
17    }
18    pub fn new(ugli: &Ugli, antialias: bool, prefix: Option<(String, String)>) -> Self {
19        let mut library = Self::empty(ugli);
20        let mut prelude = include_str!("prelude.glsl").to_owned();
21        if antialias {
22            prelude = "#define GENG_ANTIALIAS\n".to_owned() + &prelude;
23        }
24        fn default_prefix() -> (String, String) {
25            let common_glsl = "precision highp int;\nprecision highp float;\n";
26            if cfg!(any(target_arch = "wasm32", target_os = "android")) {
27                (
28                    format!("{common_glsl}#define VERTEX_SHADER\n"),
29                    format!("#extension GL_OES_standard_derivatives : enable\n{common_glsl}#define FRAGMENT_SHADER\n"),
30                )
31            } else {
32                (
33                    format!("#version 150\n{common_glsl}#define VERTEX_SHADER\n"),
34                    format!("#version 150\n{common_glsl}#define FRAGMENT_SHADER\n"),
35                )
36            }
37        }
38        library.prefix = Some(prefix.unwrap_or_else(default_prefix));
39        library.add("prelude", &prelude);
40        library.add("noise", include_str!("noise.glsl"));
41        library
42    }
43
44    pub fn add(&mut self, file_name: &str, source: &str) {
45        self.files.insert(file_name.to_owned(), source.to_owned());
46    }
47
48    fn preprocess(&self, source: &str) -> Result<String, anyhow::Error> {
49        let mut result = String::new();
50        for line in source.lines() {
51            if line.starts_with("#include") {
52                let mut iter = line.split_whitespace();
53                iter.next();
54                let file = iter.next().expect("Expected path to include");
55                assert!(iter.next().is_none(), "Unexpected token");
56                assert!(
57                    file.starts_with('<') && file.ends_with('>'),
58                    "include path should be enclosed in angular brackets"
59                );
60                let file = file.trim_start_matches('<').trim_end_matches('>');
61                if let Some(file) = self.files.get(file) {
62                    result.push_str(&self.preprocess(file)?);
63                } else {
64                    anyhow::bail!("{:?} not found in shader library", file);
65                }
66            } else {
67                result.push_str(line);
68                result.push('\n');
69            }
70        }
71        Ok(result)
72    }
73    pub fn process(
74        &self,
75        shader_type: ugli::ShaderType,
76        source: &str,
77    ) -> Result<String, anyhow::Error> {
78        let mut result = String::new();
79        if let Some((vertex_prefix, fragment_prefix)) = &self.prefix {
80            result.push_str(match shader_type {
81                ugli::ShaderType::Vertex => vertex_prefix,
82                ugli::ShaderType::Fragment => fragment_prefix,
83            });
84            result.push('\n');
85        }
86        result.push_str(match shader_type {
87            ugli::ShaderType::Vertex => "#define VERTEX_SHADER\n",
88            ugli::ShaderType::Fragment => "#define FRAGMENT_SHADER\n",
89        });
90        result.push_str(&self.preprocess("#include <prelude>")?);
91        result.push_str(&self.preprocess(source)?);
92        Ok(result)
93    }
94    pub fn compile(&self, source: &str) -> Result<ugli::Program, anyhow::Error> {
95        let shader = |shader_type| -> anyhow::Result<ugli::Shader> {
96            Ok(ugli::Shader::new(
97                &self.ugli,
98                shader_type,
99                &self.process(shader_type, source)?,
100            )?)
101        };
102        Ok(ugli::Program::new(
103            &self.ugli,
104            [
105                &shader(ugli::ShaderType::Vertex)?,
106                &shader(ugli::ShaderType::Fragment)?,
107            ],
108        )?)
109    }
110}