Skip to content

Commit bb71161

Browse files
Add support for wgsl directives. (#123)
I started down the path of allowing imports to also be able to define directives that then get merged and validated when composing the final module, but this started to get too complicated and since this is already pretty niche just went with top level modules being able to declare directives. A lot of that is still there though if someone wanted to implement. --------- Co-authored-by: Elabajaba <Elabajaba@users.noreply.github.com>
1 parent 6eee1e6 commit bb71161

7 files changed

Lines changed: 346 additions & 9 deletions

File tree

src/compose/error.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ pub enum ComposerErrorInner {
134134
},
135135
#[error("#define statements are only allowed at the start of the top-level shaders")]
136136
DefineInModule(usize),
137+
#[error("Invalid WGSL directive '{directive}' at line {position}: {reason}")]
138+
InvalidWgslDirective {
139+
directive: String,
140+
position: usize,
141+
reason: String,
142+
},
137143
}
138144

139145
struct ErrorSources<'a> {
@@ -239,7 +245,8 @@ impl ComposerError {
239245
| ComposerErrorInner::OverrideNotVirtual { pos, .. }
240246
| ComposerErrorInner::GlslInvalidVersion(pos)
241247
| ComposerErrorInner::DefineInModule(pos)
242-
| ComposerErrorInner::InvalidShaderDefDefinitionValue { pos, .. } => {
248+
| ComposerErrorInner::InvalidShaderDefDefinitionValue { pos, .. }
249+
| ComposerErrorInner::InvalidWgslDirective { position: pos, .. } => {
243250
(vec![Label::primary((), *pos..*pos)], vec![])
244251
}
245252
ComposerErrorInner::WgslBackError(e) => {

src/compose/mod.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,17 @@ use crate::{
140140

141141
pub use self::error::{ComposerError, ComposerErrorInner, ErrSource};
142142
use self::preprocess::Preprocessor;
143+
pub use self::wgsl_directives::{
144+
DiagnosticDirective, EnableDirective, RequiresDirective, WgslDirectives,
145+
};
143146

144147
pub mod comment_strip_iter;
145148
pub mod error;
146149
pub mod parse_imports;
147150
pub mod preprocess;
148151
mod test;
149152
pub mod tokenizer;
153+
pub mod wgsl_directives;
150154

151155
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, Default)]
152156
pub enum ShaderLanguage {
@@ -270,8 +274,6 @@ pub struct ComposableModuleDefinition {
270274
modules: HashMap<ModuleKey, ComposableModule>,
271275
// used in spans when this module is included
272276
module_index: usize,
273-
// preprocessor meta data
274-
// metadata: PreprocessorMetaData,
275277
}
276278

277279
impl ComposableModuleDefinition {
@@ -580,11 +582,17 @@ impl Composer {
580582
language: ShaderLanguage,
581583
imports: &[ImportDefinition],
582584
shader_defs: &HashMap<String, ShaderDefValue>,
585+
wgsl_directives: Option<WgslDirectives>,
583586
) -> Result<IrBuildResult, ComposerError> {
584587
debug!("creating IR for {} with defs: {:?}", name, shader_defs);
585588

589+
let mut wgsl_string = String::new();
590+
if let Some(wgsl_directives) = &wgsl_directives {
591+
trace!("adding WGSL directives for {}", name);
592+
wgsl_string = wgsl_directives.to_wgsl_string();
593+
}
586594
let mut module_string = match language {
587-
ShaderLanguage::Wgsl => String::new(),
595+
ShaderLanguage::Wgsl => wgsl_string,
588596
#[cfg(feature = "glsl")]
589597
ShaderLanguage::Glsl => String::from("#version 450\n"),
590598
};
@@ -842,6 +850,7 @@ impl Composer {
842850
demote_entrypoints: bool,
843851
source: &str,
844852
imports: Vec<ImportDefWithOffset>,
853+
wgsl_directives: Option<WgslDirectives>,
845854
) -> Result<ComposableModule, ComposerError> {
846855
let mut imports: Vec<_> = imports
847856
.into_iter()
@@ -975,6 +984,7 @@ impl Composer {
975984
module_definition.language,
976985
&imports,
977986
shader_defs,
987+
wgsl_directives,
978988
)?;
979989

980990
// from here on errors need to be reported using the modified source with start_offset
@@ -1376,6 +1386,7 @@ impl Composer {
13761386
true,
13771387
&preprocessed_source,
13781388
imports,
1389+
None,
13791390
)
13801391
.map_err(|err| err.into())
13811392
}
@@ -1519,6 +1530,7 @@ impl Composer {
15191530
name: module_name,
15201531
mut imports,
15211532
mut effective_defs,
1533+
cleaned_source,
15221534
..
15231535
} = self
15241536
.preprocessor
@@ -1595,7 +1607,7 @@ impl Composer {
15951607

15961608
let module_set = ComposableModuleDefinition {
15971609
name: module_name.clone(),
1598-
sanitized_source: substituted_source,
1610+
sanitized_source: cleaned_source,
15991611
file_path: file_path.to_owned(),
16001612
language,
16011613
effective_defs: effective_defs.into_iter().collect(),
@@ -1650,7 +1662,13 @@ impl Composer {
16501662

16511663
let sanitized_source = self.sanitize_and_set_auto_bindings(source);
16521664

1653-
let PreprocessorMetaData { name, defines, .. } = self
1665+
let PreprocessorMetaData {
1666+
name,
1667+
defines,
1668+
wgsl_directives,
1669+
cleaned_source,
1670+
..
1671+
} = self
16541672
.preprocessor
16551673
.get_preprocessor_metadata(&sanitized_source, true)
16561674
.map_err(|inner| ComposerError {
@@ -1667,7 +1685,7 @@ impl Composer {
16671685

16681686
let PreprocessOutput { imports, .. } = self
16691687
.preprocessor
1670-
.preprocess(&sanitized_source, &shader_defs)
1688+
.preprocess(&cleaned_source, &shader_defs)
16711689
.map_err(|inner| ComposerError {
16721690
inner,
16731691
source: ErrSource::Constructing {
@@ -1734,7 +1752,7 @@ impl Composer {
17341752

17351753
let definition = ComposableModuleDefinition {
17361754
name,
1737-
sanitized_source: sanitized_source.clone(),
1755+
sanitized_source: cleaned_source.clone(),
17381756
language: shader_type.into(),
17391757
file_path: file_path.to_owned(),
17401758
module_index: 0,
@@ -1751,7 +1769,7 @@ impl Composer {
17511769
imports,
17521770
} = self
17531771
.preprocessor
1754-
.preprocess(&sanitized_source, &shader_defs)
1772+
.preprocess(&cleaned_source, &shader_defs)
17551773
.map_err(|inner| ComposerError {
17561774
inner,
17571775
source: ErrSource::Constructing {
@@ -1770,6 +1788,7 @@ impl Composer {
17701788
false,
17711789
&preprocessed_source,
17721790
imports,
1791+
Some(wgsl_directives),
17731792
)
17741793
.map_err(|e| ComposerError {
17751794
inner: e.inner,

src/compose/preprocess.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use regex::Regex;
66
use super::{
77
comment_strip_iter::CommentReplaceExt,
88
parse_imports::{parse_imports, substitute_identifiers},
9+
wgsl_directives::{DiagnosticDirective, EnableDirective, RequiresDirective, WgslDirectives},
910
ComposerErrorInner, ImportDefWithOffset, ShaderDefValue,
1011
};
1112

@@ -22,6 +23,9 @@ pub struct Preprocessor {
2223
import_regex: Regex,
2324
define_import_path_regex: Regex,
2425
define_shader_def_regex: Regex,
26+
enable_regex: Regex,
27+
requires_regex: Regex,
28+
diagnostic_regex: Regex,
2529
}
2630

2731
impl Default for Preprocessor {
@@ -42,6 +46,10 @@ impl Default for Preprocessor {
4246
define_import_path_regex: Regex::new(r"^\s*#\s*define_import_path\s+([^\s]+)").unwrap(),
4347
define_shader_def_regex: Regex::new(r"^\s*#\s*define\s+([\w|\d|_]+)\s*([-\w|\d]+)?")
4448
.unwrap(),
49+
enable_regex: Regex::new(r"^\s*enable\s+([^;]+)\s*;").unwrap(),
50+
requires_regex: Regex::new(r"^\s*requires\s+([^;]+)\s*;").unwrap(),
51+
diagnostic_regex: Regex::new(r"^\s*diagnostic\s*\(\s*([^,]+)\s*,\s*([^)]+)\s*\)\s*;")
52+
.unwrap(),
4553
}
4654
}
4755
}
@@ -52,6 +60,8 @@ pub struct PreprocessorMetaData {
5260
pub imports: Vec<ImportDefWithOffset>,
5361
pub defines: HashMap<String, ShaderDefValue>,
5462
pub effective_defs: HashSet<String>,
63+
pub wgsl_directives: WgslDirectives,
64+
pub cleaned_source: String,
5565
}
5666

5767
enum ScopeLevel {
@@ -133,6 +143,145 @@ pub struct PreprocessOutput {
133143
}
134144

135145
impl Preprocessor {
146+
fn parse_enable_directive(
147+
&self,
148+
line: &str,
149+
line_idx: usize,
150+
) -> Result<Option<EnableDirective>, ComposerErrorInner> {
151+
if let Some(cap) = self.enable_regex.captures(line) {
152+
let extensions_str = cap.get(1).unwrap().as_str().trim();
153+
let extensions: Vec<String> = extensions_str
154+
.split(',')
155+
.map(|s| s.trim().to_string())
156+
.filter(|s| !s.is_empty())
157+
.collect();
158+
159+
if extensions.is_empty() {
160+
return Err(ComposerErrorInner::InvalidWgslDirective {
161+
directive: line.to_string(),
162+
position: line_idx,
163+
reason: "No extensions specified".to_string(),
164+
});
165+
}
166+
167+
Ok(Some(EnableDirective {
168+
extensions,
169+
source_location: line_idx,
170+
}))
171+
} else {
172+
Ok(None)
173+
}
174+
}
175+
176+
fn parse_requires_directive(
177+
&self,
178+
line: &str,
179+
line_idx: usize,
180+
) -> Result<Option<RequiresDirective>, ComposerErrorInner> {
181+
if let Some(cap) = self.requires_regex.captures(line) {
182+
let extensions_str = cap.get(1).unwrap().as_str().trim();
183+
let extensions: Vec<String> = extensions_str
184+
.split(',')
185+
.map(|s| s.trim().to_string())
186+
.filter(|s| !s.is_empty())
187+
.collect();
188+
189+
if extensions.is_empty() {
190+
return Err(ComposerErrorInner::InvalidWgslDirective {
191+
directive: line.to_string(),
192+
position: line_idx,
193+
reason: "No extensions specified".to_string(),
194+
});
195+
}
196+
197+
Ok(Some(RequiresDirective {
198+
extensions,
199+
source_location: line_idx,
200+
}))
201+
} else {
202+
Ok(None)
203+
}
204+
}
205+
206+
fn parse_diagnostic_directive(
207+
&self,
208+
line: &str,
209+
line_idx: usize,
210+
) -> Result<Option<DiagnosticDirective>, ComposerErrorInner> {
211+
if let Some(cap) = self.diagnostic_regex.captures(line) {
212+
let severity = cap.get(1).unwrap().as_str().trim().to_string();
213+
let rule = cap.get(2).unwrap().as_str().trim().to_string();
214+
215+
match severity.as_str() {
216+
"off" | "info" | "warn" | "error" => {}
217+
_ => {
218+
return Err(ComposerErrorInner::InvalidWgslDirective {
219+
directive: line.to_string(),
220+
position: line_idx,
221+
reason: format!(
222+
"Invalid severity '{}'. Must be one of: off, info, warn, error",
223+
severity
224+
),
225+
});
226+
}
227+
}
228+
229+
Ok(Some(DiagnosticDirective {
230+
severity,
231+
rule,
232+
source_location: line_idx,
233+
}))
234+
} else {
235+
Ok(None)
236+
}
237+
}
238+
239+
pub fn extract_wgsl_directives(
240+
&self,
241+
source: &str,
242+
) -> Result<(String, WgslDirectives), ComposerErrorInner> {
243+
let mut directives = WgslDirectives::default();
244+
let mut cleaned_lines = Vec::new();
245+
let mut in_directive_section = true;
246+
247+
for (line_idx, line) in source.lines().enumerate() {
248+
let trimmed = line.trim();
249+
250+
if trimmed.is_empty() || trimmed.starts_with("//") {
251+
cleaned_lines.push(line);
252+
continue;
253+
}
254+
255+
if in_directive_section {
256+
if let Some(enable) = self.parse_enable_directive(trimmed, line_idx)? {
257+
cleaned_lines.push("");
258+
directives.enables.push(enable);
259+
continue;
260+
} else if let Some(requires) = self.parse_requires_directive(trimmed, line_idx)? {
261+
cleaned_lines.push("");
262+
directives.requires.push(requires);
263+
continue;
264+
} else if let Some(diagnostic) =
265+
self.parse_diagnostic_directive(trimmed, line_idx)?
266+
{
267+
cleaned_lines.push("");
268+
directives.diagnostics.push(diagnostic);
269+
continue;
270+
} else if !trimmed.starts_with("enable")
271+
&& !trimmed.starts_with("requires")
272+
&& !trimmed.starts_with("diagnostic")
273+
{
274+
in_directive_section = false;
275+
}
276+
}
277+
278+
cleaned_lines.push(line);
279+
}
280+
281+
let cleaned_source = cleaned_lines.join("\n");
282+
Ok((cleaned_source, directives))
283+
}
284+
136285
fn check_scope<'a>(
137286
&self,
138287
shader_defs: &HashMap<String, ShaderDefValue>,
@@ -379,6 +528,8 @@ impl Preprocessor {
379528
shader_str: &str,
380529
allow_defines: bool,
381530
) -> Result<PreprocessorMetaData, ComposerErrorInner> {
531+
let (shader_str, wgsl_directives) = self.extract_wgsl_directives(shader_str)?;
532+
382533
let mut declared_imports = IndexMap::default();
383534
let mut used_imports = IndexMap::default();
384535
let mut name = None;
@@ -477,6 +628,8 @@ impl Preprocessor {
477628
imports: used_imports.into_values().collect(),
478629
defines,
479630
effective_defs,
631+
wgsl_directives,
632+
cleaned_source: shader_str.to_string(),
480633
})
481634
}
482635
}

0 commit comments

Comments
 (0)