┏━━━━━━━━┓ ┏━━━━━━┓ ┃ SPIR━V ┃ ┃ WGSL ┃ ┗━━━━┃━━━┛ ┗━━━┃━━┛ ▼ ▼ ┏━━━━━━━━━┃━━━━━━━━━━━━━━━━━━━━━━━━━━━┃━━━━━━━━┓ ┃ ┃ Reader ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┏━━━━━━━┻━━━━━━┓ ┏━━━━━━┻━━━━━━┓ ┃ ┃ ┃ SPIRV-Reader ┃ ┃ WGSL-Reader ┃ ┃ ┃ ┗━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┛ ▼ ┏━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━┓ ┃ ProgramBuilder ┃ ┃ (mutable) ┃ ┏━━━━━━━━━━━━►┫ ┏━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━━━┓ ┃ ┃ ┃ ┃ AST ┃ ┃ Types ┃ ┃ Symbols ┃ ┃ ┃ ┃ ┗━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━━━┛ ┃ ┃ ┗━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┛ ┃ ▼ ┃ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐ ▲ ┆ Build ▼ ┆ ┏━━━┻━━━┓ ┆ ┏━━━━━━━━┻━━━━━━━━┓ ┆ ┃ Clone ┃ ┆ ┃ Resolver ┃ ┆ ┗━━━┳━━━┛ ┆ ┗━━━━━━━━━━━━━━━━━┛ ┆ ▲ └┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┃┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘ ┃ ▼ ┃ ┏━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━┓ ┃ ┃ Program ┃ ┃ ┃ (immutable) ┃ ┣━━━━━━◄┫ ┏━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━━━━┓ ┏━━━━━━━━━┓ ┃ ┃ ┃ ┃ AST ┃ ┃ Types ┃ ┃ Semantic ┃ ┃ Symbols ┃ ┃ ┃ ┃ ┗━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━━━━┛ ┗━━━━━━━━━┛ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┛ ▲ ▼ ┏━━━━━┻━━━━━┓ ┃ ┏━━━━━━━━━━━┓ ┃ Transform ┃◄━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━►┃ Inspector ┃ ┗━━━━━━━━━━━┛ ┃ ┗━━━━━━━━━━━┛ ▼ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Writer ┃ ┃ ┃ ┃ ┏━━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━━┓ ┏━━━━━━━━━━━━┓ ┃ ┃ ┃ SPIRV-Writer ┃ ┃ WGSL-Writer ┃ ┃ HLSL-Writer ┃ ┃ MSL-Writer ┃ ┃ ┃ ┗━━━━━━━┳━━━━━━┛ ┗━━━━━━┳━━━━━━┛ ┗━━━━━━┳━━━━━━┛ ┗━━━━━━┳━━━━━┛ ┃ ┗━━━━━━━━━┃━━━━━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━━━━━┃━━━━━━━━━━━━━━━━━━┃━━━━━━━┛ ▼ ▼ ▼ ▼ ┏━━━━┻━━━┓ ┏━━━┻━━┓ ┏━━━┻━━┓ ┏━━┻━━┓ ┃ SPIR-V ┃ ┃ WGSL ┃ ┃ HLSL ┃ ┃ MSL ┃ ┗━━━━━━━━┛ ┗━━━━━━┛ ┗━━━━━━┛ ┗━━━━━┛
Readers are responsible for parsing a shader program and populating a
ProgramBuilder with the parsed AST, type and symbol information.
The WGSL reader is a recursive descent parser. It closely follows the WGSL grammar in the naming of the parse methods.
ProgramBuilder is the primary interface to construct an immutable
Program. There are a number of methods exposed which make creating of the
Program simpler. A
ProgramBuilder can only be used once, and must be discarded after the
Program is constructed.
Program is built from the
ProgramBuilder to a new
Program object. When built, resolution is performed so the produced
Program will contain all the needed semantic information.
At any time before building the
ProgramBuilder::IsValid() may be called to ensure the AST is structurally correct. This checks that things like
if statements have a condition and body attached.
If further changes to the
Program are needed (say via a
Transform) then a new
ProgramBuilder can be produced by cloning the
Program into a new
ProgramBuilders are not part of the public Tint API.
The Abstract Syntax Tree is a directed acyclic graph of
ast::Nodes which encode the syntactic structure of the WGSL program.
The root of the AST is the
ast::Module class which holds each of the declared functions, variables and user defined types (type aliases and structures).
ast::Node represents a single part of the program's source, and so
ast::Nodes are not shared.
The AST does not perform any verification of its content. For example, the
ast::StrideAttribute node has numeric stride parameter, which is a count of the number of bytes from the start of one array element to the start of the next. The AST node itself does not constrain the set of stride values that you can set, aside from storing it as an unsigned integer.
Types are constructed during the Reader and resolution phases, and are held by the
ProgramBuilder. AST and semantic nodes can both reference types.
type::Type node uniquely represents a particular spelling of a WGSL type within the program, so you can compare
type::Type* pointers to check for equivalence of type expressions. For example, there is only one
type::Type node for the
i32 type, no matter how many times it is mentioned in the source program. However, if
MyI32 is a type alias for
i32, then they will have two different type nodes.
Semantic information is held by
sem::Nodes which describe the program at a higher / more abstract level than the AST. This includes information such as the resolved type of each expression, the resolved overload of a builtin function call, and the module scoped variables used by each function.
Semantic information is generated by the
Resolver when the
Program is built from a
sem::Info class holds a map of
sem::Nodes. This map is many-to-one - i.e. while a AST node might have a single corresponding semantic node, the reverse may not be true. For example: many
ast::IdentifierExpression nodes may map to a single
sem::Variable, and so the
sem::Variable does not have a single corresponding
ast::Nodes, semantic nodes may not necessarily form a directed acyclic graph, and the semantic graph may contain diamonds.
Symbols represent a unique string identifier in the source program. These string identifiers are transformed into symbols within the
During the Writer phase, symbols may be emitted as strings using a
Namer may output the symbol in any form that preserves the uniqueness of that symbol.
Resolver will automatically run when a
Program is built. A
Resolver creates the
Programs semantic information by analyzing the
Programs AST and type information.
Resolver will validate to make sure the generated
Program is semantically valid.
Program holds an immutable version of the information from the
ProgramBuilder along with semantic information generated by the
Program::IsValid() may be called to ensure the AST is structurally correct and semantically valid, and that the
Resolver did not report any errors.
Program is fully immutable, and is part of the public Tint API. The immutable nature of
Programs make these entirely safe to share between multiple threads without the use of synchronization primitives.
The inspectors job is to go through the
Program and pull out various pieces of information. The information may be used to pass information into the downstream compilers (things like specialization constants) or may be used to pass into transforms to update the AST before generating the resulting code.
Program to the inspector must be valid (pass validation).
There maybe various transforms we want to run over the
Program. This is for things like Vertex Pulling or Robust Buffer Access.
A transform operates by cloning the input
Program into a new
ProgramBuilder, applying the required changes, and then finally building and returning a new output
Program. As the resolver is always run when a
Program is built, Transforms will always emit a
Program with semantic information.
Program to a transform must be valid (pass validation). If the input
Program of a transform is valid then the transform must guarantee that the output program is also valid.
A writer is responsible for writing the
Program in the target shader language.
Program to a writer must be valid (pass validation).