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