1use alloc::{borrow::Cow, format, string::String, vec::Vec};
8use core::fmt::Write as _;
9
10use hashbrown::HashMap;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum GlslMacroKind {
15 Bool,
16 Sint,
17 Uint,
18 Float,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct GlslMacro {
24 pub constant: &'static str,
26 pub name: &'static str,
28 pub kind: GlslMacroKind,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub struct GlslBlock {
34 pub name: &'static str,
35 pub storage: bool,
36 pub binding: naga::ResourceBinding,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct GlslTexture {
43 pub name: &'static str,
44 pub storage: bool,
45 pub binding: naga::ResourceBinding,
46 pub sampler: Option<naga::ResourceBinding>,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub struct GlslTemplate {
53 pub entry_point: &'static str,
54 pub stage: naga::ShaderStage,
55 pub glsl: &'static [&'static str],
59 pub macros: &'static [GlslMacro],
60 pub blocks: &'static [GlslBlock],
61 pub textures: &'static [GlslTexture],
62 pub varyings: &'static [(&'static str, u32)],
64 pub clip_distance_count: u32,
65}
66
67impl GlslTemplate {
68 pub fn expand(&self, constants: &naga::back::PipelineConstants) -> Result<String, String> {
74 let mut macros = Macros::default();
75 for item in self.macros {
76 if let Some(&value) = constants.get(item.constant) {
77 macros.define(item.name, Cow::Owned(macro_value(item, value)?));
78 }
79 }
80 let length = self.glsl.iter().map(|piece| piece.len()).sum::<usize>();
81 let mut code = String::with_capacity(length);
82 let mut version = "";
83 let mut kept: Vec<bool> = Vec::new();
85 for line in self.glsl.iter().flat_map(|piece| piece.split_inclusive('\n')) {
86 let keeping = kept.last().copied().unwrap_or(true);
87 let Some(directive) = line.trim_start().strip_prefix('#') else {
88 if keeping {
89 code.push_str(line);
90 }
91 continue;
92 };
93 let directive = directive.trim_end();
94 let (name, rest) = directive.split_once(' ').unwrap_or((directive, ""));
95 match name {
96 "version" => version = line,
97 "ifndef" => kept.push(keeping && !macros.defined(rest.trim())),
98 "if" => {
99 let holds = keeping && macros.evaluate(rest)? != 0;
100 kept.push(holds);
101 }
102 "endif" => {
103 kept.pop().ok_or("#endif without #if")?;
104 }
105 "define" if keeping => {
106 let (macro_name, value) = rest.split_once(' ').unwrap_or((rest, ""));
107 macros.define(macro_name, Cow::Borrowed(value));
108 }
109 "define" => {}
110 other => return Err(format!("#{other} in a GLSL template")),
111 }
112 }
113 if !kept.is_empty() {
114 return Err("a GLSL template leaves a conditional open".into());
115 }
116 let mut glsl = String::with_capacity(version.len() + 48 * self.macros.len() + code.len());
117 glsl.push_str(version);
118 for item in self.macros {
119 if let Some(value) = macros.values.get(item.name) {
120 let _ = writeln!(glsl, "#define {} {value}", item.name);
121 }
122 }
123 glsl.push_str(&code);
124 Ok(glsl)
125 }
126}
127
128fn macro_value(item: &GlslMacro, value: f64) -> Result<String, String> {
132 let unfit = || format!("{}: {value} is no {:?}", item.constant, item.kind);
133 Ok(match item.kind {
134 GlslMacroKind::Bool => format!("{}", u8::from(value != 0.0)),
135 GlslMacroKind::Sint => {
136 if value % 1.0 != 0.0
137 || !(f64::from(i32::MIN)..=f64::from(i32::MAX)).contains(&value)
138 {
139 return Err(unfit());
140 }
141 format!("{}", value as i32)
142 }
143 GlslMacroKind::Uint => {
146 if value % 1.0 != 0.0 || !(0.0..=f64::from(u32::MAX)).contains(&value) {
147 return Err(unfit());
148 }
149 let value = value as u32;
150 if value > i32::MAX as u32 {
151 format!("{value:#X}")
152 } else {
153 format!("{value}")
154 }
155 }
156 GlslMacroKind::Float => {
157 if !value.is_finite() {
158 return Err(unfit());
159 }
160 format!("{:?}", value as f32)
161 }
162 })
163}
164
165#[derive(Default)]
168struct Macros<'t> {
169 values: HashMap<&'t str, Cow<'t, str>>,
170 evaluated: HashMap<&'t str, i64>,
171}
172
173impl<'t> Macros<'t> {
174 fn define(&mut self, name: &'t str, value: Cow<'t, str>) {
176 self.values.entry(name).or_insert(value);
177 }
178
179 fn defined(&self, name: &str) -> bool {
180 self.values.contains_key(name)
181 }
182
183 fn evaluate(&mut self, text: &str) -> Result<i64, String> {
185 Evaluator {
186 values: &self.values,
187 evaluated: &mut self.evaluated,
188 }
189 .evaluate(text)
190 }
191}
192
193struct Evaluator<'a, 't> {
195 values: &'a HashMap<&'t str, Cow<'t, str>>,
196 evaluated: &'a mut HashMap<&'t str, i64>,
197}
198
199impl Evaluator<'_, '_> {
200 fn evaluate(&mut self, text: &str) -> Result<i64, String> {
201 let mut parser = Parser { text, at: 0 };
202 let value = parser.binary(self, 0)?;
203 parser.skip_space();
204 if parser.at != text.len() {
205 return Err(format!("`{text}` does not end at {}", parser.at));
206 }
207 Ok(value)
208 }
209
210 fn value(&mut self, name: &str) -> Result<i64, String> {
211 if let Some(&value) = self.evaluated.get(name) {
212 return Ok(value);
213 }
214 let values = self.values;
215 let (&key, text) = values
216 .get_key_value(name)
217 .ok_or_else(|| format!("`{name}` is not defined"))?;
218 let value = self.evaluate(text)?;
219 self.evaluated.insert(key, value);
220 Ok(value)
221 }
222}
223
224const LEVELS: [&[&str]; 10] = [
226 &["||"],
227 &["&&"],
228 &["|"],
229 &["^"],
230 &["&"],
231 &["==", "!="],
232 &["<=", ">=", "<", ">"],
233 &["<<", ">>"],
234 &["+", "-"],
235 &["*", "/", "%"],
236];
237
238struct Parser<'s> {
239 text: &'s str,
240 at: usize,
241}
242
243impl Parser<'_> {
244 fn skip_space(&mut self) {
245 while self.text[self.at..].starts_with([' ', '\t']) {
246 self.at += 1;
247 }
248 }
249
250 fn operator(&mut self, level: usize) -> Option<&'static str> {
252 self.skip_space();
253 let rest = &self.text[self.at..];
254 let found = LEVELS[level].iter().copied().find(|operator| {
255 rest.starts_with(operator)
256 && !(operator.len() == 1 && rest[1..].starts_with(*operator))
259 })?;
260 self.at += found.len();
261 Some(found)
262 }
263
264 fn binary(&mut self, macros: &mut Evaluator<'_, '_>, level: usize) -> Result<i64, String> {
265 if level == LEVELS.len() {
266 return self.unary(macros);
267 }
268 let mut value = self.binary(macros, level + 1)?;
269 while let Some(operator) = self.operator(level) {
270 let right = self.binary(macros, level + 1)?;
271 value = match operator {
272 "||" => i64::from(value != 0 || right != 0),
273 "&&" => i64::from(value != 0 && right != 0),
274 "|" => value | right,
275 "^" => value ^ right,
276 "&" => value & right,
277 "==" => i64::from(value == right),
278 "!=" => i64::from(value != right),
279 "<" => i64::from(value < right),
280 ">" => i64::from(value > right),
281 "<=" => i64::from(value <= right),
282 ">=" => i64::from(value >= right),
283 "<<" => value.wrapping_shl(right as u32),
284 ">>" => value.wrapping_shr(right as u32),
285 "+" => value.wrapping_add(right),
286 "-" => value.wrapping_sub(right),
287 "*" => value.wrapping_mul(right),
288 "/" => value.checked_div(right).ok_or("division by zero")?,
289 _ => value.checked_rem(right).ok_or("division by zero")?,
290 };
291 }
292 Ok(value)
293 }
294
295 fn unary(&mut self, macros: &mut Evaluator<'_, '_>) -> Result<i64, String> {
296 self.skip_space();
297 let rest = &self.text[self.at..];
298 let Some(first) = rest.chars().next() else {
299 return Err(format!("`{}` ends where a value goes", self.text));
300 };
301 match first {
302 '!' | '~' | '-' | '+' => {
303 self.at += 1;
304 let value = self.unary(macros)?;
305 Ok(match first {
306 '!' => i64::from(value == 0),
307 '~' => !value,
308 '-' => value.wrapping_neg(),
309 _ => value,
310 })
311 }
312 '(' => {
313 self.at += 1;
314 let value = self.binary(macros, 0)?;
315 self.skip_space();
316 if !self.text[self.at..].starts_with(')') {
317 return Err(format!("`{}` leaves a parenthesis open", self.text));
318 }
319 self.at += 1;
320 Ok(value)
321 }
322 _ => {
323 let length = rest
324 .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_'))
325 .unwrap_or(rest.len());
326 let word = &rest[..length];
327 self.at += length;
328 if first.is_ascii_digit() {
329 let (digits, radix) = match word.strip_prefix("0x").or_else(|| word.strip_prefix("0X")) {
330 Some(hex) => (hex, 16),
331 None => (word, 10),
332 };
333 i64::from_str_radix(digits.trim_end_matches(['u', 'U']), radix)
334 .map_err(|error| format!("`{word}`: {error}"))
335 } else if word.is_empty() {
336 Err(format!("`{}` has no value at {}", self.text, self.at))
337 } else {
338 macros.value(word)
339 }
340 }
341 }
342 }
343}