azul_webrender_build/
shader.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! Functionality for managing source code for shaders.
6//!
7//! This module is used during precompilation (build.rs) and regular compilation,
8//! so it has minimal dependencies.
9
10use std::borrow::Cow;
11use std::fs::File;
12use std::io::Read;
13use std::path::Path;
14use std::collections::HashSet;
15use std::collections::hash_map::DefaultHasher;
16use crate::MAX_VERTEX_TEXTURE_WIDTH;
17
18pub use crate::shader_features::*;
19
20lazy_static! {
21    static ref MAX_VERTEX_TEXTURE_WIDTH_STRING: String = MAX_VERTEX_TEXTURE_WIDTH.to_string();
22}
23
24#[derive(Clone, Copy, Debug)]
25pub enum ShaderKind {
26    Vertex,
27    Fragment,
28}
29
30#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
31pub enum ShaderVersion {
32    Gl,
33    Gles,
34}
35
36impl ShaderVersion {
37    /// Return the full variant name, for use in code generation.
38    pub fn variant_name(&self) -> &'static str {
39        match self {
40            ShaderVersion::Gl => "ShaderVersion::Gl",
41            ShaderVersion::Gles => "ShaderVersion::Gles",
42        }
43    }
44}
45
46#[derive(PartialEq, Eq, Hash, Debug, Clone, Default)]
47#[cfg_attr(feature = "serialize_program", derive(Deserialize, Serialize))]
48pub struct ProgramSourceDigest(u64);
49
50impl ::std::fmt::Display for ProgramSourceDigest {
51    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
52        write!(f, "{:02x}", self.0)
53    }
54}
55
56impl From<DefaultHasher> for ProgramSourceDigest {
57    fn from(hasher: DefaultHasher) -> Self {
58        use std::hash::Hasher;
59        ProgramSourceDigest(hasher.finish())
60    }
61}
62
63const SHADER_IMPORT: &str = "#include ";
64
65pub struct ShaderSourceParser {
66    included: HashSet<String>,
67}
68
69impl ShaderSourceParser {
70    pub fn new() -> Self {
71        ShaderSourceParser {
72            included: HashSet::new(),
73        }
74    }
75
76    /// Parses a shader string for imports. Imports are recursively processed, and
77    /// prepended to the output stream.
78    pub fn parse<F: FnMut(&str), G: Fn(&str) -> Cow<'static, str>>(
79        &mut self,
80        source: Cow<'static, str>,
81        get_source: &G,
82        output: &mut F,
83    ) {
84        for line in source.lines() {
85            if line.starts_with(SHADER_IMPORT) {
86                let imports = line[SHADER_IMPORT.len() ..].split(',');
87
88                // For each import, get the source, and recurse.
89                for import in imports {
90                    if self.included.insert(import.into()) {
91                        let include = get_source(import);
92                        self.parse(include, get_source, output);
93                    } else {
94                        output(&format!("// {} is already included\n", import));
95                    }
96                }
97            } else {
98                output(line);
99                output("\n");
100            }
101        }
102    }
103}
104
105/// Reads a shader source file from disk into a String.
106pub fn shader_source_from_file(shader_path: &Path) -> String {
107    assert!(shader_path.exists(), "Shader not found {:?}", shader_path);
108    let mut source = String::new();
109    File::open(&shader_path)
110        .expect("Shader not found")
111        .read_to_string(&mut source)
112        .unwrap();
113    source
114}
115
116/// Creates heap-allocated strings for both vertex and fragment shaders.
117pub fn build_shader_strings<G: Fn(&str) -> Cow<'static, str>>(
118    gl_version: ShaderVersion,
119    features: &[&str],
120    base_filename: &str,
121    get_source: &G,
122) -> (String, String) {
123   let mut vs_source = String::new();
124   do_build_shader_string(
125       gl_version,
126       features,
127       ShaderKind::Vertex,
128       base_filename,
129       get_source,
130       |s| vs_source.push_str(s),
131   );
132
133   let mut fs_source = String::new();
134   do_build_shader_string(
135       gl_version,
136       features,
137       ShaderKind::Fragment,
138       base_filename,
139       get_source,
140       |s| fs_source.push_str(s),
141   );
142
143   (vs_source, fs_source)
144}
145
146/// Walks the given shader string and applies the output to the provided
147/// callback. Assuming an override path is not used, does no heap allocation
148/// and no I/O.
149pub fn do_build_shader_string<F: FnMut(&str), G: Fn(&str) -> Cow<'static, str>>(
150   gl_version: ShaderVersion,
151   features: &[&str],
152   kind: ShaderKind,
153   base_filename: &str,
154   get_source: &G,
155   mut output: F,
156) {
157   build_shader_prefix_string(gl_version, features, kind, base_filename, &mut output);
158   build_shader_main_string(base_filename, get_source, &mut output);
159}
160
161/// Walks the prefix section of the shader string, which manages the various
162/// defines for features etc.
163pub fn build_shader_prefix_string<F: FnMut(&str)>(
164   gl_version: ShaderVersion,
165   features: &[&str],
166   kind: ShaderKind,
167   base_filename: &str,
168   output: &mut F,
169) {
170    // GLSL requires that the version number comes first.
171    let gl_version_string = match gl_version {
172        ShaderVersion::Gl => "#version 150\n",
173        ShaderVersion::Gles if features.contains(&"TEXTURE_EXTERNAL_ESSL1") => "#version 100\n",
174        ShaderVersion::Gles => "#version 300 es\n",
175    };
176    output(gl_version_string);
177
178    // Insert the shader name to make debugging easier.
179    output("// shader: ");
180    output(base_filename);
181    output(" ");
182    for (i, feature) in features.iter().enumerate() {
183        output(feature);
184        if i != features.len() - 1 {
185            output(",");
186        }
187    }
188    output("\n");
189
190    // Define a constant depending on whether we are compiling VS or FS.
191    let kind_string = match kind {
192        ShaderKind::Vertex => "#define WR_VERTEX_SHADER\n",
193        ShaderKind::Fragment => "#define WR_FRAGMENT_SHADER\n",
194    };
195    output(kind_string);
196
197    // detect which platform we're targeting
198    let is_macos = match std::env::var("CARGO_CFG_TARGET_OS") {
199        Ok(os) => os == "macos",
200        // if this is not called from build.rs (e.g. the gpu_cache_update shader or
201        // if the optimized shader pref is disabled) we want to use the runtime value
202        Err(_) => cfg!(target_os = "macos"),
203    };
204    let is_android = match std::env::var("CARGO_CFG_TARGET_OS") {
205        Ok(os) => os == "android",
206        Err(_) => cfg!(target_os = "android"),
207    };
208    if is_macos {
209        output("#define PLATFORM_MACOS\n");
210    } else if is_android {
211        output("#define PLATFORM_ANDROID\n");
212    }
213
214    // Define a constant for the vertex texture width.
215    output("#define WR_MAX_VERTEX_TEXTURE_WIDTH ");
216    output(&MAX_VERTEX_TEXTURE_WIDTH_STRING);
217    output("U\n");
218
219    // Add any defines for features that were passed by the caller.
220    for feature in features {
221        assert!(!feature.is_empty());
222        output("#define WR_FEATURE_");
223        output(feature);
224        output("\n");
225    }
226}
227
228/// Walks the main .glsl file, including any imports.
229pub fn build_shader_main_string<F: FnMut(&str), G: Fn(&str) -> Cow<'static, str>>(
230   base_filename: &str,
231   get_source: &G,
232   output: &mut F,
233) {
234   let shared_source = get_source(base_filename);
235   ShaderSourceParser::new().parse(
236       shared_source,
237       &|f| get_source(f),
238       output
239   );
240}