WebGPU Shading Language

Editor’s Draft,

More details about this document
This version:
https://gpuweb.github.io/gpuweb/wgsl/
https://github.com/gpuweb/gpuweb/blob/5ce21beb8e03644535b2cb8730546fd508c73e47/wgsl/index.bs
Latest published version:
https://www.w3.org/TR/WGSL/
Feedback:
[email protected] with subject line “[WGSL] … message topic …” (archives)
GitHub
Editors:
(Google)
(Google)
Former Editors:
(Apple Inc.)
(Google)
Participate:
File an issue (open issues)
Test Suite:
WebGPU CTS shader/

Abstract

Shading language for WebGPU.

Status of this document

This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C standards and drafts index.

Feedback and comments on this specification are welcome. GitHub Issues are preferred for discussion on this specification. Alternatively, you can send comments to the GPU for the Web Working Group’s mailing-list, [email protected] (archives). This draft highlights some of the pending issues that are still to be discussed in the working group. No decision has been taken on the outcome of these issues including whether they are valid.

This document was published by the GPU for the Web Working Group as an Editor’s Draft.

The group expects to demonstrate implementation of each feature in at least two deployed browsers on top of modern GPU system APIs. The test suite will be used to build an implementation report.

This document is maintained and updated at any time. Some parts of this document are work in progress.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent that the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 18 August 2025 W3C Process Document.

1. Introduction

WebGPU Shading Language (WGSL) is the shader language for [WebGPU]. That is, an application using the WebGPU API uses WGSL to express the programs, known as shaders, that run on the GPU.

// A fragment shader which lights textured geometry with point lights.

// Lights from a storage buffer binding.
struct PointLight {
  position : vec3f,
  color : vec3f,
}

struct LightStorage {
  pointCount : u32,
  point : array<PointLight>,
}
@group(0) @binding(0) var<storage> lights : LightStorage;

// Texture and sampler.
@group(1) @binding(0) var baseColorSampler : sampler;
@group(1) @binding(1) var baseColorTexture : texture_2d<f32>;

// Function arguments are values from the vertex shader.
@fragment
fn fragmentMain(@location(0) worldPos : vec3f,
                @location(1) normal : vec3f,
                @location(2) uv : vec2f) -> @location(0) vec4f {
  // Sample the base color of the surface from a texture.
  let baseColor = textureSample(baseColorTexture, baseColorSampler, uv);

  let N = normalize(normal);
  var surfaceColor = vec3f(0);

  // Loop over the scene point lights.
  for (var i = 0u; i < lights.pointCount; i++) {
    let worldToLight = lights.point[i].position - worldPos;
    let dist = length(worldToLight);
    let dir = normalize(worldToLight);

    // Determine the contribution of this light to the surface color.
    let radiance = lights.point[i].color * (1 / pow(dist, 2));
    let nDotL = max(dot(N, dir), 0);

    // Accumulate light contribution to the surface color.
    surfaceColor += baseColor.rgb * radiance * nDotL;
  }

  // Return the accumulated surface color.
  return vec4(surfaceColor, baseColor.a);
}

1.1. Overview

WebGPU issues a unit of work to the GPU in the form of a GPU command. WGSL is concerned with two kinds of GPU commands:

Both kinds of pipelines use shaders written in WGSL.

A shader is the portion of a WGSL program that executes a shader stage in a pipeline. A shader comprises:

Note: A WGSL program does not require an entry point; however, such a program cannot be executed by the API because an entry point is required to create a GPUProgrammableStage.

When executing a shader stage, the implementation:

A WGSL program is organized into:

Note: A WGSL program is currently composed of a single WGSL module.

WGSL is an imperative language: behavior is specified as a sequence of statements to execute. Statements can:

WGSL is statically typed: each value computed by a particular expression is in a specific type, determined only by examining the program source.

WGSL has types describing booleans and numbers (integers and floating point). These types can be aggregated into composites (vectors, matrices, arrays, and structures). WGSL has special types (e.g. atomics) that provide unique operations. WGSL describes the types that can be stored in memory as memory views. WGSL provides commonly used rendering types in the form of textures and samplers. These types have associated built-in functions to expose commonly provided GPU hardware for graphics rendering.

WGSL does not have implicit conversions or promotions from concrete types, but does provide implicit conversions and promotions from abstract types. Converting a value from one concrete numeric or boolean type to another requires an explicit conversion, value constructor, or reinterpretation of bits; however, WGSL does provide some limited facility to promote scalar types to vector types. This also applies to composite types.

The work of a shader stage is partitioned into one or more invocations, each of which executes the entry point, but under slightly different conditions. Invocations in a shader stage share access to certain variables:

However, the invocations act on different sets of shader stage inputs, including built-in inputs that provide an identifying value to distinguish an invocation from its peers. Each invocation has its own independent memory space in the form of variables in the private and function address spaces.

Invocations within a shader stage execute concurrently, and may often execute in parallel. The shader author is responsible for ensuring the dynamic behavior of the invocations in a shader stage:

WGSL sometimes permits several possible behaviors for a given feature. This is a portability hazard, as different implementations may exhibit the different behaviors. The design of WGSL aims to minimize such cases, but is constrained by feasibility, and goals for achieving high performance across a broad range of devices.

Behavioral requirements are actions the implementation will perform when processing or executing a WGSL program. They describe the implementation’s obligations in the contract with the programmer. The specification explicitly states these obligations when they might not be otherwise obvious.

1.2. Syntax Notation

Following syntax notation describes the conventions of the syntactic grammar of WGSL:

1.3. Mathematical Terms and Notation

Angles:

A hyperbolic angle is a unitless area, not an angle in the traditional sense. Specifically:

Then the area a is a hyperbolic angle such that x is the hyperbolic cosine of a, and y is the hyperbolic sine of a.

Positive infinity, denoted by +∞, is a unique value strictly greater than all real numbers.

Negative infinity, denoted by −∞, is a unique value strictly lower than all real numbers.

The extended real numbers (also known as the affinely extended real numbers) is the set of real numbers together with +∞ and −∞. Computers use floating point types to approximately represent the extended reals, including values for both infinities. See § 15.7 Floating Point Evaluation.

An interval is a contiguous set of numbers with a lower and upper bound. Depending on context, they are sets of integers, floating point numbers, real numbers, or extended real numbers.

The floor expression is defined for extended real numbers x:

The ceiling expression is defined for extended real numbers x:

The truncate function is defined for extended real numbers x:

The roundUp function is defined for positive integers k and n as:

The transpose of an c-column r-row matrix A is the r-column c-row matrix AT formed by copying the rows of A as the columns of AT:

The transpose of a column vector is defined by interpreting the column vector as a 1-row matrix. Similarly, the transpose of a row vector is defined by interpreting the row vector as a 1-column matrix.

2. WGSL Module

A WGSL program is composed of a single WGSL module.

A module is a sequence of optional directives followed by module scope declarations and assertions. A module is organized into:

translation_unit :

global_directive * ( global_decl | global_assert | ';' ) *

global_decl :

global_variable_decl ';'

| global_value_decl ';'

| type_alias_decl ';'

| struct_decl

| function_decl

2.1. Shader Lifecycle

There are four key events in the lifecycle of a WGSL program and the shaders it may contain. The first two correspond to the WebGPU API methods used to prepare a WGSL program for execution. The last two are the start and end of execution of a shader.

The events are:

  1. Shader module creation

    • This occurs when the WebGPU createShaderModule() method is called. The source text for a WGSL program is provided at this time.

  2. Pipeline creation

    • This occurs when the WebGPU createComputePipeline() method or the WebGPU createRenderPipeline() method is invoked. These methods use one or more previously created shader modules, together with other configuration information.

    • Only the code forming the shader of the specified entry point of the GPUProgrammableStage is considered during pipeline creation. That is, code unrelated to the entry point is effectively stripped before compilation.

    • Note: Each shader stage is considered to be compiled separately and, thus, might include different portions of the module.

  3. Shader execution start

  4. Shader execution end

    • This occurs when all work in the shader completes:

      • all its invocations terminate, and

      • all accesses to resources complete, and

      • outputs, if any, are passed to downstream pipeline stages.

The events are ordered due to:

2.2. Errors

A WebGPU implementation may fail to process a shader for two reasons:

A processing error may occur during three phases in the shader lifecycle:

Note: For example, a data race may not be detectable.

Each requirement will be checked at the earliest opportunity. That is:

When unclear from context, this specification indicates whether failure to meet a particular requirement results in a shader-creation, pipeline-creation, or dynamic error.

The consequences of an error are as follows:

2.3. Diagnostics

An implementation can generate diagnostics during shader module creation or pipeline creation. A diagnostic is a message produced by the implementation for the benefit of the application author.

A diagnostic is created, or triggered, when a particular condition is met, known as the triggering rule. The place in the source text where the condition is met, expressed as a point or range in the source text, is known as the triggering location.

A diagnostic has the following properties:

The severity of a diagnostic is one of the following, ordered from greatest to least:

error

The diagnostic is an error. This corresponds to a shader-creation error or to a pipeline-creation error.

warning

The diagnostic describes an anomaly that merits the attention of the application developer, but is not an error.

info

The diagnostic describes a notable condition that merits attention of the application developer, but is not an error or warning.

off

The diagnostic is disabled. It will not be conveyed to the application.

The name of a triggering rule is either:

diagnostic_rule_name :

diagnostic_name_token

| diagnostic_name_token '.' diagnostic_name_token

2.3.1. Diagnostic Processing

Triggered diagnostics will be processed as follows:

  1. For each diagnostic D, find the diagnostic filter with the smallest affected range that contains D’s triggering location, and which has the same triggering rule.

    • If such a filter exists, apply it to D, updating D's severity.

    • Otherwise D remains unchanged.

  2. Discard diagnostics that have severity off.

  3. If at least one remaining diagnostic DI has severity info, then:

    • Other info diagnostics with same triggering rule may be discarded, leaving only the original diagnostic DI.

  4. If at least one remaining diagnostic DW has severity warning, then:

    • Other info or warning diagnostics with same triggering rule may be discarded, leaving only the original diagnostic DW.

  5. If at least one remaining diagnostic has error severity, then:

  6. If processing during shader module creation time, the remaining diagnostics populate the messages member of the WebGPU GPUCompilationInfo object.

  7. If processing during pipeline creation, error diagnostics result in WebGPU validation failure when validating GPUProgrammableStage.

Note: The rules allow an implementation to stop processing a WGSL module as soon as an error is detected. Additionally, an analysis for a particular kind of warning can stop on the first warning, and an analysis for a particular kind of info diagnostic can stop on the first occurrence. WGSL does not specify the order to perform different kinds of analyses, or an ordering within a single analysis. Therefore, for the same WGSL module, different implementations may report different instances of diagnostics with the same severity.

2.3.2. Filterable Triggering Rules

Most diagnostics are unconditionally reported to the WebGPU application. Some kinds of diagnostics can be filtered, in part by naming their triggering rule. The following table lists the standard set of triggering rules that can be filtered.

Filterable diagnostic triggering rules
Filterable Triggering Rule Default Severity Triggering Location Description
derivative_uniformity error The location of the call site for any builtin function that computes a derivative. That is, the location of a call to any of: A call to a builtin function computes derivatives, but uniformity analysis cannot prove that the call occurs in uniform control flow.

See § 15.2 Uniformity.

subgroup_uniformity error The location of the call site for any subgroup or quad built-in function. A call to a subgroup or quad builtin function, but uniformity analysis cannot prove that the call occurs in uniform control flow. Additionally, when uniformity analysis cannot prove that the following parameter values are uniform:

See § 15.2 Uniformity.

Using an unrecognized triggering rule consisting of a single diagnostic name-token should trigger a warning from the user agent.

An implementation may support triggering rules not specified here, provided they are spelled using the multiple-token form of diagnostic_rule_name. Using an unrecognized triggering rule spelled in the multiple-token form may itself trigger a diagnostic.

Future versions of this specification may remove a particular rule or weaken its default severity (i.e. replace its current default with a less severe default) and still be deemed as satisfying backward compatibility. For example, a future version of WGSL may change the default severity for derivative_uniformity from error to either warning or info. After such a change to the specification, previously valid programs would remain valid.

2.3.3. Diagnostic Filtering

Once a diagnostic with a filterable triggering rule is triggered, WGSL provides mechanisms to discard the diagnostic, or to modify its severity.

A diagnostic filter DF has three parameters:

Applying a diagnostic filter DF(AR,NS,TR) to a diagnostic D has the following effect:

A range diagnostic filter is a diagnostic filter whose affected range is a specified range of source text. A range diagnostic filter is specified as a @diagnostic attribute at the start of the affected source range, as specified in the following table. A @diagnostic attribute must not appear anywhere else.

Placement of a range diagnostic filter
Placement Affected Range
Beginning of a compound statement. The compound statement.
Beginning of a function declaration. The function declaration.
Beginning of an if statement. The if statement: the if_clause and all associated else_if_clause and else_clause clauses, including all controlling condition expressions.
Beginning of a switch statement. The switch statement: the selector expression and the switch_body.
Beginning of a switch_body. The switch_body.
Beginning of a loop statement. The loop statement.
Beginning of a while statement. The while statement: both the condition expression and the loop body.
Beginning of a for statement. The for statement: the for_header and the loop body.
Immediately before the opening brace ('{') of the loop body of a loop, while, or for loop. The loop body.
Beginning of a continuing_compound_statement. The continuing_compound_statement.

Note: The following are also compound statements: a function body, a case clause, a default-alone clause, the bodies of while and for loops, and the bodies of if_clause, else_if_clause, and else_clause.

EXAMPLE: Range diagnostic filter on texture sampling
var<private> d: f32;
fn helper() -> vec4<f32> {
  // Disable the derivative_uniformity diagnostic in the
  // body of the "if".
  if (d < 0.5) @diagnostic(off,derivative_uniformity) {
    return textureSample(t,s,vec2(0,0));
  }
  return vec4(0.0);
}

A global diagnostic filter can be used to apply a diagnostic filter to the entire WGSL module.

EXAMPLE: Global diagnostic filter for derivative uniformity
diagnostic(off,derivative_uniformity);
var<private> d: f32;
fn helper() -> vec4<f32> {
  if (d < 0.5) {
    // The derivative_uniformity diagnostic is disabled here
    // by the global diagnostic filter.
    return textureSample(t,s,vec2(0,0));
  } else {
    // The derivative_uniformity diagnostic is set to 'warning' severity.
    @diagnostic(warning,derivative_uniformity) {
      return textureSample(t,s,vec2(0,0));
    }
  }
  return vec4(0.0);
}

Two diagnostic filters DF(AR1,NS1,TR1) and DF(AR2,NS2,TR2) conflict when:

Diagnostic filters must not conflict.

Note: Multiple global diagnostic filters are permitted when they do not conflict.

WGSL’s diagnostic filters are designed so their affected ranges nest perfectly. If the affected range of DF1 overlaps with the affected range of DF2, then either DF1’s affected range is fully contained in DF2’s affected range, or the other way around.

The nearest enclosing diagnostic filter for source location L and triggering rule TR, if one exists, is the diagnostic filter DF(AR,NS,TR) where:

Because affected ranges nest, the nearest enclosing diagnostic:

2.4. Limits

A WGSL implementation will support shaders that satisfy the following limits. A WGSL implementation may support shaders that go beyond the specified limits.

Note: A WGSL implementation should issue an error if it does not support a shader that goes beyond the specified limits.

Quantifiable shader complexity limits
Limit Minimum supported value
Maximum number of members in a structure type 1023
Maximum nesting depth of a composite type 15
Maximum nesting depth of brace-enclosed statements in a function 127
Maximum number of parameters for a function 255
Maximum number of case selector values in a switch statement. The sum of, for each case statement, the number of case values, including the default clause. 1023
Maximum combined byte-size of all variables instantiated in the private address space that are statically accessed by a single shader 8192
Maximum combined byte-size of all variables instantiated in the function address space that are declared in a single function 8192
Maximum combined byte-size of all variables instantiated in the workgroup address space that are statically accessed by a single shader

For the purposes of this limit, a fixed-footprint array is treated as a creation-fixed footprint array when substituting the override value.

This maps the WebGPU maxComputeWorkgroupStorageSize limit into a standalone WGSL limit.

16384
Maximum number of elements in value constructor expression of array type 2047

3. Textual Structure

The text/wgsl media type is used to identify content as a WGSL module. See Appendix A: The text/wgsl Media Type.

A WGSL module is Unicode text using the UTF-8 encoding, with no byte order mark (BOM).

WGSL module text consists of a sequence of Unicode code points, grouped into contiguous non-empty sets forming:

The program text must not include a null code point (U+0000).

3.1. Parsing

To parse a WGSL module:

  1. Remove comments:

    • Replace the first comment with a space code point (U+0020).

    • Repeat until no comments remain.

  2. Find template lists, using the algorithm in § 3.9 Template Lists.

  3. Parse the whole text, attempting to match the translation_unit grammar rule. Parsing uses a LALR(1) parser (one token of lookahead) [DeRemer1969], with the following customization:

    • Tokenization is interleaved with parsing, and is context-aware. When the parser requests the next token:

A shader-creation error results if:

3.2. Blankspace and Line Breaks

Blankspace is any combination of one or more of code points from the Unicode Pattern_White_Space property. The following is the set of code points in Pattern_White_Space:

A line break is a contiguous sequence of blankspace code points indicating the end of a line. It is defined as the blankspace signalling a "mandatory break" as defined by UAX14 Section 6.1 Non-tailorable Line Breaking Rules LB4 and LB5. That is, a line break is any of:

Note: Diagnostics that report source text locations in terms of line numbers should use line breaks to count lines.

3.3. Comments

A comment is a span of text that does not influence the validity or meaning of a WGSL program, except that a comment can separate tokens. Shader authors can use comments to document their programs.

A line-ending comment is a kind of comment consisting of the two code points // (U+002F followed by U+002F) and the code points that follow, up until but not including:

A block comment is a kind of comment consisting of:

Note: Block comments can be nested. Since a block comment requires matching start and end text sequences, and allows arbitrary nesting, a block comment cannot be recognized with a regular expression. This is a consequence of the Pumping Lemma for Regular Languages.

EXAMPLE: Comments
const f = 1.5; // This is line-ending comment.
const g = 2.5; /* This is a block comment
                that spans lines.
                /* Block comments can nest.
                 */
                But all block comments must terminate.
               */

3.4. Tokens

A token is a contiguous sequence of code points forming one of:

3.5. Literals

A literal is one of:

literal :

int_literal

| float_literal

| bool_literal

3.5.1. Boolean Literals

EXAMPLE: boolean literals
const a = true;
const b = false;
bool_literal :

'true'

| 'false'

3.5.2. Numeric Literals

The form of a numeric literal is defined via pattern-matching.

An integer literal is:

Note: A leading zero on a non-zero integer literal (e.g. 012) is forbidden, so as to avoid confusion with other languages' leading-zero-means-octal notation.

int_literal :

decimal_int_literal

| hex_int_literal

decimal_int_literal :

/0[iu]?/

| /[1-9][0-9]*[iu]?/

EXAMPLE: decimal integer literals
const a = 1u;
const b = 123;
const c = 0;
const d = 0i;
hex_int_literal :

/0[xX][0-9a-fA-F]+[iu]?/

EXAMPLE: hexadecimal integer literals
const a = 0x123;
const b = 0X123u;
const c = 0x3f;

A floating point literal is either a decimal floating point literal or a hexadecimal floating point literal.

float_literal :

decimal_float_literal

| hex_float_literal

A floating point literal has two logical parts: a significand to represent a fraction, and an optional exponent. Roughly, the value of the literal is the significand multiplied by a base value raised to the given exponent. A significand digit is significant if it is non-zero, or if there are significand digits to its left and to its right that are both non-zero. Significant digits are counted from left-to-right: the N'th significant digit has N-1 significant digits to its left.

A decimal floating point literal is:

decimal_float_literal :

/0[fh]/

| /[1-9][0-9]*[fh]/

| /[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[fh]?/

| /[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?[fh]?/

| /[0-9]+[eE][+-]?[0-9]+[fh]?/

EXAMPLE: decimal floating point literals
const a = 0.e+4f;
const b = 01.;
const c = .01;
const d = 12.34;
const f = .0f;
const g = 0h;
const h = 1e-3;
The mathematical value of a decimal floating point literal is computed as follows:

Note: The decimal significand is truncated after 20 decimal digits, preserving approximately log(10)/log(2)×20 ≈ 66.4 significant bits in the fraction.

A hexadecimal floating point literal is:

hex_float_literal :

/0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+([pP][+-]?[0-9]+[fh]?)?/

| /0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*([pP][+-]?[0-9]+[fh]?)?/

| /0[xX][0-9a-fA-F]+[pP][+-]?[0-9]+[fh]?/

EXAMPLE: hexadecimal floating point literals
const a = 0xa.fp+2;
const b = 0x1P+4f;
const c = 0X.3;
const d = 0x3p+2h;
const e = 0X1.fp-4;
const f = 0x3.2p+2h;
The mathematical value of a hexadecimal floating point literal is computed as follows:

Note: The hexadecimal significand is truncated after 16 hexadecimal digits, preserving approximately 4 ×16 = 64 significant bits in the fraction.

When a numeric literal has a suffix, the literal denotes a value in a specific concrete scalar type. Otherwise, the literal denotes a value one of the abstract numeric types defined below. In either case, the value denoted by the literal is its mathematical value after conversion to the target type, following the rules in § 15.7.6 Floating Point Conversion.

Mapping numeric literals to types
Numeric Literal Suffix Type Examples
integer literal i i32 42i
integer literal u u32 42u
integer literal AbstractInt 124
floating point literal f f32 42f 1e5f 1.2f 0x1.0p10f
floating point literal h f16 42h 1e5h 1.2h 0x1.0p10h
floating point literal AbstractFloat 1e5 1.2 0x1.0p10

A shader-creation error results if:

Note: The hexadecimal float value 0x1.00000001p0 requires 33 significand bits to be represented exactly, but f32 only has 23 explicit significand bits.

Note: If you want to use an f suffix to force a hexadecimal float literal to be of type, the literal must also use a binary exponent. For example, write 0x1p0f. In comparison, 0x1f is a hexadecimal integer literal.

3.6. Keywords

A keyword is a token which refers to a predefined language concept. See § 16.1 Keyword Summary for the list of WGSL keywords.

3.7. Identifiers

An identifier is a kind of token used as a name. See § 5 Declaration and Scope.

WGSL uses two grammar nonterminals to separate use cases:

ident :

ident_pattern_token _disambiguate_template

member_ident :

ident_pattern_token

The form of an identifier is based on the Unicode Standard Annex #31 for Unicode Version 14.0.0, with the following elaborations.

Identifiers use the following profile described in terms of UAX31 Grammar:

<Identifier> := <Start> <Continue>* (<Medial> <Continue>+)*

<Start> := XID_Start + U+005F
<Continue> := <Start> + XID_Continue
<Medial> :=

This means identifiers with non-ASCII code points like these are valid: Δέλτα, réflexion, Кызыл, 𐰓𐰏𐰇, 朝焼け, سلام, 검정, שָׁלוֹם, गुलाबी, փիրուզ.

With the following exceptions:

ident_pattern_token :

/([_\p{XID_Start}][\p{XID_Continue}]+)|([\p{XID_Start}])/u

Unicode Character Database for Unicode Version 14.0.0 includes non-normative listing with all valid code points of both XID_Start and XID_Continue.

Note: The return type for some built-in functions are structure types whose name cannot be used WGSL source. Those structure types are described as if they were predeclared with a name starting with two underscores. The result value can be saved into newly declared let or var using type inferencing, or immediately have one of its members immediately extracted by name. See example usages in the description of frexp and modf.

3.7.1. Identifier Comparison

Two WGSL identifiers are the same if and only if they consist of the same sequence of code points.

Note: This specification does not permit Unicode normalization of values for the purposes of comparison. Values that are visually and semantically identical but use different Unicode character sequences will not match. Content authors are advised to use the same encoding sequence consistently or to avoid potentially troublesome characters when choosing values. For more information, see [CHARMOD-NORM].

Note: A user agent should issue developer-visible warnings when the meaning of a WGSL module would change if all instances of an identifier are replaced with one of that identifier’s homographs. (A homoglyph is a sequence of code points that may appear the same to a reader as another sequence of code points. Examples of mappings to detect homoglyphs are the transformations, mappings, and matching algorithms mentioned in the previous paragraph. Two sequences of code points are homographs if the identifier can transform one into the other by repeatedly replacing a subsequence with its homoglyph.)

3.8. Context-Dependent Names

A context-dependent name is a token used to name a concept, but only in specific grammatical contexts. The spelling of the token may be the same as an identifier, but the token does not resolve to a declared object. This section lists the tokens used as context-dependent names. The token must not be a keyword or reserved word.

3.8.1. Attribute Names

See § 12 Attributes.

The attribute names are:

3.8.2. Built-in Value Names

A built-in value name-token is a token used in the name of a built-in value.

See § 13.3.1.1 Built-in Inputs and Outputs.

builtin_value_name :

ident_pattern_token

The built-in value names are:

3.8.3. Diagnostic Rule Names

A diagnostic name-token is a token used in the name of a diagnostic triggering rule.

See § 2.3 Diagnostics.

diagnostic_name_token :

ident_pattern_token

The pre-defined diagnostic rule names are:

3.8.4. Diagnostic Severity Control Names

The valid diagnostic filter severity control names are listed in § 2.3 Diagnostics but have the same form as an identifier:

severity_control_name :

ident_pattern_token

The diagnostic filter severity control names are:

3.8.5. Extension Names

The valid enable-extension names are listed in § 4.1.1 Enable Extensions but in general have the same form as an identifier:

enable_extension_name :

ident_pattern_token

The enable-extension names are:

The valid language extension names are listed in § 4.1.2 Language Extensions but in general have the same form as an identifier:

language_extension_name :

ident_pattern_token

The language extension names are:

3.8.6. Interpolation Type Names

An interpolation type name-token is a token used in the name of an interpolation type for interpolate_type_name.

See § 13.3.1.4 Interpolation.

The interpolation type names are:

3.8.7. Interpolation Sampling Names

An interpolation sampling name-token is a token used in the name of an interpolation sampling.

See § 13.3.1.4 Interpolation.

interpolate_sampling_name :

ident_pattern_token

The interpolation sampling names are:

3.8.8. Swizzle Names

The swizzle names are used in vector access expressions:

swizzle_name :

/[rgba]/

| /[rgba][rgba]/

| /[rgba][rgba][rgba]/

| /[rgba][rgba][rgba][rgba]/

| /[xyzw]/

| /[xyzw][xyzw]/

| /[xyzw][xyzw][xyzw]/

| /[xyzw][xyzw][xyzw][xyzw]/

3.9. Template Lists

Template parameterization is a way to specify parameters that modify a general concept. To write a template parameterization, write the general concept, followed by a template list.

Ignoring comments and blankspace, a template list is:

The form of a template parameter is implicitly defined by the template list discovery algorithm below. Generally, they are names, expressions, or types.

Note: For example, the phrase vec3<f32> is a template parameterization where vec3 is the general concept being modified, and <f32> is a template list containing one parameter, the f32 type. Together, vec3<f32> denotes a specific vector type.

Note: For example, the phrase var<storage,read_write> modifies the general var concept with template parameters storage and read_write.

Note:For example, the phrase array<vec4<f32>> has two template parameterizations:

The '<' (U+003C) and '>' (U+003E) code points that delimit a template list are also used when spelling:

The syntactic ambiguity is resolved in favour of template lists:

The template list discovery algorithm is given below. It uses the following assumptions and properties:

  1. A template parameter is an expression, and therefore does not start with either a '<' (U+003C) or a '=' (U+003D) code point.

  2. An expression does not contain code points ';' (U+003B), '{' (U+007B), or ':' (U+003A).

  3. An expression does not contain an assignment.

  4. The only time a '=' (U+003D) code point appears is as part of a comparison operation, i.e. in one of '<=', '>=', '==', or '!='. Otherwise, a '=' (U+003D) code point appears as part of an assignment.

  5. Template list delimiters respect nested expressions formed by parentheses '(...)', and array indexing '[...]'. The start and end of a template list must appear at the same nesting level.

Algorithm: Template list discovery

Input: The program source text.

Record types:

Let UnclosedCandidate be a record type containing:

Let TemplateList be a record type containing:

Output: DiscoveredTemplateLists, a list of TemplateList records.

Procedure:

Note:The algorithm can be modified to find the source ranges for template parameters, as follows:

Note: The algorithm explicitly skips past literals because some numeric literals end in a letter, for example 1.0f. The terminating f should not be mistaken as the start of an ident_pattern_token.

Note: In the phrase A ( B < C, D > ( E ) ), the segment < C, D > is a template list.

Note: The algorithm respects expression nesting: The start and end of a particular template list cannot appear at different expression nesting levels. For example, in array<i32,select(2,3,a>b)>, the template list has three parameters, where the last one is select(2,3,a>b). The '>' in a>b does not terminate the template list because it is enclosed in a parenthesized part of the expression calling the select function.

Note: Both ends of a template list must appear within the same indexing expression. For example a[b<d]>() does not contain a valid template list.

Note: In the phrase A<B<<C>, the phrase B<<C is parsed as B followed by the left-shift operator '<<' followed by C. The template discovery algorithm starts examining B then '<' (U+003C) but then sees that the next '<' (U+003C) code point cannot start a template argument, and so the '<' immediately after the B is not the start of a template list. The initial '<' and final '>' are the only template list delimiters, and it has template parameter B<<C.

Note: The phrase A<B<=C> is analyzed similarly to the previous note, so the phrase B<=C is parsed as B followed by the less-than-or-equal operator '<=' followed by C. The template discovery algorithm starts examining B then '<' (U+003C) but then sees that the next '=' (U+003D) code point cannot start a template argument, and so the '<' immediately after the B is not the start of a template list. The initial '<' and final '>' are the only template list delimiters, and it has template parameter B<=C.

Note: When examining the phrase A<(B>=C)>, there is one template list, starting at the first '<' (U+003C) code point and ending at the last '>' (U+003E) code point, and having template argument B>=C. After examining the first '>' (U+003C) code point (after B), the '=' (U+003D) code point needs to be recognized specially so it isn’t assumed to be part of an assignment.

Note: When examining the phrase A<(B!=C)>, there is one template list, starting at the first '<' (U+003C) code point and ending at the last '>' (U+003E) code point, and having template argument B!=C. After examining the '!' (U+0021) code point (after 'B'), the '=' (U+003D) code point needs to be recognized specially so it isn’t assumed to be part of an assignment.

Note: When examining the phrase A<(B==C)>, there is one template list, starting at the first '<' (U+003C) code point and ending at the last '>' (U+003E) code point, and having template argument B==C. After examining the first '=' (U+003D) code point (after 'B'), the second '=' (U+003D) code point needs to be recognized specially so neither are assumed to be part of an assignment.

After template list discovery completes, parsing will attempt to match each template list to the template_list grammar rule.

template_list :

_template_args_start template_arg_comma_list _template_args_end

template_arg_comma_list :

template_arg_expression ( ',' template_arg_expression ) * ',' ?

template_arg_expression :

expression

4. Directives

A directive is a token sequence which modifies how a WGSL program is processed by a WebGPU implementation.

Directives are optional. If present, all directives must appear before any declarations or const assertions.

global_directive :

diagnostic_directive

| enable_directive

| requires_directive

4.1. Extensions

WGSL is expected to evolve over time.

An extension is a named grouping of a coherent set of modifications to the WGSL specification, consisting of any combination of:

Hypothetically, extensions could:

There are two kinds of extensions: enable-extensions and language extensions.

4.1.1. Enable Extensions

An enable-extension is an extension whose functionality is available only if:

Enable-extensions are intended to expose hardware functionality that is not universally available.

An enable directive is a directive that turns on support for one or more enable-extensions. A shader-creation error results if the implementation does not support all the listed enable-extensions.

enable_directive :

'enable' enable_extension_list ';'

enable_extension_list :

enable_extension_name ( ',' enable_extension_name ) * ',' ?

Like other directives, if an enable directive is present, it must appear before all declarations and const assertions. Extension names are not identifiers: they do not resolve to declarations.

The valid enable-extensions are listed in the following table.

Enable-extensions
WGSL enable-extension WebGPU GPUFeatureName Description
f16 "shader-f16" The f16 type is valid to use in the WGSL module. Otherwise, using f16 (directly or indirectly) will result in a shader-creation error.
clip_distances "clip-distances" The built-in variable clip_distances is valid to use in the WGSL module. Otherwise, using clip_distances will result in a shader-creation error.
dual_source_blending "dual-source-blending" The attribute blend_src is valid to use in the WGSL module. Otherwise, using blend_src will result in a shader-creation error.
subgroups "subgroups" It is valid to use subgroup built-in values, subgroup built-in functions, and quad built-in functions in a WGSL module. Otherwise, any use will result in a shader-creation error.
primitive_index "primitive-index" The built-in variable primitive_index is valid to use in the WGSL module. Otherwise, using primitive_index will result in a shader-creation error.
EXAMPLE: Using hypothetical enable-extensions
// Enable a hypothetical extension for arbitrary precision floating point types.
enable arbitrary_precision_float;
enable arbitrary_precision_float; // A redundant enable directive is ok.

// Enable a hypothetical extension to control the rounding mode.
enable rounding_mode;

// Assuming arbitrary_precision_float enables use of:
//    - a type f<E,M>
//    - as a type in function return, formal parameters and let-declarations
//    - as a value constructor from AbstractFloat
//    - operands to division operator: /
// Assuming @rounding_mode attribute is enabled by the rounding_mode enable directive.
@rounding_mode(round_to_even)
fn halve_it(x : f<8, 7>) -> f<8, 7> {
  let two = f<8, 7>(2);
  return x / 2; // uses round to even rounding mode.
}

4.1.2. Language Extensions

A language extension is an extension which is automatically available if the implementation supports it. The program does not have to explicitly request it.

Language extensions embody functionality which could reasonably be supported on any WebGPU implementation. If the feature is not universally available, that it is because some WebGPU implementation has not yet implemented it.

Note: For example, do-while loops could be a language extension.

The wgslLanguageFeatures member of the WebGPU GPU object lists the set of language extensions supported by the implementation.

A requires-directive is a directive that documents the program’s use of one or more language extensions. It does not change the functionality exposed by the implementation. A shader-creation error results if the implementation does not support one of the required extensions.

A WGSL module can use a requires-directive to signal the potential for non-portability, and to signal the intended minimum bar for portability.

Note: Tooling outside of a WebGPU implementation could check whether all the language extensions used by a program are covered by requires-directives in the program.

requires_directive :

'requires' language_extension_list ';'

language_extension_list :

language_extension_name ( ',' language_extension_name ) * ',' ?

Like other directives, if a requires-directive is present, it must appear before all declarations and const assertions. Extension names are not identifiers: they do not resolve to declarations.

Language extensions
WGSL language extension Description
readonly_and_readwrite_storage_textures Allows the use of read and read_write access modes with storage textures. Additionally, adds the textureBarrier built-in function.
packed_4x8_integer_dot_product Supports using 32-bit integer scalars packing 4-component vectors of 8-bit integers as inputs to the dot product instructions with dot4U8Packed and dot4I8Packed built-in functions. Additionally, adds packing and unpacking instructions with packed 4-component vectors of 8-bit integers with pack4xI8, pack4xU8, pack4xI8Clamp, pack4xU8Clamp, unpack4xI8, and unpack4xU8 built-in functions.
unrestricted_pointer_parameters Removes the following restrictions from user-defined functions:

For user-defined functions, a parameter of pointer type must be in one of the following address spaces:

Each argument of pointer type to a user-defined function must have the same memory view as its root identifier.

pointer_composite_access Supports composite-value decomposition expressions where the root expression is a pointer, yielding a reference.

For example, if p is a pointer to a structure with member m, then p.m is a reference to the memory locations for m inside the structure p points to.

Similarly, if pa is a pointer to an array, then pa[i] is a reference to the memory locations for the i’th element of the array pa points to.

Note: The intent is that, over time, WGSL will define language extensions embodying all functionality in language extensions commonly supported at that time. In a requires-directive, these serve as a shorthand for listing all those common features. They represent progressively increasing sets of functionality, and can be thought of as language versions, of a sort.

4.2. Global Diagnostic Filter

A global diagnostic filter is a diagnostic filter whose affected range is the whole WGSL module. It is a directive, thus appearing before any module-scope declarations. It is spelled like the attribute form, but without the leading @ (U+0040) code point, and with a terminating semicolon.

diagnostic_directive :

'diagnostic' diagnostic_control ';'

5. Declaration and Scope

A declaration associates an identifier with one of the following kinds of objects:

In other words, a declaration introduces a name for an object.

A declaration is at module scope if the declaration appears in the program source, but outside the text of any other declaration.

A function declaration appears at module-scope. A function declaration contains declarations for formal parameters, if it has any, and it may contain variable and value declarations inside its body. Those contained declarations are therefore not at module-scope.

Note: The only kind of declaration that contain another declaration is a function declaration.

Certain objects are provided by the WebGPU implementation, and are treated as if they have been declared before the start of the WGSL module source. We say such objects are predeclared. For example, WGSL predeclares:

The scope of a declaration is the set of program source locations where a declared identifier potentially denotes its associated object. We say the identifier is in scope (of the declaration) at those source locations.

Where a declaration appears determines its scope:

Two declarations in the same WGSL source program must not simultaneously:

Note: A predeclared object does not have a declaration in the WGSL source. So a user-specified declaration at module-scope or inside a function can have the same name as a predeclared object.

Identifiers are used as follows, distinguished by grammatical context:

When an ident token appears as a name denoting an object declared elsewhere, it must be in scope for some declaration. The object denoted by the identifier token is determined as follows:

When the above algorithm is used to map an identifier to a declaration, we say the identifier resolves to that declaration. Similarly, we also say the identifier resolves to the declared object.

It is a shader-creation error if any module scope declaration is recursive. That is, no cycles can exist among the declarations:

Consider the directed graph where:

This graph must not have a cycle.

Note: The function body is part of the function declaration, thus functions must not be recursive, either directly or indirectly.

Note: Non-module scope identifier declarations must precede their uses in the text.

EXAMPLE: Valid and invalid declarations
// Valid, user-defined variables can have the same name as a built-in function.
var<private> modf: f32 = 0.0;

// Valid, foo_1 is in scope for the entire program.
var<private> foo: f32 = 0.0; // foo_1

// Valid, bar_1 is in scope for the entire program.
var<private> bar: u32 = 0u; // bar_1

// Valid, my_func_1 is in scope for the entire program.
// Valid, foo_2 is in scope until the end of the function.
fn my_func(foo: f32) { // my_func_1, foo_2
  // Any reference to 'foo' resolves to the function parameter.

  // Invalid, modf resolves to the module-scope variable.
  let res = modf(foo);

  // Invalid, the scope of foo_2 ends at the of the function.
  var foo: f32; // foo_3

  // Valid, bar_2 is in scope until the end of the function.
  var bar: u32; // bar_2
  // References to 'bar' resolve to bar_2
  {
    // Valid, foo_4 is in scope until the end of the compound statement.
    var foo : f32; // foo_4

    // Valid, bar_3 is in scope until the end of the compound statement.
    var bar: u32; // bar_3
    // References to 'bar' resolve to bar_3

    // Invalid, bar_4 has the same end scope as bar_3.
    var bar: i32; // bar_4

    // Valid, i_1 is in scope until the end of the for loop
    for ( var i: i32 = 0; i < 10; i++ ) { // i_1
      // Invalid, i_2 has the same end scope as i_1.
      var i: i32 = 1; // i_2.
    }
  }

  // Invalid, bar_5 has the same end scope as bar_2.
  var bar: u32; // bar_5

  // Valid, later_def, a module scope declaration, is in scope for the entire program.
  var early_use : i32 = later_def;
}

// Invalid, bar_6 has the same scope as bar_1.
var<private> bar: u32 = 1u; // bar_6

// Invalid, my_func_2 has the same end scope as my_func_1.
fn my_func() { } // my_func_2

// Valid, my_foo_1 is in scope for the entire program.
fn my_foo( //my_foo_1
  // Valid, my_foo_2 is in scope until the end of the function.
  my_foo: i32 // my_foo_2
) { }

var<private> later_def : i32 = 1;
EXAMPLE: Shadowing predeclared objects
// This declaration hides the predeclared 'min' built-in function.
// Since this declaration is at module-scope, it is in scope over the entire
// source.  The built-in function is no longer accessible.
fn min() -> u32 { return 0; }

const rgba8unorm = 12; // This shadows the predeclared 'rgba8unorm' enumerant.

6. Types

Programs calculate values.

In WGSL, a type is a set of values, and each value belongs to exactly one type. A value’s type determines the syntax and semantics of operations that can be performed on that value.

For example, the mathematical number 1 corresponds to these distinct values in WGSL:

WGSL treats these as different because their machine representation and operations differ.

A type is either predeclared, or created in WGSL source via a declaration.

Some types are expressed as template parameterizations. A type-generator is a predeclared object which, when parameterized with a template list, denotes a type. For example, the type atomic<u32> combines the type-generator atomic with template list <u32>.

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 specification is the same as its WGSL syntax. For example:

Some WGSL types are only used for analyzing a source program and for determining the program’s runtime behavior. This specification will describe such types, but they do not appear in WGSL source text.

Note: Reference types are not written in WGSL modules. See § 6.4.3 Reference and Pointer Types.

6.1. Type Checking

A WGSL value is computed by evaluating an expression. An expression is a segment of source text parsed as one of the WGSL grammar rules whose name ends with "expression". An expression E can contain subexpressions which are expressions properly contained in the outer expression E. A top-level expression is an expression that is not itself a subexpression. See § 8.18 Expression Grammar Summary.

The particular value produced by an expression evaluation depends on:

The values that may result from evaluating a particular expression will always belong to a specific WGSL type, known as the static type of the expression. The rules of WGSL are designed so that the static type of an expression depends only on the expression’s static context.

A type assertion is a mapping from some WGSL source expression to a WGSL type. The notation

e : T

is a type assertion meaning T is the static type of WGSL expression e.

Note: A type assertion is a statement of fact about the text of a program. It is not a runtime check.

Statements often use expressions, and may place requirements on the static types of those expressions. For example:

Type checking a successfully parsed WGSL module is the process of mapping each expression to its static type, and verifying that type requirements of each statement are satisfied. If type checking fails, a special case of a shader-creation error, called a type error, results.

Type checking can be performed by recursively applying type rules to syntactic phrases, where a syntactic phrase is either an expression or a statement. A type rule describes how the static context for a syntactic phrase determines the static type for expressions contained within that phrase. A type rule has two parts:

Type rules may have type parameters in their preconditions and conclusions. When a type rule’s conclusion or preconditions contain type parameters, we say it is parameterized. When they do not, we say the rule is fully elaborated. We can make a fully elaborated type rule from a parameterized one by substituting a type for each of its type parameters, using the same type for all occurrences of a given parameter in the rule. An assignment of types to a rule’s type parameters is called a substitution.

For example, here is the type rule for logical negation (an expression of the form !e):

Precondition Conclusion
e: T
T is bool or vecN<bool>
!e: T

This is a parameterized rule, because it contains the type parameter T, which can represent any one of four types bool, vec2<bool>, vec3<bool>, or vec4<bool>. Applying the substitution that maps T to vec3<bool> produces the fully elaborated type rule:

Precondition Conclusion
e: vec3<bool>
!e: vec3<bool>

Each fully elaborated rule we can produce from a parameterized rule by applying some substitution that meets the rule’s other conditions is called an overload of the parameterized rule. For example, the boolean negation rule has four overloads, because there are four possible ways to assign a type to its type parameter T.

Note: In other words, a parameterized type rule provides the pattern for a collection of fully elaborated type rules, each one produced by applying a different substitution to the parameterized rule.

A type rule applies to a syntactic phrase when:

A parameterized type rule applies to an expression if there exists a substitution producing a fully elaborated type rule that applies to the expression.

Consider the expression, 1u+2u. It has two literal subexpressions: 1u and 2u, both of type u32. The top-level expression is an addition. Referring to the rules in § 8.7 Arithmetic Expressions, the type rule for addition applies to the expression, because:

When analyzing a syntactic phrase, three cases may occur:

Continuing the example above, only one type rule applies to the expression 1u+2u, and so type checking accepts the conclusion of that type rule, which is that 1u+2u is of type u32.

A WGSL source program is well-typed when:

Otherwise there is a type error and the source program is not a valid WGSL module.

WGSL is a statically typed language because type checking a WGSL module will either succeed or discover a type error, while only having to inspect the program source text.

6.1.1. Type Rule Tables

The WGSL type rules for expressions are organized into type rule tables, with one row per type rule.

The semantics of an expression is the effect of evaluating that expression, and is primarily the production of a result value. The Description column of the type rule that applies to an expression will specify the expression’s semantics. The semantics usually depends on the values of the type rule parameters, including the assumed values of any subexpressions. Sometimes the semantics of an expression includes effects other than producing a result value, such as the non-result-value effects of its subexpressions.

EXAMPLE: Side-effect of an expression
fn foo(p : ptr<function, i32>) -> i32 {
  let x = *p;
  *p += 1;
  return x;
}

fn bar() {
  var a: i32;
  let x = foo(&a); // the call to foo returns a value
                   // and updates the value of a
}

6.1.2. Conversion Rank

When a type assertion e:T is used as a type rule precondition, it is satisfied when:

The rule is codified by the ConversionRank function over pairs of types, defined in the table below. The ConversionRank function expresses the preference and feasibility of automatically converting a value of one type (Src) to another type (Dest). Lower ranks are more desirable.

A feasible automatic conversion converts a value from type Src to type Dest, and is allowed when ConversionRank(Src,Dest) is finite. Such conversions are value-preserving, subject to limitations described in § 15.7 Floating Point Evaluation.

Note: Automatic conversions only occur in two kinds of situations. First, when converting a const-expression to its corresponding typed numeric value that can be used on the GPU. Second, when a load from a reference-to-memory occurs, yielding the value stored in that memory.

Note: A conversion of infinite rank is infeasible, i.e. not allowed.

Note: When no conversion is performed, the conversion rank is zero.

ConversionRank from one type to another
Src Dest ConversionRank(Src,Dest) Description
T T 0 Identity. No conversion performed.
ref<AS,T,AM>
for address space AS, and where access mode AM is read or read_write.
T 0 Apply the Load Rule to load a value from a memory reference.
AbstractFloat f32 1 See § 15.7.6 Floating Point Conversion
AbstractFloat f16 2 See § 15.7.6 Floating Point Conversion
AbstractInt i32 3 Identity if the value is in i32. Produces a shader-creation error otherwise.
AbstractInt u32 4 Identity if the value is in u32. Produces a shader-creation error otherwise.
AbstractInt AbstractFloat 5 See § 15.7.6 Floating Point Conversion
AbstractInt f32 6 Behaves as AbstractInt to AbstractFloat, and then AbstractFloat to f32
AbstractInt f16 7 Behaves as AbstractInt to AbstractFloat, and then AbstractFloat to f16
vecN<S> vecN<T> ConversionRank(S,T) Inherit conversion rank from component type.
matCxR<S> matCxR<T> ConversionRank(S,T) Inherit conversion rank from component type.
array<S,N> array<T,N> ConversionRank(S,T) Inherit conversion rank from component type. Note: Only fixed-size arrays may have an abstract component type.
__frexp_result_abstract __frexp_result_f32 1
__frexp_result_abstract __frexp_result_f16 2
__frexp_result_vecN_abstract __frexp_result_vecN_f32 1
__frexp_result_vecN_abstract __frexp_result_vecN_f16 2
__modf_result_abstract __modf_result_f32 1
__modf_result_abstract __modf_result_f16 2
__modf_result_vecN_abstract __modf_result_vecN_f32 1
__modf_result_vecN_abstract __modf_result_vecN_f16 2
S T
where above cases don’t apply
infinity There are no automatic conversions between other types.

The type T is the concretization of type S if:

The concretization of a value e of type T is the value resulting from applying, to e, the feasible conversion that maps T to the concretization of T.

Note: Conversion to f32 is always preferred over f16, therefore automatic conversion will only ever produce an f16 if extension is enabled in the module.

6.1.3. Overload Resolution

When more than one type rule applies to a syntactic phrase, a tie-breaking procedure is used to determine which one should take effect. This procedure is called overload resolution, and assumes type checking has already succeeded in finding static types for subexpressions.

Consider a syntactic phrase P, and all type rules that apply to P. The overload resolution algorithm calls these type rules overload candidates. For each candidate:

Overload resolution for P proceeds as follows, with the goal of finding a single most preferable overload candidate:

  1. For each candidate C, enumerate conversion ranks for subexpressions in the syntactic phrase. The candidate’s preconditions have been met, and so for the i’th subexpression in the P:

    • Its static type has been computed.

    • There is a feasible automatic conversion from the expression’s static type to the type required by the corresponding type assertion in the preconditions. Let C.R(i) be the ConversionRank of that conversion.

  2. Eliminate any candidate where one of its subexpressions resolves to an abstract type after feasible automatic conversions, but another of the candidate’s subexpressions is not a const-expression.

    Note: As a consequence, if any subexpression in the phrase is not a const-expression, then all subexpressions in the phrase must have a concrete type.

  3. Rank candidates: Given two overload candidates C1 and C2, C1 is preferred over C2 if:

    • For each expression position i in P, C1.R(i) ≤ C2.R(i).

      • That is, each expression conversion required to apply C1 to P is at least as preferable as the corresponding expression conversion required to apply C2 to P.

    • There is at least one expression position i where C1.R(i) < C2.R(i).

      • That is, there is at least one expression conversion required to apply C1 that is strictly more preferable than the corresponding conversion required to apply C2.

  4. If there is a single candidate C which is preferred over all the others, then overload resolution succeeds, yielding the candidate type rule C. Otherwise, overload resolution fails.

6.2. Plain Types

Plain types are types for the machine representation of boolean values, numbers, vectors, matrices, or aggregations of such values.

A plain type is either a scalar type, an atomic type, or a composite type.

Note: Plain types in WGSL are similar to Plain-Old-Data types in C++, but also include atomic types and abstract numeric types.

6.2.1. Abstract Numeric Types

These types cannot be spelled in WGSL source. They are only used by type checking.

Certain expressions are evaluated at shader-creation time, and with a numeric range and precision that may be larger than directly implemented by the GPU.

WGSL defines two abstract numeric types for these evaluations:

An evaluation of an expression in one of these types must not overflow or produce an infinite or NaN value.

A type is abstract if it is an abstract numeric type or contains an abstract numeric type. A type is concrete if it is not abstract.

A numeric literal without a suffix denotes a value in an abstract numeric type:

Example: The expression log2(32) is analyzed as follows:

Example: The expression 1 + 2.5 is analyzed as follows:

Example: let x = 1 + 2.5;

Example: 1u + 2.5 results in a shader-creation error:

Example: -1 * i32(-2147483648) does not result in a shader-creation error:

EXAMPLE: Type inference for literals
// Explicitly-typed unsigned integer literal.
var u32_1 = 1u; // variable holds a u32

// Explicitly-typed signed integer literal.
var i32_1 = 1i; // variable holds a i32

// Explicitly-typed floating point literal.
var f32_1 = 1f; // variable holds a f32

// Explicitly-typed unsigned integer literal cannot be negated.
var u32_neg = -1u; // invalid: unary minus does not support u32

// When a concrete type is required, but no part of the statement or
// expression forces a particular concrete type, an integer literal is
// interpreted as an i32 value:
//   Initializer for a let-declaration must be constructible (or pointer).
//   The most preferred automatic conversion from AbstractInt to a constructible type
//   is AbstractInt to i32, with conversion rank 2.  So '1' is inferred as i32.
let some_i32 = 1; // like let some_i32: i32 = 1i;

// Inferred from declaration type.
var i32_from_type : i32 = 1; // variable holds i32.  AbstractInt to i32, conversion rank 2
var u32_from_type : u32 = 1; // variable holds u32.  AbstractInt to u32, conversion rank 3

// Unsuffixed integer literal can convert to floating point when needed:
//   Automatically convert AbstractInt to f32, with conversion rank 5.
var f32_promotion : f32 = 1; // variable holds f32

// Invalid: no feasible conversion from floating point to integer
var i32_demotion : i32 = 1.0; // Invalid

// Inferred from expression.
var u32_from_expr = 1 + u32_1; // variable holds u32
var i32_from_expr = 1 + i32_1; // variable holds i32

// Values must be representable.
let u32_too_large   : u32 = 1234567890123456890; // invalid, overflow
let i32_too_large   : i32 = 1234567890123456890; // invalid, overflow
let u32_large : u32 = 2147483649; // valid
let i32_large : i32 = 2147483649; // invalid, overflow
let f32_out_of_range1 = 0x1p500; // invalid, out of range
let f32_hex_lost_bits = 0x1.0000000001p0; // invalid, not exactly representable in f32

// Minimum integer: unary negation over AbstractInt, then infer i32.
// Most preferred conversion from AbstractInt to a constructible type (with lowest
// conversion rank) is AbstractInt to i32.
let i32_min = -2147483648;  // has type i32

// Invalid.  Select AbstractInt to i32 as above, but the value is out of
// range, producing shader-creation error.
let i32_too_large_2 = 2147483648; // Invalid.

// Subexpressions can resolve to AbstractInt and AbstractFloat.
// The following examples are all valid and the value of the variable is 6u.
var u32_expr1 = (1 + (1 + (1 + (1 + 1)))) + 1u;
var u32_expr2 = 1u + (1 + (1 + (1 + (1 + 1))));
var u32_expr3 = (1 + (1 + (1 + (1u + 1)))) + 1;
var u32_expr4 = 1 + (1 + (1 + (1 + (1u + 1))));

// Inference based on built-in function parameters.

// Most-preferred candidate is clamp(i32,i32,i32)->i32
let i32_clamp = clamp(1, -5, 5);
// Most preferred candidate is clamp(u32,u32,u32).
// Literals use automatic conversion AbstractInt to u32.
let u32_clamp = clamp(5, 0, u32_from_expr);
// Most preferred candidate is clamp(f32,f32,f32)->f32
// literals use automatic conversion AbstractInt to f32.
let f32_clamp = clamp(0, f32_1, 1);

// The following examples all promote to f32 with an initial value of 10f.
let f32_promotion1 = 1.0 + 2 + 3 + 4;
let f32_promotion2 = 2 + 1.0 + 3 + 4;
let f32_promotion3 = 1f + ((2 + 3) + 4);
let f32_promotion4 = ((2 + (3 + 1f)) + 4);

// Type rule violations.

// Invalid, the initializer can only resolve to f32:
// No feasible automatic conversion from AbstractFloat to u32.
let mismatch : u32 = 1.0;

// Invalid. There is no overload of clamp that allows mixed sign parameters.
let ambiguous_clamp = clamp(1u, 0, 1i);

// Inference completes at the statement level.

// Initializer for a let-declaration must be constructible (or pointer).
// The most preferred automatic conversion from AbstractInt to a constructible type
// is AbstractInt to i32, with conversion rank 2.  So '1' is inferred as i32.
let some_i32 = 1; // like let some_i32: i32 = 1i;

let some_f32 : f32 = some_i32; // Type error: i32 cannot be assigned to f32

// Another overflow case
let overflow_u32 = (1 -2) + 1u; // invalid, -1 is out of range of u32

// Ideal value out of range of 32-bits, but brought back into range
let out_and_in_again = (0x1ffffffff / 8);

// Similar, but invalid
let out_of_range = (0x1ffffffff / 8u); // requires computation is done in 32-bits,
                                       // making 0x1ffffffff out of range.

6.2.2. Boolean Type

The bool type contains the values true and false.

Boolean literal type rules
Precondition Conclusion Description
true: bool The true value.
false: bool The false value.

6.2.3. Integer Types

The u32 type is the set of 32-bit unsigned integers.

The i32 type is the set of 32-bit signed integers. It uses the two’s complement representation, with the sign bit in the most significant bit position.

Expressions on concrete integer types that overflow produce a result that is modulo 2bitwidth

Extreme values for integer types
Type Lowest value Highest value
i32 i32(-2147483648) 2147483647i
i32(-0x80000000) 0x7fffffffi
u32 0u 4294967295u
0x0u 0xffffffffu

Note: AbstractInt is also an integer type.

6.2.4. Floating Point Types

The f32 type is the set of 32-bit floating point values of the IEEE-754 binary32 (single precision) format. See § 15.7 Floating Point Evaluation for details.

The f16 type is the set of 16-bit floating point values of the IEEE-754 binary16 (half precision) format. It is a shader-creation error if the f16 type is used unless the program contains the enable f16; directive to enable the f16 extension. See § 15.7 Floating Point Evaluation for details.

The following table lists certain extreme values for floating point types. Each has a corresponding negative value.

Extreme values for floating point types
Type Smallest positive subnormal Smallest positive normal Largest positive finite Largest finite power of 2
f32 1.40129846432481707092e-45f 1.17549435082228750797e-38f 3.40282346638528859812e+38f 0x1p+127f
0x1p-149f 0x1p-126f 0x1.fffffep+127f
f16 5.9604644775390625e-8h 0.00006103515625h 65504.0h 0x1p+15h
0x1p-24h 0x1p-14h 0x1.ffcp+15h

Note: AbstractFloat is also a floating point type.

6.2.5. Scalar Types

The scalar types are bool, AbstractInt, AbstractFloat, i32, u32, f32, and f16.

The numeric scalar types are AbstractInt, AbstractFloat, i32, u32, f32, and f16.

The integer scalar types are AbstractInt, i32, and u32.

A scalar conversion maps a value in one scalar type to a value in a different scalar type. Generally the result value is close to the original value, within the limitations of the destination type. Scalar conversions occur either:

6.2.6. Vector Types

A vector is a grouped sequence of 2, 3, or 4 scalar components.

Type Description
vecN<T> Vector of N components of type T. N must be in {2, 3, 4} and T must be one of the scalar types. We say T is the component type of the vector.

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

Key use cases of a vector include:

Many operations on vectors (and matrices) act component-wise, i.e. the result is formed by operating on each scalar component independently.

EXAMPLE: Vector
vec2<f32>  // is a vector of two f32s.
EXAMPLE: Component-wise addition
let x : vec3<f32> = a + b; // a and b are vec3<f32>
// x[0] = a[0] + b[0]
// x[1] = a[1] + b[1]
// x[2] = a[2] + b[2]

WGSL also predeclares the following type aliases:

Predeclared alias Original type Restrictions
vec2i vec2<i32>
vec3i vec3<i32>
vec4i vec4<i32>
vec2u vec2<u32>
vec3u vec3<u32>
vec4u vec4<u32>
vec2f vec2<f32>
vec3f vec3<f32>
vec4f vec4<f32>
vec2h vec2<f16> Requires the f16 extension.
vec3h vec3<f16>
vec4h vec4<f16>

6.2.7. Matrix Types

A matrix is a grouped sequence of 2, 3, or 4 floating point vectors.

Type Description
matCxR<T> Matrix of C columns and R rows of type T, where C and R are both in {2, 3, 4}, and T must be f32, f16, or AbstractFloat. Equivalently, it can be viewed as C column vectors of type vecR<T>.

The key use case for a matrix is to embody a linear transformation. In this interpretation, the vectors of a matrix are treated as column vectors.

The product operator (*) is used to either:

See § 8.7 Arithmetic Expressions.

EXAMPLE: Matrix
mat2x3<f32>  // This is a 2 column, 3 row matrix of 32-bit floats.
             // Equivalently, it is 2 column vectors of type vec3<f32>.

WGSL also predeclares the following type aliases:

Predeclared alias Original type Restrictions
mat2x2f mat2x2<f32>
mat2x3f mat2x3<f32>
mat2x4f mat2x4<f32>
mat3x2f mat3x2<f32>
mat3x3f mat3x3<f32>
mat3x4f mat3x4<f32>
mat4x2f mat4x2<f32>
mat4x3f mat4x3<f32>
mat4x4f mat4x4<f32>
mat2x2h mat2x2<f16> Requires the f16 extension.
mat2x3h mat2x3<f16>
mat2x4h mat2x4<f16>
mat3x2h mat3x2<f16>
mat3x3h mat3x3<f16>
mat3x4h mat3x4<f16>
mat4x2h mat4x2<f16>
mat4x3h mat4x3<f16>
mat4x4h mat4x4<f16>

6.2.8. Atomic Types

An atomic type encapsulates a concrete integer scalar type such that:

Type Description
atomic<T> Atomic of type T. T must be either u32 or i32.

An expression must not evaluate to an atomic type.

Atomic types may only be instantiated by variables in the workgroup address space or by storage buffer variables with a read_write access mode. The memory scope of operations on the type is determined by the address space it is instantiated in. Atomic types in the workgroup address space have a memory scope of Workgroup, while those in the storage address space have a memory scope of QueueFamily.

An atomic modification is any operation on an atomic object which sets the content of the object. The operation counts as a modification even if the new value is the same as the object’s existing value.

In WGSL, atomic modifications are mutually ordered, for each object. That is, during execution of a shader stage, for each atomic object A, all agents observe the same order of modification operations applied to A. The ordering for distinct atomic objects may not be related in any way; no causality is implied. Note that variables in workgroup space are shared within a workgroup, but are not shared between different workgroups.

6.2.9. Array Types

An array is an indexable sequence of element values.

Type Description
array<E,N> A fixed-size array with N elements of type E.
N is called the element count of the array.
array<E> A runtime-sized array of elements of type E. These may only appear in specific contexts.

The first element in an array is at index 0, and each successive element is at the next integer index. See § 8.5.3 Array Access Expression.

An expression must not evaluate to a runtime-sized array type.

The element count expression N of a fixed-size array is subject to the following constraints:

Note: The element count value is fully determined at pipeline creation time if N depends on any override-declarations, and shader module creation otherwise.

Note: To qualify for type-equivalency, any override expression that is not a const expression must be an identifier. See Workgroup variables sized by overridable constants

The number of elements in a runtime-sized array is determined by the size of buffer binding associated with the corresponding storage buffer variable. See § 13.3.4 Buffer Binding Determines Runtime-Sized Array Element Count.

An array element type must be one of:

Note: The element type must be a plain type.

Two array types are the same if and only if all of the following are true:

EXAMPLE: Example fixed-size array types, non-overridable element count
// array<f32,8> and array<i32,8> are different types:
// different element types
var<private> a: array<f32,8>;
var<private> b: array<i32,8>;
var<private> c: array<i32,8u>;  // array<i32,8> and array<i32,8u> are the same type

const width = 8;
const height = 8;

// array<i32,8>, array<i32,8u>, and array<i32,width> are the same type.
// Their element counts evaluate to 8.
var<private> d: array<i32,width>;

// array<i32,height> and array<i32,width> are the same type.
var<private> e: array<i32,width>;
var<private> f: array<i32,height>;

Note: The only valid use of an array type sized by an overridable constant is as a memory view in the workgroup address space. This includes the store type of a workgroup variable. See § 7 Variable and Value Declarations.

EXAMPLE: Workgroup variables sized by overridable constants
override blockSize = 16;

var<workgroup> odds: array<i32,blockSize>;
var<workgroup> evens: array<i32,blockSize>; // Same type

// None of the following have the same type as 'odds' and 'evens'.

// Different type: Not the identifier 'blockSize'
var<workgroup> evens_0: array<i32,16>;
// Different type: Uses arithmetic to express the element count.
var<workgroup> evens_1: array<i32,(blockSize * 2 / 2)>;
// Different type: Uses parentheses, not just an identifier.
var<workgroup> evens_2: array<i32,(blockSize)>;

// An invalid example, because the overridable element count may only occur
// at the outer level.
// var<workgroup> both: array<array<i32,blockSize>,2>;

// An invalid example, because the overridable element count is only
// valid for workgroup variables.
// var<private> bad_address_space: array<i32,blockSize>;

6.2.10. Structure Types

A structure is a named grouping of named member values.

Type Description
struct AStructName {
  M1 : T1,
  ...
  MN : TN,
}
A declaration of a structure type named by the identifier AStructName and having N members, where member i is named by the identifier Mi and is of the type Ti.

N must be at least 1.

Two members of the same structure type must not have the same name.

Structure types are declared at module scope. Elsewhere in the program source, a structure type is denoted by its identifier name. See § 5 Declaration and Scope.

Two structure types are the same if and only if they have the same name.

A structure member type must be one of:

Note: All user-declared structure types are concrete.

Note: Each member type must be a plain type.

Some consequences of the restrictions structure member and array element types are:

EXAMPLE: Structure
// A structure with three members.
struct Data {
  a: i32,
  b: vec2<f32>,
  c: array<i32,10>, // last comma is optional
}

// Declare a variable storing a value of type Data.
var<private> some_data: Data;
struct_decl :

'struct' ident struct_body_decl

struct_body_decl :

'{' struct_member ( ',' struct_member ) * ',' ? '}'

struct_member :

attribute * member_ident ':' type_specifier

The following attributes can be applied to structure members:

Attributes builtin, location, blend_src, interpolate, and invariant are IO attributes. An IO attribute on a member of a structure S has effect only when S is used as the type of a formal parameter or return type of an entry point. See § 13.3.1 Inter-stage Input and Output Interface.

Attributes align and size are layout attributes, and may be required if the structure type is used to define a uniform buffer or a storage buffer. See § 14.4 Memory Layout.

EXAMPLE: Structure declaration
struct my_struct {
  a: f32,
  b: vec4<f32>
}
EXAMPLE: Structure used to declare a buffer
// Runtime Array
alias RTArr = array<vec4<f32>>;
struct S {
  a: f32,
  b: f32,
  data: RTArr
}
@group(0) @binding(0) var<storage> buffer: S;

6.2.11. Composite Types

A type is composite if it has internal structure expressed as a composition of other types. The internal parts do not overlap, and are called components. A composite value may be decomposed into its components. See § 8.5 Composite Value Decomposition Expressions.

The composite types are:

For a composite type T, the nesting depth of T, written NestDepth(T) is:

6.2.12. Constructible Types

Many kinds of values can be created, loaded, stored, passed into functions, and returned from functions. We call these constructible.

A type is constructible if it is one of:

Note: All constructible types have a creation-fixed footprint.

Note: Atomic types and runtime-sized array types are not constructible. Composite types containing atomics and runtime-sized arrays are not constructible.

6.2.13. Fixed-Footprint Types

The memory footprint of a variable is the number of memory locations used to store the contents of the variable. The memory footprint of a variable depends on its store type and becomes finalized at some point in the shader lifecycle. Most variables are sized very early, at shader creation time. Some variables may be sized later, at pipeline creation time, and others as late as the start of shader execution.

A type has a creation-fixed footprint if its concretization has a size that is fully determined at shader creation time.

A type has a fixed footprint if its size is fully determined at pipeline creation time.

Note: All concrete creation-fixed footprint and fixed footprint types are storable.

Note: Pipeline creation depends on shader creation, so a type with creation-fixed footprint also has fixed footprint.

The types with creation-fixed footprint are:

Note: A constructible type has a creation-fixed footprint.

The plain types with fixed footprint are any of:

Note: The only valid use of a fixed-size array with an element count that is an override-expression that is not a const-expression is as a memory view in the workgroup address space. This includes the store type of a workgroup variable.

Note: A fixed-footprint type may contain an atomic type, either directly or indirectly, while a constructible type cannot.

Note: Fixed-footprint types exclude runtime-sized arrays, and any structure that contains a runtime-sized array.

6.3. Enumeration Types

An enumeration type is a limited set of named values. An enumeration is used to distinguish among the set of possibilities for a specific concept, such as the set of valid texel formats.

An enumerant is one of the named values in an enumeration. Each enumerant is distinct from all other enumerants, and distinct from all other kinds of values.

There is no mechanism for declaring new enumerants or new enumeration types in WGSL source.

Note: Enumerants are used as template parameters.

Note: There is no way to copy or to create an alternative name for an enumerant:

6.3.1. Predeclared enumerants

The following table lists the enumeration types in WGSL, and their predeclared enumerants. The enumeration types exist, but cannot be spelled in WGSL source.

Predeclared enumerants
Enumeration
(Cannot be spelled in WGSL)
Predeclared enumerant
access mode read
write
read_write
address space

Note: The handle address space is never written in a WGSL source.

function
private
workgroup
uniform
storage
texel format rgba8unorm
rgba8snorm
rgba8uint
rgba8sint
rgba16unorm
rgba16snorm
rgba16uint
rgba16sint
rgba16float
rg8unorm
rg8snorm
rg8uint
rg8sint
rg16unorm
rg16snorm
rg16uint
rg16sint
rg16float
r32uint
r32sint
r32float
rg32uint
rg32sint
rg32float
rgba32uint
rgba32sint
rgba32float
bgra8unorm
r8unorm
r8snorm
r8uint
r8sint
r16unorm
r16snorm
r16uint
r16sint
r16float
rgb10a2unorm
rgb10a2uint
rg11b10ufloat

6.4. Memory Views

In addition to calculating with plain values, a WGSL program will also often read values from memory or write values to memory, via memory access operations. Each memory access is performed via a memory view.

A memory view comprises:

The access mode of a memory view must be supported by the address space. See § 7 Variable and Value Declarations.

6.4.1. Storable Types

The value contained in a variable must be of a storable type. A storable type may have an explicit representation defined by WGSL, as described in § 14.4.4 Internal Layout of Values, or it may be opaque, such as for textures and samplers.

A type is storable if it is both concrete and one of:

Note: That is, the storable types are the concrete plain types, texture types, and sampler types.

6.4.2. Host-shareable Types

Host-shareable types are used to describe the contents of buffers which are shared between the host and the GPU, or copied between host and GPU without format translation. When used for this purpose, the type may additionally have layout attributes applied as described in § 14.4 Memory Layout. As described in § 7.3 var Declarations, the store type of uniform buffer and storage buffer variables must be host-shareable.

A type is host-shareable if it is both concrete and one of:

Note: Restrictions on the types of inter-stage inputs and outputs are described in § 13.3.1 Inter-stage Input and Output Interface and subsequent sections. Those types are also sized, but the counting differs.

Note: Textures and samplers can also be shared between the host and the GPU, but their contents are opaque. The host-shareable types in this section are specifically for use in storage and uniform buffers.

6.4.3. Reference and Pointer Types

WGSL has two kinds of types for representing memory views: reference types and pointer types.

Constraint Type Description
AS is an address space,
T is a storable type,
AM is an access mode
ref<AS,T,AM> The reference type identified with the set of memory views for memory locations in AS holding values of type T, supporting memory accesses described by mode AM.

Here, T is the store type.

Reference types are not written in WGSL source; instead they are used to analyze a WGSL module.

AS is an address space,
T is a storable type,
AM is an access mode
ptr<AS,T,AM> The pointer type identified with the set of memory views for memory locations in AS holding values of type T, supporting memory accesses described by mode AM.

Here, T is the store type.

Pointer types may appear in WGSL source.

Two pointer types are the same if and only if they have the same address space, store type, and access mode.

When analyzing a WGSL module, reference and pointer types are fully parameterized by an address space, a storable type, and an access mode. In code examples in this specification, the comments show this fully parameterized form.

However, in WGSL source text:

EXAMPLE: Pointer type
fn my_function(
  /* 'ptr<function,i32,read_write>' is the type of a pointer value that references
     memory for keeping an 'i32' value, using memory locations in the 'function'
     address space.  Here 'i32' is the store type.
     The implied access mode is 'read_write'.
     See "Address Space" section for defaults. */
  ptr_int: ptr<function,i32>,

  // 'ptr<private,array<f32,50>,read_write>' is the type of a pointer value that
  // refers to memory for keeping an array of 50 elements of type 'f32', using
  // memory locations in the 'private' address space.
  // Here the store type is 'array<f32,50>'.
  // The implied access mode is 'read_write'.
  // See the "Address space section for defaults.
  ptr_array: ptr<private, array<f32, 50>>
) { }

Reference types and pointer types are both sets of memory views: a particular memory view is associated with a unique reference value and also a unique pointer value:

Each pointer value p of type ptr<AS,T,AM> corresponds to a unique reference value r of type ref<AS,T,AM>, and vice versa, where p and r describe the same memory view.

6.4.4. Valid and Invalid Memory References

A reference value is either valid or invalid.

References are formed as described in detail in § 6.4.8 Forming Reference and Pointer Values. Generally, a valid reference is formed by:

Generally, an invalid memory reference is formed by:

A valid pointer is a pointer that corresponds to a valid reference. An invalid pointer is a pointer that corresponds to an invalid memory reference.

6.4.5. Originating Variable

The originating variable for a reference value R is defined as follows:

The originating variable of a pointer value is defined as the originating variable of the corresponding reference value.

Note: The originating variable is a dynamic concept. The originating variable for a formal parameter of a function depends on the call sites for the function. Different call sites may supply pointers into different originating variables.

A valid reference always corresponds to a non-empty memory view for some or all of the memory locations for some variable.

Note: A reference can correspond to memory locations inside a variable, and still be invalid. This can occur when an index is too large for the type being indexed, but the referenced locations would be inside a subsequent sibling data member.

In the following example, the reference the_particle.position[i] is valid if and only if i is 0 or 1. When i is 2, the reference will be an invalid memory reference, but would otherwise correspond the memory locations for the_particle.color_index.

EXAMPLE: Invalid memory reference still inside a variable
struct Particle {
   position: vec2f,
   velocity: vec2f,
   color_index: i32,
}

@group(0) @binding(0)
var<storage,read_write> the_particle: Particle;

fn particle_velocity_component(p: Particle, i: i32) -> f32 {
  return the_particle.velocity[i]; // A valid reference when i is 0 or 1.
}

6.4.6. Out-of-Bounds Access

An operation that accesses an invalid memory reference is an out-of-bounds access.

An out-of-bounds access is a program defect, because if it were performed as written, it would typically:

For this reason, an implementation will not perform the access as written. Executing an out-of-bounds access generates a dynamic error.

Note: An example of interpreting the store type incorrectly occurs in the example from the previous section. When i is 2, the expression the_particle.velocity[i] has type ref<storage,f32,read_write>, meaning it is a memory view with f32 as its store type. However, the memory locations are allocated to for the color_index member, so the stored value is actually of type i32.

Note:An out-of-bounds access causes a dynamic error, which allows for many possible outcomes.

Those outcomes include, but are not limited to, the following:

Trap

The shader invocation immediately terminates, and shader stage outputs are set to zero values.

Invalid Load

Loads from an invalid reference may return one of:

Invalid Store

Stores to an invalid reference may do one of:

A data race may occur if an invalid load or store is redirected to access different locations inside a variable in a shared address space. For example, the accesses of several concurrently executing invocations may be redirected to the first element in an array. If at least one access is a write, and they are not otherwise synchronized, then the result is a data race, and hence a dynamic error.

An out-of-bounds access invalidates the assumptions of uniformity analysis. For example, if an invocation terminates early due to an out-of-bounds access, then it can no longer particpate in collective operations. In particular, a call to workgroupBarrier may hang the shader, and derivatives may yield invalid results.

6.4.7. Use Cases for References and Pointers

References and pointers are distinguished by how they are used:

Defining references in this way enables simple idiomatic use of variables:

EXAMPLE: Reference types enable simple use of variables
@compute @workgroup_size(1)
fn main() {
  // 'i' has reference type ref<function,i32,read_write>
  // The memory locations for 'i' store the i32 value 0.
  var i: i32 = 0;

  // 'i + 1' can only match a type rule where the 'i' subexpression is of type i32.
  // So the expression 'i + 1' has type i32, and at evaluation, the 'i' subexpression
  // evaluates to the i32 value stored in the memory locations for 'i' at the time
  // of evaluation.
  let one: i32 = i + 1;

  // Update the value in the locations referenced by 'i' so they hold the value 2.
  i = one + 1;

  // Update the value in the locations referenced by 'i' so they hold the value 5.
  // The evaluation of the right-hand-side occurs before the assignment takes effect.
  i = i + 3;
}
EXAMPLE: Returning a reference returns the value loaded via the reference
var<private> age: i32;
fn get_age() -> i32 {
  // The type of the expression in the return statement must be 'i32' since it
  // must match the declared return type of the function.
  // The 'age' expression is of type ref<private,i32,read_write>.
  // Apply the Load Rule, since the store type of the reference matches the
  // required type of the expression, and no other type rule applies.
  // The evaluation of 'age' in this context is the i32 value loaded from the
  // memory locations referenced by 'age' at the time the return statement is
  // executed.
  return age;
}

fn caller() {
  age = 21;
  // The copy_age constant will get the i32 value 21.
  let copy_age: i32 = get_age();
}

Defining pointers in this way enables two key use cases:

EXAMPLE: Using a pointer as a short name for part of a variable
struct Particle {
  position: vec3<f32>,
  velocity: vec3<f32>
}
struct System {
  active_index: i32,
  timestep: f32,
  particles: array<Particle,100>
}
@group(0) @binding(0) var<storage,read_write> system: System;

@compute @workgroup_size(1)
fn main() {
  // Form a pointer to a specific Particle in storage memory.
  let active_particle = &system.particles[system.active_index];

  let delta_position: vec3<f32> = (*active_particle).velocity * system.timestep;
  let current_position: vec3<f32>  = (*active_particle).position;
  (*active_particle).position = delta_position + current_position;
}
EXAMPLE: Using a pointer as a formal parameter
fn add_one(x: ptr<function,i32>) {
  /* Update the locations for 'x' to contain the next higher integer value,
     (or to wrap around to the largest negative i32 value).
     On the left-hand side, unary '*' converts the pointer to a reference that
     can then be assigned to. It has a read_write access mode, by default.
     /* On the right-hand side:
        - Unary '*' converts the pointer to a reference, with a read_write
          access mode.
        - The only matching type rule is for addition (+) and requires '*x' to
          have type i32, which is the store type for '*x'.  So the Load Rule
          applies and '*x' evaluates to the value stored in the memory for '*x'
          at the time of evaluation, which is the i32 value for 0.
        - Add 1 to 0, to produce a final value of 1 for the right-hand side. */
     Store 1 into the memory for '*x'. */
  *x = *x + 1;
}

@compute @workgroup_size(1)
fn main() {
  var i: i32 = 0;

  // Modify the contents of 'i' so it will contain 1.
  // Use unary '&' to get a pointer value for 'i'.
  // This is a clear signal that the called function has access to the memory
  // for 'i', and may modify it.
  add_one(&i);
  let one: i32 = i;  // 'one' has value 1.
}

6.4.8. Forming Reference and Pointer Values

A reference value is formed in one of the following ways:

In all cases, the access mode of the result is the same as the access mode of the original reference.

EXAMPLE: Component reference from a composite reference
struct S {
    age: i32,
    weight: f32
}
var<private> person: S;
// Elsewhere, 'person' denotes the reference to the memory underlying the variable,
// and will have type ref<private,S,read_write>.

fn f() {
    var uv: vec2<f32>;
    // For the remainder of this function body, 'uv' denotes the reference
    // to the memory underlying the variable, and will have type
    // ref<function,vec2<f32>,read_write>.

    // Evaluate the left-hand side of the assignment:
    //   Evaluate 'uv.x' to yield a reference:
    //   1. First evaluate 'uv', yielding a reference to the memory for
    //      the 'uv' variable. The result has type ref<function,vec2<f32>,read_write>.
    //   2. Then apply the '.x' vector access phrase, yielding a reference to
    //      the memory for the first component of the vector pointed at by the
    //      reference value from the previous step.
    //      The result has type ref<function,f32,read_write>.
    // Evaluating the right-hand side of the assignment yields the f32 value 1.0.
    // Store the f32 value 1.0 into the storage memory locations referenced by uv.x.
    uv.x = 1.0;

    // Evaluate the left-hand side of the assignment:
    //   Evaluate 'uv[1]' to yield a reference:
    //   1. First evaluate 'uv', yielding a reference to the memory for
    //      the 'uv' variable. The result has type ref<function,vec2<f32>,read_write>.
    //   2. Then apply the '[1]' array index phrase, yielding a reference to
    //      the memory for second component of the vector referenced from
    //      the previous step.  The result has type ref<function,f32,read_write>.
    // Evaluating the right-hand side of the assignment yields the f32 value 2.0.
    // Store the f32 value 2.0 into the storage memory locations referenced by uv[1].
    uv[1] = 2.0;

    var m: mat3x2<f32>;
    // When evaluating 'm[2]':
    // 1. First evaluate 'm', yielding a reference to the memory for
    //    the 'm' variable. The result has type ref<function,mat3x2<f32>,read_write>.
    // 2. Then apply the '[2]' array index phrase, yielding a reference to
    //    the memory for the third column vector pointed at by the reference
    //    value from the previous step.
    //    Therefore the 'm[2]' expression has type ref<function,vec2<f32>,read_write>.
    // The 'let' declaration is for type vec2<f32>, so the declaration
    // statement requires the initializer to be of type vec2<f32>.
    // The Load Rule applies (because no other type rule can apply), and
    // the evaluation of the initializer yields the vec2<f32> value loaded
    // from the memory locations referenced by 'm[2]' at the time the declaration
    // is executed.
    let p_m_col2: vec2<f32> = m[2];

    var A: array<i32,5>;
    // When evaluating 'A[4]'
    // 1. First evaluate 'A', yielding a reference to the memory for
    //    the 'A' variable. The result has type ref<function,array<i32,5>,read_write>.
    // 2. Then apply the '[4]' array index phrase, yielding a reference to
    //    the memory for the fifth element of the array referenced by
    //    the reference value from the previous step.
    //    The result value has type ref<function,i32,read_write>.
    // The let-declaration requires the right-hand-side to be of type i32.
    // The Load Rule applies (because no other type rule can apply), and
    // the evaluation of the initializer yields the i32 value loaded from
    // the memory locations referenced by 'A[4]' at the time the declaration
    // is executed.
    let A_4_value: i32 = A[4];

    // When evaluating 'person.weight'
    // 1. First evaluate 'person', yielding a reference to the memory for
    //    the 'person' variable declared at module scope.
    //    The result has type ref<private,S,read_write>.
    // 2. Then apply the '.weight' member access phrase, yielding a reference to
    //    the memory for the second member of the memory referenced by
    //    the reference value from the previous step.
    //    The result has type ref<private,f32,read_write>.
    // The let-declaration requires the right-hand-side to be of type f32.
    // The Load Rule applies (because no other type rule can apply), and
    // the evaluation of the initializer yields the f32 value loaded from
    // the memory locations referenced by 'person.weight' at the time the
    // declaration is executed.
    let person_weight: f32 = person.weight;

    // Alternatively, references can also be formed from pointers using
    // the same syntax.

    let uv_ptr = &uv;
    // For the remainder of this function body, 'uv_ptr' denotes a pointer
    // to the memory underlying 'uv', and will have the type
    // ptr<function,vec2<f32>,read_write>.

    // Evaluate the left-hand side of the assignment:
    //   Evaluate '*uv_ptr' to yield a reference:
    //   1. First evaluate 'uv_ptr', yielding a pointer to the memory for
    //      the 'uv' variable. The result has type ptr<function,vec2<f32>,read_write>.
    //   2. Then apply the indirection expression operator, yielding a
    //      reference to memory for 'uv'.
    // Evaluating the right-hand side of the assignment yields the vec2<f32> value (1.0, 2.0).
    // Store the value (1.0, 2.0) into the storage memory locations referenced by uv.
    *uv_ptr = vec2f(1.0, 2.0);

    // Evaluate the left-hand side of the assignment:
    //   Evaluate 'uv_ptr.x' to yield a reference:
    //   1. First evaluate 'uv_ptr', yielding a pointer to the memory for
    //      the 'uv' variable. The result has type ptr<function,vec2<f32>,read_write>.
    //   2. Then apply the '.x' vector access phrase, yielding a reference to
    //      the memory for the first component of the vector pointed at by the
    //      reference value from the previous step.
    //      The result has type ref<function,f32,read_write>.
    // Evaluating the right-hand side of the assignment yields the f32 value 1.0.
    // Store the f32 value 1.0 into the storage memory locations referenced by uv.x.
    uv_ptr.x = 1.0;

    // Evaluate the left-hand side of the assignment:
    //   Evaluate 'uv_ptr[1]' to yield a reference:
    //   1. First evaluate 'uv_ptr', yielding a pointer to the memory for
    //      the 'uv' variable. The result has type ptr<function,vec2<f32>,read_write>.
    //   2. Then apply the '[1]' array index phrase, yielding a reference to
    //      the memory for second component of the vector referenced from
    //      the previous step.  The result has type ref<function,f32,read_write>.
    // Evaluating the right-hand side of the assignment yields the f32 value 2.0.
    // Store the f32 value 2.0 into the storage memory locations referenced by uv[1].
    uv_ptr[1] = 2.0;

    let m_ptr = &m;
    // When evaluating 'm_ptr[2]':
    // 1. First evaluate 'm_ptr', yielding a pointer to the memory for
    //    the 'm' variable. The result has type ptr<function,mat3x2<f32>,read_write>.
    // 2. Then apply the '[2]' array index phrase, yielding a reference to
    //    the memory for the third column vector pointed at by the reference
    //    value from the previous step.
    //    Therefore the 'm[2]' expression has type ref<function,vec2<f32>,read_write>.
    // The 'let' declaration is for type vec2<f32>, so the declaration
    // statement requires the initializer to be of type vec2<f32>.
    // The Load Rule applies (because no other type rule can apply), and
    // the evaluation of the initializer yields the vec2<f32> value loaded
    // from the memory locations referenced by 'm[2]' at the time the declaration
    // is executed.
    let p_m_col2: vec2<f32> = m_ptr[2];

    let A_ptr = &A;
    // When evaluating 'A[4]'
    // 1. First evaluate 'A', yielding a pointer to the memory for
    //    the 'A' variable. The result has type ptr<function,array<i32,5>,read_write>.
    // 2. Then apply the '[4]' array index phrase, yielding a reference to
    //    the memory for the fifth element of the array referenced by
    //    the reference value from the previous step.
    //    The result value has type ref<function,i32,read_write>.
    // The let-declaration requires the right-hand-side to be of type i32.
    // The Load Rule applies (because no other type rule can apply), and
    // the evaluation of the initializer yields the i32 value loaded from
    // the memory locations referenced by 'A[4]' at the time the declaration
    // is executed.
    let A_4_value: i32 = A_ptr[4];

    let person_ptr = &person;
    // When evaluating 'person.weight'
    // 1. First evaluate 'person_ptr', yielding a pointer to the memory for
    //    the 'person' variable declared at module scope.
    //    The result has type ptr<private,S,read_write>.
    // 2. Then apply the '.weight' member access phrase, yielding a reference to
    //    the memory for the second member of the memory referenced by
    //    the reference value from the previous step.
    //    The result has type ref<private,f32,read_write>.
    // The let-declaration requires the right-hand-side to be of type f32.
    // The Load Rule applies (because no other type rule can apply), and
    // the evaluation of the initializer yields the f32 value loaded from
    // the memory locations referenced by 'person.weight' at the time the
    // declaration is executed.
    let person_weight: f32 = person_ptr.weight;
}

A pointer value is formed in one of the following ways:

In all cases, the access mode of the result is the same as the access mode of the original pointer.

EXAMPLE: Pointer from a variable
// Declare a variable in the private address space, for storing an f32 value.
var<private> x: f32;

fn f() {
    // Declare a variable in the function address space, for storing an i32 value.
    var y: i32;

    // The name 'x' resolves to the module-scope variable 'x',
    // and has reference type ref<private,f32,read_write>.
    // Applying the unary '&' operator converts the reference to a pointer.
    // The access mode is the same as the access mode of the original variable, so
    // the fully specified type is ptr<private,f32,read_write>.  But read_write
    // is the default access mode for function address space, so read_write does not
    // have to be spelled in this case
    let x_ptr: ptr<private,f32> = &x;

    // The name 'y' resolves to the function-scope variable 'y',
    // and has reference type ref<private,i32,read_write>.
    // Applying the unary '&' operator converts the reference to a pointer.
    // The access mode defaults to 'read_write'.
    let y_ptr: ptr<function,i32> = &y;

    // A new variable, distinct from the variable declared at module scope.
    var x: u32;

    // Here, the name 'x' resolves to the function-scope variable 'x' declared in
    // the previous statement, and has type ref<function,u32,read_write>.
    // Applying the unary '&' operator converts the reference to a pointer.
    // The access mode defaults to 'read_write'.
    let inner_x_ptr: ptr<function,u32> = &x;
}

6.4.9. Comparison with References and Pointers in Other Languages

This section is informative, not normative.

References and pointers in WGSL are more restricted than in other languages. In particular:

Note: From the above rules, it is not possible to form a "dangling" pointer, i.e. a pointer that does not reference the memory for a "live" originating variable. A memory view may be an invalid memory reference, but it will never access memory locations not associated with the originating variable or buffer.

6.5. Texture and Sampler Types

A texel is a scalar or vector used as the smallest independently accessible element of a texture. The word texel is short for texture element.

A texture is a collection of texels supporting special operations useful for rendering. In WGSL, those operations are invoked via texture builtin functions. See § 17.7 Texture Built-in Functions for a complete list.

A WGSL texture corresponds to a WebGPU GPUTexture.

A texture has the following features:

texel format

The data representation of each texel. See § 6.5.1 Texel Formats.

dimensionality

The number of dimensions in the grid coordinates, and how the coordinates are interpreted. The number of dimensions is 1, 2, or 3. Most textures use cartesian coordinates. Cube textures have six square faces, and are sampled with a three dimensional coordinate interpreted as a direction vector from the origin toward the cube centered on the origin.

See GPUTextureViewDimension.

size

The extent of grid coordinates along each dimension. This is a function of mip level.

mip level count

The mip level count is at least 1 for sampled textures and depth textures, and equal to 1 for storage textures.
Mip level 0 contains a full size version of the texture. Each successive mip level contains a filtered version of the previous mip level at half the size (within rounding) of the previous mip level.
When sampling a texture, an explicit or implicitly-computed level-of-detail is used to select the mip levels from which to read texel data. These are then combined via filtering to produce the sampled value.

arrayed

Whether the texture is arrayed.

  • A non-arrayed texture is a grid of texels.

  • An arrayed texture is a homogeneous array of grids of texels.

array size

The number of homogeneous grids, if the texture is arrayed.

sample count

The number of samples, if the texture is multisampled.

Each texel in a texture is associated with a unique logical texel address, which is an integer tuple having:

A texture’s physical organization is typically optimized for rendering operations. To achieve this, many details are hidden from the programmer, including data layouts, data types, and internal operations that cannot be expressed directly in the shader language.

As a consequence, a shader does not have direct access to the texel memory within a texture variable. Instead, access is mediated through an opaque handle:

In this way, the set of supported operations for a texture type is determined by the availability of texture built-in functions having a formal parameter with that texture type.

Note: The handle stored by a texture variable cannot be changed by the shader. That is, the variable is read-only, even if the underlying texture to which it provides access may be mutable (e.g. a write-only storage texture).

The texture types are the set of types defined in:

A sampler is an opaque handle that controls how texels are accessed from a sampled texture or a depth texture.

A WGSL sampler maps to a WebGPU GPUSampler.

Texel access is controlled via several properties of the sampler:

addressing mode

Controls how texture boundaries and out-of-bounds coordinates are resolved. The addressing mode for each texture dimension can be set independently. See WebGPU GPUAddressMode.

filter mode

Controls which texels are accessed to produce the final result. Filtering can either use the nearest texel or interpolate between multiple texels. Multiple filter modes can be set independently. See WebGPU GPUFilterMode.

LOD clamp

Controls the min and max levels of details that are accessed.

comparison

Controls the type of comparison done for comparison sampler. See WebGPU GPUCompareFunction.

max anisotropy

Controls the maximum anisotropy value used by the sampler.

Samplers cannot be created in WGSL modules and their state (e.g. the properties listed above) are immutable within a shader and can only be set by the WebGPU API.

It is a pipeline-creation error if a filtering sampler (i.e. any sampler using interpolative filtering) is used with texture that has a non-filterable format.

Note: The handle stored by a sampler variable cannot be changed by the shader.

6.5.1. Texel Formats

In WGSL, certain texture types are parameterized by texel format.

A texel format is characterized by:

channels

Each channel contains a scalar. A texel format has up to four channels: r, g, b, and a, normally corresponding to the concepts of red, green, blue, and alpha channels.

channel format

The number of bits in the channel, and how those bits are interpreted.

Each texel format in WGSL corresponds to a WebGPU GPUTextureFormat with the same name.

Only certain texel formats are used in WGSL source code. The channel formats used to define those texel formats are listed in the Channel Formats table. The second last column specifies the conversion from the stored channel bits to the value used in the shader. This is also known as the channel transfer function, or CTF. The last column specifies the conversion from the shader value to the stored channel bits. This is also known as the inverse channel transfer function, or ICTF.

Note: The channel transfer function for 8unorm maps {0,...,255} to the floating point interval [0.0, 1.0].

Note: The channel transfer function for 8snorm maps {-128,...,127} to the floating point interval [-1.0, 1.0].

Channel Formats
Channel format Number of stored bits Interpretation of stored bits Shader type Shader value (Channel Transfer Function) Write value T (Inverse Channel Transfer Function)
8unorm 8 unsigned integer v ∈ {0,...,255} f32 v ÷ 255 max(0, min(1, T))
8snorm 8 signed integer v ∈ {-128,...,127} f32 v ÷ 127 max(-1, min(1, T))
8uint 8 unsigned integer v ∈ {0,...,255} u32 v min(255, T)
8sint 8 signed integer v ∈ {-128,...,127} i32 v max(-128, min(127, T))
16unorm 16 unsigned integer v ∈ {0,...,65535} f32 v ÷ 65535 max(0, min(1, T))
16snorm 16 signed integer v ∈ {-32768,...,32767} f32 v ÷ 32767 max(-1, min(1, T))
16uint 16 unsigned integer v ∈ {0,...,65535} u32 v min(65535, T)
16sint 16 signed integer v ∈ {-32768,...,32767} i32 v max(-32768, min(32767, T))
16float 16 IEEE-754 binary16 16-bit floating point value v f32 v quantizeToF16(T)
32uint 32 32-bit unsigned integer value v u32 v T
32sint 32 32-bit signed integer value v i32 v T
32float 32 IEEE-754 binary32 32-bit floating point value v f32 v T
2unorm 2 unsigned integer v ∈ {0,...,3} f32 v ÷ 3 max(0, min(1, T))
2uint 2 unsigned integer v ∈ {0,...,3} u32 v min(3, T)
10unorm 10 unsigned integer v ∈ {0,...,1023} f32 v ÷ 1023 max(0, min(1, T))
10uint 10 unsigned integer v ∈ {0,...,1023} u32 v min(1023, T)
10float 10 10-bit floating point value: 5 bits of biased exponent, 5 bits of fraction v f32 v max(0, T)
11float 11 11-bit floating point value: 5 bits of biased exponent, 6 bits of fraction v f32 v max(0, T)

The texel formats listed in the Texel Formats for Storage Textures table correspond to the WebGPU plain color formats which support the WebGPU STORAGE_BINDING usage with at least one access mode. These texel formats are used to parameterize the storage texture types defined in § 6.5.5 Storage Texture Types.

When the texel format does not have all four channels, then:

The last column in the table below uses the format-specific channel transfer function from the channel formats table.

Texel Formats for Storage Textures
Texel format Channel format Channels in memory order Corresponding shader value
rgba8unorm 8unorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba8snorm 8snorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba8uint 8uint r, g, b, a vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba8sint 8sint r, g, b, a vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16unorm 16unorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16snorm 16snorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16uint 16uint r, g, b, a vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16sint 16sint r, g, b, a vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba16float 16float r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rg8unorm 8unorm r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rg8snorm 8snorm r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rg8uint 8uint r, g vec4<u32>(CTF(r), CTF(g), 0u, 1u)
rg8sint 8sint r, g vec4<i32>(CTF(r), CTF(g), 0, 1)
rg16unorm 16unorm r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rg16snorm 16snorm r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rg16uint 16uint r, g vec4<u32>(CTF(r), CTF(g), 0u, 1u)
rg16sint 16sint r, g vec4<i32>(CTF(r), CTF(g), 0, 1)
rg16float 16float r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
r32uint 32uint r vec4<u32>(CTF(r), 0u, 0u, 1u)
r32sint 32sint r vec4<i32>(CTF(r), 0, 0, 1)
r32float 32float r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
rg32uint 32uint r, g vec4<u32>(CTF(r), CTF(g), 0u, 1u)
rg32sint 32sint r, g vec4<i32>(CTF(r), CTF(g), 0, 1)
rg32float 32float r, g vec4<f32>(CTF(r), CTF(g), 0.0, 1.0)
rgba32uint 32uint r, g, b, a vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba32sint 32sint r, g, b, a vec4<i32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgba32float 32float r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
bgra8unorm 8unorm b, g, r, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
r8unorm 8unorm r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
r8snorm 8snorm r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
r8uint 8uint r vec4<u32>(CTF(r), 0u, 0u, 1u)
r8sint 8sint r vec4<i32>(CTF(r), 0, 0, 1)
r16unorm 16unorm r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
r16snorm 16snorm r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
r16uint 16uint r vec4<u32>(CTF(r), 0u, 0u, 1u)
r16sint 16sint r vec4<i32>(CTF(r), 0, 0, 1)
r16float 16float r vec4<f32>(CTF(r), 0.0, 0.0, 1.0)
rgb10a2unorm r, g, b: 10unorm a: 2unorm r, g, b, a vec4<f32>(CTF(r), CTF(g), CTF(b), CTF(a))
rgb10a2uint r, g, b: 10uint a: 2uint r, g, b, a vec4<u32>(CTF(r), CTF(g), CTF(b), CTF(a))
rg11b10ufloat r, g: 11float b: 10float r, g, b vec4<f32>(CTF(r), CTF(g), CTF(b), 1.0)

WGSL predeclares an enumerant for each of the texel formats in the table.

6.5.2. Sampled Texture Types

A sampled texture is capable of being accessed in conjunction with a sampler. It can also be accessed without the use of a sampler. Sampled textures only allow read accesses.

The texel format is the format attribute of the GPUTexture bound to the texture variable. WebGPU validates compatibility between the texture, the sampleType of the bind group layout, and the sampled type of the texture variable.

The texture is parameterized by a sampled type and must be f32, i32, or u32.

Type Dimensionality Arrayed
texture_1d<T> 1D No
texture_2d<T> 2D No
texture_2d_array<T> 2D Yes
texture_3d<T> 3D No
texture_cube<T> Cube No
texture_cube_array<T> Cube Yes

6.5.3. Multisampled Texture Types

A multisampled texture has a sample count of 1 or more. Despite the name, it cannot be used with a sampler. It effectively stores multiple texels worth of data per logical texel address if the sample index is ignored.

The texel format is the format attribute of the GPUTexture bound to the texture variable. WebGPU validates compatibility between the texture, the sampleType of the bind group layout, and the sampled type of the texture variable.

texture_multisampled_2d is parameterized by a sampled type and must be f32, i32, or u32.

Type Dimensionality Arrayed
texture_multisampled_2d<T> 2D No
texture_depth_multisampled_2d 2D No

6.5.4. External Sampled Texture Types

An External texture is an opaque two-dimensional float-sampled texture type similar to texture_2d<f32> but potentially with a different representation. It can be read using textureLoad or textureSampleBaseClampToEdge built-in functions, which handle these different representations.

See WebGPU § 6.4 GPUExternalTexture.

Type Dimensionality Arrayed
texture_external