Expand description
Universal shader translator.
The central structure of the crate is Module
. A Module
contains:
-
Function
s, which have arguments, a return type, local variables, and a body, -
EntryPoint
s, which are specialized functions that can serve as the entry point for pipeline stages like vertex shading or fragment shading, -
Constant
s andGlobalVariable
s used byEntryPoint
s andFunction
s, and -
Type
s used by the above.
The body of an EntryPoint
or Function
is represented using two types:
-
An
Expression
produces a value, but has no side effects or control flow.Expressions
include variable references, unary and binary operators, and so on. -
A
Statement
can have side effects and structured control flow.Statement
s do not produce a value, other than by storing one in some designated place.Statements
include blocks, conditionals, and loops, but also operations that have side effects, like stores and function calls.
Statement
s form a tree, with pointers into the DAG of Expression
s.
Restricting side effects to statements simplifies analysis and code generation.
A Naga backend can generate code to evaluate an Expression
however and
whenever it pleases, as long as it is certain to observe the side effects of all
previously executed Statement
s.
Many Statement
variants use the Block
type, which is Vec<Statement>
,
with optional span info, representing a series of statements executed in order. The body of an
EntryPoint
s or Function
is a Block
, and Statement
has a
Block
variant.
If the clone
feature is enabled, Arena
, UniqueArena
, Type
, TypeInner
,
Constant
, Function
, EntryPoint
and Module
can be cloned.
Arenas
To improve translator performance and reduce memory usage, most structures are
stored in an Arena
. An Arena<T>
stores a series of T
values, indexed by
Handle<T>
values, which are just wrappers around integer indexes.
For example, a Function
’s expressions are stored in an Arena<Expression>
,
and compound expressions refer to their sub-expressions via Handle<Expression>
values. (When examining the serialized form of a Module
, note that the first
element of an Arena
has an index of 1, not 0.)
A UniqueArena
is just like an Arena
, except that it stores only a single
instance of each value. The value type must implement Eq
and Hash
. Like an
Arena
, inserting a value into a UniqueArena
returns a Handle
which can be
used to efficiently access the value, without a hash lookup. Inserting a value
multiple times returns the same Handle
.
If the span
feature is enabled, both Arena
and UniqueArena
can associate a
source code span with each element.
Function Calls
Naga’s representation of function calls is unusual. Most languages treat
function calls as expressions, but because calls may have side effects, Naga
represents them as a kind of statement, Statement::Call
. If the function
returns a value, a call statement designates a particular Expression::CallResult
expression to represent its return value, for use by subsequent statements and
expressions.
Expression
evaluation time
It is essential to know when an Expression
should be evaluated, because its
value may depend on previous Statement
s’ effects. But whereas the order of
execution for a tree of Statement
s is apparent from its structure, it is not
so clear for Expressions
, since an expression may be referred to by any number
of Statement
s and other Expression
s.
Naga’s rules for when Expression
s are evaluated are as follows:
-
Literal
,Constant
, andZeroValue
expressions are considered to be implicitly evaluated before execution begins. -
FunctionArgument
andLocalVariable
expressions are considered implicitly evaluated upon entry to the function to which they belong. Function arguments cannot be assigned to, andLocalVariable
expressions produce a pointer to the variable’s value (for use withLoad
andStore
). Neither varies while the function executes, so it suffices to consider these expressions evaluated once on entry. -
Similarly,
GlobalVariable
expressions are considered implicitly evaluated before execution begins, since their value does not change while code executes, for one of two reasons:-
Most
GlobalVariable
expressions produce a pointer to the variable’s value, for use withLoad
andStore
, asLocalVariable
expressions do. Although the variable’s value may change, its address does not. -
A
GlobalVariable
expression referring to a global in theAddressSpace::Handle
address space produces the value directly, not a pointer. Such global variables hold opaque types like shaders or images, and cannot be assigned to.
-
-
A
CallResult
expression that is theresult
of aStatement::Call
, representing the call’s return value, is evaluated when theCall
statement is executed. -
Similarly, an
AtomicResult
expression that is theresult
of anAtomic
statement, representing the result of the atomic operation, is evaluated when theAtomic
statement is executed. -
A
RayQueryProceedResult
expression, which is a boolean indicating if the ray query is finished, is evaluated when theRayQuery
statement whoseProceed::result
points to it is executed. -
All other expressions are evaluated when the (unique)
Statement::Emit
statement that covers them is executed.
Now, strictly speaking, not all Expression
variants actually care when they’re
evaluated. For example, you can evaluate a BinaryOperator::Add
expression
any time you like, as long as you give it the right operands. It’s really only a
very small set of expressions that are affected by timing:
-
Load
,ImageSample
, andImageLoad
expressions are influenced by stores to the variables or images they access, and must execute at the proper time relative to them. -
Derivative
expressions are sensitive to control flow uniformity: they must not be moved out of an area of uniform control flow into a non-uniform area. -
More generally, any expression that’s used by more than one other expression or statement should probably be evaluated only once, and then stored in a variable to be cited at each point of use.
Naga tries to help back ends handle all these cases correctly in a somewhat
circuitous way. The ModuleInfo
structure returned by Validator::validate
provides a reference count for each expression in each function in the module.
Naturally, any expression with a reference count of two or more deserves to be
evaluated and stored in a temporary variable at the point that the Emit
statement covering it is executed. But if we selectively lower the reference
count threshold to one for the sensitive expression types listed above, so
that we always generate a temporary variable and save their value, then the
same code that manages multiply referenced expressions will take care of
introducing temporaries for time-sensitive expressions as well. The
Expression::bake_ref_count
method (private to the back ends) is meant to help
with this.
Expression
scope
Each Expression
has a scope, which is the region of the function within
which it can be used by Statement
s and other Expression
s. It is a validation
error to use an Expression
outside its scope.
An expression’s scope is defined as follows:
-
The scope of a
Constant
,GlobalVariable
,FunctionArgument
orLocalVariable
expression covers the entireFunction
in which it occurs. -
The scope of an expression evaluated by an
Emit
statement covers the subsequent expressions in thatEmit
, the subsequent statements in theBlock
to which thatEmit
belongs (if any) and their sub-statements (if any). -
The
result
expression of aCall
orAtomic
statement has a scope covering the subsequent statements in theBlock
in which the statement occurs (if any) and their sub-statements (if any).
For example, this implies that an expression evaluated by some statement in a
nested Block
is not available in the Block
’s parents. Such a value would
need to be stored in a local variable to be carried upwards in the statement
tree.
Constant expressions
A Naga constant expression is one of the following Expression
variants, whose operands (if any) are also constant expressions:
Literal
Constant
, forConstant
s whoseoverride
isNone
ZeroValue
, for fixed-size typesCompose
Access
AccessIndex
Splat
Swizzle
Unary
Binary
Select
Relational
Math
As
A constant expression can be evaluated at module translation time.
Override expressions
A Naga override expression is the same as a constant expression,
except that it is also allowed to refer to Constant
s
whose override
is something other than None
.
An override expression can be evaluated at pipeline creation time.
Modules
- Backend functions that export shader
Module
s into binary and text formats. - Frontend parsers that consume binary and text shaders and load them into
Module
s. Module
processing functionality.- Shader validator.
Structs
- An arena holding some kind of component (e.g., type, constant, instruction, etc.) that can be referenced.
- Memory barrier flags.
- A code block is a vector of statements, with maybe a vector of spans.
- Constant value.
- Early fragment tests.
- The main function for a pipeline stage.
- A function defined in the module.
- A function argument.
- A function result.
- Variable defined at module level.
- A strongly typed reference to an arena item.
- Variable defined at function level.
- Shader module.
- A strongly typed range of handles.
- Pipeline binding information for global resources.
- Characteristics of a scalar type.
- A human-readable representation for a span, tailored for text source.
- A source code span, used for error reporting.
- Set of special types that can be optionally generated by the frontends.
- Flags describing an image.
- Member of a user-defined structure.
- A case for a switch statement.
- A data type declared in the module.
- An arena whose elements are guaranteed to be unique.
- Wrapper class for
Error
, augmenting it with a list ofSpanContext
s.
Enums
- Addressing space of variables.
- Size of an array.
- Function on an atomic value.
- Operation that can be applied on two values.
- Describes how an input/output variable is to be bound.
- Built-in inputs and outputs.
- Enables adjusting depth without disabling early Z.
- Axis on which to compute a derivative.
- Hint at which precision to compute a derivative.
- An expression that can be evaluated to obtain a value.
- Sub-class of the image type.
- The number of dimensions an image has.
- Type of an image query.
- The interpolation qualifier of a binding or struct field.
- Built-in shader function for math.
- Return types predeclared for the frexp, modf, and atomicCompareExchangeWeak built-in functions.
- An operation that a
RayQuery
statement applies to itsquery
operand. - Built-in shader function for testing relation between values.
- Sampling modifier to control the level of detail.
- The sampling qualifiers of a binding or struct field.
- Primitive type for a scalar.
- Stage of the programmable pipeline.
- Instructions which make up an executable block.
- Image storage format.
- The value of the switch case.
- Component selection for a vector swizzle.
- Enum with additional information, depending on the kind of type.
- Operation that can be applied on a single value.
- Number of components in a vector.
Constants
- Width of abstract types, in bytes.
- Width of a boolean type, in bytes.
Type Aliases
- Number of bytes per scalar.
- Hash map that is faster but not resilient to DoS attacks.
- Hash set that is faster but not resilient to DoS attacks.
- Insertion-order-preserving hash map (
IndexMap<K, V>
), but with the same hasher asFastHashMap<K, V>
(faster but not resilient to DoS attacks). - Insertion-order-preserving hash set (
IndexSet<K>
), but with the same hasher asFastHashSet<K>
(faster but not resilient to DoS attacks). - A source code span together with “context”, a user-readable description of what part of the error it refers to.