1use std::collections::{HashMap, HashSet};
2
3use indexmap::IndexMap;
4use regex::Regex;
5
6use super::{
7 comment_strip_iter::CommentReplaceExt,
8 parse_imports::{parse_imports, substitute_identifiers},
9 ComposerErrorInner, ImportDefWithOffset, ShaderDefValue,
10};
11
12#[derive(Debug)]
13pub struct Preprocessor {
14 version_regex: Regex,
15 ifdef_regex: Regex,
16 ifndef_regex: Regex,
17 ifop_regex: Regex,
18 else_regex: Regex,
19 endif_regex: Regex,
20 def_regex: Regex,
21 def_regex_delimited: Regex,
22 import_regex: Regex,
23 define_import_path_regex: Regex,
24 define_shader_def_regex: Regex,
25}
26
27impl Default for Preprocessor {
28 fn default() -> Self {
29 Self {
30 version_regex: Regex::new(r"^\s*#version\s+([0-9]+)").unwrap(),
31 ifdef_regex: Regex::new(r"^\s*#\s*(else\s+)?\s*ifdef\s+([\w|\d|_]+)").unwrap(),
32 ifndef_regex: Regex::new(r"^\s*#\s*(else\s+)?\s*ifndef\s+([\w|\d|_]+)").unwrap(),
33 ifop_regex: Regex::new(
34 r"^\s*#\s*(else\s+)?\s*if\s+([\w|\d|_]+)\s*([=!<>]*)\s*([-\w|\d]+)",
35 )
36 .unwrap(),
37 else_regex: Regex::new(r"^\s*#\s*else").unwrap(),
38 endif_regex: Regex::new(r"^\s*#\s*endif").unwrap(),
39 def_regex: Regex::new(r"#\s*([\w|\d|_]+)").unwrap(),
40 def_regex_delimited: Regex::new(r"#\s*\{([\w|\d|_]+)\}").unwrap(),
41 import_regex: Regex::new(r"^\s*#\s*import\s").unwrap(),
42 define_import_path_regex: Regex::new(r"^\s*#\s*define_import_path\s+([^\s]+)").unwrap(),
43 define_shader_def_regex: Regex::new(r"^\s*#\s*define\s+([\w|\d|_]+)\s*([-\w|\d]+)?")
44 .unwrap(),
45 }
46 }
47}
48
49#[derive(Debug)]
50pub struct PreprocessorMetaData {
51 pub name: Option<String>,
52 pub imports: Vec<ImportDefWithOffset>,
53 pub defines: HashMap<String, ShaderDefValue>,
54 pub effective_defs: HashSet<String>,
55}
56
57enum ScopeLevel {
58 Active, PreviouslyActive, NotActive, }
62
63struct Scope(Vec<ScopeLevel>);
64
65impl Scope {
66 fn new() -> Self {
67 Self(vec![ScopeLevel::Active])
68 }
69
70 fn branch(
71 &mut self,
72 is_else: bool,
73 condition: bool,
74 offset: usize,
75 ) -> Result<(), ComposerErrorInner> {
76 if is_else {
77 let prev_scope = self.0.pop().unwrap();
78 let parent_scope = self
79 .0
80 .last()
81 .ok_or(ComposerErrorInner::ElseWithoutCondition(offset))?;
82 let new_scope = if !matches!(parent_scope, ScopeLevel::Active) {
83 ScopeLevel::NotActive
84 } else if !matches!(prev_scope, ScopeLevel::NotActive) {
85 ScopeLevel::PreviouslyActive
86 } else if condition {
87 ScopeLevel::Active
88 } else {
89 ScopeLevel::NotActive
90 };
91
92 self.0.push(new_scope);
93 } else {
94 let parent_scope = self.0.last().unwrap_or(&ScopeLevel::Active);
95 let new_scope = if matches!(parent_scope, ScopeLevel::Active) && condition {
96 ScopeLevel::Active
97 } else {
98 ScopeLevel::NotActive
99 };
100
101 self.0.push(new_scope);
102 }
103
104 Ok(())
105 }
106
107 fn pop(&mut self, offset: usize) -> Result<(), ComposerErrorInner> {
108 self.0.pop();
109 if self.0.is_empty() {
110 Err(ComposerErrorInner::TooManyEndIfs(offset))
111 } else {
112 Ok(())
113 }
114 }
115
116 fn active(&self) -> bool {
117 matches!(self.0.last().unwrap(), ScopeLevel::Active)
118 }
119
120 fn finish(&self, offset: usize) -> Result<(), ComposerErrorInner> {
121 if self.0.len() != 1 {
122 Err(ComposerErrorInner::NotEnoughEndIfs(offset))
123 } else {
124 Ok(())
125 }
126 }
127}
128
129#[derive(Debug)]
130pub struct PreprocessOutput {
131 pub preprocessed_source: String,
132 pub imports: Vec<ImportDefWithOffset>,
133}
134
135impl Preprocessor {
136 fn check_scope<'a>(
137 &self,
138 shader_defs: &HashMap<String, ShaderDefValue>,
139 line: &'a str,
140 scope: Option<&mut Scope>,
141 offset: usize,
142 ) -> Result<(bool, Option<&'a str>), ComposerErrorInner> {
143 if let Some(cap) = self.ifdef_regex.captures(line) {
144 let is_else = cap.get(1).is_some();
145 let def = cap.get(2).unwrap().as_str();
146 let cond = shader_defs.contains_key(def);
147 scope.map_or(Ok(()), |scope| scope.branch(is_else, cond, offset))?;
148 return Ok((true, Some(def)));
149 } else if let Some(cap) = self.ifndef_regex.captures(line) {
150 let is_else = cap.get(1).is_some();
151 let def = cap.get(2).unwrap().as_str();
152 let cond = !shader_defs.contains_key(def);
153 scope.map_or(Ok(()), |scope| scope.branch(is_else, cond, offset))?;
154 return Ok((true, Some(def)));
155 } else if let Some(cap) = self.ifop_regex.captures(line) {
156 let is_else = cap.get(1).is_some();
157 let def = cap.get(2).unwrap().as_str();
158 let op = cap.get(3).unwrap();
159 let val = cap.get(4).unwrap();
160
161 if scope.is_none() {
162 return Ok((true, Some(def)));
164 }
165
166 fn act_on<T: Eq + Ord>(
167 a: T,
168 b: T,
169 op: &str,
170 pos: usize,
171 ) -> Result<bool, ComposerErrorInner> {
172 match op {
173 "==" => Ok(a == b),
174 "!=" => Ok(a != b),
175 ">" => Ok(a > b),
176 ">=" => Ok(a >= b),
177 "<" => Ok(a < b),
178 "<=" => Ok(a <= b),
179 _ => Err(ComposerErrorInner::UnknownShaderDefOperator {
180 pos,
181 operator: op.to_string(),
182 }),
183 }
184 }
185
186 let def_value = shader_defs
187 .get(def)
188 .ok_or(ComposerErrorInner::UnknownShaderDef {
189 pos: offset,
190 shader_def_name: def.to_string(),
191 })?;
192
193 let invalid_def = |ty: &str| ComposerErrorInner::InvalidShaderDefComparisonValue {
194 pos: offset,
195 shader_def_name: def.to_string(),
196 value: val.as_str().to_string(),
197 expected: ty.to_string(),
198 };
199
200 let new_scope = match def_value {
201 ShaderDefValue::Bool(def_value) => {
202 let val = val.as_str().parse().map_err(|_| invalid_def("bool"))?;
203 act_on(*def_value, val, op.as_str(), offset)?
204 }
205 ShaderDefValue::Int(def_value) => {
206 let val = val.as_str().parse().map_err(|_| invalid_def("int"))?;
207 act_on(*def_value, val, op.as_str(), offset)?
208 }
209 ShaderDefValue::UInt(def_value) => {
210 let val = val.as_str().parse().map_err(|_| invalid_def("uint"))?;
211 act_on(*def_value, val, op.as_str(), offset)?
212 }
213 };
214
215 scope.map_or(Ok(()), |scope| scope.branch(is_else, new_scope, offset))?;
216 return Ok((true, Some(def)));
217 } else if self.else_regex.is_match(line) {
218 scope.map_or(Ok(()), |scope| scope.branch(true, true, offset))?;
219 return Ok((true, None));
220 } else if self.endif_regex.is_match(line) {
221 scope.map_or(Ok(()), |scope| scope.pop(offset))?;
222 return Ok((true, None));
223 }
224
225 Ok((false, None))
226 }
227
228 pub fn preprocess(
233 &self,
234 shader_str: &str,
235 shader_defs: &HashMap<String, ShaderDefValue>,
236 validate_len: bool,
237 ) -> Result<PreprocessOutput, ComposerErrorInner> {
238 let mut declared_imports = IndexMap::new();
239 let mut used_imports = IndexMap::new();
240 let mut scope = Scope::new();
241 let mut final_string = String::new();
242 let mut offset = 0;
243
244 #[cfg(debug)]
245 let len = shader_str.len();
246
247 let mut lines = shader_str.lines();
249 let mut lines = lines.replace_comments().zip(shader_str.lines()).peekable();
250
251 while let Some((mut line, original_line)) = lines.next() {
252 let mut output = false;
253
254 if let Some(cap) = self.version_regex.captures(&line) {
255 let v = cap.get(1).unwrap().as_str();
256 if v != "440" && v != "450" {
257 return Err(ComposerErrorInner::GlslInvalidVersion(offset));
258 }
259 } else if self
260 .check_scope(shader_defs, &line, Some(&mut scope), offset)?
261 .0
262 || self.define_import_path_regex.captures(&line).is_some()
263 || self.define_shader_def_regex.captures(&line).is_some()
264 {
265 } else if scope.active() {
267 if self.import_regex.is_match(&line) {
268 let mut import_lines = String::default();
269 let mut open_count = 0;
270 let initial_offset = offset;
271
272 loop {
273 final_string.extend(std::iter::repeat(" ").take(line.len()));
275 offset += line.len() + 1;
276
277 open_count += line.match_indices('{').count();
280 open_count = open_count.saturating_sub(line.match_indices('}').count());
281
282 import_lines.push_str(&line);
286 import_lines.push('\n');
287
288 if open_count == 0 || lines.peek().is_none() {
289 break;
290 }
291
292 final_string.push('\n');
293 line = lines.next().unwrap().0;
294 }
295
296 parse_imports(import_lines.as_str(), &mut declared_imports).map_err(
297 |(err, line_offset)| {
298 ComposerErrorInner::ImportParseError(
299 err.to_owned(),
300 initial_offset + line_offset,
301 )
302 },
303 )?;
304 output = true;
305 } else {
306 let replaced_lines = [original_line, &line].map(|input| {
307 let mut output = input.to_string();
308 for capture in self.def_regex.captures_iter(input) {
309 let def = capture.get(1).unwrap();
310 if let Some(def) = shader_defs.get(def.as_str()) {
311 output = self
312 .def_regex
313 .replace(&output, def.value_as_string())
314 .to_string();
315 }
316 }
317 for capture in self.def_regex_delimited.captures_iter(input) {
318 let def = capture.get(1).unwrap();
319 if let Some(def) = shader_defs.get(def.as_str()) {
320 output = self
321 .def_regex_delimited
322 .replace(&output, def.value_as_string())
323 .to_string();
324 }
325 }
326 output
327 });
328
329 let original_line = &replaced_lines[0];
330 let decommented_line = &replaced_lines[1];
331
332 let item_replaced_line = substitute_identifiers(
334 original_line,
335 offset,
336 &declared_imports,
337 &mut Default::default(),
338 true,
339 )
340 .unwrap();
341 let _ = substitute_identifiers(
343 decommented_line,
344 offset,
345 &declared_imports,
346 &mut used_imports,
347 false,
348 )
349 .map_err(|pos| {
350 ComposerErrorInner::ImportParseError(
351 "Ambiguous import path for item".to_owned(),
352 pos,
353 )
354 })?;
355
356 final_string.push_str(&item_replaced_line);
357 let diff = line.len().saturating_sub(item_replaced_line.len());
358 final_string.extend(std::iter::repeat(" ").take(diff));
359 offset += original_line.len() + 1;
360 output = true;
361 }
362 }
363
364 if !output {
365 final_string.extend(std::iter::repeat(" ").take(line.len()));
367 offset += line.len() + 1;
368 }
369 final_string.push('\n');
370 }
371
372 scope.finish(offset)?;
373
374 #[cfg(debug)]
375 if validate_len {
376 let revised_len = final_string.len();
377 assert_eq!(len, revised_len);
378 }
379 #[cfg(not(debug))]
380 let _ = validate_len;
381
382 Ok(PreprocessOutput {
383 preprocessed_source: final_string,
384 imports: used_imports.into_values().collect(),
385 })
386 }
387
388 pub fn get_preprocessor_metadata(
390 &self,
391 shader_str: &str,
392 allow_defines: bool,
393 ) -> Result<PreprocessorMetaData, ComposerErrorInner> {
394 let mut declared_imports = IndexMap::default();
395 let mut used_imports = IndexMap::default();
396 let mut name = None;
397 let mut offset = 0;
398 let mut defines = HashMap::default();
399 let mut effective_defs = HashSet::default();
400
401 let mut lines = shader_str.lines();
402 let mut lines = lines.replace_comments().peekable();
403
404 while let Some(mut line) = lines.next() {
405 let (is_scope, def) = self.check_scope(&HashMap::default(), &line, None, offset)?;
406
407 if is_scope {
408 if let Some(def) = def {
409 effective_defs.insert(def.to_owned());
410 }
411 } else if self.import_regex.is_match(&line) {
412 let mut import_lines = String::default();
413 let mut open_count = 0;
414 let initial_offset = offset;
415
416 loop {
417 open_count += line.match_indices('{').count();
420 open_count = open_count.saturating_sub(line.match_indices('}').count());
421
422 import_lines.push_str(&line);
426 import_lines.push('\n');
427
428 if open_count == 0 || lines.peek().is_none() {
429 break;
430 }
431
432 offset += line.len() + 1;
434
435 line = lines.next().unwrap();
436 }
437
438 parse_imports(import_lines.as_str(), &mut declared_imports).map_err(
439 |(err, line_offset)| {
440 ComposerErrorInner::ImportParseError(
441 err.to_owned(),
442 initial_offset + line_offset,
443 )
444 },
445 )?;
446 } else if let Some(cap) = self.define_import_path_regex.captures(&line) {
447 name = Some(cap.get(1).unwrap().as_str().to_string());
448 } else if let Some(cap) = self.define_shader_def_regex.captures(&line) {
449 if allow_defines {
450 let def = cap.get(1).unwrap();
451 let name = def.as_str().to_string();
452
453 let value = if let Some(val) = cap.get(2) {
454 if let Ok(val) = val.as_str().parse::<u32>() {
455 ShaderDefValue::UInt(val)
456 } else if let Ok(val) = val.as_str().parse::<i32>() {
457 ShaderDefValue::Int(val)
458 } else if let Ok(val) = val.as_str().parse::<bool>() {
459 ShaderDefValue::Bool(val)
460 } else {
461 ShaderDefValue::Bool(false) }
463 } else {
464 ShaderDefValue::Bool(true)
465 };
466
467 defines.insert(name, value);
468 } else {
469 return Err(ComposerErrorInner::DefineInModule(offset));
470 }
471 } else {
472 for cap in self
473 .def_regex
474 .captures_iter(&line)
475 .chain(self.def_regex_delimited.captures_iter(&line))
476 {
477 effective_defs.insert(cap.get(1).unwrap().as_str().to_owned());
478 }
479
480 substitute_identifiers(&line, offset, &declared_imports, &mut used_imports, true)
481 .unwrap();
482 }
483
484 offset += line.len() + 1;
485 }
486
487 Ok(PreprocessorMetaData {
488 name,
489 imports: used_imports.into_values().collect(),
490 defines,
491 effective_defs,
492 })
493 }
494}
495
496#[cfg(test)]
497mod test {
498 use super::*;
499
500 #[rustfmt::skip]
501 const WGSL_ELSE_IFDEF: &str = r"
502struct View {
503 view_proj: mat4x4<f32>,
504 world_position: vec3<f32>,
505};
506@group(0) @binding(0)
507var<uniform> view: View;
508
509#ifdef TEXTURE
510// Main texture
511@group(1) @binding(0)
512var sprite_texture: texture_2d<f32>;
513#else ifdef SECOND_TEXTURE
514// Second texture
515@group(1) @binding(0)
516var sprite_texture: texture_2d<f32>;
517#else ifdef THIRD_TEXTURE
518// Third texture
519@group(1) @binding(0)
520var sprite_texture: texture_2d<f32>;
521#else
522@group(1) @binding(0)
523var sprite_texture: texture_2d_array<f32>;
524#endif
525
526struct VertexOutput {
527 @location(0) uv: vec2<f32>,
528 @builtin(position) position: vec4<f32>,
529};
530
531@vertex
532fn vertex(
533 @location(0) vertex_position: vec3<f32>,
534 @location(1) vertex_uv: vec2<f32>
535) -> VertexOutput {
536 var out: VertexOutput;
537 out.uv = vertex_uv;
538 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
539 return out;
540}
541";
542
543 #[test]
545 fn process_shader_def_unknown_operator() {
546 #[rustfmt::skip]
547 const WGSL: &str = r"
548struct View {
549 view_proj: mat4x4<f32>,
550 world_position: vec3<f32>,
551};
552@group(0) @binding(0)
553var<uniform> view: View;
554#if TEXTURE !! true
555@group(1) @binding(0)
556var sprite_texture: texture_2d<f32>;
557#endif
558struct VertexOutput {
559 @location(0) uv: vec2<f32>,
560 @builtin(position) position: vec4<f32>,
561};
562@vertex
563fn vertex(
564 @location(0) vertex_position: vec3<f32>,
565 @location(1) vertex_uv: vec2<f32>
566) -> VertexOutput {
567 var out: VertexOutput;
568 out.uv = vertex_uv;
569 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
570 return out;
571}
572";
573
574 let processor = Preprocessor::default();
575
576 let result_missing = processor.preprocess(
577 WGSL,
578 &[("TEXTURE".to_owned(), ShaderDefValue::Bool(true))].into(),
579 true,
580 );
581
582 let expected: Result<Preprocessor, ComposerErrorInner> =
583 Err(ComposerErrorInner::UnknownShaderDefOperator {
584 pos: 124,
585 operator: "!!".to_string(),
586 });
587
588 assert_eq!(format!("{result_missing:?}"), format!("{expected:?}"),);
589 }
590 #[test]
591 fn process_shader_def_equal_int() {
592 #[rustfmt::skip]
593 const WGSL: &str = r"
594struct View {
595 view_proj: mat4x4<f32>,
596 world_position: vec3<f32>,
597};
598@group(0) @binding(0)
599var<uniform> view: View;
600#if TEXTURE == 3
601@group(1) @binding(0)
602var sprite_texture: texture_2d<f32>;
603#endif
604struct VertexOutput {
605 @location(0) uv: vec2<f32>,
606 @builtin(position) position: vec4<f32>,
607};
608@vertex
609fn vertex(
610 @location(0) vertex_position: vec3<f32>,
611 @location(1) vertex_uv: vec2<f32>
612) -> VertexOutput {
613 var out: VertexOutput;
614 out.uv = vertex_uv;
615 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
616 return out;
617}
618";
619
620 #[rustfmt::skip]
621 const EXPECTED_EQ: &str = r"
622struct View {
623 view_proj: mat4x4<f32>,
624 world_position: vec3<f32>,
625};
626@group(0) @binding(0)
627var<uniform> view: View;
628
629@group(1) @binding(0)
630var sprite_texture: texture_2d<f32>;
631
632struct VertexOutput {
633 @location(0) uv: vec2<f32>,
634 @builtin(position) position: vec4<f32>,
635};
636@vertex
637fn vertex(
638 @location(0) vertex_position: vec3<f32>,
639 @location(1) vertex_uv: vec2<f32>
640) -> VertexOutput {
641 var out: VertexOutput;
642 out.uv = vertex_uv;
643 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
644 return out;
645}
646";
647
648 #[rustfmt::skip]
649 const EXPECTED_NEQ: &str = r"
650struct View {
651 view_proj: mat4x4<f32>,
652 world_position: vec3<f32>,
653};
654@group(0) @binding(0)
655var<uniform> view: View;
656
657
658
659
660struct VertexOutput {
661 @location(0) uv: vec2<f32>,
662 @builtin(position) position: vec4<f32>,
663};
664@vertex
665fn vertex(
666 @location(0) vertex_position: vec3<f32>,
667 @location(1) vertex_uv: vec2<f32>
668) -> VertexOutput {
669 var out: VertexOutput;
670 out.uv = vertex_uv;
671 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
672 return out;
673}
674";
675 let processor = Preprocessor::default();
676 let result_eq = processor
677 .preprocess(
678 WGSL,
679 &[("TEXTURE".to_string(), ShaderDefValue::Int(3))].into(),
680 true,
681 )
682 .unwrap();
683 assert_eq!(result_eq.preprocessed_source, EXPECTED_EQ);
684
685 let result_neq = processor
686 .preprocess(
687 WGSL,
688 &[("TEXTURE".to_string(), ShaderDefValue::Int(7))].into(),
689 true,
690 )
691 .unwrap();
692 assert_eq!(result_neq.preprocessed_source, EXPECTED_NEQ);
693
694 let result_missing = processor.preprocess(WGSL, &Default::default(), true);
695
696 let expected_err: Result<
697 (Option<String>, String, Vec<ImportDefWithOffset>),
698 ComposerErrorInner,
699 > = Err(ComposerErrorInner::UnknownShaderDef {
700 pos: 124,
701 shader_def_name: "TEXTURE".to_string(),
702 });
703 assert_eq!(format!("{result_missing:?}"), format!("{expected_err:?}"),);
704
705 let result_wrong_type = processor.preprocess(
706 WGSL,
707 &[("TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
708 true,
709 );
710
711 let expected_err: Result<
712 (Option<String>, String, Vec<ImportDefWithOffset>),
713 ComposerErrorInner,
714 > = Err(ComposerErrorInner::InvalidShaderDefComparisonValue {
715 pos: 124,
716 shader_def_name: "TEXTURE".to_string(),
717 expected: "bool".to_string(),
718 value: "3".to_string(),
719 });
720
721 assert_eq!(
722 format!("{result_wrong_type:?}"),
723 format!("{expected_err:?}")
724 );
725 }
726
727 #[test]
728 fn process_shader_def_equal_bool() {
729 #[rustfmt::skip]
730 const WGSL: &str = r"
731struct View {
732 view_proj: mat4x4<f32>,
733 world_position: vec3<f32>,
734};
735@group(0) @binding(0)
736var<uniform> view: View;
737#if TEXTURE == true
738@group(1) @binding(0)
739var sprite_texture: texture_2d<f32>;
740#endif
741struct VertexOutput {
742 @location(0) uv: vec2<f32>,
743 @builtin(position) position: vec4<f32>,
744};
745@vertex
746fn vertex(
747 @location(0) vertex_position: vec3<f32>,
748 @location(1) vertex_uv: vec2<f32>
749) -> VertexOutput {
750 var out: VertexOutput;
751 out.uv = vertex_uv;
752 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
753 return out;
754}
755";
756
757 #[rustfmt::skip]
758 const EXPECTED_EQ: &str = r"
759struct View {
760 view_proj: mat4x4<f32>,
761 world_position: vec3<f32>,
762};
763@group(0) @binding(0)
764var<uniform> view: View;
765
766@group(1) @binding(0)
767var sprite_texture: texture_2d<f32>;
768
769struct VertexOutput {
770 @location(0) uv: vec2<f32>,
771 @builtin(position) position: vec4<f32>,
772};
773@vertex
774fn vertex(
775 @location(0) vertex_position: vec3<f32>,
776 @location(1) vertex_uv: vec2<f32>
777) -> VertexOutput {
778 var out: VertexOutput;
779 out.uv = vertex_uv;
780 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
781 return out;
782}
783";
784
785 #[rustfmt::skip]
786 const EXPECTED_NEQ: &str = r"
787struct View {
788 view_proj: mat4x4<f32>,
789 world_position: vec3<f32>,
790};
791@group(0) @binding(0)
792var<uniform> view: View;
793
794
795
796
797struct VertexOutput {
798 @location(0) uv: vec2<f32>,
799 @builtin(position) position: vec4<f32>,
800};
801@vertex
802fn vertex(
803 @location(0) vertex_position: vec3<f32>,
804 @location(1) vertex_uv: vec2<f32>
805) -> VertexOutput {
806 var out: VertexOutput;
807 out.uv = vertex_uv;
808 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
809 return out;
810}
811";
812 let processor = Preprocessor::default();
813 let result_eq = processor
814 .preprocess(
815 WGSL,
816 &[("TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
817 true,
818 )
819 .unwrap();
820 assert_eq!(result_eq.preprocessed_source, EXPECTED_EQ);
821
822 let result_neq = processor
823 .preprocess(
824 WGSL,
825 &[("TEXTURE".to_string(), ShaderDefValue::Bool(false))].into(),
826 true,
827 )
828 .unwrap();
829 assert_eq!(result_neq.preprocessed_source, EXPECTED_NEQ);
830 }
831
832 #[test]
833 fn process_shader_def_not_equal_bool() {
834 #[rustfmt::skip]
835 const WGSL: &str = r"
836struct View {
837 view_proj: mat4x4<f32>,
838 world_position: vec3<f32>,
839};
840@group(0) @binding(0)
841var<uniform> view: View;
842#if TEXTURE != false
843@group(1) @binding(0)
844var sprite_texture: texture_2d<f32>;
845#endif
846struct VertexOutput {
847 @location(0) uv: vec2<f32>,
848 @builtin(position) position: vec4<f32>,
849};
850@vertex
851fn vertex(
852 @location(0) vertex_position: vec3<f32>,
853 @location(1) vertex_uv: vec2<f32>
854) -> VertexOutput {
855 var out: VertexOutput;
856 out.uv = vertex_uv;
857 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
858 return out;
859}
860";
861
862 #[rustfmt::skip]
863 const EXPECTED_EQ: &str = r"
864struct View {
865 view_proj: mat4x4<f32>,
866 world_position: vec3<f32>,
867};
868@group(0) @binding(0)
869var<uniform> view: View;
870
871@group(1) @binding(0)
872var sprite_texture: texture_2d<f32>;
873
874struct VertexOutput {
875 @location(0) uv: vec2<f32>,
876 @builtin(position) position: vec4<f32>,
877};
878@vertex
879fn vertex(
880 @location(0) vertex_position: vec3<f32>,
881 @location(1) vertex_uv: vec2<f32>
882) -> VertexOutput {
883 var out: VertexOutput;
884 out.uv = vertex_uv;
885 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
886 return out;
887}
888";
889
890 #[rustfmt::skip]
891 const EXPECTED_NEQ: &str = r"
892struct View {
893 view_proj: mat4x4<f32>,
894 world_position: vec3<f32>,
895};
896@group(0) @binding(0)
897var<uniform> view: View;
898
899
900
901
902struct VertexOutput {
903 @location(0) uv: vec2<f32>,
904 @builtin(position) position: vec4<f32>,
905};
906@vertex
907fn vertex(
908 @location(0) vertex_position: vec3<f32>,
909 @location(1) vertex_uv: vec2<f32>
910) -> VertexOutput {
911 var out: VertexOutput;
912 out.uv = vertex_uv;
913 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
914 return out;
915}
916";
917 let processor = Preprocessor::default();
918 let result_eq = processor
919 .preprocess(
920 WGSL,
921 &[("TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
922 true,
923 )
924 .unwrap();
925 assert_eq!(result_eq.preprocessed_source, EXPECTED_EQ);
926
927 let result_neq = processor
928 .preprocess(
929 WGSL,
930 &[("TEXTURE".to_string(), ShaderDefValue::Bool(false))].into(),
931 true,
932 )
933 .unwrap();
934 assert_eq!(result_neq.preprocessed_source, EXPECTED_NEQ);
935
936 let result_missing = processor.preprocess(WGSL, &[].into(), true);
937 let expected_err: Result<
938 (Option<String>, String, Vec<ImportDefWithOffset>),
939 ComposerErrorInner,
940 > = Err(ComposerErrorInner::UnknownShaderDef {
941 pos: 124,
942 shader_def_name: "TEXTURE".to_string(),
943 });
944 assert_eq!(format!("{result_missing:?}"), format!("{expected_err:?}"),);
945
946 let result_wrong_type = processor.preprocess(
947 WGSL,
948 &[("TEXTURE".to_string(), ShaderDefValue::Int(7))].into(),
949 true,
950 );
951
952 let expected_err: Result<
953 (Option<String>, String, Vec<ImportDefWithOffset>),
954 ComposerErrorInner,
955 > = Err(ComposerErrorInner::InvalidShaderDefComparisonValue {
956 pos: 124,
957 shader_def_name: "TEXTURE".to_string(),
958 expected: "int".to_string(),
959 value: "false".to_string(),
960 });
961 assert_eq!(
962 format!("{result_wrong_type:?}"),
963 format!("{expected_err:?}"),
964 );
965 }
966
967 #[test]
968 fn process_shader_def_replace() {
969 #[rustfmt::skip]
970 const WGSL: &str = r"
971struct View {
972 view_proj: mat4x4<f32>,
973 world_position: vec3<f32>,
974};
975@group(0) @binding(0)
976var<uniform> view: View;
977struct VertexOutput {
978 @location(0) uv: vec2<f32>,
979 @builtin(position) position: vec4<f32>,
980};
981@vertex
982fn vertex(
983 @location(0) vertex_position: vec3<f32>,
984 @location(1) vertex_uv: vec2<f32>
985) -> VertexOutput {
986 var out: VertexOutput;
987 out.uv = vertex_uv;
988 var a: i32 = #FIRST_VALUE;
989 var b: i32 = #FIRST_VALUE * #SECOND_VALUE;
990 var c: i32 = #MISSING_VALUE;
991 var d: bool = #BOOL_VALUE;
992 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
993 return out;
994}
995";
996
997 #[rustfmt::skip]
998 const EXPECTED_REPLACED: &str = r"
999struct View {
1000 view_proj: mat4x4<f32>,
1001 world_position: vec3<f32>,
1002};
1003@group(0) @binding(0)
1004var<uniform> view: View;
1005struct VertexOutput {
1006 @location(0) uv: vec2<f32>,
1007 @builtin(position) position: vec4<f32>,
1008};
1009@vertex
1010fn vertex(
1011 @location(0) vertex_position: vec3<f32>,
1012 @location(1) vertex_uv: vec2<f32>
1013) -> VertexOutput {
1014 var out: VertexOutput;
1015 out.uv = vertex_uv;
1016 var a: i32 = 5;
1017 var b: i32 = 5 * 3;
1018 var c: i32 = #MISSING_VALUE;
1019 var d: bool = true;
1020 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1021 return out;
1022}
1023";
1024 let processor = Preprocessor::default();
1025 let result = processor
1026 .preprocess(
1027 WGSL,
1028 &[
1029 ("BOOL_VALUE".to_string(), ShaderDefValue::Bool(true)),
1030 ("FIRST_VALUE".to_string(), ShaderDefValue::Int(5)),
1031 ("SECOND_VALUE".to_string(), ShaderDefValue::Int(3)),
1032 ]
1033 .into(),
1034 true,
1035 )
1036 .unwrap();
1037 assert_eq!(result.preprocessed_source, EXPECTED_REPLACED);
1038 }
1039
1040 #[test]
1041 fn process_shader_define_in_shader() {
1042 #[rustfmt::skip]
1043 const WGSL: &str = r"
1044#define NOW_DEFINED
1045#ifdef NOW_DEFINED
1046defined
1047#endif
1048";
1049
1050 #[rustfmt::skip]
1051 const EXPECTED: &str = r"
1052
1053
1054defined
1055
1056";
1057 let processor = Preprocessor::default();
1058 let PreprocessorMetaData {
1059 defines: shader_defs,
1060 ..
1061 } = processor.get_preprocessor_metadata(&WGSL, true).unwrap();
1062 println!("defines: {:?}", shader_defs);
1063 let result = processor.preprocess(&WGSL, &shader_defs, true).unwrap();
1064 assert_eq!(result.preprocessed_source, EXPECTED);
1065 }
1066
1067 #[test]
1068 fn process_shader_define_in_shader_with_value() {
1069 #[rustfmt::skip]
1070 const WGSL: &str = r"
1071#define DEFUINT 1
1072#define DEFINT -1
1073#define DEFBOOL false
1074#if DEFUINT == 1
1075uint: #DEFUINT
1076#endif
1077#if DEFINT == -1
1078int: #DEFINT
1079#endif
1080#if DEFBOOL == false
1081bool: #DEFBOOL
1082#endif
1083";
1084
1085 #[rustfmt::skip]
1086 const EXPECTED: &str = r"
1087
1088
1089
1090
1091uint: 1
1092
1093
1094int: -1
1095
1096
1097bool: false
1098
1099";
1100 let processor = Preprocessor::default();
1101 let PreprocessorMetaData {
1102 defines: shader_defs,
1103 ..
1104 } = processor.get_preprocessor_metadata(&WGSL, true).unwrap();
1105 println!("defines: {:?}", shader_defs);
1106 let result = processor.preprocess(&WGSL, &shader_defs, true).unwrap();
1107 assert_eq!(result.preprocessed_source, EXPECTED);
1108 }
1109
1110 #[test]
1111 fn process_shader_def_else_ifdef_ends_up_in_else() {
1112 #[rustfmt::skip]
1113 const EXPECTED: &str = r"
1114struct View {
1115 view_proj: mat4x4<f32>,
1116 world_position: vec3<f32>,
1117};
1118@group(0) @binding(0)
1119var<uniform> view: View;
1120@group(1) @binding(0)
1121var sprite_texture: texture_2d_array<f32>;
1122struct VertexOutput {
1123 @location(0) uv: vec2<f32>,
1124 @builtin(position) position: vec4<f32>,
1125};
1126@vertex
1127fn vertex(
1128 @location(0) vertex_position: vec3<f32>,
1129 @location(1) vertex_uv: vec2<f32>
1130) -> VertexOutput {
1131 var out: VertexOutput;
1132 out.uv = vertex_uv;
1133 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1134 return out;
1135}
1136";
1137 let processor = Preprocessor::default();
1138 let result = processor
1139 .preprocess(&WGSL_ELSE_IFDEF, &[].into(), true)
1140 .unwrap();
1141 assert_eq!(
1142 result
1143 .preprocessed_source
1144 .replace(" ", "")
1145 .replace("\n", "")
1146 .replace("\r", ""),
1147 EXPECTED
1148 .replace(" ", "")
1149 .replace("\n", "")
1150 .replace("\r", "")
1151 );
1152 }
1153
1154 #[test]
1155 fn process_shader_def_else_ifdef_no_match_and_no_fallback_else() {
1156 #[rustfmt::skip]
1157 const WGSL_ELSE_IFDEF_NO_ELSE_FALLBACK: &str = r"
1158struct View {
1159 view_proj: mat4x4<f32>,
1160 world_position: vec3<f32>,
1161};
1162@group(0) @binding(0)
1163var<uniform> view: View;
1164
1165#ifdef TEXTURE
1166// Main texture
1167@group(1) @binding(0)
1168var sprite_texture: texture_2d<f32>;
1169#else ifdef OTHER_TEXTURE
1170// Other texture
1171@group(1) @binding(0)
1172var sprite_texture: texture_2d<f32>;
1173#endif
1174
1175struct VertexOutput {
1176 @location(0) uv: vec2<f32>,
1177 @builtin(position) position: vec4<f32>,
1178};
1179
1180@vertex
1181fn vertex(
1182 @location(0) vertex_position: vec3<f32>,
1183 @location(1) vertex_uv: vec2<f32>
1184) -> VertexOutput {
1185 var out: VertexOutput;
1186 out.uv = vertex_uv;
1187 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1188 return out;
1189}
1190";
1191
1192 #[rustfmt::skip]
1193 const EXPECTED: &str = r"
1194struct View {
1195 view_proj: mat4x4<f32>,
1196 world_position: vec3<f32>,
1197};
1198@group(0) @binding(0)
1199var<uniform> view: View;
1200struct VertexOutput {
1201 @location(0) uv: vec2<f32>,
1202 @builtin(position) position: vec4<f32>,
1203};
1204@vertex
1205fn vertex(
1206 @location(0) vertex_position: vec3<f32>,
1207 @location(1) vertex_uv: vec2<f32>
1208) -> VertexOutput {
1209 var out: VertexOutput;
1210 out.uv = vertex_uv;
1211 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1212 return out;
1213}
1214";
1215 let processor = Preprocessor::default();
1216 let result = processor
1217 .preprocess(&WGSL_ELSE_IFDEF_NO_ELSE_FALLBACK, &[].into(), true)
1218 .unwrap();
1219 assert_eq!(
1220 result
1221 .preprocessed_source
1222 .replace(" ", "")
1223 .replace("\n", "")
1224 .replace("\r", ""),
1225 EXPECTED
1226 .replace(" ", "")
1227 .replace("\n", "")
1228 .replace("\r", "")
1229 );
1230 }
1231
1232 #[test]
1233 fn process_shader_def_else_ifdef_ends_up_in_first_clause() {
1234 #[rustfmt::skip]
1235 const EXPECTED: &str = r"
1236struct View {
1237 view_proj: mat4x4<f32>,
1238 world_position: vec3<f32>,
1239};
1240@group(0) @binding(0)
1241var<uniform> view: View;
1242
1243// Main texture
1244@group(1) @binding(0)
1245var sprite_texture: texture_2d<f32>;
1246
1247struct VertexOutput {
1248 @location(0) uv: vec2<f32>,
1249 @builtin(position) position: vec4<f32>,
1250};
1251
1252@vertex
1253fn vertex(
1254 @location(0) vertex_position: vec3<f32>,
1255 @location(1) vertex_uv: vec2<f32>
1256) -> VertexOutput {
1257 var out: VertexOutput;
1258 out.uv = vertex_uv;
1259 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1260 return out;
1261}
1262";
1263 let processor = Preprocessor::default();
1264 let result = processor
1265 .preprocess(
1266 &WGSL_ELSE_IFDEF,
1267 &[("TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
1268 true,
1269 )
1270 .unwrap();
1271 assert_eq!(
1272 result
1273 .preprocessed_source
1274 .replace(" ", "")
1275 .replace("\n", "")
1276 .replace("\r", ""),
1277 EXPECTED
1278 .replace(" ", "")
1279 .replace("\n", "")
1280 .replace("\r", "")
1281 );
1282 }
1283
1284 #[test]
1285 fn process_shader_def_else_ifdef_ends_up_in_second_clause() {
1286 #[rustfmt::skip]
1287 const EXPECTED: &str = r"
1288struct View {
1289 view_proj: mat4x4<f32>,
1290 world_position: vec3<f32>,
1291};
1292@group(0) @binding(0)
1293var<uniform> view: View;
1294// Second texture
1295@group(1) @binding(0)
1296var sprite_texture: texture_2d<f32>;
1297struct VertexOutput {
1298 @location(0) uv: vec2<f32>,
1299 @builtin(position) position: vec4<f32>,
1300};
1301@vertex
1302fn vertex(
1303 @location(0) vertex_position: vec3<f32>,
1304 @location(1) vertex_uv: vec2<f32>
1305) -> VertexOutput {
1306 var out: VertexOutput;
1307 out.uv = vertex_uv;
1308 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1309 return out;
1310}
1311";
1312 let processor = Preprocessor::default();
1313 let result = processor
1314 .preprocess(
1315 &WGSL_ELSE_IFDEF,
1316 &[("SECOND_TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
1317 true,
1318 )
1319 .unwrap();
1320 assert_eq!(
1321 result
1322 .preprocessed_source
1323 .replace(" ", "")
1324 .replace("\n", "")
1325 .replace("\r", ""),
1326 EXPECTED
1327 .replace(" ", "")
1328 .replace("\n", "")
1329 .replace("\r", "")
1330 );
1331 }
1332
1333 #[test]
1334 fn process_shader_def_else_ifdef_ends_up_in_third_clause() {
1335 #[rustfmt::skip]
1336 const EXPECTED: &str = r"
1337struct View {
1338 view_proj: mat4x4<f32>,
1339 world_position: vec3<f32>,
1340};
1341@group(0) @binding(0)
1342var<uniform> view: View;
1343// Third texture
1344@group(1) @binding(0)
1345var sprite_texture: texture_2d<f32>;
1346struct VertexOutput {
1347 @location(0) uv: vec2<f32>,
1348 @builtin(position) position: vec4<f32>,
1349};
1350@vertex
1351fn vertex(
1352 @location(0) vertex_position: vec3<f32>,
1353 @location(1) vertex_uv: vec2<f32>
1354) -> VertexOutput {
1355 var out: VertexOutput;
1356 out.uv = vertex_uv;
1357 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1358 return out;
1359}
1360";
1361 let processor = Preprocessor::default();
1362 let result = processor
1363 .preprocess(
1364 &WGSL_ELSE_IFDEF,
1365 &[("THIRD_TEXTURE".to_string(), ShaderDefValue::Bool(true))].into(),
1366 true,
1367 )
1368 .unwrap();
1369 assert_eq!(
1370 result
1371 .preprocessed_source
1372 .replace(" ", "")
1373 .replace("\n", "")
1374 .replace("\r", ""),
1375 EXPECTED
1376 .replace(" ", "")
1377 .replace("\n", "")
1378 .replace("\r", "")
1379 );
1380 }
1381
1382 #[test]
1383 fn process_shader_def_else_ifdef_only_accepts_one_valid_else_ifdef() {
1384 #[rustfmt::skip]
1385 const EXPECTED: &str = r"
1386struct View {
1387 view_proj: mat4x4<f32>,
1388 world_position: vec3<f32>,
1389};
1390@group(0) @binding(0)
1391var<uniform> view: View;
1392// Second texture
1393@group(1) @binding(0)
1394var sprite_texture: texture_2d<f32>;
1395struct VertexOutput {
1396 @location(0) uv: vec2<f32>,
1397 @builtin(position) position: vec4<f32>,
1398};
1399@vertex
1400fn vertex(
1401 @location(0) vertex_position: vec3<f32>,
1402 @location(1) vertex_uv: vec2<f32>
1403) -> VertexOutput {
1404 var out: VertexOutput;
1405 out.uv = vertex_uv;
1406 out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
1407 return out;
1408}
1409";
1410 let processor = Preprocessor::default();
1411 let result = processor
1412 .preprocess(
1413 &WGSL_ELSE_IFDEF,
1414 &[
1415 ("SECOND_TEXTURE".to_string(), ShaderDefValue::Bool(true)),
1416 ("THIRD_TEXTURE".to_string(), ShaderDefValue::Bool(true)),
1417 ]
1418 .into(),
1419 true,
1420 )
1421 .unwrap();
1422 assert_eq!(
1423 result
1424 .preprocessed_source
1425 .replace(" ", "")
1426 .replace("\n", "")
1427 .replace("\r", ""),
1428 EXPECTED
1429 .replace(" ", "")
1430 .replace("\n", "")
1431 .replace("\r", "")
1432 );
1433 }
1434
1435 #[test]
1436 fn process_shader_def_else_ifdef_complicated_nesting() {
1437 #[rustfmt::skip]
1443 const WGSL_COMPLICATED_ELSE_IFDEF: &str = r"
1444#ifdef NOT_DEFINED
1445// not defined
1446#else ifdef IS_DEFINED
1447// defined 1
1448#ifdef NOT_DEFINED
1449// not defined
1450#else
1451// should be here
1452#ifdef NOT_DEFINED
1453// not defined
1454#else ifdef ALSO_NOT_DEFINED
1455// not defined
1456#else ifdef IS_DEFINED
1457// defined 2
1458#endif
1459#endif
1460#endif
1461";
1462
1463 #[rustfmt::skip]
1464 const EXPECTED: &str = r"
1465// defined 1
1466// should be here
1467// defined 2
1468";
1469 let processor = Preprocessor::default();
1470 let result = processor
1471 .preprocess(
1472 &WGSL_COMPLICATED_ELSE_IFDEF,
1473 &[("IS_DEFINED".to_string(), ShaderDefValue::Bool(true))].into(),
1474 true,
1475 )
1476 .unwrap();
1477 assert_eq!(
1478 result
1479 .preprocessed_source
1480 .replace(" ", "")
1481 .replace("\n", "")
1482 .replace("\r", ""),
1483 EXPECTED
1484 .replace(" ", "")
1485 .replace("\n", "")
1486 .replace("\r", "")
1487 );
1488 }
1489
1490 #[test]
1491 fn process_shader_def_else_ifndef() {
1492 #[rustfmt::skip]
1493 const INPUT: &str = r"
1494#ifdef NOT_DEFINED
1495fail 1
1496#else ifdef ALSO_NOT_DEFINED
1497fail 2
1498#else ifndef ALSO_ALSO_NOT_DEFINED
1499ok
1500#else
1501fail 3
1502#endif
1503";
1504
1505 const EXPECTED: &str = r"ok";
1506 let processor = Preprocessor::default();
1507 let result = processor.preprocess(&INPUT, &[].into(), true).unwrap();
1508 assert_eq!(
1509 result
1510 .preprocessed_source
1511 .replace(" ", "")
1512 .replace("\n", "")
1513 .replace("\r", ""),
1514 EXPECTED
1515 .replace(" ", "")
1516 .replace("\n", "")
1517 .replace("\r", "")
1518 );
1519 }
1520
1521 #[test]
1522 fn process_shader_def_else_if() {
1523 #[rustfmt::skip]
1524 const INPUT: &str = r"
1525#ifdef NOT_DEFINED
1526fail 1
1527#else if x == 1
1528fail 2
1529#else if x == 2
1530ok
1531#else
1532fail 3
1533#endif
1534";
1535
1536 const EXPECTED: &str = r"ok";
1537 let processor = Preprocessor::default();
1538 let result = processor
1539 .preprocess(
1540 &INPUT,
1541 &[("x".to_owned(), ShaderDefValue::Int(2))].into(),
1542 true,
1543 )
1544 .unwrap();
1545 assert_eq!(
1546 result
1547 .preprocessed_source
1548 .replace(" ", "")
1549 .replace("\n", "")
1550 .replace("\r", ""),
1551 EXPECTED
1552 .replace(" ", "")
1553 .replace("\n", "")
1554 .replace("\r", "")
1555 );
1556 }
1557}