WebGPU Shading Language

Editor’s Draft,

Issue Tracking:
GitHub
Inline In Spec
Editors:
(Google)
(Apple Inc.)
Participate:
File an issue (open issues)

Abstract

Shading language for WebGPU.

Status of this document

This specification was published by the GPU for the Web Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

1. Introduction

[[location 0]] var<out> gl_FragColor : vec4<f32>;
fn main() -> void {
    gl_FragColor = vec4<f32>(0.4, 0.4, 0.8, 1.0);
    return;
}
entry_point fragment = main;

1.1. Goals

2. Formal Type Definitions

Note: For the syntax of declaring types in WGSL please see the § 4 Grammar.

Programs calculate values. Each value in WGSL belongs to exactly one type. A type is a set of (mathematical) values.

We distinguish between the concept of a type and the syntax in WGSL to denote that type. In many cases the spelling of a type in this document is the same as its WGSL syntax. The spelling is different for structure types, or types containing structures.

2.1. Void type

Type Category Description
void Void No value.

The void type contains no values. It is used where a type is required by the language but where no values are produced or consumed. For example, it is used for the return type of a function which does not produce a value.

2.2. Scalar Types

Type Category Description
bool Boolean Values are true or false
i32 Numeric scalar 32 bit signed integer, two’s complement representation
u32 Numeric scalar 32 bit unsigned integer
f32 Numeric scalar 32 bit IEEE 754 floating point number, including infinities and NaNs

A numeric scalar type is any of the scalar types except bool.

2.3. Vector Types

Type Description
vecN<T> Vector of N elements of type T. N must be in {2, 3, 4} and T must be one of the § 2.2 Scalar Types. We say T is the component type of the vector

A vector type is a numeric vector type if its component type is a numeric scalar.

EXAMPLE: Vector
vec2<f32>  # is a vector of two f32s.

2.4. Matrix Types

Type Description
matNxM<T> Matrix of N columns and M rows, where N and M are both in {2, 3, 4}. T must be f32.
EXAMPLE: Matrix
mat2x3<f32>  # is a 2 column, 3 row matrix of 32-bit floats.

2.5. Array Types

Type Description
array<E,N> An N-element array of elements of type E.
array<E> A runtime-sized array of elements of type E, also known as a runtime array. These may only appear in specific contexts.

(dneto): Complete description of Array<E,N>

(dneto): the last element of a struct defining the contents of a storage buffer.

2.6. Structure Types

Type Description
struct<T1,...,Tn> An ordered tuple of N members of types T1 through Tn, with N being an integer greater than 0.
EXAMPLE: Structure
struct Data {
  a : i32;
  b : vec2<f32>;
}

2.7. Storable types

The following types are storable:

2.7.1. Zero values

Each storable type T has a unique zero value, written in WGSL as the type followed by an empty pair of parentheses: T ().

The zero values are as follows:

EXAMPLE: Zero-valued vectors
vec2<f32>()                 # The zero-valued vector of two f32 elements.
vec2<f32>(0.0, 0.0)         # The same value, written explicitly.

vec3<i32>()                 # The zero-valued vector of four i32 elements.
vec3<i32>(0, 0, 0)          # The same value, written explicitly.
EXAMPLE: Zero-valued arrays
array<bool,2>()               # The zero-valued array of two booleans.
array<bool,2>(false, false)   # The same value, written explicitly.
EXAMPLE: Zero-valued structures
struct Student {
  grade : i32;
  GPA : f32;
  attendance : array<bool,4>;
};

# The zero value for Student
Student()

# The same value, written explicitly.
Student(0, 0.0, array<bool,4>(false,false,false,false))

# The same value, written with zero-valued members.
Student(i32(), f32(), array<bool,4>())

2.8. Host-shareable types

The following types are host-shareable:

2.9. Pointer Types

Type Description
ptr<SC,T> Pointer (or reference) to storage in § 4.16 Storage Classes SC which can hold a value of the § 2.7 Storable types T. Here, T is the known as the pointee type.

Note: We’ve described a SPIR-V logical pointer type.

Note: Pointers are not storable.

EXAMPLE: Pointer
ptr<storage_buffer, i32>
ptr<private, array<i32, 12>>

2.10. Composite types

A type is composite if its values have a well-defined internal structure of typed components.

The following types are composite types:

WGSL has operations for:

2.11. Typed storage

In WGSL, a value of § 2.7 Storable types may be stored in memory, for later retrieval.

A pointer value P supports the following operations:

P.Write(V) Place a value V into the referenced storage. V’s type must match P’s pointee type.
P.Read() An evaluation yielding the value currently in the P’s referenced storage. The result type is P’s pointee type.
P.Subaccess(K) Valid for pointers with a composite pointee type where K must evaluate to an integer between 0 and one less than the number of components in P’s pointee type. The subaccess evaluation yields a pointer to the storage for the K’th component within P’s referenced storage, using zero-based indexing. If P’s storage class is SC, and the K’th member of P’s pointee type is of type T, then the result type is ptr<SC,T>.

Note: Assignment of swizzled values is not permitted (SubaccessSwizzle).
e.g. vec4<i32> v; v.xz = vec2<i32>(0, 1); is not allowed.

2.12. Pointer evaluation

A pointer may appear in exactly the following contexts

Indexing A subaccessing evaluation
  • E.g. a[12]

    • If a is a pointer to an array, this evaluates to a.Subaccess(12)

  • E.g. s.foo

    • If s is a pointer to a structure of type S, k is the index of the foo element of S, this evaluates to s.Subaccess(k)

Assigning (L-Value) On the left hand side of an assignment operation, and the right hand side matches the pointee type of the pointer.
  • E.g. v = 12; assuming prior declaration var v : i32

Copying On the right hand side of a const-declaration, and the type of the const-declaration matches the pointer type.
  • E.g. const v2 : ptr<private,i32> = v; assuming prior declaration var<private> v:i32

Parameter Used in a function call, where the function’s parameter type matches the pointer type.
Reading (R-Value) Any other context. Evaluates to P.Read(), yielding a value of P’s pointee type.

2.13. Variables

A variable is a named reference to storage that can contain a value of a particular storable type.

Two types are associated with a variable: its store type (the type of value that may be placed in the referenced storage) and its reference type (the type of the variable itself). If a variable has store type T and storage class S, then its reference type is pointer-to-T-in-S.

A variable declaration

Two variables with overlapping lifetimes must not have overlapping storage.

When a variable is created, its storage contains an initial value as follows:

(dneto) It feels like this needs some reorganization. Perhaps "Evaluation and Execution" between § 2 Formal Type Definitions and § 4 Grammar.

Consider the following snippet of WGSL:
var i: i32;         # Initial value is 0.  Not recommended style.
loop {
  var twice: i32 = 2 * i;   # Re-evaluated each iteration.
  i = i + 1;
  break if (i == 5);
}

The loop body will execute five times. Variable i will take on values 0, 1, 2, 3, 4, 5, and variable twice will take on values 0, 2, 4, 6, 8.

Consider the following snippet of WGSL:
var x : f32 = 1.0;
const y = x * x + x + 1;

Because x is a variable, all accesses to it turn into load and store operations. If this snippet was compiled to SPIR-V, it would be represented as

%temp_1 = OpLoad %float %x
%temp_2 = OpLoad %float %x
%temp_3 = OpFMul %float %temp_1 %temp_2
%temp_4 = OpLoad %float %x
%temp_5 = OpFAdd %float %temp_3 %temp_4
%y      = OpFAdd %float %temp_5 %one

However, it is expected that either the browser or the driver optimizes this intermediate representation such that the redundant loads are eliminated.

2.14. Vector Accessors

Accessing members of a vector can be done either using array subscripting (e.g. a[2]) or using a sequence of convenience names, each mapping to an element of the source vector.

The convenience names are accessed using the . notation. (e.g. color.bgra).

NOTE: the convenience letterings can not be mixed. (i.e. you can not use rybw).

Using a convenience letter, or array subscript, which accesses an element past the end of the vector is an error.

The convenience letterings can be applied in any order, including duplicating letters as needed. You can provide 1 to 4 letters when extracing components from a vector. Providing more then 4 letters is an error.

The result type depends on the number of letters provided. Assuming a vec4<f32>

Accessor Result type
r f32
rg vec2<f32>
rgb vec3<f32>
rgba vec4<f32>
var a : vec3<f32> = vec3<f32>(1., 2., 3.);
var b : f32 = a.y;          # b = 2.0
var c : vec2<f32> = a.bb;   # c = (3.0, 3.0)
var d : vec3<f32> = a.zyx;  # d = (3.0, 2.0, 1.0)
var e : f32 = a[1];         # e = 2.0

3. Textures

3.1. Texture Types

3.1.1. Sampled Texture

texture_sampled_1d<type>
  %1 = OpTypeImage %type 1D 0 0 0 1 Unknown

texture_sampled_1d_array<type>
  %1 = OpTypeImage %type 1D 0 1 0 1 Unknown

texture_sampled_2d<type>
  %1 = OpTypeImage %type 2D 0 0 0 1 Unknown

texture_sampled_2d_array<type>
  %1 = OpTypeImage %type 2D 0 1 0 1 Unknown

texture_sampled_3d<type>
  %1 = OpTypeImage %type 3D 0 0 0 1 Unknown

texture_sampled_2d_ms<type>
  %1 = OpTypeImage %type 2D 0 0 1 1 Unknown

texture_sampled_2d_ms_array<type>
  %1 = OpTypeImage %type 2D 0 1 1 1 Unknown

texture_sampled_cube<type>
  %1 = OpTypeImage %type Cube 0 0 0 1 Unknown

texture_sampled_cube_array<type>
  %1 = OpTypeImage %type Cube 0 1 0 1 Unknown

3.1.2. Read-only Storage Texture

texture_ro_1d<type, image_storage_type>
  %1 = OpTypeImage %type 1D 0 0 0 2 image_storage_type

texture_ro_1d_array<type, image_storage_type>
  %1 = OpTypeImage %type 1D 0 1 0 2 image_storage_type

texture_ro_2d<type, image_storage_type>
  %1 = OpTypeImage %type 2D 0 0 0 2 image_storage_type

texture_ro_2d_array<type, image_storage_type>
  %1 = OpTypeImage %type 2D 0 1 0 2 image_storage_type

texture_ro_3d<type, image_storage_type>
  %1 = OpTypeImage %type 3D 0 0 0 2 image_storage_type

3.1.3. Write-only Storage Texture

texture_wo_1d<image_storage_type>
  %1 = OpTypeImage %void 1D 0 0 0 2 image_storage_type

texture_wo_1d_array<image_storage_type>
  %1 = OpTypeImage %void 1D 0 1 0 2 image_storage_type

texture_wo_2d<image_storage_type>
  %1 = OpTypeImage %void 2D 0 0 0 2 image_storage_type

texture_wo_2d_array<image_storage_type>
  %1 = OpTypeImage %void 2D 0 1 0 2 image_storage_type

texture_wo_3d<image_storage_type>
  %1 = OpTypeImage %void 3D 0 0 0 2 image_storage_type

3.1.4. Depth Texture

texture_depth_2d
  %1 = OpTypeImage %f32 2D 1 0 0 1 Unknown

texture_depth_2d_array
  %1 = OpTypeImage %f32 2D 1 1 0 1 Unknown

texture_depth_cube
  %1 = OpTypeImage %f32 Cube 1 0 0 1 Unknown

texture_depth_cube_array
  %1 = OpTypeImage %f32 Cube 1 1 0 1 Unknown

3.1.5. Samplers

sampler
  OpTypeSampler

sampler_comparison
  OpTypeSampler

4. Grammar

4.1. Scoping

A declaration introduces a name, given by an identifier token. Scoping is the set of rules determining where that name may be used, in relation to the position of the declaration in the program. If a name may be used at a particular point in the program, then we say it is in scope.

(dneto) also lifetime.

There are multiple levels of scoping depending on how and where things are declared.

A declaration must not introduce a name when that name is already in scope at the start of the declaration. That is, shadow names are not allowed in WGSL.

4.1.1. Module Scope

A variable or constant declared outside a function is at module scope. The name is available for use immediately after its declaration statement, until the end of the program.

A function declaration is always at module scope. The function name is available for use after its declaration, until the end of the program.

4.1.2. Function Scope

The names in the parameter list of a function definition are available for use in the body of the function. During a particular function evaluation, the parameter names denote the values specified to the function call expression or statement which initiated the function evaluation; the names and values are associated by position.

A variable or constant declared in a declaration statement in a function body is in function scope. The name is available for use immedately after its declaration statement, and until the end of the brace-delimited list of statements immediately enclosing the declaration.

A variable or constant declared in the first clause of a for statement is available for use in the second and third clauses and in the body of the for statement.

4.2. Comments

Comments begin with a # and continue to the end of the current line. There are no multi-line comments.

4.3. Precedence

(dsinclair) Write out precedence rules. Matches c and glsl rules ....

4.4. Type Promotions

There are no implicit type promotions in WGSL. If you want to convert between types you must use the cast syntax to do it.
var e : f32 = 3;    # error: literal is the wrong type

var f : f32 = 1.0;

var t : i32 = i32(f);

The non-promotion extends to vector classes as well. There are no overrides to shorten vector declarations based on the type or number of elements provided. If you want vec4<f32> you must provide 4 float values in the constructor.

4.5. Identifiers and Numeric Literals

Token Definition
FLOAT_LITERAL (-?[0-9]*.[0-9]+ | -?[0-9]+.[0-9]*)(e(+|-)?[0-9]+)?
INT_LITERAL -?0x[0-9a-fA-F]+ | 0 | -?[1-9][0-9]*
UINT_LITERAL 0x[0-9a-fA-F]+u | 0u | [1-9][0-9]*u
IDENT [a-zA-Z][0-9a-zA-Z_]*
STRING_LITERAL "[^"]*"

Note: literals are parsed greedy. This means that for statements like a -5 this will not parse as a minus 5 but instead as a -5 which may be unexpected. A space must be inserted after the - if the first expression is desired.

4.6. Keywords

Token Definition
ARRAY array
BOOL bool
FLOAT32 f32
INT32 i32
MAT2x2 mat2x2 # column x row
MAT2x3 mat2x3 # column x row
MAT2x4 mat2x4 # column x row
MAT3x2 mat3x2 # column x row
MAT3x3 mat3x3 # column x row
MAT3x4 mat3x4 # column x row
MAT4x2 mat4x2 # column x row
MAT4x3 mat4x3 # column x row
MAT4x4 mat4x4 # column x row
POINTER ptr
SAMPLER sampler
SAMPLER_COMPARISON sampler_comparison
STRUCT struct
TEXTURE_SAMPLED_1D texture_sampled_1d
TEXTURE_SAMPLED_1D_ARRAY texture_sampled_1d_array
TEXTURE_SAMPLED_2D texture_sampled_2d
TEXTURE_SAMPLED_2D_ARRAY texture_sampled_2d_array
TEXTURE_SAMPLED_2D_MS texture_sampled_2d_ms
TEXTURE_SAMPLED_2D_MS_ARRAY texture_sampled_2d_ms_array
TEXTURE_SAMPLED_3D texture_sampled_3d
TEXTURE_SAMPLED_CUBE texture_sampled_cube
TEXTURE_SAMPLED_CUBE_ARRAY texture_sampled_cube_array
TEXTURE_RO_1D texture_ro_1d
TEXTURE_RO_1D_ARRAY texture_ro_1d_array
TEXTURE_RO_2D texture_ro_2d
TEXTURE_RO_2D_ARRAY texture_ro_2d_array
TEXTURE_RO_3D texture_ro_3d
TEXTURE_WO_1D texture_wo_1d
TEXTURE_WO_1D_ARRAY texture_wo_1d_array
TEXTURE_WO_2D texture_wo_2d
TEXTURE_WO_2D_ARRAY texture_wo_2d_array
TEXTURE_WO_3D texture_wo_3d
TEXTURE_DEPTH_2D texture_depth_2d
TEXTURE_DEPTH_2D_ARRAY texture_depth_2d_array
TEXTURE_DEPTH_CUBE texture_depth_cube
TEXTURE_DEPTH_CUBE_ARRAY texture_depth_cube_array
UINT32 u32
VEC2 vec2
VEC3 vec3
VEC4 vec4
VOID void
AS as
BINDING binding
BLOCK block
BREAK break
BUILTIN builtin
CASE case
CAST cast
COMPUTE compute
CONST const
CONSTANT_ID constant_id
CONTINUE continue
CONTINUING continuing
DEFAULT default
DISCARD discard
ELSE else
ELSE_IF elseif
ENTRY_POINT entry_point
FALLTHROUGH fallthrough
FALSE false
FN fn
FOR for
FRAGMENT fragment
FUNCTION function
IF if
IMAGE image
IMPORT import
IN in
LOCATION location
LOOP loop
OFFSET offset
OUT out
PRIVATE private
RETURN return
SET set
STORAGE_BUFFER storage_buffer
STRIDE stride
SWITCH switch
TRUE true
TYPE type
UNIFORM uniform
UNIFORM_CONSTANT uniform_constant
VAR var
VERTEX vertex
WORKGROUP workgroup
R8UNORM r8unorm
R8SNORM r8snorm
R8UINT r8uint
R8SINT r8sint
R16UINT r16uint
R16SINT r16sint
R16FLOAT r16float
RG8UNORM rg8unorm
RG8SNORM rg8snorm
RG8UINT rg8uint
RG8SINT rg8sint
R32UINT r32uint
R32SINT r32sint
R32FLOAT r32float
RG16UINT rg16uint
RG16SINT rg16sint
RG16FLOAT rg16float
RGBA8UNORM rgba8unorm
RGBA8UNORM-SRGB rgba8unorm_srgb
RGBA8SNORM rgba8snorm
RGBA8UINT rgba8uint
RGBA8SINT rgba8sint
BGRA8UNORM bgraunorm
BGRA8UNORM-SRGB bgra8unorm_srgb
RGB10A2UNORM rgb10a2unorm
RG11B10FLOAT rg11b10float
RG32UINT rg32uint
RG32SINT rg32sint
RG32FLOAT rg32float
RGBA16UINT rgba16uint
RGBA16SINT rgba16sint
RGBA16FLOAT rgba16float
RGBA32UINT rgba32uint
RGBA32SINT rgba32sint
RGBA32FLOAT rgba32float

4.7. Reserved Keywords

The following is a list of keywords which are reserved for future expansion.
asm bf16 do enum f16
f64 i8 i16 i64
let typedef u8 u16 u64
unless using while regardless premerge

4.8. Syntactic Tokens

AND &
AND_AND &&
ARROW ->
ATTR_LEFT [[
ATTR_RIGHT ]]
FORWARD_SLASH /
BANG !
BRACKET_LEFT [
BRACKET_RIGHT ]
BRACE_LEFT {
BRACE_RIGHT }
COLON :
COMMA ,
EQUAL =
EQUAL_EQUAL ==
NOT_EQUAL !=
GREATER_THAN >
GREATER_THAN_EQUAL >=
SHIFT_RIGHT >>
LESS_THAN <
LESS_THAN_EQUAL <=
SHIFT_LEFT <<
MODULO %
MINUS -
NAMESPACE ::
PERIOD .
PLUS +
OR |
OR_OR ||
PAREN_LEFT (
PAREN_RIGHT )
SEMICOLON ;
STAR *
XOR ^

4.9. Preamble

WGSL is focused on WebGPU shaders. As such, the following is defined for all shaders which are generated:
EXAMPLE: Preamble
....
OpCapability Shader
OpCapability VulkanMemoryModel
OpMemoryModel Logical VulkanKHR
....

While we recognize that most Vulkan devices will not support VulkanMemoryModel we expect the SPIR-V generated to be converted by SPIRV-Tools after the fact to make the shader compatible.

translation_unit
  : global_decl* EOF

4.10. Global Declarations

global_decl
  : SEMICOLON
  | import_decl SEMICOLON
  | global_variable_decl SEMICOLON
  | global_constant_decl SEMICOLON
  | entry_point_decl SEMICOLON
  | type_alias SEMICOLON
  | struct_decl SEMICOLON
  | function_decl

4.11. Imports

There is one import provided which is GLSL.std.450. All other uses of import will be rejected by WGSL as being unknown. All uses of the imported methods must be prefixed by the import name as provided after the as keyword.
import_decl
  : IMPORT STRING_LITERAL AS (IDENT NAMESPACE)* IDENT

The methods defined in GLSL.std.450 become available with the given prefix. The initial import will add an OpExtInstImport instruction to the SPIR-V module header and each usage of a GLSL method will add the appropriate OpExtIns invocation.

EXAMPLE: Import
import "GLSL.std.450" as std::glsl;
  %1 = OpExtInstImport "GLSL.std.450"

4.12. Module Constants

A module constant declares a name for a value, outside of all function declarations. The name is available for use after the end of the declaration, until the end of the WGSL program.

When the declaration has no attributes, an initializer expression must be present, and the name denotes the value of that expression.

EXAMPLE: Module constants
const golden : f32 = 1.61803398875;       # The golden ratio
const e2 : vec3<i32> = vec3<i32>(0,1,0);  # The second unit vector for three dimensions.

When the declaration uses the constant_id attribute, the constant is pipeline-overridable. In this case:

What happens is the application supplies a constant ID that is not in the program? Proposal: pipeline creation fails with an error.

EXAMPLE: Module constants, pipeline-overrideable
[[constant_id 0]]    const has_point_light : bool = true;      # Algorithmic control
[[constant_id 1200]] const specular_param : f32 = 2.3;         # Numeric control
[[constant_id 1300]] const gain : f32;                         # Must be overridden

When a variable or feature is used within control flow that depends on the value of a constant, then that variable or feature is considered to be used by the program. This is true regardless of the value of the constant, whether that value is the one from the constant’s declaration or from a pipeline override.

global_constant_decl
  : global_const_decoration_list? CONST variable_ident_decl global_const_initializer?

global_const_decoration_list
  : ATTR_LEFT global_const_decoration ATTR_RIGHT

global_const_decoration
  : CONSTANT_ID INT_LITERAL

global_const_initializer
  : EQUAL const_expr

The WebGPU pipeline creation API must specify how API-supplied values are mapped to shader scalar values. For booleans, I suggest using a 32-bit integer, where only 0 maps to false. If WGSL gains non-32-bit numeric scalars, I recommend overridable constants continue being 32-bit numeric types.

4.13. Module Variables

global_variable_decl
  : variable_decoration_list? variable_decl
  | variable_decoration_list? sampler_or_texture_decl
  | variable_decoration_list? variable_decl EQUAL const_expr

global_constant_decl
  : CONST variable_ident_decl EQUAL const_expr

sampler_or_texture_decl
  : VAR variable_storage_decoration? IDENT COLON texture_sampler_types

texture_sampler_types
  : sampler_type
  | depth_texture_type
  | sampled_texture_type LESS_THAN type_decl GREATER_THAN
  | storage_texture_type LESS_THAN image_storage_type GREATER_THAN

sampler_type
  : SAMPLER
  | SAMPLER_COMPARISON

sampled_texture_type
  : TEXTURE_SAMPLED_1D
  | TEXTURE_SAMPLED_1D_ARRAY
  | TEXTURE_SAMPLED_2D
  | TEXTURE_SAMPLED_2D_ARRAY
  | TEXTURE_SAMPLED_2D_MS
  | TEXTURE_SAMPLED_2D_MS_ARRAY
  | TEXTURE_SAMPLED_3D
  | TEXTURE_SAMPLED_CUBE
  | TEXTURE_SAMPLED_CUBE_ARRAY

storage_texture_type
  : TEXTURE_RO_1D
  | TEXTURE_RO_1D_ARRAY
  | TEXTURE_RO_2D
  | TEXTURE_RO_2D_ARRAY
  | TEXTURE_RO_3D
  | TEXTURE_WO_1D
  | TEXTURE_WO_1D_ARRAY
  | TEXTURE_WO_2D
  | TEXTURE_WO_2D_ARRAY
  | TEXTURE_WO_3D

depth_texture_type
  : TEXTURE_DEPTH_2D
  | TEXTURE_DEPTH_2D_ARRAY
  | TEXTURE_DEPTH_CUBE
  | TEXTURE_DEPTH_CUBE_ARRAY

image_storage_type
  : R8UNORM
     R8  -- Capability: StorageImageExtendedFormats
  | R8SNORM
     R8Snorm  -- Capability: StorageImageExtendedFormats
  | R8UINT
     R8ui  -- Capability: StorageImageExtendedFormats
  | R8SINT
     R8i  -- Capability: StorageImageExtendedFormats
  | R16UINT
     R16ui  -- Capability: StorageImageExtendedFormats
  | R16SINT
     R16i  -- Capability: StorageImageExtendedFormats
  | R16FLOAT
     R16f  -- Capability: StorageImageExtendedFormats
  | RG8UNORM
     Rg8  -- Capability: StorageImageExtendedFormats
  | RG8SNORM
     Rg8Snorm  -- Capability: StorageImageExtendedFormats
  | RG8UINT
     Rg8ui  -- Capability: StorageImageExtendedFormats
  | RG8SINT
     Rg8i  -- Capability: StorageImageExtendedFormats
  | R32UINT
     R32ui
  | R32SINT
     R32i
  | R32FLOAT
     R32f
  | RG16UINT
     Rg16ui  -- Capability: StorageImageExtendedFormats
  | RG16SINT
     Rg16i  -- Capability: StorageImageExtendedFormats
  | RG16FLOAT
     Rg16f  -- Capability: StorageImageExtendedFormats
  | RGBA8UNORM
     Rgba8
  | RGBA8UNORM-SRGB
     ???
  | RGBA8SNORM
     Rgba8Snorm
  | RGBA8UINT
     Rgba8ui
  | RGBA8SINT
     Rgba8i
  | BGRA8UNORM
     Rgba8  ???
  | BGRA8UNORM-SRGB
     ???
  | RGB10A2UNORM
     Rgb10A2  -- Capability: StorageImageExtendedFormats
  | RG11B10FLOAT
     R11fG11fB10f  -- Capability: StorageImageExtendedFormats
  | RG32UINT
     Rg32ui  -- Capability: StorageImageExtendedFormats
  | RG32SINT
     Rg32i  -- Capability: StorageImageExtendedFormats
  | RG32FLOAT
     Rg32f  -- Capability: StorageImageExtendedFormats
  | RGBA16UINT
     Rgba16ui
  | RGBA16SINT
     Rgba16i
  | RGBA16FLOAT
     Rgba16f
  | RGBA32UINT
     Rgba32ui
  | RGBA32SINT
     Rgba32i
  | RGBA32FLOAT
     Rgba32f

variable_decoration_list
  : ATTR_LEFT (variable_decoration COMMA)* variable_decoration ATTR_RIGHT

variable_decoration
  : LOCATION INT_LITERAL
  | BUILTIN IDENT
  | BINDING INT_LITERAL
  | SET INT_LITERAL
EXAMPLE: Variable Decorations
[[location 2]]
   OpDecorate %gl_FragColor Location 2

[[binding 3, set 4]]
   OpDecorate %gl_FragColor Binding 3
   OpDecorate %gl_FragColor DescriptorSet 4
EXAMPLE: Valid Builtin Decoration Identifiers
[[builtin position]]
      OpDecorate %gl_Position BuiltIn Position

[[builtin vertex_idx]]
      OpDecorate %gl_VertexIdx BuiltIn VertexIndex

[[builtin instance_idx]]
      OpDecorate %gl_InstanceId BuiltIn InstanceIndex

[[builtin front_facing]]
      OpDecorate %gl_FrontFacing BuiltIn FrontFacing

[[builtin frag_coord]]
      OpDecorate %gl_FragCoord BuiltIn FragCoord

[[builtin frag_depth]]
      OpDecorate %gl_FragDepth BuiltIn FragDepth

[[builtin local_invocation_id]]
      OpDecorate %gl_LocalInvocationID BuiltIn LocalInvocationId

[[builtin local_invocation_idx]]
      OpDecorate %gl_LocalInvocationIndex BuiltIn LocalInvocationIndex

[[builtin global_invocation_id]]
      OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId

The usages of the variable builtin decorations is further restricted in the type, function decorations and storage class.

Name Type Restrictions
position vec4<f32> Vertex Output
vertex_idx i32 Vertex Input
instance_idx i32 Vertex Input
front_facing bool Fragment Input
frag_coord vec4<f32> Fragment Input
frag_depth f32 Fragment Output
local_invocation_id vec3<u32> Compute Input
global_invocation_id vec3<u32> Compute Input
local_invocation_idx u32 Compute Input
variable_decl
  : VAR variable_storage_decoration? variable_ident_decl

variable_ident_decl
  : IDENT COLON type_decl

variable_storage_decoration:
  : LESS_THAN storage_class GREATER_THAN

4.14. Type Alias

type_alias
  : TYPE IDENT EQUAL type_decl
EXAMPLE: Type Alias
type Arr = array<i32, 5>;

type RTArr = [[stride 16]] array<vec4<f32>>;

4.15. Type Declarations

type_decl
  : IDENT
  | BOOL
  | FLOAT32
  | INT32
  | UINT32
  | VEC2 LESS_THAN type_decl GREATER_THAN
  | VEC3 LESS_THAN type_decl GREATER_THAN
  | VEC4 LESS_THAN type_decl GREATER_THAN
  | PTR LESS_THAN storage_class, type_decl GREATER_THAN
  | array_decoration_list? ARRAY LESS_THAN type_decl COMMA INT_LITERAL GREATER_THAN
  | array_decoration_list? ARRAY LESS_THAN type_decl GREATER_THAN
  | MAT2x2 LESS_THAN type_decl GREATER_THAN
  | MAT2x3 LESS_THAN type_decl GREATER_THAN
  | MAT2x4 LESS_THAN type_decl GREATER_THAN
  | MAT3x2 LESS_THAN type_decl GREATER_THAN
  | MAT3x3 LESS_THAN type_decl GREATER_THAN
  | MAT3x4 LESS_THAN type_decl GREATER_THAN
  | MAT4x2 LESS_THAN type_decl GREATER_THAN
  | MAT4x3 LESS_THAN type_decl GREATER_THAN
  | MAT4x4 LESS_THAN type_decl GREATER_THAN

array_decoration_list
  : ATTR_LEFT (array_decoration COMMA)* array_decoration ATTR_RIGHT

array_decoration
  : STRIDE INT_LITERAL
EXAMPLE: Type Declarations
identifier
  Allows to specify types created by the type command

bool
   %1 = OpTypeBool

f32
   %2 = OpTypeFloat 32

i32
   %3 = OpTypeInt 32 1

u32
   %4 = OpTypeInt 32 0

vec2<f32>
    %7 = OpTypeVector %float 2

array<f32, 4>
   %uint_4 = OpConstant %uint 4
        %9 = OpTypeArray %float %uint_4

[[stride 32]] array<f32, 4>
             OpDecorate %9 ArrayStride 32
   %uint_4 = OpConstant %uint 4
        %9 = OpTypeArray %float %uint_4

array<f32>
   %rtarr = OpTypeRuntimeArray %float

mat2x3<f32>
   %vec = OpTypeVector %float 3
     %6 = OpTypeMatrix %vec 2

4.16. Storage Classes

storage_class
  : INPUT
  | OUTPUT
  | UNIFORM
  | WORKGROUP
  | UNIFORM_CONSTANT
  | STORAGE_BUFFER
  | IMAGE
  | PRIVATE
  | FUNCTION
Name SPIR-V Storage Class
input Input
output Output
uniform Uniform
workgroup Workgroup
uniform_constant UniformConstant
storage_buffer StorageBuffer
image Image
private Private
function Function

4.17. Structures

struct_decl
  : struct_decoration_decl? STRUCT IDENT struct_body_decl

struct_decoration_decl
  : ATTR_LEFT struct_decoration ATTR_RIGHT

struct_decoration
  : BLOCK

struct_body_decl
  : BRACE_LEFT struct_member* BRACE_RIGHT

struct_member
  : struct_member_decoration_decl variable_ident_decl SEMICOLON

struct_member_decoration_decl
  :
  | ATTR_LEFT (struct_member_decoration COMMA)* struct_member_decoration ATTR_RIGHT

struct_member_decoration
  : OFFSET INT_LITERAL

Note: Layout decorations are required if the struct is used in an SSBO, UBO or Push Constant. Otherwise, the layout will be ignored.

(dneto): MatrixStride, RowMajor, ColMajor layout decorations are needed for matrices.

EXAMPLE: Structure
struct my_struct {
  [[offset 0]] a : f32;
  [[offset 4]] b : vec4<f32>;
};

              OpName %my_struct "my_struct"
              OpMemberName %my_struct 0 "a"
              OpMemberDecorate %my_struct 0 Offset 0
              OpMemberName %my_struct 1 "b"
              OpMemberDecorate %my_struct 1 Offset 4
 %my_struct = OpTypeStruct %float %v4float

[[block]] struct S {
  [[offset 0]] a : f32;
  [[offset 4]] b : f32;
  [[offset 16]] data : RTArr;
};

4.18. Functions

Recursion is not permitted in WGSL.

Functions must end with a return statement. The return may be given with a value to be returned.

Function names must be unique over all functions and all variables in the module.

function_decl
  : function_header body_stmt

function_type_decl
  : type_decl
  | VOID

function_header
  : FN IDENT PAREN_LEFT param_list PAREN_RIGHT ARROW function_type_decl

param_list
  :
  | (variable_ident_decl COMMA)* variable_ident_decl
EXAMPLE: Function
void
    %6 = OpTypeVoid

fn my_func(i : i32, b : f32) -> i32 {
  return 2;
}

           OpName %my_func "my_func"
           OpName %a "a"
           OpName %b "b"
%my_func = OpFunction %int None %10
      %a = OpFunctionParameter %_ptr_Function_int
      %b = OpFunctionParameter %_ptr_Function_float
     %14 = OpLabel
           OpReturnValue %int_2
           OpFunctionEnd

4.19. Entry Points

The entry_point declares an entry point into the module. The entry points may be forward declarations but the functions referenced must be declared in the file.

The input and output parameters to the entry point are determined by which global variables are used in the function and any called functions.

The entry point function must be declared with no arguments and returning void.

entry_point_decl:
   : ENTRY_POINT pipeline_stage EQUAL IDENT
   | ENTRY_POINT pipeline_stage AS STRING_LITERAL EQUAL IDENT
   | ENTRY_POINT pipeline_stage AS IDENT EQUAL IDENT

pipeline_stage
  : VERTEX
  | FRAGMENT
  | COMPUTE
EXAMPLE: Entry Point
entry_point vertex = main
   OpEntryPoint Vertex %vtx_main "vtx_main" %gl_FragColor

entry_point fragment as “frag_main” = main
   OpEntryPoint Fragment %main "frag_main" %gl_FragColor

entry_point compute = comp_main
   OpEntryPoint GLCompute %comp_main "comp_main" %gl_FragColor

4.20. Statements

body_stmt:
  : BRACE_LEFT statements BRACE_RIGHT

paren_rhs_stmt
  : PAREN_LEFT short_circuit_or_expression PAREN_RIGHT

statements
  : statement*

statement
  : SEMICOLON
  | return_stmt SEMICOLON
  | if_stmt
  | switch_stmt
  | loop_stmt
  | for_stmt
  | func_call_stmt SEMICOLON
  | variable_stmt SEMICOLON
  | break_stmt SEMICOLON
  | continue_stmt SEMICOLON
  | DISCARD SEMICOLON
  | assignment_stmt SEMICOLON
  | body_stmt

4.21. Return statement

return_stmt
  : RETURN short_circuit_or_expression?

A return statement ends execution of the current function. If the function is an entry point, then the current shader invocation is terminated. Otherwise, evaluation continues with the next expression or statement after the evaluation of the call site of the current function invocation.

If the return type of the function is the void type, then the return statement must not have an expression. Otherwise the expression must be present, and is called the return value. In this case the call site of this function invocation evaluates to the return value. The type of the return value must match the return type of the function.

4.22. Variable Statement

variable_stmt
  : variable_decl
  | variable_decl EQUAL short_circuit_or_expression
  | CONST variable_ident_decl EQUAL short_circuit_or_expression

4.23. If Statement

if_stmt
  : IF paren_rhs_stmt body_stmt elseif_stmt? else_stmt?

elseif_stmt
  : ELSE_IF paren_rhs_stmt body_stmt elseif_stmt?

else_stmt
  : ELSE body_stmt

4.24. Switch Statement

switch_stmt
  : SWITCH paren_rhs_stmt BRACE_LEFT switch_body+ BRACE_RIGHT

switch_body
  : CASE case_selectors COLON BRACE_LEFT case_body BRACE_RIGHT
  | DEFAULT COLON BRACE_LEFT case_body BRACE_RIGHT

case_selectors
  : const_literal (COMMA const_literal)*

case_body
  :
  | statement case_body
  | FALLTHROUGH SEMICOLON

A switch statement transfers control to one of a set of case clauses, or to the default clause, depending the evaluation of a selector expression of a scalar integer type.

If the selector value equals a value in a case selector list, then control is transferred to the body of that case clause. If the selector value does not equal any of the case selector values, then control is transferred to the default clause.

Each switch statement must have exactly one default clause.

The case selector values must have the same type as the selector expression.

A literal value must not appear more than once in the case selectors for a switch statement.

Note: The value of the literal is what matters, not the spelling. For example 0, 00, and 0x0000 all denote the zero value.

When control reaches the end of a case body, control normally transfers to the first statement after the switch statement. Alternately, executing a fallthrough statement transfers control to the body of the next case clause or default clause, whichever appears next in the switch body. A fallthrough statement must not appear as the last statement in the last clause of a switch.

4.25. Loop Statement

loop_stmt
  : LOOP BRACE_LEFT statements continuing_stmt? BRACE_RIGHT

The loop construct causes a block of statements, the loop body, to execute repeatedly.

This repetition can be interrupted by a § 4.27 Break statement, return, or discard.

Optionally, the last statement in the loop body may be a § 4.29 Continuing statement.

Note: The loop statement is one of the biggest differences from other shader languages.

This design directly expresses loop idioms commonly found in compiled code. In particular, placing the loop update statements at the end of the loop body allows them to naturally use values defined in the loop body.

EXAMPLE: GLSL Loop
int a = 2;
for (int i = 0; i < 4; i++) {
  a *= 2;
}
EXAMPLE: WGSL Loop
const a : i32 = 2;
var i : i32 = 0;      // <1>
loop {
  if (i >= 4) { break; }

  a = a * 2;

  i = i + 1;
}
EXAMPLE: GLSL Loop with continue
int a = 2;
const int step = 1;
for (int i = 0; i < 4; i += step) {
  if (i % 2 == 0) continue;
  a *= 2;
}
EXAMPLE: WGSL Loop with continue
const a : i32 = 2;
var i : i32 = 0;
loop {
  if (i >= 4) { break; }

  const step : i32 = 1;

  i = i + 1;
  if (i % 2 == 0) { continue; }

  a = a * 2;
}
EXAMPLE: WGSL Loop with continue and continuing
const a : i32 = 2;
var i : i32 = 0;
loop {
  if (i >= 4) { break; }

  const step : i32 = 1;

  if (i % 2 == 0) { continue; }

  a = a * 2;

  continuing {   // <2>
    i = i + step;
  }
}

4.25.1. For statement

for_stmt
  : FOR PAREN_LEFT for_header PAREN_RIGHT BRACE_LEFT statements BRACE_RIGHT

for_header
  : (variable_stmt | assignment_stmt | func_call_stmt)? SEMICOLON
     short_circuit_or_expression? SEMICOLON
     (assignment_stmt | func_call_stmt)?

The for(var i : i32 = 0; i < 4; i = i + 1) {} statement is syntactic sugar on top of the § 4.25 Loop Statement. The for transforms into loop as:

EXAMPLE: For to Loop transformation
for(var i : i32 = 0; i < 4; i = i + 1) {
  if (a == 0) {
    continue;
  }
  a = a + 2;
}

Converts too:

{ # Introduce new scope for loop variable i
  var i : i32 = 0;
  loop {
    if (!(i < 4)) {
      break;
    }

    if (a == 0) {
      continue;
    }
    a = a + 2;

    continuing {
      i = i + 1;
    }
  }
}

4.26. Function call statemnet

func_call_stmt
  : IDENT PAREN_LEFT argument_expression_list* PAREN_RIGHT

4.27. Break statement

break_stmt
  : BREAK

Use a break statement to transfer control to the first statement after the body of the nearest-enclosing § 4.25 Loop Statement or § 4.24 Switch Statement.

When a break statement is placed such that it would exit from a loop’s § 4.29 Continuing statement, then:

EXAMPLE: WGSL Valid loop if-break from a continuing clause
const a : i32 = 2;
var i : i32 = 0;
loop {
  const step : i32 = 1;

  if (i % 2 == 0) { continue; }

  a = a * 2;

  continuing {
    i = i + step;
    if (i >= 4) { break; }
  }
}
EXAMPLE: WGSL Valid loop if-else-break from a continuing clause
const a : i32 = 2;
var i : i32 = 0;
loop {
  const step : i32 = 1;

  if (i % 2 == 0) { continue; }

  a = a * 2;

  continuing {
    i = i + step;
    if (i < 4) {} else { break; }
  }
}
EXAMPLE: WGSL Invalid breaks from a continuing clause
const a : i32 = 2;
var i : i32 = 0;
loop {
  const step : i32 = 1;

  if (i % 2 == 0) { continue; }

  a = a * 2;

  continuing {
    i = i + step;
    break;                                     // Invalid: too early
    if (i < 4) { i = i + 1; } else { break; }  // Invalid: if is too complex, and too early
    if (i >= 4) { break; } else { i = i + 1; } // Invalid: if is too complex
  }
}

4.28. Continue statement

continue_stmt
  : CONTINUE

Use a continue statement to transfer control in the nearest-enclosing § 4.25 Loop Statement:

A continue statement must not be placed such that it would transfer control to an enclosing § 4.29 Continuing statement. (It is a forward branch when branching to a continuing statement.)

A continue statement must not be placed such that it would transfer control past a declaration used in the targeted continuing construct.

EXAMPLE: Invalid continue bypasses declaration
var i : i32 = 0;
loop {
  if (i >= 4) { break; }
  if (i % 2 == 0) { continue; } // <3>

  const step : i32 = 2;

  continuing {
    i = i + step;
  }
}

4.29. Continuing statement

continuing_stmt:
  : CONTINUING body_stmt

A continuing construct is a block of statements to be executed at the end of a loop iteration. The construct is optional.

The block of statements must not contain a return or discard statement.

4.30. Expression statement

primary_expression
  : (IDENT NAMESPACE)* IDENT
  | type_decl PAREN_LEFT argument_expression_list* PAREN_RIGHT
  | const_literal
  | paren_rhs_stmt
  | CAST LESS_THAN type_decl GREATER_THAN paren_rhs_stmt
      OpConvertFToU
      OpConvertFToS
      OpConvertSToF
      OpConvertUToF
      OpUConvert
      OpSConvert
      OpFConvert
  | AS LESS_THAN type_decl GREATER_THAN paren_rhs_stmt
      OpBitcast

postfix_expression
  :
  | BRACKET_LEFT short_circuit_or_expression BRACKET_RIGHT postfix_expression
  | PAREN_LEFT argument_expression_list* PAREN_RIGHT postfix_expression
  | PERIOD IDENT postfix_expression

argument_expression_list
  : (short_circuit_or_expression COMMA)* short_circuit_or_expression

unary_expression
  : singular_expression
  | MINUS unary_expression
      OpSNegate
      OpFNegate
  | BANG unary_expression
      OpNot

singular_expression
  : primary_expression postfix_expression

multiplicative_expression
  : unary_expression
  | multiplicative_expression STAR unary_expression
      OpVectorTimesScalar
      OpMatrixTimesScalar
      OpVectorTimesMatrix
      OpMatrixTimesVector
      OpMatrixTimesMatrix
      OpIMul
      OpFMul
  | multiplicative_expression FORWARD_SLASH unary_expression
      OpUDiv
      OpSDiv
      OpFDiv
  | multiplicative_expression MODULO unary_expression
      OpUMOd
      OpSMod
      OpFMod

additive_expression
  : multiplicative_expression
  | additive_expression PLUS multiplicative_expression
      OpIAdd
      OpFAdd
  | additive_expression MINUS multiplicative_expression
      OpFSub
      OpISub

shift_expression
  : additive_expression
  | shift_expression SHIFT_LEFT additive_expression
        OpShiftLeftLogical
  | shift_expression SHIFT_RIGHT additive_expression
        OpShiftRightLogical or OpShiftRightArithmetic

relational_expression
  : shift_expression
  | relational_expression LESS_THAN shift_expression
        OpULessThan
        OpFOrdLessThan
  | relational_expression GREATER_THAN shift_expression
        OpUGreaterThan
        OpFOrdGreaterThan
  | relational_expression LESS_THAN_EQUAL shift_expression
        OpULessThanEqual
        OpFOrdLessThanEqual
  | relational_expression GREATER_THAN_EQUAL shift_expression
        OpUGreaterThanEqual
        OpFOrdGreaterThanEqual

equality_expression
  : relational_expression
  | relational_expression EQUAL_EQUAL relational_expression
        OpIEqual
        OpFOrdEqual
  | relational_expression NOT_EQUAL relational_expression
        OpINotEqual
        OpFOrdNotEqual

and_expression
  : equality_expression
  | and_expression AND equality_expression

exclusive_or_expression
  : and_expression
  | exclusive_or_expression XOR and_expression

inclusive_or_expression
  : exclusive_or_expression
  | inclusive_or_expression OR exclusive_or_expression

short_circuit_and_expression
  : inclusive_or_expression
  | short_circuit_and_expression AND_AND inclusive_or_expression

short_circuit_or_expression
  : short_circuit_and_expression
  | short_circuit_or_expression OR_OR short_circuit_and_expression

assignment_stmt
  : singular_expression EQUAL short_circuit_or_expression
      If singular_expression is a variable, this maps to OpStore to the variable.
      Otherwise, singular expression is a pointer expression in an Assigning (L-value) context
      which maps to OpAccessChain followed by OpStore

4.31. Literal Statement

const_literal
  : INT_LITERAL
  | UINT_LITERAL
  | FLOAT_LITERAL
  | TRUE
  | FALSE

const_expr
  : type_decl PAREN_LEFT (const_expr COMMA)* const_expr PAREN_RIGHT
  | const_literal
EXAMPLE: Constants
-1
   %a = OpConstant %int -1

2
   %b = OpConstant %uint 2

3.2
   %c = OpConstant %float 3.2

true
    %d = OpConstantTrue

false
    %e = OpConstant False

vec4<f32>(1.2, 2.3, 3.4, 2.3)
    %f0 = OpConstant %float 1.2
    %f1 = OpConstant %float 2.3
    %f2 = OpConstant %float 3.4
     %f = OpConstantComposite %v4float %f0 %f1 %f2 %f1

5. Validation

Each validation item will be given a unique ID and a test must be provided when the validation is added. The tests will reference the validation ID in the test name.

6. Type Checking

Type checking is the process of mapping terms in the WGSL source language to § 2 Formal Type Definitions.

Generally, we start by determining types for the smallest WGSL source phrases, and then build up via combining rules.

If we can derive a type for the whole WGSL source program via the type rules, then we say the program is well-typed. Otherwise there is a type error and is not a valid WGSL program.

(dneto) complete

6.1. Preamble for those familiar with formal type checking

Much of it can be bottom-up, like usual.

The interesting bit is that the type of a pointer expression is either straightforward pointer type itself, or the pointee type, depending on its § 2.12 Pointer evaluation context:

6.2. How to read the rules

A type assertion is a mapping from some WGSL source expression to an WGSL type. When we write

e : T

we are saying the WGSL expression e is of type T In the rules below, the WGSL source expression will often have placeholders in italics that represent sub-expressions in the grammar.

In the following tables, each row represents a type deduction rule: If the conditions in the precondition column are satisfied, then the type assertion in the conclusion column is also satisfied.

For convenience, we will use the following shorthands:

Scalar § 2.2 Scalar Types, one of bool, i32, u32, f32
BoolVec § 2.3 Vector Types with bool component
Int i32 or u32
IntVec § 2.3 Vector Types with an Int component
Integral Int or § 2.3 Vector Types with an Int component
FloatVec § 2.3 Vector Types with f32 component
Floating f32 or FloatVec
Arity(T) number of components in § 2.3 Vector Types T

(dneto): Do we have to explicitly list the type environment Gamma? That’s confusing to newcomers.

6.3. Literal and unary expression type rules

Scalar literal type rules
Precondition Conclusion Notes
true : bool OpConstantTrue %bool
false : bool OpConstantFalse %bool
INT_LITERAL : i32 OpConstant %int literal
UINT_LITERAL : u32 OpConstant %uint literal
FLOAT_LITERAL : f32 OpConstant %float literal
Boolean constructor type rules
Precondition Conclusion Notes
bool() : bool Zero value (OpConstantNull for bool)
e : bool bool(e) : bool Pass-through (OpCopyObject)
Numeric scalar constructor type rules
Precondition Conclusion Notes
i32() : i32 Zero value (OpConstantNull for i32)
u32() : u32 Zero value (OpConstantNull for u32)
f32() : f32 Zero value (OpConstantNull for f32)
e : i32 i32(e) : i32 Pass-through (OpCopyObject)
e : u32 i32(e) : i32 Reinterpretation of bits (OpBitcast)
e : f32 i32(e) : i32 Value conversion, including invalid cases (OpConvertFToS)
e : i32 u32(e) : u32 Reinterpretation of bits (OpBitcast)
e : u32 u32(e) : u32 Pass-through (OpCopyObject)
e : f32 u32(e) : u32 Value conversion, including invalid cases (OpConvertFToU)
e : i32 f32(e) : f32 Value conversion, including invalid cases (OpConvertSToF)
e : u32 f32(e) : f32 Value conversion, including invalid cases (OpConvertUToF)
e : f32 f32(e) : f32 Pass-through (OpCopyObject)
Vector constructor type rules, where T is a scalar type
Precondition Conclusion Notes
vec2<T>() : vec2<T> Zero value (OpConstantNull)
vec3<T>() : vec3<T> Zero value (OpConstantNull)
vec4<T>() : vec4<T> Zero value (OpConstantNull)
e1 : T
e2 : T
vec2<T>(e1,e2) : vec2<T> OpCompositeConstruct
e1 : T
e2 : T
e3 : T
vec3<T>(e1,e2,e3) : vec3<T> OpCompositeConstruct
e1 : T
e2 : T
e3 : T
e4 : T
vec4<T>(e1,e2,e3,e4) : vec4<T> OpCompositeConstruct
e1 : T
e2 : vec2<T>
vec3<T>(e1,e2) : vec3<T>
vec3<T>(e2,e1) : vec3<T>
OpCompositeConstruct
e1 : T
e2 : T
e3 : vec2<T>
vec4<T>(e1,e2,e3) : vec4<T>
vec4<T>(e1,e3,e2) : vec4<T>
vec4<T>(e3,e1,e2) : vec4<T>
OpCompositeConstruct
e1 : vec2<T>
e2 : vec2<T>
vec4<T>(e1,e2) : vec4<T> OpCompositeConstruct
e1 : T
e2 : vec3<T>
vec4<T>(e1,e2) : vec4<T>
vec4<T>(e2,e1) : vec4<T>
OpCompositeConstruct
Matrix constructor type rules
Precondition Conclusion Notes
mat2x2<f32>() : mat2x2
mat3x2<f32>() : mat3x2
mat4x2<f32>() : mat4x2
Zero value (OpConstantNull)
mat2x3<f32>() : mat2x3
mat3x3<f32>() : mat3x3
mat4x3<f32>() : mat4x3
Zero value (OpConstantNull)
mat2x4<f32>() : mat2x4
mat3x4<f32>() : mat3x4
mat4x4<f32>() : mat4x4
Zero value (OpConstantNull)
e1 : vec2
e2 : vec2
e3 : vec2
e4 : vec2
mat2x2<f32>(e1,e2) : mat2x2
mat3x2<f32>(e1,e2,e3) : mat3x2
mat4x2<f32>(e1,e2,e3,e4) : mat4x2
Column by column construction.
OpCompositeConstruct
e1 : vec3
e2 : vec3
e3 : vec3
e4 : vec3
mat2x3<f32>(e1,e2) : mat2x3
mat3x3<f32>(e1,e2,e3) : mat3x3
mat4x3<f32>(e1,e2,e3,e4) : mat4x3
Column by column construction.
OpCompositeConstruct
e1 : vec4
e2 : vec4
e3 : vec4
e4 : vec4
mat2x4<f32>(e1,e2) : mat2x4
mat3x4<f32>(e1,e2,e3) : mat3x4
mat4x4<f32>(e1,e2,e3,e4) : mat4x4
Column by column construction.
OpCompositeConstruct
Array constructor type rules
Precondition Conclusion Notes
e1 : T
...
eN : T
array<T,N>(e1,...,eN) : array<T, N> Construction of an array from elements
T is storable array<T,N>() : array<T, N> Zero-valued array (OpConstantNull)
Structure constructor type rules
Precondition Conclusion Notes
e1 : T1
...
eN : TN
T1 is storable
...
TN is storable
S is a type alias for a structure type with members having types T1 ... TN
S(e1,...,eN) : S Construction of a structure from members
e1 : T1
...
eN : TN
T1 is storable
...
TN is storable
S is a type alias for a structure type with members having types T1 ... TN
S() : S Zero-valued structure (OpConstantNull)
Unary operators
Precondition Conclusion Notes
e : T, T is Integral -e : T OpSNegate
e : T, T is Floating -e : T OpFNegate
e : bool !e : bool OpLogicalNot
e : BoolVec any(e) : bool OpAny
e : BoolVec all(e) : bool OpAll
e : f32 is_nan(e) : bool OpIsNan
e : T, T is FloatVec is_nan(e) : bool<N>, where N = Arity(T) OpIsNan
e : f32 is_inf(e) : bool OpIsInf
e : T, T is FloatVec is_inf(e) : bool<N>, where N = Arity(T) OpIsInf
e : f32 is_finite(e) : bool OpIsFinite
e : T, T is FloatVec is_finite(e) : bool<N>, where N = Arity(T) OpIsFinite
e : f32 is_normal(e) : bool OpIsNormal
e : T, T is FloatVec is_normal(e) : bool<N>, where N = Arity(T) OpIsNormal

(dneto): remaining unary operators

(dneto): Bitwise-complement is under discussion. https://github.com/gpuweb/gpuweb/pull/727

6.4. Binary expression type rules

Binary arithmetic expressions over scalars
Precondition Conclusion Notes
e1 : u32
e2 : u32
e1 + e2 : u32 Integer addition, modulo 232 (OpIAdd)
e1 : i32
e2 : i32
e1 + e2 : i32 Integer addition, modulo 232 (OpIAdd)
e1 : f32
e2 : f32
e1 + e2 : f32 Floating point addition (OpFAdd)
e1 : u32
e2 : u32
e1 - e2 : u32 Integer subtraction, modulo 232 (OpISub)
e1 : i32
e2 : i32
e1 - e2 : i32 Integer subtraction, modulo 232 (OpISub)
e1 : f32
e2 : f32
e1 - e2 : f32 Floating point subtraction (OpFSub)
e1 : u32
e2 : u32
e1 * e2 : u32 Integer multiplication, modulo 232 (OpIMul)
e1 : i32
e2 : i32
e1 * e2 : i32 Integer multiplication, modulo 232 (OpIMul)
e1 : f32
e2 : f32
e1 * e2 : f32 Floating point multiplication (OpFMul)
e1 : u32
e2 : u32
e1 / e2 : u32 Unsigned integer division (OpUDiv)
e1 : i32
e2 : i32
e1 / e2 : i32 Signed integer division (OpSDiv)
e1 : f32
e2 : f32
e1 / e2 : f32 Floating point division (OpFAdd)
e1 : u32
e2 : u32
e1 % e2 : u32 Unsigned integer modulus (OpUMod)
e1 : i32
e2 : i32
e1 % e2 : i32 Signed integer remainder, where sign of non-zero result matches sign of e2 (OpSMod)
e1 : f32
e2 : f32
e1 % e2 : f32 Floating point modulus, where sign of non-zero result matches sign of e2 (OpFMod)
Binary arithmetic expressions over vectors
Precondition Conclusion Notes
e1 : T
e2 : T
T is IntVec
e1 + e2 : T Component-wise integer addition (OpIAdd)
e1 : T
e2 : T
T is FloatVec
e1 + e2 : T Component-wise floating point addition (OpIAdd)
e1 : T
e2 : T
T is IntVec
e1 - e2 : T Component-wise integer subtraction (OpISub)
e1 : T
e2 : T
T is FloatVec
e1 - e2 : T Component-wise floating point subtraction (OpISub)
e1 : T
e2 : T
T is IntVec
e1 * e2 : T Component-wise integer multiplication (OpIMul)
e1 : T
e2 : T
T is FloatVec
e1 * e2 : T Component-wise floating point multiplication (OpIMul)
e1 : T
e2 : T
T is IntVec with unsigned component
e1 / e2 : T Component-wise unsigned integer division (OpUDiv)
e1 : T
e2 : T
T is IntVec with signed component
e1 / e2 : T Component-wise signed integer division (OpSDiv)
e1 : T
e2 : T
T is FloatVec
e1 / e2 : T Component-wise floating point division (OpFDiv)
e1 : T
e2 : T
T is IntVec with unsigned component
e1 % e2 : T Component-wise unsigned integer modulus (OpUMod)
e1 : T
e2 : T
T is IntVec with signed component
e1 % e2 : T Component-wise signed integer remainder (OpSMod)
e1 : T
e2 : T
T is FloatVec
e1 % e2 : T Component-wise floating point modulus (OpFMod)
Binary arithmetic expressions with mixed scalar, vector, and matrix operands
Precondition Conclusion Notes
e1 : f32
e2 : T
T is FloatVec
e1 * e2 : T
e2 * e1 : T
Multiplication of a vector and a scalar (OpVectorTimesScalar)
e1 : f32
e2 : T
T is matNxM<f32>
e1 * e2 : T
e2 * e1 : T
Multiplication of a matrix and a scalar (OpMatrixTimesScalar)
e1 : vecM<f32>
e2 : matNxM<f32>
e1 * e2 : vecN<f32>
Vector times matrix (OpVectorTimesMatrix)
e1 : matNxM<f32>
e2 : vecN<f32>
e1 * e2 : vecM<f32>
Matrix times vector (OpMatrixTimesVector)
e1 : matKxN<f32>
e2 : matMxK<f32>
e1 * e2 : matMxN<f32>
Matrix times matrix (OpMatrixTimesMatrix)
Bit shift expressions
Precondition Conclusion Notes
e1 : T
e2 : u32
T is Int
e1 << e2 : T Shift left:
Shift e1 left, inserting zero bits at the least significant positions, and discarding the most significant bits. The number of bits to shift is the value of e2 modulo the bit width of e1.
(OpShiftLeftLogical)
e1 : vecN<T>
e2 : vecN<u32>
T is Int
e1 << e2 : vecN<T> Component-wise shift left:
Component i of the result is (e1[i] <<e2[i])
(OpShiftLeftLogical)
e1 : u32
e2 : u32
e1 >> e2 : u32 Logical shift right:
Shift e1 right, inserting zero bits at the most significant positions, and discarding the least significant bits. The number of bits to shift is the value of e2 modulo the bit width of e1. (OpShiftRightLogical)
e1 : vecN<u32>
e2 : u32
e1 >> e2 : vecN<u32> Component-wise logical shift right:
Component i of the result is (e1[i] >>e2[i]) (OpShiftRightLogical)
e1 : i32
e2 : u32
e1 >> e2 : i32 Arithmetic shift right:
Shift e1 right, copying the sign bit of e1 into the most significant positions, and discarding the least significant bits. The number of bits to shift is the value of e2 modulo the bit width of e1. (OpShiftRightArithmetic)
e1 : vecN<i32>
e2 : vecN<u32>
e1 >> e2 : vecN<i32> Component-wise arithmetic shift right:
Component i of the result is (e1[i] >>e2[i]) (OpShiftRightArithmetic)
Binary bitwise operations
Precondition Conclusion Notes
e1 : T
e2 : T
T is Integral
e1 | e2 : T Bitwise-or
e1 : T
e2 : T
T is Integral
e1 & e2 : T Bitwise-and
e1 : T
e2 : T
T is Integral
e1 ^ e2 : T Bitwise-exclusive-or
Comparisons over scalars
Precondition Conclusion Notes
e1 : bool
e2 : bool
e1 == e2 : bool Equality (OpLogicalEqual)
e1 : bool
e2 : bool
e1 != e2 : bool Inequality (OpLogicalNotEqual)
e1 : i32
e2 : i32
e1 == e2 : bool Equality (OpIEqual)
e1 : i32
e2 : i32
e1 != e2 : bool Inequality (OpINotEqual)
e1 : i32
e2 : i32
e1 < e2 : bool Less than (OpSLessThan)
e1 : i32
e2 : i32
e1 <= e2 : bool Less than or equal (OpSLessThanEqual)
e1 : i32
e2 : i32
e1 >= e2 : bool Greater than or equal (OpSGreaterThanEqual)
e1 : i32
e2 : i32
e1 > e2 : bool Greater than or equal (OpSGreaterThan)
e1 : u32
e2 : u32
e1 == e2 : bool Equality (OpIEqual)
e1 : u32
e2 : u32
e1 != e2 : bool Inequality (OpINotEqual)
e1 : u32
e2 : u32
e1 < e2 : bool Less than (OpULessThan)
e1 : u32
e2 : u32
e1 <= e2 : bool Less than or equal (OpULessThanEqual)
e1 : u32
e2 : u32
e1 >= e2 : bool Greater than or equal (OpUGreaterThanEqual)
e1 : u32
e2 : u32
e1 > e2 : bool Greater than or equal (OpUGreaterThan)
e1 : f32
e2 : f32
e1 == e2 : bool Equality (OpFOrdEqual)
e1 : f32
e2 : f32
e1 != e2 : bool Equality (OpFOrdNotEqual)
e1 : f32
e2 : f32
e1 < e2 : bool Less than (OpFOrdLessThan)
e1 : f32
e2 : f32
e1 <= e2 : bool Less than or equal (OpFOrdLessThanEqual)
e1 : f32
e2 : f32
e1 >= e2 : bool Greater than or equal (OpFOrdGreaterThanEqual)
e1 : f32
e2 : f32
e1 > e2 : bool Greater than or equal (OpFOrdGreaterThan)
Comparisons over vectors
Precondition Conclusion Notes
e1 : T
e2 : T
T is vecN<bool>
e1 == e2 : vecN<bool> Component-wise equality
Component i of the result is (e1[i] ==e2[i])
(OpLogicalEqual)
e1 : T
e2 : T
T is vecN<bool>
e1 != e2 : vecN<bool> Component-wise inequality
Component i of the result is (e1[i] !=e2[i])
(OpLogicalNotEqual)
e1 : T
e2 : T
T is vecN<i32>
e1 == e2 : vecN<bool> Component-wise equality (OpIEqual)
e1 : T
e2 : T
T is vecN<i32>
e1 != e2 : vecN<bool> Component-wise inequality (OpINotEqual)
e1 : T
e2 : T
T is vecN<i32>
e1 < e2 : vecN<bool> Component-wise less than (OpSLessThan)
e1 : T
e2 : T
T is vecN<i32>
e1 <= e2 : vecN<bool> Component-wise less than or equal (OpSLessThanEqual)
e1 : T
e2 : T
T is vecN<i32>
e1 >= e2 : vecN<bool> Component-wise greater than or equal (OpSGreaterThanEqual)
e1 : T
e2 : T
T is vecN<i32>
e1 > e2 : vecN<bool> Component-wise greater than or equal (OpSGreaterThan)
e1 : T
e2 : T
T is vecN<u32>
e1 == e2 : vecN<bool> Component-wise equality (OpIEqual)
e1 : T
e2 : T
T is vecN<u32>
e1 != e2 : vecN<bool> Component-wise inequality (OpINotEqual)
e1 : T
e2 : T
T is vecN<u32>
e1 < e2 : vecN<bool> Component-wise less than (OpULessThan)
e1 : T
e2 : T
T is vecN<u32>
e1 <= e2 : vecN<bool> Component-wise less than or equal (OpULessThanEqual)
e1 : T
e2 : T
T is vecN<u32>
e1 >= e2 : vecN<bool> Component-wise greater than or equal (OpUGreaterThanEqual)
e1 : T
e2 : T
T is vecN<u32>
e1 > e2 : vecN<bool> Component-wise greater than or equal (OpUGreaterThan) T is vecN<u32>
e1 : T
e2 : T
T is vecN<f32>
e1 == e2 : vecN<bool> Component-wise equality (OpFOrdEqual)
e1 : T
e2 : T
T is vecN<f32>
e1 != e2 : vecN<bool> Component-wise inequality (OpFOrdNotEqual)
e1 : T
e2 : T
T is vecN<f32>
e1 < e2 : vecN<bool> Component-wise less than (OpFOrdLessThan)
e1 : T
e2 : T
T is vecN<f32>
e1 <= e2 : vecN<bool> Component-wise less than or equal (OpFOrdLessThanEqual)
e1 : T
e2 : T
T is vecN<f32>
e1 >= e2 : vecN<bool> Component-wise greater than or equal (OpFOrdGreaterThanEqual)
e1 : T
e2 : T
T is vecN<f32>
e1 > e2 : vecN<bool> Component-wise greater than or equal (OpFOrdGreaterThan)
Binary logical expressions
Precondition Conclusion Notes
e1 : bool
e2 : bool
e1 || e2 : bool Short-circuiting "or". Yields true if either e1 or e2 are true; evaluates e2 only if e1 is false.
e1 : bool
e2 : bool
e1 && e2 : bool Short-circuiting "and". Yields true if both e1 and e2 are true; evaluates e2 only if e1 is true.
e1 : bool
e2 : bool
e1 | e2 : bool Logical "or". Evaluates both e1 and e2; yields true if either are true.
e1 : bool
e2 : bool
e1 & e2 : bool Logical "and". Evaluates both e1 and e2; yields true if both are true.
e1 : T
e2 : T
T is BoolVec
e1 | e2 : T Component-wise logical "or"
e1 : T
e2 : T
T is BoolVec
e1 & e2 : T Component-wise logical "and"

7. Built-in functions

Certain functions are always available in a WGSL program, and are provided by the implementation. These are called built-in functions.

Since a built-in function is always in scope, it is an error to attempt to redefine one or to use the name of a built-in function as an identifier for any other kind of declaration.

Unlike ordinary functions defined in a WGSL program, a built-in function may use the same function name with different sets of parameters. In other words, a built-in function may have more than one overload, but ordinary function definitions in WGSL may not.

When calling a built-in function, all arguments to the function are evaluated before function evaulation begins.

TODO(dneto): Elaborate the descriptions of the built-in functions. So far I’ve only reorganized the contents of the existing table.

7.1. Logical built-in functions

Logical built-in functions SPIR-V
all(BoolVec) -> bool OpAll
any(BoolVec) -> bool OpAny
select(T,T,bool) -> T For scalar or vector type T. select(a,b,c) evaluates to a when c is true, and b otherwise.
OpSelect
select(vecN<T>,vecN<T>,vecN<bool>) -> vecN<T> For scalar type T. select(a,b,c) evaluates to a vector with component i being select(a[i], b[i], c[i]).
OpSelect

7.2. Value-testing built-in functions

Value-testing built-in functions SPIR-V
is_finite(float) -> bool OpIsFinite
is_inf(float) -> bool OpIsInf
is_nan(float) -> bool OpIsNan
is_normal(float) -> bool OpIsNormal

7.3. Vector built-in functions

Vector built-in functions SPIR-V
dot(vecN<f32>, vecN<f32>) -> float OpDot
outer_product(vecN<f32>, vecM<f32>) -> matNxM<f32> OpOuterProduct

7.4. Derivative built-in functions

Derivative built-in functions SPIR-V
dpdx(IDENT) -> float OpDPdx
dpdx_coarse(IDENT) -> float OpDPdxCoarse
dpdx_fine(IDENT) -> float OpDPdxFine
dpdy(IDENT) -> float OpDPdy
dpdy_coarse(IDENT) -> float OpDPdyCoarse
dpdy_fine(IDENT) -> float OpDPdyFine
fwidth(IDENT) -> float OpFwidth
fwidth_coarse(IDENT) -> float OpFwidthCoarse
fwidth_fine(IDENT) -> float OpFwidthFine

7.5. Texture built-in functions

vec4<type> texture_load(texture_ro, i32 coords, i32 level_of_detail)
vec4<type> texture_load(texture_ro, vec2<i32> coords, i32 level_of_detail)
vec4<type> texture_load(texture_ro, vec3<i32> coords, i32 level_of_detail)
  %32 = OpImageFetch %v4float %texture_ro %coords Lod %level_of_detail

TODO(dsinclair): Texture load on a sampled texture

TODO(dsinclair): Texture load on multi-sampled texture

TODO(dsinclair): Add texture_write method for texture_wo with integral coords

TODO(dsinclair): Allow a small constant offset on the coordinate? May not be portable.

vec4<type> texture_sample(texture_sampled, sampler, f32 coords)
vec4<type> texture_sample(texture_sampled, sampler, vec2<f32> coords)
vec4<type> texture_sample(texture_sampled, sampler, vec3<f32> coords)
vec4<type> texture_sample(texture_sampled, sampler, vec4<f32> coords)
  %24 = OpImageSampleImplicitLod %v4float %sampled_image %coords

vec4<type> texture_sample_level(texture_sampled, sampler, f32 coords, f32 lod)
vec4<type> texture_sample_level(texture_sampled, sampler, vec2<f32> coords, f32 lod)
vec4<type> texture_sample_level(texture_sampled, sampler, vec3<f32> coords, f32 lod)
vec4<type> texture_sample_level(texture_sampled, sampler, vec4<f32> coords, f32 lod)
  %25 = OpImageSampleExplicitLod %v4float %sampled_image %coords Lod %lod

vec4<type> texture_sample_bias(texture_sampled, sampler, f32 coords, f32 bias)
vec4<type> texture_sample_bias(texture_sampled, sampler, vec2<f32> coords, f32 bias)
vec4<type> texture_sample_bias(texture_sampled, sampler, vec3<f32> coords, f32 bias)
vec4<type> texture_sample_bias(texture_sampled, sampler, vec4<f32> coords, f32 bias)
  %19 = OpImageSampleImplicitLod %v4float %sampled_image %coords Bias %bias

f32 texture_sample_compare(texture_depth, sampler_comparison, f32 coords, f32 depth_reference)
f32 texture_sample_compare(texture_depth, sampler_comparison, vec2<f32> coords, f32 depth_reference)
f32 texture_sample_compare(texture_depth, sampler_comparison, vec3<f32> coords, f32 depth_reference)
  %65 = OpImageSampleDrefImplicitLod %float %sampled_image %coord %depth_reference Lod %float_0

TODO(dsinclair): Add Level-of-Detail via explicit gradient. "Grad" image operand in SPIR-V

TODO(dsinclair): Need gather operations

8. Glossary

Term Definition
Dominates Basic block A dominates basic block B if:
  • A and B are both in the same function F

  • Every control flow path in F that goes to B must also to through A

Strictly dominates A strictly dominates B if A dominates B and A != B
DomBy(A) The basic blocks dominated by A

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

References

Normative References

[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119

Issues Index

(dneto): Complete description of Array<E,N>
(dneto): the last element of a struct defining the contents of a storage buffer.
(dneto) It feels like this needs some reorganization. Perhaps "Evaluation and Execution" between § 2 Formal Type Definitions and § 4 Grammar.
(dneto) also lifetime.
(dsinclair) Write out precedence rules. Matches c and glsl rules ....
What happens is the application supplies a constant ID that is not in the program? Proposal: pipeline creation fails with an error.
The WebGPU pipeline creation API must specify how API-supplied values are mapped to shader scalar values. For booleans, I suggest using a 32-bit integer, where only 0 maps to false. If WGSL gains non-32-bit numeric scalars, I recommend overridable constants continue being 32-bit numeric types.
(dneto): MatrixStride, RowMajor, ColMajor layout decorations are needed for matrices.
(dneto) complete
(dneto): Do we have to explicitly list the type environment Gamma? That’s confusing to newcomers.
(dneto): remaining unary operators
(dneto): Bitwise-complement is under discussion. https://github.com/gpuweb/gpuweb/pull/727