naga/front/glsl/
variables.rs

1use alloc::{format, string::String, vec::Vec};
2
3use super::{
4    ast::*,
5    context::{Context, ExprPos},
6    error::{Error, ErrorKind},
7    Frontend, Result, Span,
8};
9use crate::{
10    AddressSpace, Binding, BuiltIn, Constant, Expression, GlobalVariable, Handle, Interpolation,
11    LocalVariable, ResourceBinding, Scalar, ScalarKind, ShaderStage, SwizzleComponent, Type,
12    TypeInner, VectorSize,
13};
14
15pub struct VarDeclaration<'a, 'key> {
16    pub qualifiers: &'a mut TypeQualifiers<'key>,
17    pub ty: Handle<Type>,
18    pub name: Option<String>,
19    pub init: Option<Handle<Expression>>,
20    pub meta: Span,
21}
22
23/// Information about a builtin used in [`add_builtin`](Frontend::add_builtin).
24struct BuiltInData {
25    /// The type of the builtin.
26    inner: TypeInner,
27    /// The associated builtin class.
28    builtin: BuiltIn,
29    /// Whether the builtin can be written to or not.
30    mutable: bool,
31    /// The storage used for the builtin.
32    storage: StorageQualifier,
33}
34
35pub enum GlobalOrConstant {
36    Global(Handle<GlobalVariable>),
37    Constant(Handle<Constant>),
38}
39
40impl Frontend {
41    /// Adds a builtin and returns a variable reference to it
42    fn add_builtin(
43        &mut self,
44        ctx: &mut Context,
45        name: &str,
46        data: BuiltInData,
47        meta: Span,
48    ) -> Result<Option<VariableReference>> {
49        let ty = ctx.module.types.insert(
50            Type {
51                name: None,
52                inner: data.inner,
53            },
54            meta,
55        );
56
57        let handle = ctx.module.global_variables.append(
58            GlobalVariable {
59                name: Some(name.into()),
60                space: AddressSpace::Private,
61                binding: None,
62                ty,
63                init: None,
64            },
65            meta,
66        );
67
68        let idx = self.entry_args.len();
69        self.entry_args.push(EntryArg {
70            name: Some(name.into()),
71            binding: Binding::BuiltIn(data.builtin),
72            handle,
73            storage: data.storage,
74        });
75
76        self.global_variables.push((
77            name.into(),
78            GlobalLookup {
79                kind: GlobalLookupKind::Variable(handle),
80                entry_arg: Some(idx),
81                mutable: data.mutable,
82            },
83        ));
84
85        let expr = ctx.add_expression(Expression::GlobalVariable(handle), meta)?;
86
87        let var = VariableReference {
88            expr,
89            load: true,
90            mutable: data.mutable,
91            constant: None,
92            entry_arg: Some(idx),
93        };
94
95        ctx.symbol_table.add_root(name.into(), var.clone());
96
97        Ok(Some(var))
98    }
99
100    pub(crate) fn lookup_variable(
101        &mut self,
102        ctx: &mut Context,
103        name: &str,
104        meta: Span,
105    ) -> Result<Option<VariableReference>> {
106        if let Some(var) = ctx.symbol_table.lookup(name).cloned() {
107            return Ok(Some(var));
108        }
109
110        let data = match name {
111            "gl_Position" => BuiltInData {
112                inner: TypeInner::Vector {
113                    size: VectorSize::Quad,
114                    scalar: Scalar::F32,
115                },
116                builtin: BuiltIn::Position { invariant: false },
117                mutable: true,
118                storage: StorageQualifier::Output,
119            },
120            "gl_FragCoord" => BuiltInData {
121                inner: TypeInner::Vector {
122                    size: VectorSize::Quad,
123                    scalar: Scalar::F32,
124                },
125                builtin: BuiltIn::Position { invariant: false },
126                mutable: false,
127                storage: StorageQualifier::Input,
128            },
129            "gl_PointCoord" => BuiltInData {
130                inner: TypeInner::Vector {
131                    size: VectorSize::Bi,
132                    scalar: Scalar::F32,
133                },
134                builtin: BuiltIn::PointCoord,
135                mutable: false,
136                storage: StorageQualifier::Input,
137            },
138            "gl_GlobalInvocationID"
139            | "gl_NumWorkGroups"
140            | "gl_WorkGroupSize"
141            | "gl_WorkGroupID"
142            | "gl_LocalInvocationID" => BuiltInData {
143                inner: TypeInner::Vector {
144                    size: VectorSize::Tri,
145                    scalar: Scalar::U32,
146                },
147                builtin: match name {
148                    "gl_GlobalInvocationID" => BuiltIn::GlobalInvocationId,
149                    "gl_NumWorkGroups" => BuiltIn::NumWorkGroups,
150                    "gl_WorkGroupSize" => BuiltIn::WorkGroupSize,
151                    "gl_WorkGroupID" => BuiltIn::WorkGroupId,
152                    "gl_LocalInvocationID" => BuiltIn::LocalInvocationId,
153                    _ => unreachable!(),
154                },
155                mutable: false,
156                storage: StorageQualifier::Input,
157            },
158            "gl_FrontFacing" => BuiltInData {
159                inner: TypeInner::Scalar(Scalar::BOOL),
160                builtin: BuiltIn::FrontFacing,
161                mutable: false,
162                storage: StorageQualifier::Input,
163            },
164            "gl_PointSize" | "gl_FragDepth" => BuiltInData {
165                inner: TypeInner::Scalar(Scalar::F32),
166                builtin: match name {
167                    "gl_PointSize" => BuiltIn::PointSize,
168                    "gl_FragDepth" => BuiltIn::FragDepth,
169                    _ => unreachable!(),
170                },
171                mutable: true,
172                storage: StorageQualifier::Output,
173            },
174            "gl_ClipDistance" | "gl_CullDistance" => {
175                let base = ctx.module.types.insert(
176                    Type {
177                        name: None,
178                        inner: TypeInner::Scalar(Scalar::F32),
179                    },
180                    meta,
181                );
182
183                BuiltInData {
184                    inner: TypeInner::Array {
185                        base,
186                        size: crate::ArraySize::Dynamic,
187                        stride: 4,
188                    },
189                    builtin: match name {
190                        "gl_ClipDistance" => BuiltIn::ClipDistance,
191                        "gl_CullDistance" => BuiltIn::CullDistance,
192                        _ => unreachable!(),
193                    },
194                    mutable: self.meta.stage == ShaderStage::Vertex,
195                    storage: StorageQualifier::Output,
196                }
197            }
198            _ => {
199                let builtin = match name {
200                    "gl_BaseVertex" => BuiltIn::BaseVertex,
201                    "gl_BaseInstance" => BuiltIn::BaseInstance,
202                    "gl_PrimitiveID" => BuiltIn::PrimitiveIndex,
203                    "gl_InstanceIndex" => BuiltIn::InstanceIndex,
204                    "gl_VertexIndex" => BuiltIn::VertexIndex,
205                    "gl_SampleID" => BuiltIn::SampleIndex,
206                    "gl_LocalInvocationIndex" => BuiltIn::LocalInvocationIndex,
207                    "gl_DrawID" => BuiltIn::DrawID,
208                    _ => return Ok(None),
209                };
210
211                BuiltInData {
212                    inner: TypeInner::Scalar(Scalar::U32),
213                    builtin,
214                    mutable: false,
215                    storage: StorageQualifier::Input,
216                }
217            }
218        };
219
220        self.add_builtin(ctx, name, data, meta)
221    }
222
223    pub(crate) fn make_variable_invariant(
224        &mut self,
225        ctx: &mut Context,
226        name: &str,
227        meta: Span,
228    ) -> Result<()> {
229        if let Some(var) = self.lookup_variable(ctx, name, meta)? {
230            if let Some(index) = var.entry_arg {
231                if let Binding::BuiltIn(BuiltIn::Position { ref mut invariant }) =
232                    self.entry_args[index].binding
233                {
234                    *invariant = true;
235                }
236            }
237        }
238        Ok(())
239    }
240
241    pub(crate) fn field_selection(
242        &mut self,
243        ctx: &mut Context,
244        pos: ExprPos,
245        expression: Handle<Expression>,
246        name: &str,
247        meta: Span,
248    ) -> Result<Handle<Expression>> {
249        let (ty, is_pointer) = match *ctx.resolve_type(expression, meta)? {
250            TypeInner::Pointer { base, .. } => (&ctx.module.types[base].inner, true),
251            ref ty => (ty, false),
252        };
253        match *ty {
254            TypeInner::Struct { ref members, .. } => {
255                let index = members
256                    .iter()
257                    .position(|m| m.name == Some(name.into()))
258                    .ok_or_else(|| Error {
259                        kind: ErrorKind::UnknownField(name.into()),
260                        meta,
261                    })?;
262                let pointer = ctx.add_expression(
263                    Expression::AccessIndex {
264                        base: expression,
265                        index: index as u32,
266                    },
267                    meta,
268                )?;
269
270                Ok(match pos {
271                    ExprPos::Rhs if is_pointer => {
272                        ctx.add_expression(Expression::Load { pointer }, meta)?
273                    }
274                    _ => pointer,
275                })
276            }
277            // swizzles (xyzw, rgba, stpq)
278            TypeInner::Vector { size, .. } => {
279                let check_swizzle_components = |comps: &str| {
280                    name.chars()
281                        .map(|c| {
282                            comps
283                                .find(c)
284                                .filter(|i| *i < size as usize)
285                                .map(|i| SwizzleComponent::from_index(i as u32))
286                        })
287                        .collect::<Option<Vec<SwizzleComponent>>>()
288                };
289
290                let components = check_swizzle_components("xyzw")
291                    .or_else(|| check_swizzle_components("rgba"))
292                    .or_else(|| check_swizzle_components("stpq"));
293
294                if let Some(components) = components {
295                    if let ExprPos::Lhs = pos {
296                        let not_unique = (1..components.len())
297                            .any(|i| components[i..].contains(&components[i - 1]));
298                        if not_unique {
299                            self.errors.push(Error {
300                                kind: ErrorKind::SemanticError(
301                                    format!(
302                                        concat!(
303                                            "swizzle cannot have duplicate components in ",
304                                            "left-hand-side expression for \"{:?}\""
305                                        ),
306                                        name
307                                    )
308                                    .into(),
309                                ),
310                                meta,
311                            })
312                        }
313                    }
314
315                    let mut pattern = [SwizzleComponent::X; 4];
316                    for (pat, component) in pattern.iter_mut().zip(&components) {
317                        *pat = *component;
318                    }
319
320                    // flatten nested swizzles (vec.zyx.xy.x => vec.z)
321                    let mut expression = expression;
322                    if let Expression::Swizzle {
323                        size: _,
324                        vector,
325                        pattern: ref src_pattern,
326                    } = ctx[expression]
327                    {
328                        expression = vector;
329                        for pat in &mut pattern {
330                            *pat = src_pattern[pat.index() as usize];
331                        }
332                    }
333
334                    let size = match components.len() {
335                        // Swizzles with just one component are accesses and not swizzles
336                        1 => {
337                            match pos {
338                                // If the position is in the right hand side and the base
339                                // vector is a pointer, load it, otherwise the swizzle would
340                                // produce a pointer
341                                ExprPos::Rhs if is_pointer => {
342                                    expression = ctx.add_expression(
343                                        Expression::Load {
344                                            pointer: expression,
345                                        },
346                                        meta,
347                                    )?;
348                                }
349                                _ => {}
350                            };
351                            return ctx.add_expression(
352                                Expression::AccessIndex {
353                                    base: expression,
354                                    index: pattern[0].index(),
355                                },
356                                meta,
357                            );
358                        }
359                        2 => VectorSize::Bi,
360                        3 => VectorSize::Tri,
361                        4 => VectorSize::Quad,
362                        _ => {
363                            self.errors.push(Error {
364                                kind: ErrorKind::SemanticError(
365                                    format!("Bad swizzle size for \"{name:?}\"").into(),
366                                ),
367                                meta,
368                            });
369
370                            VectorSize::Quad
371                        }
372                    };
373
374                    if is_pointer {
375                        // NOTE: for lhs expression, this extra load ends up as an unused expr, because the
376                        // assignment will extract the pointer and use it directly anyway. Unfortunately we
377                        // need it for validation to pass, as swizzles cannot operate on pointer values.
378                        expression = ctx.add_expression(
379                            Expression::Load {
380                                pointer: expression,
381                            },
382                            meta,
383                        )?;
384                    }
385
386                    Ok(ctx.add_expression(
387                        Expression::Swizzle {
388                            size,
389                            vector: expression,
390                            pattern,
391                        },
392                        meta,
393                    )?)
394                } else {
395                    Err(Error {
396                        kind: ErrorKind::SemanticError(
397                            format!("Invalid swizzle for vector \"{name}\"").into(),
398                        ),
399                        meta,
400                    })
401                }
402            }
403            _ => Err(Error {
404                kind: ErrorKind::SemanticError(
405                    format!("Can't lookup field on this type \"{name}\"").into(),
406                ),
407                meta,
408            }),
409        }
410    }
411
412    pub(crate) fn add_global_var(
413        &mut self,
414        ctx: &mut Context,
415        VarDeclaration {
416            qualifiers,
417            mut ty,
418            name,
419            init,
420            meta,
421        }: VarDeclaration,
422    ) -> Result<GlobalOrConstant> {
423        let storage = qualifiers.storage.0;
424        let (ret, lookup) = match storage {
425            StorageQualifier::Input | StorageQualifier::Output => {
426                let input = storage == StorageQualifier::Input;
427                // TODO: glslang seems to use a counter for variables without
428                // explicit location (even if that causes collisions)
429                let location = qualifiers
430                    .uint_layout_qualifier("location", &mut self.errors)
431                    .unwrap_or(0);
432                let interpolation = qualifiers.interpolation.take().map(|(i, _)| i).or_else(|| {
433                    let kind = ctx.module.types[ty].inner.scalar_kind()?;
434                    Some(match kind {
435                        ScalarKind::Float => Interpolation::Perspective,
436                        _ => Interpolation::Flat,
437                    })
438                });
439                let sampling = qualifiers.sampling.take().map(|(s, _)| s);
440
441                let handle = ctx.module.global_variables.append(
442                    GlobalVariable {
443                        name: name.clone(),
444                        space: AddressSpace::Private,
445                        binding: None,
446                        ty,
447                        init,
448                    },
449                    meta,
450                );
451
452                let blend_src = qualifiers
453                    .layout_qualifiers
454                    .remove(&QualifierKey::Index)
455                    .and_then(|(value, _span)| match value {
456                        QualifierValue::Uint(index) => Some(index),
457                        _ => None,
458                    });
459
460                let idx = self.entry_args.len();
461                self.entry_args.push(EntryArg {
462                    name: name.clone(),
463                    binding: Binding::Location {
464                        location,
465                        interpolation,
466                        sampling,
467                        blend_src,
468                    },
469                    handle,
470                    storage,
471                });
472
473                let lookup = GlobalLookup {
474                    kind: GlobalLookupKind::Variable(handle),
475                    entry_arg: Some(idx),
476                    mutable: !input,
477                };
478
479                (GlobalOrConstant::Global(handle), lookup)
480            }
481            StorageQualifier::Const => {
482                let init = init.ok_or_else(|| Error {
483                    kind: ErrorKind::SemanticError("const values must have an initializer".into()),
484                    meta,
485                })?;
486
487                let constant = Constant {
488                    name: name.clone(),
489                    ty,
490                    init,
491                };
492                let handle = ctx.module.constants.append(constant, meta);
493
494                let lookup = GlobalLookup {
495                    kind: GlobalLookupKind::Constant(handle, ty),
496                    entry_arg: None,
497                    mutable: false,
498                };
499
500                (GlobalOrConstant::Constant(handle), lookup)
501            }
502            StorageQualifier::AddressSpace(mut space) => {
503                match space {
504                    AddressSpace::Storage { ref mut access } => {
505                        if let Some((allowed_access, _)) = qualifiers.storage_access.take() {
506                            *access = allowed_access;
507                        }
508                    }
509                    AddressSpace::Uniform => match ctx.module.types[ty].inner {
510                        TypeInner::Image {
511                            class,
512                            dim,
513                            arrayed,
514                        } => {
515                            if let crate::ImageClass::Storage {
516                                mut access,
517                                mut format,
518                            } = class
519                            {
520                                if let Some((allowed_access, _)) = qualifiers.storage_access.take()
521                                {
522                                    access = allowed_access;
523                                }
524
525                                match qualifiers.layout_qualifiers.remove(&QualifierKey::Format) {
526                                    Some((QualifierValue::Format(f), _)) => format = f,
527                                    // TODO: glsl supports images without format qualifier
528                                    // if they are `writeonly`
529                                    None => self.errors.push(Error {
530                                        kind: ErrorKind::SemanticError(
531                                            "image types require a format layout qualifier".into(),
532                                        ),
533                                        meta,
534                                    }),
535                                    _ => unreachable!(),
536                                }
537
538                                ty = ctx.module.types.insert(
539                                    Type {
540                                        name: None,
541                                        inner: TypeInner::Image {
542                                            dim,
543                                            arrayed,
544                                            class: crate::ImageClass::Storage { format, access },
545                                        },
546                                    },
547                                    meta,
548                                );
549                            }
550
551                            space = AddressSpace::Handle
552                        }
553                        TypeInner::Sampler { .. } => space = AddressSpace::Handle,
554                        _ => {
555                            if qualifiers.none_layout_qualifier("push_constant", &mut self.errors) {
556                                space = AddressSpace::PushConstant
557                            }
558                        }
559                    },
560                    AddressSpace::Function => space = AddressSpace::Private,
561                    _ => {}
562                };
563
564                let binding = match space {
565                    AddressSpace::Uniform | AddressSpace::Storage { .. } | AddressSpace::Handle => {
566                        let binding = qualifiers.uint_layout_qualifier("binding", &mut self.errors);
567                        if binding.is_none() {
568                            self.errors.push(Error {
569                                kind: ErrorKind::SemanticError(
570                                    "uniform/buffer blocks require layout(binding=X)".into(),
571                                ),
572                                meta,
573                            });
574                        }
575                        let set = qualifiers.uint_layout_qualifier("set", &mut self.errors);
576                        binding.map(|binding| ResourceBinding {
577                            group: set.unwrap_or(0),
578                            binding,
579                        })
580                    }
581                    _ => None,
582                };
583
584                let handle = ctx.module.global_variables.append(
585                    GlobalVariable {
586                        name: name.clone(),
587                        space,
588                        binding,
589                        ty,
590                        init,
591                    },
592                    meta,
593                );
594
595                let lookup = GlobalLookup {
596                    kind: GlobalLookupKind::Variable(handle),
597                    entry_arg: None,
598                    mutable: true,
599                };
600
601                (GlobalOrConstant::Global(handle), lookup)
602            }
603        };
604
605        if let Some(name) = name {
606            ctx.add_global(&name, lookup)?;
607
608            self.global_variables.push((name, lookup));
609        }
610
611        qualifiers.unused_errors(&mut self.errors);
612
613        Ok(ret)
614    }
615
616    pub(crate) fn add_local_var(
617        &mut self,
618        ctx: &mut Context,
619        decl: VarDeclaration,
620    ) -> Result<Handle<Expression>> {
621        let storage = decl.qualifiers.storage;
622        let mutable = match storage.0 {
623            StorageQualifier::AddressSpace(AddressSpace::Function) => true,
624            StorageQualifier::Const => false,
625            _ => {
626                self.errors.push(Error {
627                    kind: ErrorKind::SemanticError("Locals cannot have a storage qualifier".into()),
628                    meta: storage.1,
629                });
630                true
631            }
632        };
633
634        let handle = ctx.locals.append(
635            LocalVariable {
636                name: decl.name.clone(),
637                ty: decl.ty,
638                init: decl.init,
639            },
640            decl.meta,
641        );
642        let expr = ctx.add_expression(Expression::LocalVariable(handle), decl.meta)?;
643
644        if let Some(name) = decl.name {
645            let maybe_var = ctx.add_local_var(name.clone(), expr, mutable);
646
647            if maybe_var.is_some() {
648                self.errors.push(Error {
649                    kind: ErrorKind::VariableAlreadyDeclared(name),
650                    meta: decl.meta,
651                })
652            }
653        }
654
655        decl.qualifiers.unused_errors(&mut self.errors);
656
657        Ok(expr)
658    }
659}