1use 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 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 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 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
105pub 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
116pub 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
146pub 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
161pub 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 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 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 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 let is_macos = match std::env::var("CARGO_CFG_TARGET_OS") {
199 Ok(os) => os == "macos",
200 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 output("#define WR_MAX_VERTEX_TEXTURE_WIDTH ");
216 output(&MAX_VERTEX_TEXTURE_WIDTH_STRING);
217 output("U\n");
218
219 for feature in features {
221 assert!(!feature.is_empty());
222 output("#define WR_FEATURE_");
223 output(feature);
224 output("\n");
225 }
226}
227
228pub 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}