Shaders can now have #else ifdef chains (#7431)
# Objective Currently, shaders may only have syntax such as ```wgsl #ifdef FOO // foo code #else #ifdef BAR // bar code #else #ifdef BAZ // baz code #else // fallback code #endif #endif #endif ``` This is hard to read and follow. Add a way to allow writing `#else ifdef DEFINE` to reduce the number of scopes introduced and to increase readability. ## Solution Refactor the current preprocessing a bit and add logic to allow `#else ifdef DEFINE`. This includes per-scope tracking of whether a branch has been accepted. Add a few tests for this feature. With these changes we may now write: ```wgsl #ifdef FOO // foo code #else ifdef BAR // bar code #else ifdef BAZ // baz code #else // fallback code #endif ``` instead. --- ## Changelog - Add `#else ifdef` to shader preprocessing.
This commit is contained in:
		
							parent
							
								
									3af6179076
								
							
						
					
					
						commit
						12f30f5667
					
				| @ -385,6 +385,7 @@ pub struct ShaderProcessor { | |||||||
|     ifdef_regex: Regex, |     ifdef_regex: Regex, | ||||||
|     ifndef_regex: Regex, |     ifndef_regex: Regex, | ||||||
|     ifop_regex: Regex, |     ifop_regex: Regex, | ||||||
|  |     else_ifdef_regex: Regex, | ||||||
|     else_regex: Regex, |     else_regex: Regex, | ||||||
|     endif_regex: Regex, |     endif_regex: Regex, | ||||||
|     def_regex: Regex, |     def_regex: Regex, | ||||||
| @ -397,6 +398,7 @@ impl Default for ShaderProcessor { | |||||||
|             ifdef_regex: Regex::new(r"^\s*#\s*ifdef\s*([\w|\d|_]+)").unwrap(), |             ifdef_regex: Regex::new(r"^\s*#\s*ifdef\s*([\w|\d|_]+)").unwrap(), | ||||||
|             ifndef_regex: Regex::new(r"^\s*#\s*ifndef\s*([\w|\d|_]+)").unwrap(), |             ifndef_regex: Regex::new(r"^\s*#\s*ifndef\s*([\w|\d|_]+)").unwrap(), | ||||||
|             ifop_regex: Regex::new(r"^\s*#\s*if\s*([\w|\d|_]+)\s*([^\s]*)\s*([\w|\d]+)").unwrap(), |             ifop_regex: Regex::new(r"^\s*#\s*if\s*([\w|\d|_]+)\s*([^\s]*)\s*([\w|\d]+)").unwrap(), | ||||||
|  |             else_ifdef_regex: Regex::new(r"^\s*#\s*else\s+ifdef\s*([\w|\d|_]+)").unwrap(), | ||||||
|             else_regex: Regex::new(r"^\s*#\s*else").unwrap(), |             else_regex: Regex::new(r"^\s*#\s*else").unwrap(), | ||||||
|             endif_regex: Regex::new(r"^\s*#\s*endif").unwrap(), |             endif_regex: Regex::new(r"^\s*#\s*endif").unwrap(), | ||||||
|             def_regex: Regex::new(r"#\s*([\w|\d|_]+)").unwrap(), |             def_regex: Regex::new(r"#\s*([\w|\d|_]+)").unwrap(), | ||||||
| @ -405,6 +407,41 @@ impl Default for ShaderProcessor { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | struct Scope { | ||||||
|  |     // Is the current scope one in which we should accept new lines into the output?
 | ||||||
|  |     accepting_lines: bool, | ||||||
|  | 
 | ||||||
|  |     // Has this scope ever accepted lines?
 | ||||||
|  |     // Needs to be tracked for #else ifdef chains.
 | ||||||
|  |     has_accepted_lines: bool, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Scope { | ||||||
|  |     fn new(should_lines_be_accepted: bool) -> Self { | ||||||
|  |         Self { | ||||||
|  |             accepting_lines: should_lines_be_accepted, | ||||||
|  |             has_accepted_lines: should_lines_be_accepted, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn is_accepting_lines(&self) -> bool { | ||||||
|  |         self.accepting_lines | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn stop_accepting_lines(&mut self) { | ||||||
|  |         self.accepting_lines = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn start_accepting_lines_if_appropriate(&mut self) { | ||||||
|  |         if !self.has_accepted_lines { | ||||||
|  |             self.has_accepted_lines = true; | ||||||
|  |             self.accepting_lines = true; | ||||||
|  |         } else { | ||||||
|  |             self.accepting_lines = false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl ShaderProcessor { | impl ShaderProcessor { | ||||||
|     pub fn process( |     pub fn process( | ||||||
|         &self, |         &self, | ||||||
| @ -430,18 +467,23 @@ impl ShaderProcessor { | |||||||
|                     (k.clone(), v.clone()) |                     (k.clone(), v.clone()) | ||||||
|                 } |                 } | ||||||
|             })); |             })); | ||||||
|         let mut scopes = vec![true]; |         let mut scopes = vec![Scope::new(true)]; | ||||||
|         let mut final_string = String::new(); |         let mut final_string = String::new(); | ||||||
|         for line in shader_str.lines() { |         for line in shader_str.lines() { | ||||||
|             if let Some(cap) = self.ifdef_regex.captures(line) { |             if let Some(cap) = self.ifdef_regex.captures(line) { | ||||||
|                 let def = cap.get(1).unwrap(); |                 let def = cap.get(1).unwrap(); | ||||||
|                 scopes | 
 | ||||||
|                     .push(*scopes.last().unwrap() && shader_defs_unique.contains_key(def.as_str())); |                 let current_valid = scopes.last().unwrap().is_accepting_lines(); | ||||||
|  |                 let has_define = shader_defs_unique.contains_key(def.as_str()); | ||||||
|  | 
 | ||||||
|  |                 scopes.push(Scope::new(current_valid && has_define)); | ||||||
|             } else if let Some(cap) = self.ifndef_regex.captures(line) { |             } else if let Some(cap) = self.ifndef_regex.captures(line) { | ||||||
|                 let def = cap.get(1).unwrap(); |                 let def = cap.get(1).unwrap(); | ||||||
|                 scopes.push( | 
 | ||||||
|                     *scopes.last().unwrap() && !shader_defs_unique.contains_key(def.as_str()), |                 let current_valid = scopes.last().unwrap().is_accepting_lines(); | ||||||
|                 ); |                 let has_define = shader_defs_unique.contains_key(def.as_str()); | ||||||
|  | 
 | ||||||
|  |                 scopes.push(Scope::new(current_valid && !has_define)); | ||||||
|             } else if let Some(cap) = self.ifop_regex.captures(line) { |             } else if let Some(cap) = self.ifop_regex.captures(line) { | ||||||
|                 let def = cap.get(1).unwrap(); |                 let def = cap.get(1).unwrap(); | ||||||
|                 let op = cap.get(2).unwrap(); |                 let op = cap.get(2).unwrap(); | ||||||
| @ -498,21 +540,83 @@ impl ShaderProcessor { | |||||||
|                         act_on(*def, val, op.as_str())? |                         act_on(*def, val, op.as_str())? | ||||||
|                     } |                     } | ||||||
|                 }; |                 }; | ||||||
|                 scopes.push(*scopes.last().unwrap() && new_scope); | 
 | ||||||
|             } else if self.else_regex.is_match(line) { |                 let current_valid = scopes.last().unwrap().is_accepting_lines(); | ||||||
|                 let mut is_parent_scope_truthy = true; | 
 | ||||||
|  |                 scopes.push(Scope::new(current_valid && new_scope)); | ||||||
|  |             } else if let Some(cap) = self.else_ifdef_regex.captures(line) { | ||||||
|  |                 // When should we accept the code in an
 | ||||||
|  |                 //
 | ||||||
|  |                 //  #else ifdef FOO
 | ||||||
|  |                 //      <stuff>
 | ||||||
|  |                 //  #endif
 | ||||||
|  |                 //
 | ||||||
|  |                 // block? Conditions:
 | ||||||
|  |                 //  1. The parent scope is accepting lines.
 | ||||||
|  |                 //  2. The current scope is _not_ accepting lines.
 | ||||||
|  |                 //  3. FOO is defined.
 | ||||||
|  |                 //  4. We haven't already accepted another #ifdef (or #else ifdef) in the current scope.
 | ||||||
|  | 
 | ||||||
|  |                 // Condition 1
 | ||||||
|  |                 let mut parent_accepting = true; | ||||||
|  | 
 | ||||||
|                 if scopes.len() > 1 { |                 if scopes.len() > 1 { | ||||||
|                     is_parent_scope_truthy = scopes[scopes.len() - 2]; |                     parent_accepting = scopes[scopes.len() - 2].is_accepting_lines(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if let Some(current) = scopes.last_mut() { | ||||||
|  |                     // Condition 2
 | ||||||
|  |                     let current_accepting = current.is_accepting_lines(); | ||||||
|  | 
 | ||||||
|  |                     // Condition 3
 | ||||||
|  |                     let def = cap.get(1).unwrap(); | ||||||
|  |                     let has_define = shader_defs_unique.contains_key(def.as_str()); | ||||||
|  | 
 | ||||||
|  |                     if parent_accepting && !current_accepting && has_define { | ||||||
|  |                         // Condition 4: Enforced by [`Scope`].
 | ||||||
|  |                         current.start_accepting_lines_if_appropriate(); | ||||||
|  |                     } else { | ||||||
|  |                         current.stop_accepting_lines(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else if self.else_regex.is_match(line) { | ||||||
|  |                 let mut parent_accepting = true; | ||||||
|  | 
 | ||||||
|  |                 if scopes.len() > 1 { | ||||||
|  |                     parent_accepting = scopes[scopes.len() - 2].is_accepting_lines(); | ||||||
|  |                 } | ||||||
|  |                 if let Some(current) = scopes.last_mut() { | ||||||
|  |                     // Using #else means that we only want to accept those lines in the output
 | ||||||
|  |                     // if the stuff before #else was _not_ accepted.
 | ||||||
|  |                     // That's why we stop accepting here if we were currently accepting.
 | ||||||
|  |                     //
 | ||||||
|  |                     // Why do we care about the parent scope?
 | ||||||
|  |                     // Because if we have something like this:
 | ||||||
|  |                     //
 | ||||||
|  |                     //  #ifdef NOT_DEFINED
 | ||||||
|  |                     //      // Not accepting lines
 | ||||||
|  |                     //      #ifdef NOT_DEFINED_EITHER
 | ||||||
|  |                     //          // Not accepting lines
 | ||||||
|  |                     //      #else
 | ||||||
|  |                     //          // This is now accepting lines relative to NOT_DEFINED_EITHER
 | ||||||
|  |                     //          <stuff>
 | ||||||
|  |                     //      #endif
 | ||||||
|  |                     //  #endif
 | ||||||
|  |                     //
 | ||||||
|  |                     // We don't want to actually add <stuff>.
 | ||||||
|  | 
 | ||||||
|  |                     if current.is_accepting_lines() || !parent_accepting { | ||||||
|  |                         current.stop_accepting_lines(); | ||||||
|  |                     } else { | ||||||
|  |                         current.start_accepting_lines_if_appropriate(); | ||||||
|                     } |                     } | ||||||
|                 if let Some(last) = scopes.last_mut() { |  | ||||||
|                     *last = is_parent_scope_truthy && !*last; |  | ||||||
|                 } |                 } | ||||||
|             } else if self.endif_regex.is_match(line) { |             } else if self.endif_regex.is_match(line) { | ||||||
|                 scopes.pop(); |                 scopes.pop(); | ||||||
|                 if scopes.is_empty() { |                 if scopes.is_empty() { | ||||||
|                     return Err(ProcessShaderError::TooManyEndIfs); |                     return Err(ProcessShaderError::TooManyEndIfs); | ||||||
|                 } |                 } | ||||||
|             } else if *scopes.last().unwrap() { |             } else if scopes.last().unwrap().is_accepting_lines() { | ||||||
|                 if let Some(cap) = SHADER_IMPORT_PROCESSOR |                 if let Some(cap) = SHADER_IMPORT_PROCESSOR | ||||||
|                     .import_asset_path_regex |                     .import_asset_path_regex | ||||||
|                     .captures(line) |                     .captures(line) | ||||||
| @ -715,6 +819,83 @@ struct VertexOutput { | |||||||
|     @builtin(position) position: vec4<f32>, |     @builtin(position) position: vec4<f32>, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @vertex | ||||||
|  | fn vertex( | ||||||
|  |     @location(0) vertex_position: vec3<f32>, | ||||||
|  |     @location(1) vertex_uv: vec2<f32> | ||||||
|  | ) -> VertexOutput { | ||||||
|  |     var out: VertexOutput; | ||||||
|  |     out.uv = vertex_uv; | ||||||
|  |     out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | ";
 | ||||||
|  | 
 | ||||||
|  |     const WGSL_ELSE_IFDEF: &str = r" | ||||||
|  | struct View { | ||||||
|  |     view_proj: mat4x4<f32>, | ||||||
|  |     world_position: vec3<f32>, | ||||||
|  | }; | ||||||
|  | @group(0) @binding(0) | ||||||
|  | var<uniform> view: View; | ||||||
|  | 
 | ||||||
|  | #ifdef TEXTURE | ||||||
|  | // Main texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | #else ifdef SECOND_TEXTURE | ||||||
|  | // Second texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | #else ifdef THIRD_TEXTURE | ||||||
|  | // Third texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | #else | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d_array<f32>; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | struct VertexOutput { | ||||||
|  |     @location(0) uv: vec2<f32>, | ||||||
|  |     @builtin(position) position: vec4<f32>, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | @vertex | ||||||
|  | fn vertex( | ||||||
|  |     @location(0) vertex_position: vec3<f32>, | ||||||
|  |     @location(1) vertex_uv: vec2<f32> | ||||||
|  | ) -> VertexOutput { | ||||||
|  |     var out: VertexOutput; | ||||||
|  |     out.uv = vertex_uv; | ||||||
|  |     out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | ";
 | ||||||
|  | 
 | ||||||
|  |     const WGSL_ELSE_IFDEF_NO_ELSE_FALLBACK: &str = r" | ||||||
|  | struct View { | ||||||
|  |     view_proj: mat4x4<f32>, | ||||||
|  |     world_position: vec3<f32>, | ||||||
|  | }; | ||||||
|  | @group(0) @binding(0) | ||||||
|  | var<uniform> view: View; | ||||||
|  | 
 | ||||||
|  | #ifdef TEXTURE | ||||||
|  | // Main texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | #else ifdef OTHER_TEXTURE | ||||||
|  | // Other texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | struct VertexOutput { | ||||||
|  |     @location(0) uv: vec2<f32>, | ||||||
|  |     @builtin(position) position: vec4<f32>, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| @vertex | @vertex | ||||||
| fn vertex( | fn vertex( | ||||||
|     @location(0) vertex_position: vec3<f32>, |     @location(0) vertex_position: vec3<f32>, | ||||||
| @ -918,6 +1099,306 @@ fn vertex( | |||||||
|         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); |         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn process_shader_def_else_ifdef_ends_up_in_else() { | ||||||
|  |         #[rustfmt::skip] | ||||||
|  |     const EXPECTED: &str = r" | ||||||
|  | struct View { | ||||||
|  |     view_proj: mat4x4<f32>, | ||||||
|  |     world_position: vec3<f32>, | ||||||
|  | }; | ||||||
|  | @group(0) @binding(0) | ||||||
|  | var<uniform> view: View; | ||||||
|  | 
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d_array<f32>; | ||||||
|  | 
 | ||||||
|  | struct VertexOutput { | ||||||
|  |     @location(0) uv: vec2<f32>, | ||||||
|  |     @builtin(position) position: vec4<f32>, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | @vertex | ||||||
|  | fn vertex( | ||||||
|  |     @location(0) vertex_position: vec3<f32>, | ||||||
|  |     @location(1) vertex_uv: vec2<f32> | ||||||
|  | ) -> VertexOutput { | ||||||
|  |     var out: VertexOutput; | ||||||
|  |     out.uv = vertex_uv; | ||||||
|  |     out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | ";
 | ||||||
|  |         let processor = ShaderProcessor::default(); | ||||||
|  |         let result = processor | ||||||
|  |             .process( | ||||||
|  |                 &Shader::from_wgsl(WGSL_ELSE_IFDEF), | ||||||
|  |                 &[], | ||||||
|  |                 &HashMap::default(), | ||||||
|  |                 &HashMap::default(), | ||||||
|  |             ) | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn process_shader_def_else_ifdef_no_match_and_no_fallback_else() { | ||||||
|  |         #[rustfmt::skip] | ||||||
|  |     const EXPECTED: &str = r" | ||||||
|  | struct View { | ||||||
|  |     view_proj: mat4x4<f32>, | ||||||
|  |     world_position: vec3<f32>, | ||||||
|  | }; | ||||||
|  | @group(0) @binding(0) | ||||||
|  | var<uniform> view: View; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | struct VertexOutput { | ||||||
|  |     @location(0) uv: vec2<f32>, | ||||||
|  |     @builtin(position) position: vec4<f32>, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | @vertex | ||||||
|  | fn vertex( | ||||||
|  |     @location(0) vertex_position: vec3<f32>, | ||||||
|  |     @location(1) vertex_uv: vec2<f32> | ||||||
|  | ) -> VertexOutput { | ||||||
|  |     var out: VertexOutput; | ||||||
|  |     out.uv = vertex_uv; | ||||||
|  |     out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | ";
 | ||||||
|  |         let processor = ShaderProcessor::default(); | ||||||
|  |         let result = processor | ||||||
|  |             .process( | ||||||
|  |                 &Shader::from_wgsl(WGSL_ELSE_IFDEF_NO_ELSE_FALLBACK), | ||||||
|  |                 &[], | ||||||
|  |                 &HashMap::default(), | ||||||
|  |                 &HashMap::default(), | ||||||
|  |             ) | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn process_shader_def_else_ifdef_ends_up_in_first_clause() { | ||||||
|  |         #[rustfmt::skip] | ||||||
|  |     const EXPECTED: &str = r" | ||||||
|  | struct View { | ||||||
|  |     view_proj: mat4x4<f32>, | ||||||
|  |     world_position: vec3<f32>, | ||||||
|  | }; | ||||||
|  | @group(0) @binding(0) | ||||||
|  | var<uniform> view: View; | ||||||
|  | 
 | ||||||
|  | // Main texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | 
 | ||||||
|  | struct VertexOutput { | ||||||
|  |     @location(0) uv: vec2<f32>, | ||||||
|  |     @builtin(position) position: vec4<f32>, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | @vertex | ||||||
|  | fn vertex( | ||||||
|  |     @location(0) vertex_position: vec3<f32>, | ||||||
|  |     @location(1) vertex_uv: vec2<f32> | ||||||
|  | ) -> VertexOutput { | ||||||
|  |     var out: VertexOutput; | ||||||
|  |     out.uv = vertex_uv; | ||||||
|  |     out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | ";
 | ||||||
|  |         let processor = ShaderProcessor::default(); | ||||||
|  |         let result = processor | ||||||
|  |             .process( | ||||||
|  |                 &Shader::from_wgsl(WGSL_ELSE_IFDEF), | ||||||
|  |                 &["TEXTURE".into()], | ||||||
|  |                 &HashMap::default(), | ||||||
|  |                 &HashMap::default(), | ||||||
|  |             ) | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn process_shader_def_else_ifdef_ends_up_in_second_clause() { | ||||||
|  |         #[rustfmt::skip] | ||||||
|  |     const EXPECTED: &str = r" | ||||||
|  | struct View { | ||||||
|  |     view_proj: mat4x4<f32>, | ||||||
|  |     world_position: vec3<f32>, | ||||||
|  | }; | ||||||
|  | @group(0) @binding(0) | ||||||
|  | var<uniform> view: View; | ||||||
|  | 
 | ||||||
|  | // Second texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | 
 | ||||||
|  | struct VertexOutput { | ||||||
|  |     @location(0) uv: vec2<f32>, | ||||||
|  |     @builtin(position) position: vec4<f32>, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | @vertex | ||||||
|  | fn vertex( | ||||||
|  |     @location(0) vertex_position: vec3<f32>, | ||||||
|  |     @location(1) vertex_uv: vec2<f32> | ||||||
|  | ) -> VertexOutput { | ||||||
|  |     var out: VertexOutput; | ||||||
|  |     out.uv = vertex_uv; | ||||||
|  |     out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | ";
 | ||||||
|  |         let processor = ShaderProcessor::default(); | ||||||
|  |         let result = processor | ||||||
|  |             .process( | ||||||
|  |                 &Shader::from_wgsl(WGSL_ELSE_IFDEF), | ||||||
|  |                 &["SECOND_TEXTURE".into()], | ||||||
|  |                 &HashMap::default(), | ||||||
|  |                 &HashMap::default(), | ||||||
|  |             ) | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn process_shader_def_else_ifdef_ends_up_in_third_clause() { | ||||||
|  |         #[rustfmt::skip] | ||||||
|  |     const EXPECTED: &str = r" | ||||||
|  | struct View { | ||||||
|  |     view_proj: mat4x4<f32>, | ||||||
|  |     world_position: vec3<f32>, | ||||||
|  | }; | ||||||
|  | @group(0) @binding(0) | ||||||
|  | var<uniform> view: View; | ||||||
|  | 
 | ||||||
|  | // Third texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | 
 | ||||||
|  | struct VertexOutput { | ||||||
|  |     @location(0) uv: vec2<f32>, | ||||||
|  |     @builtin(position) position: vec4<f32>, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | @vertex | ||||||
|  | fn vertex( | ||||||
|  |     @location(0) vertex_position: vec3<f32>, | ||||||
|  |     @location(1) vertex_uv: vec2<f32> | ||||||
|  | ) -> VertexOutput { | ||||||
|  |     var out: VertexOutput; | ||||||
|  |     out.uv = vertex_uv; | ||||||
|  |     out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | ";
 | ||||||
|  |         let processor = ShaderProcessor::default(); | ||||||
|  |         let result = processor | ||||||
|  |             .process( | ||||||
|  |                 &Shader::from_wgsl(WGSL_ELSE_IFDEF), | ||||||
|  |                 &["THIRD_TEXTURE".into()], | ||||||
|  |                 &HashMap::default(), | ||||||
|  |                 &HashMap::default(), | ||||||
|  |             ) | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn process_shader_def_else_ifdef_only_accepts_one_valid_else_ifdef() { | ||||||
|  |         #[rustfmt::skip] | ||||||
|  |     const EXPECTED: &str = r" | ||||||
|  | struct View { | ||||||
|  |     view_proj: mat4x4<f32>, | ||||||
|  |     world_position: vec3<f32>, | ||||||
|  | }; | ||||||
|  | @group(0) @binding(0) | ||||||
|  | var<uniform> view: View; | ||||||
|  | 
 | ||||||
|  | // Second texture
 | ||||||
|  | @group(1) @binding(0) | ||||||
|  | var sprite_texture: texture_2d<f32>; | ||||||
|  | 
 | ||||||
|  | struct VertexOutput { | ||||||
|  |     @location(0) uv: vec2<f32>, | ||||||
|  |     @builtin(position) position: vec4<f32>, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | @vertex | ||||||
|  | fn vertex( | ||||||
|  |     @location(0) vertex_position: vec3<f32>, | ||||||
|  |     @location(1) vertex_uv: vec2<f32> | ||||||
|  | ) -> VertexOutput { | ||||||
|  |     var out: VertexOutput; | ||||||
|  |     out.uv = vertex_uv; | ||||||
|  |     out.position = view.view_proj * vec4<f32>(vertex_position, 1.0); | ||||||
|  |     return out; | ||||||
|  | } | ||||||
|  | ";
 | ||||||
|  |         let processor = ShaderProcessor::default(); | ||||||
|  |         let result = processor | ||||||
|  |             .process( | ||||||
|  |                 &Shader::from_wgsl(WGSL_ELSE_IFDEF), | ||||||
|  |                 &["SECOND_TEXTURE".into(), "THIRD_TEXTURE".into()], | ||||||
|  |                 &HashMap::default(), | ||||||
|  |                 &HashMap::default(), | ||||||
|  |             ) | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn process_shader_def_else_ifdef_complicated_nesting() { | ||||||
|  |         // Test some nesting including #else ifdef statements
 | ||||||
|  |         // 1. Enter an #else ifdef
 | ||||||
|  |         // 2. Then enter an #else
 | ||||||
|  |         // 3. Then enter another #else ifdef
 | ||||||
|  | 
 | ||||||
|  |         #[rustfmt::skip] | ||||||
|  |         const WGSL_COMPLICATED_ELSE_IFDEF: &str = r" | ||||||
|  | #ifdef NOT_DEFINED | ||||||
|  | // not defined
 | ||||||
|  | #else ifdef IS_DEFINED | ||||||
|  | // defined 1
 | ||||||
|  | #ifdef NOT_DEFINED | ||||||
|  | // not defined
 | ||||||
|  | #else | ||||||
|  | // should be here
 | ||||||
|  | #ifdef NOT_DEFINED | ||||||
|  | // not defined
 | ||||||
|  | #else ifdef ALSO_NOT_DEFINED | ||||||
|  | // not defined
 | ||||||
|  | #else ifdef IS_DEFINED | ||||||
|  | // defined 2
 | ||||||
|  | #endif | ||||||
|  | #endif | ||||||
|  | #endif | ||||||
|  | ";
 | ||||||
|  | 
 | ||||||
|  |         #[rustfmt::skip] | ||||||
|  |         const EXPECTED: &str = r" | ||||||
|  | // defined 1
 | ||||||
|  | // should be here
 | ||||||
|  | // defined 2
 | ||||||
|  | ";
 | ||||||
|  |         let processor = ShaderProcessor::default(); | ||||||
|  |         let result = processor | ||||||
|  |             .process( | ||||||
|  |                 &Shader::from_wgsl(WGSL_COMPLICATED_ELSE_IFDEF), | ||||||
|  |                 &["IS_DEFINED".into()], | ||||||
|  |                 &HashMap::default(), | ||||||
|  |                 &HashMap::default(), | ||||||
|  |             ) | ||||||
|  |             .unwrap(); | ||||||
|  |         assert_eq!(result.get_wgsl_source().unwrap(), EXPECTED); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn process_shader_def_unclosed() { |     fn process_shader_def_unclosed() { | ||||||
|         #[rustfmt::skip] |         #[rustfmt::skip] | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Torstein Grindvik
						Torstein Grindvik