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