dawn Documentation

Native WebGPU implementation. Mirror of File bugs here:

1,180 stars C++ C++ 94 files · ~107,212 tokens #dawn#emdawnwebgpu#webgpu
RAW Doc

Technical Documentation: google/dawn

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (README.md)

Dawn, a WebGPU implementation

Dawn is an open-source and cross-platform implementation of the WebGPU standard.
More precisely it implements webgpu.h that is a one-to-one mapping with the WebGPU IDL.
Dawn is meant to be integrated as part of a larger system and is the underlying implementation of WebGPU in Chromium.

Dawn provides several WebGPU building blocks:

  • WebGPU C/C++ headers that applications and other building blocks use.
    • The webgpu.h version that Dawn implements.
    • A C++ wrapper for the webgpu.h.
  • A "native" implementation of WebGPU using platforms' GPU APIs: D3D12, Metal, Vulkan and OpenGL. See per API support for more details.
  • A client-server implementation of WebGPU for applications that are in a sandbox without access to native drivers
  • Tint is a compiler for the WebGPU Shader Language (WGSL) that can be used in standalone to convert shaders from and to WGSL.

Helpful links:

Documentation table of content

Developer documentation:

User documentation: (TODO, figure out what overlaps with the webgpu.h docs)

License

BSD 3-Clause License, please see LICENSE.

Disclaimer

This is not an officially supported Google product.


Dawn Overview (`docs/dawn/overview.md`)

Dawn repository overview

This repository contains the implementation of Dawn, which is itself composed of two main
libraries (dawn_native and dawn_wire), along with support libraries, tests, and samples. Dawn makes
heavy use of code-generation based on the dawn.json file that describes the native WebGPU API. It
is used to generate the API headers, C++ wrapper, parts of the client-server implementation, and
more!

Directory structure

  • dawn.json: contains a description of the native WebGPU in JSON form.
    It is the data model that's used by the code generators.
  • dawn_wire.json: contains additional information used to
    generate dawn_wire files, such as commands in addition to regular WebGPU commands.
  • generator: directory containg the code generators and their templates.
    Generators are based on Jinja2 and parse data-models from JSON files.
    • dawn_json_generator.py: the main code generator
      that outputs the WebGPU headers, C++ wrapper, client-server implementation, etc.
    • templates: Jinja2 templates for the generator, with
      subdirectories for groups of templates that are all used in the same library.
  • include:
    • dawn: public headers with subdirectories for each library. Note that
      some headers are auto-generated and not present directly in the directory.
  • infra: configuration file for the commit-queue infrastructure.
  • scripts: contains a grab-bag of files that are used for building Dawn, in
    testing, etc.
  • src:
    • dawn: root directory for Dawn code
      • common: helper code that is allowed to be used by Dawn's core
        libraries, dawn_native and dawn_wire. Also allowed for use in all other Dawn targets.
      • fuzzers: various fuzzers for Dawn that are running
        in Clusterfuzz.
      • native: code for the implementation of WebGPU on top of graphics
        APIs. Files in this folder are the "frontend" while subdirectories are "backends".
        • ``: code for the implementation of the backend on a specific graphics API, for
          example d3d12, metal or vulkan.
      • samples: a small collection of samples using the native WebGPU
        API. They were mostly used when bringing up Dawn for the first time, and to test the
        WGPUSurface object.
      • tests:
        • end2end: tests for the execution of the WebGPU API and
          require a GPU to run.
        • perf_tests: benchmarks for various aspects of Dawn.
        • unittests: code unittests of internal classes, but
          also by extension WebGPU API tests that don't require a GPU to run.
          • validation: WebGPU validation tests not
            using the GPU (frontend tests)
        • white_box: tests using the GPU that need to access the
          internals of dawn_native or dawn_wire.
      • wire: code for an implementation of WebGPU as a client-server
        architecture.
      • utils: helper code to use Dawn used by tests and samples but
        disallowed for dawn_native and dawn_wire.
      • platform: definition of interfaces for dependency injection in
        dawn_native or dawn_wire.
  • third_party: directory where dependencies live as well as their
    buildfiles.

Dawn Native (`dawn_native`)

The largest library in Dawn is dawn_native which implements the WebGPU API by translating to
native graphics APIs such as D3D12, Metal or Vulkan. It is composed of a frontend that does all the
state-tracking and validation, and backends that do the actual translation to the native graphics
APIs.

dawn_native hosts the spirv-val for validation of
SPIR-V shaders and uses Tint shader translator to convert WGSL shaders to an
equivalent shader for use in the native graphics API (HLSL for D3D12, MSL for Metal or Vulkan
SPIR-V for Vulkan).

Dawn Wire (`dawn_wire`)

A second library that implements both a client that takes WebGPU commands and serializes them into a
buffer, and a server that deserializes commands from a buffer, validates they are well-formed and
calls the relevant WebGPU commands. Some server to client communication also happens so the API's
callbacks work properly.

Note that dawn_wire is meant to do as little state-tracking as possible so that the client can be
lean and defer most of the heavy processing to the server side where the server calls into
dawn_native.

Dawn Proc (`dawn_proc`)

Normally libraries implementing webgpu.h should implement function like wgpuDeviceCreateBuffer
but instead dawn_native and dawn_wire implement the dawnProcTable which is a structure
containing all the WebGPU functions Dawn implements. Then a dawn_proc library contains a static
version of this dawnProcTable and for example forwards wgpuDeviceCreateBuffer to the
procTable.deviceCreateBuffer function pointer. This is useful in two ways:

  • It allows deciding at runtime whether to use dawn_native and dawn_wire, which is useful to
    test both paths with the same binary in our infrastructure.
  • It avoids applications that know they will only use Dawn to query all entrypoints at once instead
    of using wgpuGetProcAddress repeatedly.

Code generation

When the WebGPU API evolves, a lot of places in Dawn have to be updated, so to reduce efforts, Dawn
relies heavily on code generation for things like headers, proc tables and de/serialization. For
more information, see codegen.md.


Imgs README (`docs/imgs/README.md`)

Dawn's logo and derivatives found in this folder are under the Creative Commons Attribution 4.0 International (CC BY 4.0) license.


Tint Spirv Reader Overview (`docs/tint/spirv-reader-overview.md`)

Converting SPIR-V to WGSL

This document describes the challenges in converting SPIR-V into WGSL.

Note: Unless otherwise specified, the namespace for C++ code is
tint::spirv::reader::.

Overall flow

  1. Validate the SPIR-V input.

    The SPIR-V module (binary blob) is validated against rules for
    Vulkan 1.1, using the SPIRV-Tools validator.

    This allows the rest of the flow to ignore invalid inputs.
    However, the SPIR-V might still be rejected in a later step because:

    • it uses features unavailable in WGSL, or
    • the SPIR-V Reader is insufficiently smart, or
    • the translated program tries to do something rejected by WGSL's rules
      (which are checked by Tint's Resolver).
  2. Load the SPIR-V binary into an in-memory representation.

    The SPIR-V reader uses the in-memory representation of the SPIR-V
    module defined by the SPIRV-Tools optimizer. That provides
    convenient representation of basic structures such as:

    • instructions
    • types
    • constants
    • functions
    • basic blocks

    and provides analyses for:

    • relating definitions to uses (spvtools::opt::analysis::DefUseMgr)
    • types (spvtools::opt::analysis:TypeManager)
    • constants (spvtools::opt::analysis:ConstantManager)

    Note: The SPIR-V is not modified by the SPIR-V Reader.

  3. Translate the SPIR-V module into Tint's AST.

    The AST is valid for WGSL except for some small exceptions which are
    cleaned up by transformations.

  4. Post-process the AST to make it valid for WGSL.

    Example:

    • Rewrite strided arrays and matrices (remove @stride attribute)
    • Rewrite atomic functions
    • Remove unreachable statements, to satisfy WGSL's behaviour analysis.

Overcoming mismatches between SPIR-V and WGSL

Remapping builtin inputs and outputs

SPIR-V for Vulkan models builtin inputs and outputs as variables
in Input and Output storage classes.

WGSL builtin inputs are parameters to the entry point, and
builtin outputs are result values of the entry point.

See spirv-input-output-variables.md

We only care about `gl_Position` from `gl_PerVertex`

Glslang SPIR-V output for a vertex shader has a gl_PerVertex
output variable with four members:

  • gl_Position
  • gl_PointSize
  • gl_ClipDistance
  • gl_CullDistance

WGSL only supports the position builtin variable.

The SPIR-V Reader has a bunch of carveouts so it only generates the
position variable. In partcular, it tracks which expressions are actually
accesses into the per-vertex variable, and ignores accesses to other
parts of the structure, and remaps accesses of the position member.

`gl_PointSize` must be 1.0

It's a WGSL rule. SPIR-V is more flexible, and the SPIR-V Reader
checks that any assignment to (the equivalent of) gl_PointSize
must the constant value 1.0.

Remapping sample mask inputs and outputs

There's some shenanigans here I don't recall.
See the SkipReason enum.

Integer signedness

In SPIR-V, the instruction determines the signedness of an operation,
not the types of its operands.

For example:

text
%uint = OpTypeInt 32 0 ; u32 type
%int = OpTypeInt 32 1 ; i32 type

%int_1 = OpConstant %int 1  ;  WGSL 1i
%uint_2 = OpConstant %uint 2 ; WGSL 2u

; You can mix signs of an operand, and the instruction
; tells you the result type.
%sum_uint = OpIAdd %uint %int %int_1 %uint_2
%sum_int = OpIAdd %int %int %int_1 %uint_2

However, WGSL arithmetic tends to require the operands and
result type for an operation to all have the same signedness.

So the above might translate to WGSL as:

text
let sum_uint: u32 = bitcast(1i) + 2u;
let sum_int: i32 = 1i + bitcast(2u);

See:

  • ParserImpl::RectifyOperandSignedness
  • ParserImpl::RectifySecondOperandSignedness
  • ParserImpl::RectifyForcedResultType

Translating textures and samplers

SPIR-V textures and samplers are module-scope variables
in UniformConstant storage class.
These map directly to WGSL variables.

For a sampled-image operation, SPIR-V will:

  • load the image value from a texture variable
  • load the sampler value from a sampler variable
  • form a "sampled image" value using SpvOpSampledImage
  • then use that sampled image value in a image operation
    such as SpvOpImageSampleImplicitLod

For an image operation that is not a sampled-image operation
(e.g. OpImageLoad or OpImageWrite), then the steps are similar
except without a sampler (clearly), and without invoking
OpSampledImage.

In contrast to the SPIR-V code pattern, the WGSL builtin requires
the texture and sampler value to be passed in as separate parameters.
Secondly, they are passed in by value, by naming the variables
themselves and relying on WGSL's "Load Rule" to pass the handle
value into the callee.

When the SPIR-V Reader translates a texture builtin, it traces
backward through the OpSampledImage operation (if any),
back through the load, and all the way back to the OpVariable
declaration. It does this for both the image/texture variable and
the sampler variable (if applicable). It then uses the names
of those variables as the corresponding arguments to the WGSL
texture builtin.

Passing textures and samplers into helper functions

Glslang generates SPIR-V where texture and sampler formal parameters
are as pointer-to-UniformConstant.

WGSL models them as passing texture and sampler values themselves,
conceptually as opaque handles. This is similar to GLSL, but unlike
SPIR-V.

To support textures and samplers as arguments to user-defined functions,
we extend the tracing logic so it knows to bottom out at OpFunctionParameter.

Also, code that generates function declarations now understands formal
parameters declared as a pointer to uniform-constant as
well as direct image and sampler values.

Example GLSL compute shader:

text
#version 450

layout(set=0,binding=0) uniform texture2D im;
layout(set=0,binding=1) uniform sampler s;

vec4 helper(texture2D imparam, sampler sparam) {
  return texture(sampler2D(imparam,sparam),vec2(0));
}

void main() {
  vec4 v = helper(im,s);
}

SPIR-V generated by Glslang (Shaderc's glslc):

text
; SPIR-V
; Version: 1.0
; Generator: Google Shaderc over Glslang; 10
; Bound: 32
; Schema: 0
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %main "main"
               OpExecutionMode %main LocalSize 1 1 1
               OpSource GLSL 450
               OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
               OpSourceExtension "GL_GOOGLE_include_directive"
               OpName %main "main"
               OpName %helper_t21_p1_ "helper(t21;p1;"
               OpName %imparam "imparam"
               OpName %sparam "sparam"
               OpName %v "v"
               OpName %im "im"
               OpName %s "s"
               OpDecorate %im DescriptorSet 0
               OpDecorate %im Binding 0
               OpDecorate %s DescriptorSet 0
               OpDecorate %s Binding 1
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
          %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
%_ptr_UniformConstant_7 = OpTypePointer UniformConstant %7
          %9 = OpTypeSampler
%_ptr_UniformConstant_9 = OpTypePointer UniformConstant %9
    %v4float = OpTypeVector %float 4
         %12 = OpTypeFunction %v4float %_ptr_UniformConstant_7 %_ptr_UniformConstant_9
         %19 = OpTypeSampledImage %7
    %v2float = OpTypeVector %float 2
    %float_0 = OpConstant %float 0
         %23 = OpConstantComposite %v2float %float_0 %float_0
%_ptr_Function_v4float = OpTypePointer Function %v4float
         %im = OpVariable %_ptr_UniformConstant_7 UniformConstant
          %s = OpVariable %_ptr_UniformConstant_9 UniformConstant
       %main = OpFunction %void None %3
          %5 = OpLabel
          %v = OpVariable %_ptr_Function_v4float Function
         %31 = OpFunctionCall %v4float %helper_t21_p1_ %im %s
               OpStore %v %31
               OpReturn
               OpFunctionEnd
%helper_t21_p1_ = OpFunction %v4float None %12
    %imparam = OpFunctionParameter %_ptr_UniformConstant_7
     %sparam = OpFunctionParameter %_ptr_UniformConstant_9
         %16 = OpLabel
         %17 = OpLoad %7 %imparam
         %18 = OpLoad %9 %sparam
         %20 = OpSampledImage %19 %17 %18
         %24 = OpImageSampleExplicitLod %v4float %20 %23 Lod %float_0
               OpReturnValue %24
               OpFunctionEnd

What the SPIR-V Reader currently generates:

text
@group(0) @binding(0) var im : texture_2d<f32>;

@group(0) @binding(1) var s : sampler;

fn helper_t21_p1_(imparam : texture_2d<f32>, sparam : sampler) -> vec4<f32> {
  let x_24 : vec4<f32> = textureSampleLevel(imparam, sparam, vec2<f32>(0.0f, 0.0f), 0.0f);
  return x_24;
}

fn main_1() {
  var v : vec4<f32>;
  let x_31 : vec4<f32> = helper_t21_p1_(im, s);
  v = x_31;
  return;
}

@compute @workgroup_size(1i, 1i, 1i)
fn main() {
  main_1();
}

Dimensionality mismatch in texture builtins

Vulkan SPIR-V is fairly forgiving in the dimensionality
of input coordinates and result values of texturing operations.
There is some localized rewriting of values to satisfy the overloads
of WGSL's texture builtin functions.

Reconstructing structured control flow

This is subtle.

  • Use structural dominance (but we didn't have the name at the time).
    See SPIR-V 1.6 Rev 2 for updated definitions.
  • See the big comment at the start of reader/spirv/function.cc
  • See internal presentations.

Basically:

  • Compute a "structured order" for structurally reachable basic blocks.
  • Traversing in structured order, use a stack-based algorithn to
    identify intervals of blocks corresponding to structured constructs.
    For example, loop construct, continue construct, if-selection,
    switch-selection, and case-construct. Constructs can be nested,
    hence the need for a stack. This is akin to "drawing braces"
    around statements, to form block-statements that will appear in
    the output. This step performs some validation, which may now be
    redundant with the SPIRV-Tools validator. This is defensive
    programming, and some tests skip use of the SPIRV-Tools validator.
  • Traversing in structured order, identify structured exits from the
    constructs identified in the previous step. This determines what
    control flow edges correspond to break, continue, and return,
    as needed.
  • Traversing in structured order, generate statements for instructions.
    This uses a stack corresponding to nested constructs. The kind of
    each construct being entered or exited determines emission of control
    flow constructs (WGSL's if, loop, continuing, switch, case).

Preserving execution order

An instruction inside a SPIR-V instruction is one of:

  • control flow: see the previous section
  • combinatorial: think of this as an ALU operation, i.e. the effect
    is purely to evaluate a result value from the values of its operands.
    It has no side effects, and is not affected by external state such
    as memory or the actions of other invocations in its subgroup.
    Examples: arithmetic, OpCopyObject
  • interacts with memory or other invocations in some way.
    Examples: load, store, atomics, barriers, (subgroup operations when we
    get them)
  • function calls: functions are not analyzed to see if they are pure,
    so we assume function calls are non-combinatorial.

To preserve execution order, all non-combinatorial instructions must
be translated as their own separate statement. For example, an OpStore
maps to an assignment statement.

However, combinatorial instructions can be emitted at any point
in evaluation, provided data flow constraints are satisfied: input
values are available, and such that the resulting value is generated
in time for consumption by downstream uses.

The SPIR-V Reader uses a heuristic to choose when to emit combinatorial
values:

  • if a combinatorial expression only has one use, and
  • its use is in the same structured construct as its definition, then
  • emit the expression at the place where it is consumed.

Otherwise, make a let declaration for the value.

Why:

  • If a value has many uses, then computing it once can save effort.
    Preserve that choice if it was made by an upstream optimizing compiler.
  • If a value is consumed in a different structured construct, then the
    site of its consumption may be inside a loop, and we don't want to
    sink the computation into the loop, thereby causing spurious extra
    evaluation.

This heuristic generates halfway-readable code, greatly reducing the
varbosity of code in the common case.

Hoisting and phis

SPIR-V uses SSA (static single assignment). The key requirement is
that the definition of a value must dominate its uses.

WGSL uses lexical scoping.

It is easy enough for a human or an optimizing compiler to generate
SSA cases which do not map cleanly to a lexically scoped value.

Example pseudo-GLSL:

text
void main() {
  if (cond) {
    const uint x = 1;
  } else {
    return;
  }
  const uint y = x;  // x's definition dominates this use.
}

This isn't valid GLSL and its analog would not be a valid WGSL
program because x is used outside the scope of its declaration.

Additionally, SSA uses phi nodes to transmit values from predecessor
basic blocks that would otherwise not be visible (because the
parent does not dominate the consuming basic block). An example
is sending the updated value of a loop induction variable back to
the top of the loop.

The SPIR-V reader handles these cases by tracking:

  • where a value definition occurs
  • the span of basic blocks, in structured order, where there
    are uses of the value.

If the uses of a value span structured contructs which are not
contained by the construct containing the definition (or
if the value is a phi node), then we "hoist" the value
into a variable:

  • create a function-scope variable at the top of the structured
    construct that spans all the uses, so that all the uses
    are in scope of that variable declaration.

  • for a non-phi: generate an assignment to that variable corresponding
    to the value definition in the original SPIR-V.

  • for a phi: generate an assigment to that variable at the end of
    each predecessor block for that phi, assigning the value to be
    transmitted from that phi.

This scheme works for values which can be the stored in a variable.

It does not work for pointers. However, we don't think we need
to solve this case any time soon as it is uncommon or hard/impossible
to generate via standard tooling.
See https://crbug.com/tint/98 and https://crbug.com/tint/837

Mapping types

SPIR-V has a recursive type system. Types are defined, given result IDs,
before any functions are defined, and before any constant values using
the corresponding types.

WGSL also has a recursive type system. However, except for structure types,
types are spelled inline at their uses.

Texture and sampler types

SPIR-V image types map to WGSL types, but the WGSL type is determined
more by usage (what operations are performed on it) than by declaration.

For example, Vulkan ignores the "Depth" operand of the image type
declaration (OpTypeImage).
See 16.1 Image Operations Overview.
Instead, we must infer that a texture is a depth texture because
it is used by image instructions using a depth-reference, e.g.
OpImageSampleDrefImplicitLod vs. OpImageSampleImplicitLod.

Similarly, SPIR-V only has one sampler type. The use of the
sampler determines whether it maps to a WGSL sampler or
sampler_comparison (for depth sampling).

The SPIR-V Reader scans uses of each texture and sampler
in the module to infer the appropriate target WGSL type.
See ParserImpl::RegisterHandleUsage

In Vulkan SPIR-V it is possible to use the same sampler for regular
sampling and depth-reference sampling. In this case the SPIR-V Reader
will infer a depth texture, but then the generated program will fail WGSL
validation.

For example, this GLSL fragment shader:

text
#version 450

layout(set=1,binding=0) uniform texture2D tInput;
layout(set=1,binding=1) uniform sampler s;

void main() {
  vec4 v = texture(sampler2D(tInput,s),vec2(0));
  float f = texture(sampler2DShadow(tInput,s),vec3(0));
}

Converts to this WGSL shader:

text
@group(1) @binding(0) var tInput : texture_depth_2d;

@group(1) @binding(1) var s : sampler_comparison;

fn main_1() {
  var v : vec4<f32>;
  var f : f32;
  let x_23 : vec4<f32> = vec4<f32>(textureSample(tInput, s, vec2<f32>(0.0f, 0.0f)), 0.0f, 0.0f, 0.0f);
  v = x_23;
  let x_34 : f32 = textureSampleCompare(tInput, s, vec3<f32>(0.0f, 0.0f, 0.0f).xy, vec3<f32>(0.0f, 0.0f, 0.0f).z);
  f = x_34;
  return;
}

@fragment
fn main() {
  main_1();
}

But then this fails validation:

text
error: no matching call to textureSample(texture_depth_2d, sampler_comparison, vec2<f32>)
15 candidate functions: ...

References and pointers

SPIR-V has a pointer type.

A SPIR-V pointer type corresponds to a WGSL memory view. WGSL has two
memory view types: a reference type, and a pointer type.

See spirv-ptr-ref.md for details on the translation.

Mapping buffer types

Vulkan SPIR-V expresses a Uniform Buffer Object (UBO), or
a WGSL 'uniform buffer' as:

  • an OpVariable in Uniform storage class
  • its pointee type (store type) is a Block-decorated structure type

Vulkan SPIR-V has two ways to express a Shader Storage Buffer Object (SSBO),
or a WGSL 'storage buffer' as either deprecated-style:

  • an OpVariable in Uniform storage class
  • its pointee type (store type) is a BufferBlock-decorated structure type

or as new-style:

  • an OpVariable in StorageBuffer storage class
  • its pointee type (store type) is a Block-decorated structure type

Deprecated-style storage buffer was the only option in un-extended
Vulkan 1.0. It is generated by tools that want to generate code for
the broadest reach. This includes DXC.

New-style storage buffer requires the use of the OpExtension "SPV_KHR_storage_buffer_storage_class" or SPIR-V 1.3 or later
(Vulkan 1.1 or later).

Additionally, a storage buffer in SPIR-V may be marked as NonWritable.
Perhaps surprisingly, this is typically done by marking all the
members of the top-level (Buffer)Block-decorated structure as NonWritable.
(This is the common translation because that's what Glslang does.)

Translation of uniform buffers is straightforward.

However, the SPIR-V Reader must support both the deprecated and the new
styles of storage buffers.

Additionally:

  • a storage buffer with all NonWritable members is translated with read
    access mode. This becomes a part of its WGSL reference type (and hence
    corresponding pointer type).
  • a storage buffer without all NonWritable members is translated with
    an explicit read_write access mode. This becomes a part of its
    WGSL reference type (and hence corresponding pointer type).

Note that read-only vs. read-write is a property of the pointee-type in SPIR-V,
but in WGSL it's part of the reference type (not the store type).

To handle this mismatch, the SPIR-V Reader has bookkeeping to map
each pointer value (inside a function) back to through to the originating
variable. This originating variable may be a buffer variable which then
tells us which address space and access mode to use for a locally-defined
pointer value.

Since baseline SPIR-V does not allow passing pointers to buffers into
user-defined helper functions, we don't need to handle this buffer type
remapping into function formal parameters.

Mapping OpArrayLength

The OpArrayLength instruction takes a pointer to the enclosing
structure (the pointee type of the storage buffer variable).

But the WGSL arrayLength builtin variable takes a pointer to the
member inside that structure.

A small local adjustment is sufficient here.


Becoming Committer (`docs/becoming-committer.md`)

Becoming a committer

Committers are people that are considered to have write access to the Dawn repository. All CLs
submitted to the Dawn repository need to be reviewed by one or (for non-trivial ones) two
committers. In addition to this requirement, all CLs must also be reviewed or authored by an OWNER
of the changed files.

This privilege is granted with some expectation of responsibility: committers are people who care
about the Dawn project and want to help it meet its goals. A committer is not just someone who can
make changes, but someone who has demonstrated their ability to collaborate with the team, get the
most knowledgeable people to review code, contribute high-quality code, and follow through to fix
issues (in code or tests).

A committer is a contributor to the Dawn project’s success and a citizen helping the projects
succeed.

How to become a committer?

Note to Googlers: the process is a bit different for WebGPU team members, reach out to
an @google.com top-level OWNER directly.

In a nutshell, contribute 20 non-trivial patches and get at least three different people to review
them (you'll need three people to support you). Then ask someone to nominate you. You're
demonstrating your:

  • commitment to the project (20 good patches requires a lot of your valuable time),
  • ability to collaborate with the team,
  • understanding of how the team works (policies, processes for testing and code review, etc),
  • understanding of the projects' code base and coding style, and
  • ability to write good code (last but certainly not least)

A current committer nominates you by sending email to one of the top-level OWNERs containing:

  • your first and last name
  • your email address in Gerrit
  • an explanation of why you should be a committer,
  • embedded list of links to revisions (about top 10) containing your patches

Top-level OWNERs will deliberate and come to a consensus, targeting to reach it in 5 working days.
Once you get approval, you are granted additional CR+2 review permissions. In some cases the
process could drag out slightly. Ping OWNERs and keep writing patches! Even in the rare cases where
a nomination fails, the objection is usually something easy to address like “more patches” or “not
enough people are familiar with this person’s work.”

Two-factor authentication with a security key is a requirement for adding your account as a
committer. If you haven‘t done so already, you’ll need to set up a security key on your account.
For more information about this requirement see Gerrit
ReAuth
.

Maintaining committer status

You don't really need to do much to maintain committer status: just keep being awesome and helping
the Dawn project!

In the unhappy event that a committer continues to disregard good citizenship (or actively disrupts
the project), we may need to revoke that person's status. The process is the same as for nominating
a new committer: someone suggests the revocation with a good reason and OWNERs deliberate. We hope
that's simple enough, and that we never have to test it in practice.

In addition, as a security measure, if you are inactive on Gerrit (no upload, no comment and no
review) for more than a year, we may revoke your committer privileges. An email notification is
sent about 7 days prior to the removal. This is not meant as a punishment, so if you wish to resume
contributing after that, contact top level OWNERs to ask that it be restored, and we will normally
do so.

(This document was inspired
by https://v8.dev/docs/become-committer itself inspired by
its Chromium equivalent)


Building (`docs/building.md`)

Building Dawn

Generally, Dawn's tooling and system dependencies are set by Chromium.
Therefore Chromium's build environment, provided by GN and depot_tools,
is Dawn's primary build environment. However, Dawn can be built as a
standalone C++ project, often with alternative build generators and C++
compilers, as described below.

If you are developing Dawn itself, see also
development-tips.md.

System requirements

  • Git
  • depot_tools in your path
  • A build generator, one of:
    • GN, provided by depot_tools
    • CMake 3.16 or later
  • Ninja 1.12 (or another build tool supported by CMake)
  • Python 3, for fetching dependencies
  • Go 1.23 or later: https://go.dev/
  • A C++20 compiler
    • When using GN, the default compiler will be provided automatically.
    • Otherwise, check the platform details below.
  • Linux

    • CPU:
      • x86-64 (also known as amd64)
      • arm64
      • x86: untested
      • mips, mips64: untested
    • The pkg-config command:
      sh
      # Install pkg-config on Ubuntu
      sudo apt-get install pkg-config
    • C++20 compiler:
      • Clang 19 or later. This the primary supported compiler.
      • GCC 12 or later
  • Mac

    • CPU:
      • Apple Silicon
      • Intel x86-64
    • Xcode 12.2+.
    • The macOS 13.0 SDK. Run xcode-select to check whether you have it.
      sh
      ls `xcode-select -p`/SDKs
  • Windows

    • CPU:
      • x86-64 (also known as amd64)
      • arm64 support is experimental
    • Windows SDK 10.0.26100.x or a later version.
    • C++20 compiler:
      • Clang, as provided by GN. This is the primary supported compiler.
      • MSVC 19.41, or Visual Studio 2022 v17.11 or later.
        Note: Dawn is tested daily with MSVC 19.43 or VS 2022 v17.13

Get the code and its dependencies

Using `depot_tools`

Dawn uses the Chromium build system and dependency management so you need to install depot_tools
and add it to the PATH.

sh
# Clone the repo as "dawn"
git clone https://dawn.googlesource.com/dawn dawn && cd dawn

# Bootstrap the gclient configuration
cp scripts/standalone.gclient .gclient

# Fetch external dependencies and toolchains with gclient
gclient sync

The standalone build dependencies includes Microsoft Direct 3D WARP with the following terms:

text
The Microsoft Direct 3D WARP software is available as part of this download and is
licensed to you by Microsoft Corporation (or one of its affiliates). The Microsoft
Direct 3D WARP software is governed by an end user license agreement (“EULA”)
which can be found at this link or successor links as applicable:
https://www.nuget.org/packages/Microsoft.Direct3D.WARP/1.0.20-preview/License?.
By downloading the Microsoft Direct 3D WARP software you agree to the EULA terms.

Without `depot_tools`

If you cannot or do not want to depend on depot_tools, you may use the
tools/fetch_dawn_dependencies.py to clone the dependencies' repositories:

sh
# Clone the repo as "dawn"
git clone https://dawn.googlesource.com/dawn dawn && cd dawn

# Fetch dependencies (loosely equivalent to gclient sync)
python tools/fetch_dawn_dependencies.py

Use python tools/fetch_dawn_dependencies.py -h to know more about the available options. Contrary
to depot_tools, this scripts does not figure out option-dependent requirements automatically.

On Windows, configuring CMake with both DAWN_FETCH_DEPENDENCIES=ON and
DAWN_USE_AGILITY_SDK=ON downloads the Agility SDK version pinned in DEPS.

Linux dependencies

The following packages are needed to build Dawn. (Package names are the Ubuntu names).

  • libfuse2
  • libxrandr-dev
  • libxinerama-dev
  • libxcursor-dev
    ( libxkbcommon-dev
  • libxi-dev
  • mesa-common-dev
  • libx11-xcb-dev
  • pkg-config
  • nodejs
  • npm
sh
sudo apt-get install libfuse2 libxrandr-dev libxinerama-dev libxcursor-dev libxkbcommon-dev libxi-dev mesa-common-dev libx11-xcb-dev pkg-config nodejs npm

Note, nodejs and npm are only needed if building dawn.node.

Build Dawn

Compiling using CMake + Ninja

sh
mkdir -p out/Debug
cd out/Debug
cmake -GNinja ../..
ninja # or autoninja

Compiling using CMake + make

sh
mkdir -p out/Debug
cd out/Debug
cmake ../..
make # -j N for N-way parallel build

Compiling using gn + ninja

sh
mkdir -p out/Debug
gn gen out/Debug
autoninja -C out/Debug

The most common GN build option is is_debug=true/false; otherwise
gn args out/Debug --list shows all the possible options.

On macOS you'll want to add the use_system_xcode=true in most cases.
(and if you're a googler please get XCode from go/xcode).

To generate a Microsoft Visual Studio solution, add ide=vs2022 and
ninja-executable=<dawn parent directory>\dawn\third_party\ninja\ninja.exe.
The .sln file will be created in the output directory specified.

For CMake builds, you can enable installation with the DAWN_ENABLE_INSTALL option
and use find_package(Dawn) in your CMake project to discover Dawn and link with
the dawn::webgpu_dawn target. Please see Quickstart with CMake
for step-by-step instructions.

Cross-Compilation

Cross-compilation is provided on a best-effort basis, primarily for the purpose
of compilation checks during local Dawn development. It only supports Clang.
It may not always work, and the resulting binaries are not guaranteed to work.

  1. Add your target OS to the target_os array in .gclient. Or, use
    standalone-maximal.gclient and follow the instructions in it.
    This will enable downloading toolchains and any target-specific Dawn DEPS.

    • Windows: ~16GB.
    • Mac: ~4GB.
    • Android: ~2.5GB.
  2. gclient sync.

  3. Create a new out/* directory for each target build with the relevant GN
    args. The following configurations have been tested:

    • target_os = "mac" target_cpu = "arm64"/"x64" on Linux hosts
    • target_os = "win" target_cpu = "x64" on Linux and Mac hosts
      • On Mac hosts, many Windows files can't build using Siso remote builds.
        Use autoninja --offline (or autoninja -k=0 ; autoninja --offline
        to first compile everything possible with Siso, then compile the rest
        locally). See https://crbug.com/446124900.
    • target_os = "android target_cpu = "arm64" on Linux hosts (note below)

For background, see also
this guide.

For Android targets (Linux hosts)

Cross-compilation for Android as described above works to compile targets, but
the resulting binaries have not been tested.
If you want to run them you may
need to set up an Android Chromium build (which contains Dawn) and build Dawn
targets (like dawn_unittests, dawn_end2end_tests) in the Chromium build.

Using ccache for CMake builds

There is a substantial number of source files that are needed to be
built for Dawn and its dependencies (~thousands), which can lead to
long compile times on resource bound machines (i.e. laptops),
especially when performing clean builds. The general advice for this
situation is use the gn build with a remote compile system like siso,
if available.

An alternative if remote compilation isn't available, or if using
CMake is essential for your workflow, is to use
ccache to avoid rebuilding targets. For full
details on using ccache see the project's documentation, but below is
an example of how to use it on a Debian based Linux dev env.

sh
# One time Install and setup of ccache
apt-get install ccache
ccache -F 0 -M 0  # Sets the number of files and size of the cache to unlimited, the default is 5 GiB. Choose values that make sense for your situation.

# Setup CMake + Ninja building using cccache
mkdir -p out/cache
cd out/cache
cmake -GNinja -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache <Any other CMake flags> ../.
ninja # or autoninja

# Optional: In another terminal, monitor the status of the cache. (Will have a lot of misses on the first run)
watch ccache -s

Using ccache will not help with the initial build of Dawn, since the
targets still need to be built at least once locally, but subsequent
builds, even after a clean operation should be much faster. Some
operations like linking are not cache-able, so will need to be run on
every build, but the bulk of file compilations should be
cache-able. Updating the repo, editing source files, and changing build
flags will all cause misses, since entries in the cache are based on
flags + contents of the source file.

Weird CMake build breaks on Linux

If you see errors like this:

text
error: satisfaction of constraint 'is_constructible_v<_Tp, _Up>' depends on itself
  && is_constructible_v<_Tp, _Up>
     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

or similar complaints about constraints depending on themselves, this
is a known issue
with older versions of Clang (17ish through 19ish) and newer versions
of GCC (15+).

Specifically Clang by default uses the standard library from GCC, and
GCC 15 introduces a construct that older Clang thinks is out of spec,
thought it is not.

Currently there is no provided hermetic Linux build for CMake to be
used to avoid this problem, so you either need to use a newer version
of Clang or use an older version of GCC for your entire system.

For some environments currently, Clang 19.x is the newest version
available, and GCC 15 is the default compiler, so it cannot be
down-rev'd.

In this case you will need to install a parallel GCC toolchain and
tell Clang to use that.

For Debian based systems this can be done as follows:

sh
sudo apt-get install gcc-14 libgcc-14-dev
...
cmake -DCMAKE_CXX_FLAGS="--gcc-install-dir=/usr/lib/gcc/x86_64-linux-gnu/14" <other cmake flags> <path to repo root>

This assumes gcc-14 is installed to
/usr/lib/gcc/x86_64-linux-gnu/14, which may not be true depending on
your specific distro and architecture.

It is likely CMake will not correctly pick up this type of dev env
change if you run this in an existing build directory, so it is
recommended that you setup a new build directory to use this.

Fuzzers on MacOS

As of Late Oct 2025, fuzzing on a dev Mac is not in a good state.

The old workaround for fuzzing with XCode 16.X should still work, but that is
not the Chromium supported version of XCode so there may be other issues. That
workaround does not work for XCode 26.X, which is supported by Chromium.

If you attempt to build and run the fuzzers without a workaround, you will
either get build failures like this:

sh
ld: file not found:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib/darwin/libclang_rt.fuzzer_osx.a

or if using a component build, you will get a similar runtime issue for a
missing .dylib.

The root cause for this is that the build of llvm shipped as part of the XCode
SDK does not include the standard support libraries for fuzzing.

The workaround for this depends on which version of XCode you are trying to use.

Using XCode 16.X

The workaround for 16.X has been to install a fully featured version llvm onto
your system, for example via Homebrew, brew install llvm, (you might have to
use llvm@XX where XX is the major version of llvm in the XCode SDK). And
then using environment variables, i.e. CC= cmake -GNinja ../.., to set up the build including the full llvm toolchain to get the
missing libraries onto the search path. This will actually create a hybrid
toolchain using some elements of the XCode SDK and the llvm SDK, because there
is some Apple specific framework stuff that isn't in mainstream llvm.

Using XCode 26.X

As mentioned above, the 16.X workaround appears to no longer work due to drift
between Apple's llvm and the mainline version. Trying to create a hybrid
toolchain will lead to compiling issues from the XCode standard headers using
deprecated functions according to mainstream llvm, even when using the same
major version.

For 26.X there is workaround done by installing llvm@17 (since AppleClang is
version 17) from Homebrew, and then copying over the missing library files
(i.e. libclang_rt.fuzzer_osx.a) from the Homebrew directory to the
CommandLineTools toolchain directory. (Cannot be the XCode toolchain directory,
because that is write protected).

You will then need to be using the CLT toolchain as your active toolchain

sh
sudo xcode-select -s /Library/Developer/CommandLineTools

This allows for building and debugging the fuzzers, but the impact of this on
other builds and/or system stability is unknown. It shouldn't have a significant
impact, since the standard SDK doesn't ship with these libraries, so nothing
else should be using them, but the Homebrew version of llvm will not have all of
Apple's patches, so may be incompatible in subtle ways if these libraries do get
used somehow.

Using hermetic builds

It should be possible to use the same hermetic toolchain that the bots
use for dev builds of the fuzzers, since the bots build and run the fuzzer fine.

Unfortunately the standard setup on a dev machine is 'mostly' hermetic. It does
use the hermetic version clang and other tools, but it also does use some of the
system framework stuff that comes from XCode, and that is where the build issues
come in from, when trying to build/run the fuzzers.

There is some support for getting a truly hermetic build on a dev machine,
https://source.chromium.org/chromium/chromium/src/+/main:third_party/dawn/src/cmake/HermeticXcode/,
but that has not been tested with building the fuzzers, and probably needs work
to be a drop-in solution here.

Reproducing bot specific environments on Windows + CMake

When investigating build issues being seen by CI/CQ it is sometimes necessary
to replicate the exact environment from a builder/bot for local debugging.

As part of a Dawn checkout the toolchains used by the builders should already be
checked out in your repo. For Windows, using CMake, to use them instead of the
system installed toolchain, there is a script supplied to set up the
environment.

Instead of using the system batch file to set up the environment, i.e.

sh
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat

use the appropriate script from third_party\depot_tools\win_toolchain, i.e.

sh
C:\src\dawn\third_party\depot_tools\win_toolchain\vs_files\68a20d6dee\Windows Kits\10\bin\SetEnv.cmd

Clang Tidy (`docs/clang-tidy.md`)

Running Clang-Tidy

  • Add "checkout_clang_tidy": True to .gclient file in the custom_vars
    (or use standalone-maximal.gclient which includes this).

    py
    "custom_vars": {
      "checkout_clang_tidy": True,
    }
  • gclient sync

  • Set up a build directory for the configuration you want to run. It doesn't
    need to be built yet. This can be a native build or a cross-compiled build.

  • Run tools/run-tricium-clang-tidy.py --help for instructions. Running the
    tool with --default takes about 12 minutes on a 10-core M1 Pro laptop and
    under 2.5 minutes on a 64-core (128-thread) cloud VM.

  • This will output out/*/clang-tidy-*-findings.json with the full findings,
    and out/*/clang-tidy-*-summary.txt with a nicely-formatted list of results.

  • Once you have findings, you can automatically apply fixes across any set of
    files for a specific check (if it
    supports autofix):
    Run tools/apply-clang-tidy-fixes.py --help for instructions.

Tips:

  • It's recommended to set up clangd and open a file
    in your IDE to see the errors in context (and to get an autofix button).
    Note the FastCheckFilter option mentioned there.

    In VSCode you can less the summary file to your terminal and then
    cmd-click/ctrl-click the lines to take you to the exact source line.

For additional info on running Clang-Tidy locally, see
these instructions.


Dangling Pointer Detector (`docs/dangling-pointer-detector.md`)

Dangling Pointer Detector

A pointer is dangling when it references freed memory. Typical examples can be found
here.

Dangling pointers are not a problem unless they are subsequently dereferenced and/or used for other
purposes. Proving that dangling pointers are unused has turned out to be difficult in general,
especially in face of future modifications to the code. Hence, they are a source of UaF bugs and
highly discouraged unless you are able to ensure that they can never be used after the pointed-to
objects are freed.

Dawn tests are configured to detect dangling pointers.

Level of support

The dangling pointer detector is an optional dependency of Dawn. It is enabled only when running
tests in the dawn_standalone configuration. It is also enforced as part of Chrome's tests.

It is currently enforced under this configuration:

System Compiler Build system Directory Final embedder Misc
✅ Android ✅ Clang ✅ GN ✅ src/dawn ✅ Dawn (standalone) ❌ windows + debug
✅ Windows ✅ GCC ❌ CMake ❌ src/tint ❌ Skia ❌ windows + component_build
✅ Mac ❌ MSVC ❌ Bazel ✅ Chrome ❌ sanitizers
✅ Linux ❌ _ ❌ _ ❌ _ ✅ _
✅ ChromeOS
✅ Fuchsia
❌ iOS
❌ _

raw_ptr<>

A raw_ptr<T> is a non-owning pointer. When using this kind of pointer, the severity of UAFs can be
reduced, because they are protected by
MiraclePtr/BackupRefPtr

A raw_ptr<T> is transparently usable as a T*. It should be used in class and struct member
variable.

In general, it shouldn't be used elsewhere: local variable, function arguments, etc...

raw_ptr flavors

raw_ptr<T> comes in 3 main flavors:

cpp
raw_ptr<T> ptr_never_dangling;
raw_ptr<T, DisableDanglingPtrDetection> ptr_allowed_to_dangle;
raw_ptr<T, DanglingUntriaged> ptr_dangling_to_investigate;

The DisableDanglingPtrDetection option can be used to annotate “intentional-and-safe” dangling
pointers. It is meant to be used as a last resort, only if there is no better way to re-architecture
the code.

The DanglingUntriaged means the pointer was dangling at the time of the
initial rewrite. We should investigate why. No new occurrences should be added.

Pointer arithmetic

The use of pointer arithmetic with raw_ptr<T> is discouraged and disabled by default. Usually a
container like std::span<> should be used instead of the raw_ptr.

The AllowPtrArithmetic option can be used to enable pointer arithmetic. For instance:

text
raw_ptr<T, AllowPtrArithmetic> artihmetic_ptr.

Chrome's documentation

The dangling pointer detector is part of Chrome too. You can use the main
documentation:

Dawn's specificities.

Contrary to chrome, Dawn is not yet configured to display useful StackTraces and TaskTraces. It displays the error:

text
DanglingPointerDetector: A pointer was dangling!
                         Documentation: https://source.chromium.org/chromium/chromium/src/+/main:third_party/dawn/docs/dangling-pointer-detector.md

To debug you can:

  • remotely: Find a failing bot on the CQ running tests inside Chrome. You
    will find useful debugging informations.
  • locally: Use a debugger to display the StackTrace. Dawn is configured to crash when the
    dangling raw_ptr is released. If needed, you can also understand where the memory was freed by
    replacing SetDanglingRawPtrReleasedFn by SetDanglingRawPtrDetectedFn in
    src/dawn/tests/PartitionAllocSupport.cpp`.

Other resources


Development Tips (`docs/development-tips.md`)

Tips for developing Dawn code

This doc is specifically for people working on Dawn itself (not just using it
as a dependency).

Build/run as much stuff as possible locally, to find bugs before they hit CQ

Especially useful for any change or refactor that affects multiple backends.

  • Use scripts/standalone-maximal.gclient instead of standalone.gclient.
  • Set the dawn_maximal = true and use_libfuzzer = true GN args to default to
    building as many targets as possible given your host OS.
  • Build all targets (target all, which is the default if none is specified).
  • Develop on Linux or Mac, and set up build directories that cross-compile for
    other OSes (see building.md).
    • dawn_maximal = true is optional but still useful for cross-compile builds.
  • Also note you can run gn gen --check to locally run the presubmit check
    that C++ includes match the build files.

Set up your IDE to use clangd

Provides instant inline compilation errors in the editor. Significantly speeds
up the development/iteration process, compared with recompiling the project.

  • Do the steps above, so that clangd will know how to build most files.
  • Do a full build in each build directory (to create generated files, etc.).
  • Set "custom_vars": { "checkout_clangd": True } in .gclient
    (or just use standalone-maximal.gclient).
  • Point your IDE to the binary at
    third_party/llvm-build/Release+Asserts/bin/clangd.
    • In VSCode: clangd.path
  • Configure clangd: Paste the relevant text below into .clangd and then update
    the out/ paths in that file according to your needs.
    • Note the PathMatch paths aren't intended to be perfect, but please update
      this doc if you find platform-specific files that they miss.
    • If you have just one build directory, you can alternatively pass this as
      a clangd argument: --compile-commands-dir=out/Debug
      • In VSCode: clangd.arguments
    • For Chromium builds, take a look at
      chrome-remote-index.

Other documentation that may be useful:

`.clangd`

For Mac/Linux hosts:

yml
# This first section applies by default. Additional "fragments" below *also*
# apply, and may overwrite configs from less-specific fragments.
# Docs: https://clangd.llvm.org/config
Diagnostics:
  ClangTidy:
    # Enable all Clang-Tidy checks in clangd. If this is too slow, use "Loose".
    FastCheckFilter: None
CompileFlags:
  CompilationDatabase: out/Debug
  Add:
    - "-ferror-limit=0"
    - "-fsafe-buffer-usage-suggestions"
    # Additional personal configs can go here OR in your user-wide
    # `clangd/config.yml` (https://clangd.llvm.org/config#files). Suggestions:
    # - `--header-insertion=never` if you find yourself accidentally adding
    #   random headers when autocomplete goes sideways
    # - `--background-index=false` if you only want support in the current file
    #   (and includes), and don't want to index the whole project in the
    #   background (for symbol search, find references, etc.). Saves battery
    #   but otherwise isn't super necessary for Dawn since it's pretty small.
---
If:
  PathMatch:
    - "src/dawn/native/d3d.*"
    - "src/.*_[Ww]in.*"
    - "src/.*_[Dd]3[Dd].*"
CompileFlags:
  CompilationDatabase: out/Debug-win

For Linux hosts, also add this:

yml
---
If:
  PathMatch:
    - "src/dawn/native/metal/.*"
    - "src/.*_[Mm]ac.*"
    - "src/.*_[Aa]pple.*"
    - "src/.*_[Mm]etal.*"
CompileFlags:
  CompilationDatabase: out/Debug-mac
---
If:
  PathMatch:
    - "src/.*[Aa]ndroid.*"
    - "src/.*AHardwareBuffer.*"
    - "src/.*AHB.*"
CompileFlags:
  CompilationDatabase: out/Debug-android

Quickstart Cmake (`docs/quickstart-cmake.md`)

Quickstart With CMake

This document is designed to allow you to build Dawn with CMake and link to it from other CMake projects. If you're planning to work on Dawn specifically, please use gclient and depot_tools. It ensures
that you always get the right dependencies and compilers.

Prerequisites

  • A compatible platform (e.g. Windows, macOS, Linux, etc.). Most platforms are fully supported.
  • A compatible C++ compiler supporting at least C++20. Most major compilers are supported.
  • Git for interacting with the Dawn source code repository.
  • CMake for building your project and Dawn. Dawn supports CMake 3.16+.

Getting the Dawn Code

shell
# Change to the directory where you want to create the code repository
$ git clone https://dawn.googlesource.com/dawn
Cloning into 'dawn'...

Git will create the repository within a directory named dawn. Navigate into this directory and configure the project.

Build and install Dawn with CMake

shell
$ cd dawn
$ cmake -S . -B out/Release -DDAWN_FETCH_DEPENDENCIES=ON -DDAWN_ENABLE_INSTALL=ON -DCMAKE_BUILD_TYPE=Release
...
-- Configuring done (34.9s)
-- Generating done (0.8s)
-- Build files have been written to: ${PWD}/out/Release
  • DAWN_FETCH_DEPENDENCIES=ON uses a Python script fetch_dawn_dependencies.py as a less tedious alternative to setting up depot_tools and fetching dependencies via gclient.
  • DAWN_ENABLE_INSTALL=ON configures the project to install binaries, headers and cmake configuration files in a directory of your choice. This allows other projects to discover Dawn in CMake and link with it.

Now you can build Dawn:

shell
$ cmake --build out/Release
...
[100%] Linking CXX static library libdawn_utils.a
[100%] Built target dawn_utils

Once you have built the Dawn library, install it with cmake

shell
$ cmake --install out/Release --prefix install/Release

This installs Dawn from the out/Release build tree into install/Release.

Linking your code to the Dawn installation

Create test code

shell
$ mkdir TestDawn
$ cd TestDawn

Now, create a hello_webgpu.cpp C++ file within the TestDawn directory.

text
/* Detailed source-code truncated for AI context efficiency. */

Create CMakeLists.txt file

Now, create a CMakeLists.txt file within the TestDawn directory like the following:

cmake
cmake_minimum_required(VERSION 3.13)

project(hello_webgpu)

set(CMAKE_CXX_STANDARD 20)

find_package(Dawn REQUIRED)
add_executable(hello_webgpu hello_webgpu.cpp)

# Declare dependency on the dawn::webgpu_dawn library
target_link_libraries(hello_webgpu dawn::webgpu_dawn)

For more information on how to create CMakeLists.txt files, consult the CMake Tutorial.

Build test project

Configure the CMake build from a fresh binary directory. This configuration is called an “out of source” build and is the preferred method for CMake projects.

shell
$ export CMAKE_PREFIX_PATH=/path/to/dawn/install/Release
$ cmake -S . -B out/Release -DCMAKE_BUILD_TYPE=Release
...
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: ${PWD}/out/Release

Now build our test application:

shell
$ cmake --build out/Release
[ 50%] Building CXX object CMakeFiles/hello_webgpu.dir/hello_webgpu.cpp.o
[100%] Linking CXX executable hello_webgpu
[100%] Built target hello_webgpu

Now run your binary:

shell
$ ./out/Release/hello_webgpu
VendorID: 8086
Vendor: intel
Architecture: gen-12lp
DeviceID: 9a60
Name: Intel(R) UHD Graphics (TGL GT1)
Driver description: Intel open-source Mesa driver: Mesa 23.2.1-1ubuntu3.1~22.04.2

Note: You may move the contents of install/Release to any other directory and cmake will still be able to discover dawn as long as CMAKE_PREFIX_PATH points to the new install tree.


Rolling Cts (`docs/rolling_cts.md`)

Rolling CTS

Notes and instructions for rolling CTS manually.

Setup

There is a one-time setup needed to authenticate gcloud before the cts
roller can be used:

  • sudo apt install -y google-cloud-cli
  • gcloud auth login
  • gcloud auth application-default login

Verification

You can verify that you're authenticated by executing:

  • gcloud auth list

which should list your authorizations

Rolling CTS

The CTS roll can take a long time (sometimes >15 hrs) so it's
recommended to run it in tmux, screen or some other tool to allow
you to leave the session running in the background.

  • ./tools/run cts roll

Standalone Cts Runner (`docs/standalone_cts_runner.md`)

Running the WebGPU CTS with the standalone runner

dawn_standalone_cts_runner is a single executable that statically links V8 and
Dawn and runs the WebGPU CTS directly, without a Node.js installation.

The usual way to run the CTS is dawn.node, a
native addon loaded by Node.js. The standalone runner instead embeds its own V8
and supplies the subset of the Node API the CTS uses. That removes the external
Node.js dependency. This is important for platforms like Android where Node.js
is not available.

Both runners execute the same CTS JavaScript and the same Dawn bindings.

Fetching dependencies

Follow docs/building.md, using
scripts/standalone-with-v8.gclient in the Get the code step so gclient
fetches both the Node bindings (dawn_node) and V8 (checkout_v8):

sh
# Clone the repo as "dawn"
git clone https://dawn.googlesource.com/dawn dawn && cd dawn

# Bootstrap the gclient configuration for the standalone CTS runner
cp scripts/standalone-with-v8.gclient .gclient

# Fetch external dependencies and toolchains with gclient
gclient sync

If you are configuring an existing checkout, ensure .gclient sets both
"checkout_v8": True and "dawn_node": True in custom_vars before running
gclient sync. Without checkout_v8, the dawn_standalone_cts_runner target
is not declared.

Building the runner

Generate a release build directory and compile dawn_standalone_cts_runner
(building this target compiles only the standalone runner executable and its
dependencies, not the dawn.node shared library):

sh
gn gen out/Release --args="is_debug=false dawn_use_swiftshader=false"
autoninja -C out/Release dawn_standalone_cts_runner
  • is_debug=false is strongly recommended: the default debug build compiles a
    debug V8, which runs the CTS roughly 17x slower than a release build.
  • dawn_use_swiftshader=false skips building Dawn's bundled SwiftShader
    renderer and ensures the runner uses the system's drivers. Remove this option
    if you want to test against SwiftShader.

Building the CTS JavaScript

The runner executes the CTS's compiled out-node/ tree, which is not checked
in. Any Node.js installation (>= 16.0.0) with npm and npx can build it. If
you do not have node, npm, and npx installed on your system, you can add
the prebuilt Node.js fetched by gclient sync under third_party/node/ to your
PATH first:

sh
# Optional: use the Node.js fetched by DEPS if npm/npx are not already installed.
# Replace linux-amd64 with linux-arm64, mac-amd64, or mac-arm64 as needed
# (or $PWD/third_party/node/windows-amd64 on Windows).
export PATH="$PWD/third_party/node/linux-amd64/bin:$PATH"

Install the CTS dependencies and build out-node/:

sh
cd third_party/webgpu-cts
npm ci
npx grunt node
cd ../..

npm ci alone is not sufficient: npx grunt node runs the Grunt tasks that
generate out-node/.

Running the CTS

Run from the Dawn root (or pass --cts-dir <dir> if the CTS lives outside
third_party/webgpu-cts):

sh
out/Release/dawn_standalone_cts_runner -q 'webgpu:api,operation,queue,*'

Quote the query: CTS queries contain ; and ", which most shells will
otherwise interpret. Pass --help to list all supported flags.

Selecting a GPU, and setting Dawn toggles

--dawn-flag is passed through to the Dawn provider. It takes a single
key=value and may be repeated:

sh
out/Release/dawn_standalone_cts_runner \
    --dawn-flag backend=vulkan \
    --dawn-flag enable-dawn-features=dump_shaders \
    -q 'webgpu:api,operation,queue,*'

The recognised keys are:

  • backend=<null|webgpu|d3d11|d3d12|d3d|metal|vulkan|vk|opengl|gl|opengles|gles>
    (defaults to d3d12 on Windows, vulkan on Linux/Android, and metal on
    macOS; can also be set via the DAWNNODE_BACKEND environment variable)
  • adapter=<name> — substring match against the adapter device name. Pass an
    invalid name (for example adapter=help) to list all available adapters in
    the error output
  • validate=<1|true> — enable full backend validation layers
  • verbose=1 — print the selected GPU adapter name
  • enable-dawn-features=<toggles> — comma-separated list of
    Dawn toggles
  • disable-dawn-features=<toggles> — comma-separated list of Dawn toggles

Running on Android

There is no Node.js build for Android in the checkout, so the standalone runner
is the only way to run the command-line CTS directly on an Android device.
Cross-compiling for Android currently requires a Linux host (the GN build and
Android NDK toolchain in Dawn's checkout do not support building for Android
from Windows or macOS).

Checkout

On a Linux host, add target_os = ["android"] to .gclient and sync the
Android NDK and toolchain dependencies:

sh
printf '\ntarget_os = ["android"]\n' >> .gclient
gclient sync

Build

Generate an arm64 Android release build on Linux and compile the runner:

sh
gn gen out/Android --args='target_os="android" target_cpu="arm64" is_debug=false'
autoninja -C out/Android dawn_standalone_cts_runner

Staging onto the device

Only the executable and the compiled CTS JavaScript are needed on the device.

[!NOTE]
dawn.node is deliberately not pushed. The runner's module loader returns
its statically-linked binding for require('dawn.node'), so the shared
library is never loaded.

sh
DEV=/data/local/tmp/cts
adb shell "mkdir -p $DEV/bin $DEV/webgpu-cts"

adb push out/Android/dawn_standalone_cts_runner $DEV/bin/
adb shell "chmod 755 $DEV/bin/dawn_standalone_cts_runner"

# out-node is ~2200 files; a single archive transfers far faster than letting
# adb sync each file individually. Include cmdline.ts as well, which both the
# runner and cmdline.js check as a repository-root marker.
tar -C third_party/webgpu-cts -czf /tmp/cts.tgz \
    out-node src/common/runtime/cmdline.ts
adb push /tmp/cts.tgz $DEV/webgpu-cts/
adb shell "cd $DEV/webgpu-cts && tar xzf cts.tgz && rm cts.tgz"
rm /tmp/cts.tgz

Running

sh
adb shell '/data/local/tmp/cts/bin/dawn_standalone_cts_runner \
    --cts-dir /data/local/tmp/cts/webgpu-cts \
    -q "webgpu:api,operation,queue,*"'

Future plans

The setup above is more manual than it should be. The intent is to teach
run-cts about the standalone runner, so that
selecting it becomes a flag on the tool already used to run the CTS, with the
CTS build, query sharding, expectations handling and result reporting all
handled as they are today for dawn.node.

Once that lands, most of this document reduces to a single ./tools/run run-cts
invocation, and the manual staging steps for Android are expected to be driven
by the tool as well.


Support (`docs/support.md`)

Platform and API support for Dawn/Tint

Khronos's Vulkan

Vulkan is supported with minimal features, limits and extensions being required (what's required has
been measured to be widely available). Vulkan is the preferred target API on platforms that don't
have other "modern" GPU APIs. Vulkan is supported as best effort on other platforms (e.g. Windows
and macOS).

Required version: Vulkan 1.1.

Required features: depthBiasClamp, fragmentStoresAndAtomics, fullDrawIndexUint32,
imageCubeArray, independentBlend, sampleRateShading, and either textureCompressionBC or
both of textureCompressionETC and textureCompressionASTC_LDR.

Required limits: they are too detailed to describe here, but in general should be widely
supported. See the WebGPU limits that mostly
correspond to Vulkan limits.

Operating system support:

  • Linux: Supported.
  • ChromeOS: Supported.
  • Android: Work in progress.
  • Fuchsia: Work in progress.

Microsoft's D3D12

D3D12 is supported with feature level 11.1, or feature level 11.0 with Resource Binding Tier 2. This
is the vast majority of D3D12 devices. Supported shader models are 5.1 and above. It is the
preferred target API when available.

Windows flavor support:

  • Win32: Supported.
  • UWP: Supported, best effort.
  • Xbox: Not supported, contributions welcome.

Apple's Metal

Metal 2.3 is supported and is the preferred target API when available.

Apple OS support:

  • macOS: supported on version 13.0+.
  • iOS: supported on version 14.0+, best effort.
  • tvOS/ipadOS/...: Not supported, contributions welcome.

Khronos's OpenGL family

Support for OpenGL is in progress with the aim to make OpenGL ES 3.1 (with extensions and limits)
supported through EGL. Other flavors of OpenGL (desktop OpenGL) or binding APIs (GLX, WGL, EAGL,
CGL) are supported as best effort with contributions welcome.

Microsoft's D3D11

A work-in-progress D3D11 backend is available but not considered ready for production uses yet.


Dawn Asserts Vs Check (`docs/dawn/asserts-vs-check.md`)

Dawn: Checking asserts [Bug 442860471](https://crbug.com/442860471)

petermcneeley@

Apr 15, 2026

There has been an influx of security bugs for Dawn due to a capability phase shift in AI-assisted
vulnerability discovery.

In Dawn, often these are triggered by going outside known assumptions or they become real
security issues when further assumptions are invalidated.

Fix: `DAWN_UNREACHABLE`

A good example of this is the assumption that all switches on enums are valid enums. Some minor
vulnerability can lead to full execution control via the undefined behavior of a switch table that
jumps to an invalid enum. Example https://crbug.com/499159695

To resolve this, all DAWN_UNREACHABLEs now check at runtime that unexpected fallthroughs (switch
and function) will do a full CHECK (as in immediately crash/ICE in release).

This induces ~ 2 instructions per switch and is deemed to be acceptable.

See CL: https://dawn-review.git.corp.google.com/c/dawn/+/301675

Fix: `DAWN_ASSERT` to `DAWN_CHECK`

There are other cases where an assumption would have caught a security issue but does not because
the assumption in code is DAWN_ASSERT, not a check.

Example https://crbug.com/500609033

The conclusion is certain DAWN_ASSERTs will be promoted to release DAWN_CHECKs.

This is more controversial than the unreachable case as some of these checks can be expensive
and some can be in hot code paths (paths that are called many times per frame/submit).

For the initial phase, DAWN_ASSERTs that will be promoted are:

  • Cheap 1-2 instruction checks (null/bounds/error)
  • Avoid checks that would be found via null page
    • There is no point in checking a null pointer if the very next line it will trigger a null page
      segfault (which is not considered a Chrome vulnerability)
  • Hot code paths are allowed for checking memory bounds

CL: https://dawn-review.git.corp.google.com/c/dawn/+/302235

Fix: `DAWN_ASSERT` to `DAWN_RELEASE_ASSUME`

Previously, in release, DAWN_ASSERT was converted into __builtin_assume. This is likely a
surprise to most developers and can lead to dangerous outcomes.

This assume tells the compiler that a specific expression holds. The compiler then can do code
generation based on this assumption. If the assumption turns out to have been false, the result can
be undefined behavior (UB).

Here is an example of DAWN_ASSERT being converted to an assume and then removing mandatory
bounds checks. (This was very BAD) CL: https://dawn-review.git.corp.google.com/c/dawn/+/260462

In conclusion, we should move away from an assert implying an assume, as this can lead to unintended
consequences. The macro DAWN_ASSERT will simply assert in debug and be a no-op in release. For
the cases where the intention really was to inform the compiler about assumptions we should use
DAWN_RELEASE_ASSUME.

Drawback(s)

The main drawback here is when do we get to remove these checks? The answer is likely never!

However, it is likely that within the next year we will be able to ask advanced AI security models
what would be the impact of removing specific checks. It might even be that every future change in
Dawn will get evaluated for security by an AI model.


Dawn Buffer Mapping (`docs/dawn/buffer_mapping.md`)


Dawn Codegen (`docs/dawn/codegen.md`)

Dawn's code generators.

Dawn relies on a lot of code generation to produce boilerplate code, especially webgpu.h-related
code. They start by reading some JSON files (and sometimes XML too), process the data into an
in-memory representation that's then used by some Jinja2
templates to generate the code. This is similar to the model/view separation in Web development.

Generators are based on generator_lib.py which provides
facilities for integrating in build systems and using Jinja2. Templates can be found
in generator/templates and the generated files are in
out/<Debug/Release/foo>/gen/src when building Dawn in standalone. Generated files can also be
found in Chromium's code
search
.

Dawn "JSON API" generators

Most of the code generation is done from dawn.json which is a JSON
description of the WebGPU API with extra annotation used by some of the generators. The code for
all the "Dawn JSON" generators is
in dawn_json_generator.py (with templates in the
regular template dir).

At this time it is used to generate:

  • the Dawn, Emscripten, and upstream webgpu-native webgpu.h C header
  • the Dawn and Emscripten webgpu_cpp.h C++ wrapper over the C header
  • libraries that implements webgpu.h by calling in a static or thread_local proc table
  • the Emscripten WebGPU binding implementation
  • a GMock version of the API with its proc table for testing
  • validation helper functions for dawn_native
  • the definition of dawn_native's proc table
  • dawn_native's internal version of the webgpu.h types
  • utilities for working with dawn_native's chained structs
  • a lot of dawn_wire parts, see below

Internally dawn.json is a dictionary from the "canonical name" of things to their definition.
The "canonical name" is a space-separated (mostly) lower-case version of the name that's parsed
into a Name Python object. Then that name can be turned into various casings with .CamelCase()
.SNAKE_CASE(), etc. When dawn.json things reference each other, it is always via
these "canonical names".

The "_metadata" key in the JSON file is used by flexible templates for generating various Web
Standard API that contains following metadata:

  • "api" a string, the name of the Web API
  • "namespace" a string, the namespace of C++ wrapper
  • "c_prefix" (optional) a string, the prefix of C function and data type, it will default to
    upper-case of "namespace" if it's not provided.
  • "proc_table_prefix" a string, the prefix of proc table.
  • "impl_dir" a string, the directory of API implementation
  • "native_namespace" a string, the namespace of native implementation
  • "copyright_year" (optional) a string, templates will use the year of copyright.

The basic schema is that every entry is a thing with a "category" key what determines the
sub-schema to apply to that thing. Categories and their sub-shema are defined below. Several parts
of the schema use the concept of "record" which is a list of "record members" which are a
combination of a type, a name and other metadata. For example the list of arguments of a function
is a record. The list of structure members is a record. This combined concept is useful for the
dawn_wire generator to generate code for structure and function calls in a very similar way.

Most items and sub-items can include a list of "tags", which, if specified, conditionally includes
the item if any of its tags appears in the enabled_tags configuration passed to parse_json.
This is used to include and exclude various items for Dawn, Emscripten, or upstream header
variants. Tags are applied in the "parse_json" step (rather than
later
):
this has the benefit of automatically catching when, for a particular tag configuration, an
included item references an excluded item.

When used on enum values, "tags" may add an additional prefix to the enum value. This is to
implement implementation-specific ranges of
enums
. All compat enums are to start
at 0x0002_0000, Emscripten enums are to start at 0x0004_0000, and all Dawn enums are to start
at 0x0005_0000. Use of "compat", "emscripten", or "dawn" tags will add these values,
respectively. So, an enum with value "3" but tag "emscripten" will actually use value
0x0002_0003. Multi-implementation native enums start at 0x0001_0000, so this value is added if
the "native" tag is present and no other value-impacting tag is applied.

Items (such as objects, structures, functions, callbacks, and callback infos) and object methods can also include a "stable_version" field, specified via the addins mechanism in dawn_version.json. This is a monotonically increasing integer version number corresponding to a date with the format YYYYMMDDPP, where PP is an additional two-digit identifier in case multiple patches land on the same day. Given a particular version, if the version is >= an API piece's "stable_version", Dawn guarantees that the API exists and works to the best of our ability. For objects, the top-level "stable_version" indicates when the object itself was stably introduced, i.e. when its AddRef and Release methods (and usually SetLabel) were considered stable. Individual object methods ("object::method") also have their own "stable_version" entry since they can be introduced at a later time.

A record is a list of record members, each of which is a dictionary with the following
schema:

  • "name" a string
  • "type" a string, the name of the base type for this member
  • "annotation" a string, default to "value". Define the C annotation to apply to the base type.
    Allowed annotations are "value" (the default), "*", "const*"
  • "length" (default to 1 if not set), a string. Defines length of the array pointed to for
    pointer arguments. If not set the length is implicitly 1 (so not an array), but otherwise it can
    be set to the name of another member in the same record that will contain the length of the
    array (this is heavily used in the fooCount foos pattern in the API).
  • "optional" (default to false) a boolean that says whether this member is optional. Member
    records can be optional if they are pointers (otherwise dawn_wire will always try to dereference
    them), objects (otherwise dawn_wire will always try to encode their ID and crash), or if they
    have a "default" key. Optional pointers and objects will always default to nullptr (unless
    "no_default" is set to true).
  • "default" (optional) a number or string. If set the record member will use that value as
    default value. Depending on the member's category it can be a number, a string containing a
    number, or the name of an enum/bitmask value.
    • Dawn implements "trivial defaulting" for enums, similarly to the upstream WebGPU spec's WebIDL:
      if a zero-valued enum (usually called Undefined) is passed in, Dawn applies the default
      value specified here. See WithTrivialFrontendDefaults() in api_structs_defaults.h for how
      this works.
  • "wire_is_data_only" (default to false) a boolean that says whether it is safe to directly
    return a pointer of this member that is pointing to a piece of memory in the transfer buffer
    into dawn_wire. To prevent TOCTOU attacks, by default in dawn_wire we must ensure every single
    value returned to dawn_native a copy of what's in the wire, so "wire_is_data_only" is set to
    true only when the member is data-only and don't impact control flow.

"native" native types that can be referenced by name in other things.

  • "wire transparent" (defaults to true) a boolean that indicates whether the native type should
    be transparent (serialized as is) on the wire.

"typedef" (usually only used for gradual deprecations):

  • "type": the name of the things this is a typedef for.

"enum" an uint32_t-based enum value.

  • "values" an array of enum values. Each value is a dictionary containing:
    • "name" a string
    • "value" a number that can be decimal or hexadecimal
    • "jsrepr" (optional) a string to allow overriding how this value map to Javascript for the
      Emscripten bits
    • "valid" (defaults to true) a boolean that controls whether the dawn_native validation
      utilities will consider this enum value valid.
  • "emscripten_no_enum_table" (optional) if true, skips generating an enum table in
    library_webgpu_enum_tables.js

"bitmask" an uint32_t-based bitmask. It is similar to "enum" but can be output
differently.

"function pointer" defines a function pointer type that can be used by other things.

  • "returns" a string that's the name of the return type
  • "args" a record, so an array of record members

"structure"

  • "members" a record, so an array of record members
  • "extensible" (defaults to false) a boolean defining if this is an "extensible" WebGPU
    structure (i.e. has nextInChain). "descriptor" structures should usually have this set to
    true.
  • "chained" (defaults to None) a string defining if this is a structure that can be "chained" in
    a WebGPU structure (i.e. has nextInChain and sType) and in which direction ('in' for inputs
    to WebGPU, 'out' for outputs)
  • "chain roots" (defaults to []) a list of strings that are the canonical names of structures
    that can be extended by this structure.

"object"

  • **methods** an array of methods for this object. Note that "release" and "reference" don't need
    to be specified. Each method is a dictionary containing:
    • "name" a string
    • "return_type" (default to no return type) a string that's the name of the return type.
    • "arguments" a record, so an array of record members
    • "no autolock": a boolean flag (default is false) indicates that the method's generated code
      won't automatically do thread synchronization. This flag can only be true on device or device
      child objects currently.
  • "no autolock": a boolean flag (default is false) to indicate that the object's generated code
    won't automatically do thread synchronization. This will override individual method's "no autolock" flag. This flag can only be true on device or device child objects currently.

"constant"

  • "type": a string, the name of the base data type
  • "value": a string, the value is defined with preprocessor macro

"function" declares a function that not belongs to any class.

  • "returns" a string that's the name of the return type
  • "args" a record, so an array of record members

Dawn "wire" generators

The generator for the pieces of dawn_wire need additional data which is found
in dawn_wire_json. Examples of pieces that are generated are:

  • WireCmd.cpp/.h the most important piece: the meat of the serialization / deserialization code
    for WebGPU structures and commands
  • ServerHandlers/Doers.cpp that does the complete handling of all regular WebGPU methods in the
    server
  • ApiProcs.cpp that implements the complete handling of all regular WebGPU methods in the client

Most of the WebGPU methods can be handled automatically by the wire client/server but some of them
need custom handling (for example because they handle callbacks or need client-side state
tracking). dawn_wire.json defines which methods need special handling, and extra wire commands
that can be used by that special handling (and will get WireCmd support).

The schema of dawn_wire.json is a dictionary with the following keys:

  • "commands" an array of records defining extra client->server commands that can be used in
    special-cased code path.
    • Each record member can have an extra "skip_serialize" key that's a boolean that default
      to false and makes WireCmd skip it on its on-wire format.
  • "return commands" like "commands" but in revers, an array of records defining extra
    server->client commands
  • "special items" a dictionary containing various lists of methods or object that require special
    handling in places in the dawn_wire autogenerated files
    • "client_side_structures": a list of structure that we shouldn't generate
      serialization/deserialization code for because they are client-side only
    • "client_handwritten_commands": a list of methods that are written manually and won't be
      automatically generated in the client
    • "client_side_commands": a list of methods that won't be automatically generated in the
      server. Gets added to "client_handwritten_commands"
    • "client_special_objects": a list of objects that need special manual state-tracking in the
      client and won't be autogenerated
    • "server_custom_pre_handler_commands": a list of methods that will run custom "pre-handlers"
      before calling the autogenerated handlers in the server
    • "server_handwrittten_commands": a list of methods that are written manually and won't be
      automatically generated in the server.
    • server_reverse_object_lookup_objects: a list of objects for which the server will maintain an
      object -> ID mapping.

Dawn "native" generators

The generator for the pieces of dawn_native need additional data which is found
in dawn_native_json. Examples of pieces that are generated
are:

  • ProcTable.cpp that implements all of the WebGPU function by converting arguments and forwarding
    to the correct method/function in dawn::native.
  • dawn_platform_autogen.h and wgpu_structs_autogen.cpp that define the dawn::native
    equivalents of types in webgpu.h (for example with objects being actual pointers to objects
    and not just opaque handles) as well as conversion functions.
  • api_absl_format.cpp to make it easy to print any WebGPU type in error messages.

The schema of dawn_native.json is a dictionary with the following keys:

  • "metadata" a dictionary containing various other containers that can be used in templates. Its
    keys are:
    • addins: A dictionary mapping fully qualified instance names (structs, struct members,
      functions, function arguments, objects, object methods, or method arguments) to a dictionary
      of attribute addins (e.g. {"spanify": false} or {"index_type": "BindingIndex"}).

Dawn "Kotlin" generators

The generator for the Android Kotlin/JNI bindings needs additional data which is found
in dawn_kotlin.json. It generates the Kotlin classes (one per
object, structure, enum and callback) and the JNI glue that converts between Kotlin objects and
webgpu.h structures.

The schema of dawn_kotlin.json is a dictionary with the following keys:

  • "kotlin_package" a string, the package for the generated Kotlin classes.
  • "jni_primitives" / "jni_signatures" dictionaries mapping native type names to their JNI C
    type and JNI signature letter.
  • "kdocs_blocklist" / "kdocs_replacements" filters applied to the upstream documentation when
    generating KDoc.
  • "metadata" a dictionary with the same shape as in dawn_native.json:
    • addins: maps fully qualified instance names to attribute addins. Supported addins are:
      • "omitted": true on a structure, enum, function pointer, function, object method,
        or structure member to exclude it from the Kotlin API. It is not supported on
        function/method arguments (the generator asserts) since the native call would silently
        receive a zero-initialized value.
      • "kotlin_name": "<name>" on a function or object method to override its Kotlin name (e.g.
        "create instance": {"kotlin_name": "createGPUInstance"}).
      • "additional_members": [...] on a structure to add Kotlin-only members that have no native
        counterpart. Each entry has a "name", a "type" (either a dawn.json type name or a
        Kotlin type when "category": "kotlin type"), and optional "category", "annotation",
        "optional" and "default_value" keys.
      • "graduated_batches": [[...], ...] on a structure, a list of member-name batches that were
        added after the structure first shipped, used to emit hidden legacy constructors for binary
        compatibility.

OpenGL loader generator

The code to load OpenGL entrypoints from a GetProcAddress function is generated
from gl.xml and the list of
extensions
it supports.


Dawn Debug Markers (`docs/dawn/debug_markers.md`)

Debug Markers

Dawn provides debug tooling integration for each backend.

Debugging markers are exposed through this API: partial GPUProgrammablePassEncoder { void pushDebugGroup(const char * markerLabel); void popDebugGroup(); void insertDebugMarker(const char * markerLabel); };

These APIs will result in silent no-ops if they are used without setting up the execution
environment properly. Each backend has a specific process for setting up this environment.

D3D12

Debug markers on D3D12 are implemented with the PIX Event
Runtime
.

To enable marker functionality, you must: 1. Click the download link on
https://www.nuget.org/packages/WinPixEventRuntime 2. Rename the .nupkg file to a .zip extension,
then extract its contents. 3. Copy bin\WinPixEventRuntime.dll into the same directory as
libdawn_native.dll. 4. Launch your application.

You may now call the debug marker APIs mentioned above and see them from your GPU debugging tool.
When using your tool, it is supported to both launch your application with the debugger attached,
or attach the debugger while your application is running.

D3D12 debug markers have been tested with Microsoft PIX
and Intel Graphics Frame Analyzer.

Unfortunately, PIX's UI does does not lend itself to capturing single frame applications like tests.
You must enable capture from within your application. To do this in Dawn tests, pass
the --begin-capture-on-startup flag to dawn_end2end_tests.exe.

Vulkan

Debug markers on Vulkan are implemented
with VK_EXT_debug_utils.

To enable marker functionality, you must launch your application from your debugging tool. Attaching
to an already running application is not supported.

Vulkan markers have been tested with RenderDoc.

Metal

Debug markers on Metal are used with the XCode debugger.

To enable marker functionality, you must launch your application from XCode and use GPU Frame
Capture
.

OpenGL

Debug markers on OpenGL are not implemented and will result in a silent no-op. This is due to low
adoption of the GL_EXT_debug_marker extension in Linux device drivers.


Dawn Debugging (`docs/dawn/debugging.md`)

Debugging Dawn

toggles:

  • dump_shaders: Log input WGSL shaders and translated backend shaders (MSL/ HLSL/DXBC/DXIL /
    SPIR-V).
  • disable_symbol_renaming: As much as possible, disable renaming of symbols (variables, function
    names, etc.). This can make dumped shaders more readable.
  • emit_hlsl_debug_symbols: Sets the D3DCOMPILE_SKIP_OPTIMIZATION and D3DCOMPILE_DEBUG compilation
    flags when compiling HLSL code.
  • use_user_defined_labels_in_backend: Forward object labels to the backend so that they can be
    seen in native debugging tools like RenderDoc, PIX, or Mac Instruments.

Toggles may be enabled/disabled in different ways.

  • In code:

    Use extension struct DawnTogglesDescriptor chained on DeviceDescriptor.

    For example:

    c++
    const char* const enabledToggles[] = {"dump_shaders", "disable_symbol_renaming"};
    
    wgpu::DawnTogglesDescriptor deviceTogglesDesc;
    deviceTogglesDesc.enabledToggles = enabledToggles;
    deviceTogglesDesc.enabledTogglesCount = 2;
    
    wgpu::DeviceDescriptor deviceDescriptor;
    deviceDescriptor.nextInChain = &deviceTogglesDesc;
  • Command-line for Chrome

    Run Chrome with command line flags--enable-dawn-features and/or --disable-dawn-features to
    force enable/disable toggles. Toggles should be comma-delimited.

    For example:
    --enable-dawn-features=dump_shaders,disable_symbol_renaming

  • Command-line for dawn_end2end_tests/dawn_unittests

    Run Dawn test binaries with command line flags--enable-toggles and/or --disable-toggles to
    force enable/disable toggles. Toggles should be comma-delimited.

    For example:
    dawn_end2end_tests --enable-toggles=dump_shaders,disable_symbol_renaming

Environment Variables

  • DAWN_DEBUG_BREAK_ON_ERROR

    Errors in WebGPU are reported asynchronously which may make debugging difficult because at the
    time an error is reported, you can't easily create a breakpoint to inspect the callstack in
    your application.

    Setting DAWN_DEBUG_BREAK_ON_ERROR to a non-empty, non-zero value will immediately causes the
    program to terminate as fast as possible, at an address that's inside the function that used
    the macro, and with a signal that's different from a segfault.
    See (DAWN_DETAIL_TRAP)

Tracing Native GPU API usage

Setting the environment variable DAWN_TRACE_FILE_BASE to some filename tells dawn to save a GPU
trace. Setting DAWN_TRACE_DEVICE_FILTER to some string lets you filter which devices are traced
based on the device's label. If DAWN_TRACE_DEVICE_FILTER is set then, if the label contains that
substring the device will be traced. Otherwise all devices are traced.

Tracing happens from device creation until device destruction.

Note that tracing is not currently implemented on all backends.

  • DirectX11 - not yet implemented

  • DirectX12 - not yet implemented

  • OpenGL - not yet implemented

  • Metal - implemented.

    Saves a .gputrace file that can be loaded into XCode's Metal Debugger.

    MTL_CAPTURE_ENABLED=1 must also be set. Note that (anecdotally) this appears
    to disable (some?) validation layer outputs.

  • Vulkan - not yet implemented

Capturing with RenderDoc in Chrome

Currently only supported on Windows using the D3D12 backend.

Launch Chrome with the following flags:

bash
set RENDERDOC_HOOK_EGL=0 && path\to\chrome.exe --no-sandbox --disable-gpu-sandbox --disable-direct-composition --gpu-startup-dialog --enable-dawn-features=enable_renderdoc_process_injection <path\to\chrome.exe>

This will start Chrome with a dialog that says "Gpu starting with pid: ".

In RenderDoc, in Tools -> Settings, check "Enable process injection" and restart RenderDoc.

Go to File -> Inject into Process, search for "gpu" in the list of processes and double-click the
chrome.exe one with the title "Google Chrome GPU". This is the GPU process.

Go back to the Chrome GPU dialog and click OK to close it. The GPU process will resume.

Now open a WebGPU application in a tab, and as soon as it starts rendering, each frame will be
automatically captured and sent to RenderDoc. To stop the captures, switch tabs, close the tab, or
exit Chrome.

Note that you can use the "Refresh" button in RenderDoc's "Inject into Process" tab the next time
Chrome is launched to select the GPU process again.


Dawn Device Facilities (`docs/dawn/device_facilities.md`)

Devices

In Dawn the Device is a "god object" that contains a lot of facilities useful for the whole object
graph that descends from it. There a number of facilities common to all backends that live in the
frontend and backend-specific facilities. Example of frontend facilities are the management of
content-less object caches, or the toggle management. Example of backend facilities are GPU memory
allocators or the backing API function pointer table.

Frontend facilities

Error Handling

Dawn (dawn_native) uses the Error.h error handling to robustly
handle errors. With DAWN_TRY errors bubble up all the way to, and are "consumed" by the
entry-point that was called by the application. Error consumption uses Device::ConsumeError that
expose them via the WebGPU "error scopes" and can also influence the device lifecycle by notifying
of a device loss, or triggering a device loss..

See Error.h for more information about using errors.

Device Lifecycle

The device lifecycle is a bit more complicated than other objects in Dawn for multiple reasons:

  • The device initialization creates facilities in both the backend and the frontend, which can
    fail. When a device fails to initialize, it should still be possible to destroy it without
    crashing.
  • Execution of commands on the GPU must be finished before the device can be destroyed (because
    there's no-one to "DeleteWhenUnused" the device).
  • On creation a device might want to run some GPU commands (like initializing zero-buffers), which
    must be completed before it is destroyed.
  • A device can become "disconnected" when a TDR or hot-unplug happens. In this case, destruction of
    the device doesn't need to wait on GPU commands to finish because they just disappeared.

There is a state machine State defined in Device.h that controls
all of the above. The most common state is Alive when there are potentially GPU commands
executing.

Initialization of a device looks like the following:

  • DeviceBase::DeviceBase is called and does mostly nothing except setting State to
    BeingCreated (and initial toggles).
  • backend::Device::Initialize creates things like the underlying device and other stuff that
    doesn't run GPU commands.
  • It then calls DeviceBase::Initialize that enables the DeviceBase facilities and sets the
    State to Alive.
  • Optionally, backend::Device::Initialize can now enqueue GPU commands for its initialization.
  • The device is ready to be used by the application!

While it is Alive the device can notify it has been disconnected by the backend, in which case it
jumps directly to the Disconnected state. Internal errors, or a call to LoseForTesting can also
disconnect the device, but in the underlying API commands are still running, so the frontend will
finish all commands (with WaitForIdleForDesctruction) and prevent any new commands to be
enqueued (by setting state to BeingDisconnected). After this the device is set in the
Disconnected state. If an Alive device is destroyed, then a similar flow to LoseForTesting happens.

All this ensures that during destruction or forceful disconnect of the device, it properly gets to
the Disconnected state with no commands executing on the GPU. After disconnecting, frontend will
call backend::Device::DestroyImpl so that it can properly free driver objects.

Toggles

Toggles are booleans that control code paths inside of Dawn, like lazy-clearing resources or using
D3D12 render passes. They aren't just booleans close to the code path they control, because
embedders of Dawn like Chromium want to be able to surface what toggles are used by a device (like
in about:gpu).

Toggles are to be used for any optional code path in Dawn, including:

  • Workarounds for driver bugs.
  • Disabling select parts of the validation or robustness.
  • Enabling limitations that help with testing.
  • Using more advanced or optional backend API features.

Toggles can be queried using DeviceBase::IsToggleEnabled:

text
bool useRenderPass = device->IsToggleEnabled(Toggle::UseD3D12RenderPass);

Toggles are defined in a table in Toggles.cpp that also
includes their name and description. The name can be used to require enabling of a toggle or, at
the contrary, require the disabling of a toggle. This is particularly useful in tests so that the
two sides of a code path can be tested (for example using D3D12 render passes and not).

Here's an example of a test that is run in the D3D12 backend both with the D3D12 render passes
required to be disabled, and in the default configuration.

text
DAWN_INSTANTIATE_TEST(RenderPassTest, D3D12Backend(),
D3D12Backend({}, {"use_d3d12_render_pass"}));
// The {} is the list of required enabled toggles, {"..."} the required disabled ones.

The toggles state of a device is decided by the adapter when creating it. The steps of device
toggles state decision looks as follows:

  • The device toggles state initialized to required device toggles from the DawnTogglesDescriptor
    chained in device descriptor.
  • The frontend (i.e. not backend-specific) default toggles are set (unless already required) using
    TogglesState::Default.
  • Any backend device toggle that not supported is forced set to a proper state in
    Adapter::SetupBackendDeviceToggles using TogglesState::ForceSet.
  • The backend device default toggles are applied (unless already set) in
    Adapter::SetupBackendDeviceToggles using TogglesState::Default.

Forcing toggles should only be done when there is no "safe" option for the toggle. This is to avoid
crashes during testing when the tests try to use both sides of a toggle. For toggles that are safe
to enable, like workarounds, the tests can run against the base configuration and with the toggle
enabled. For toggles that are safe to disable, like using more advanced backing API features, the
tests can run against the base configuration and with the toggle disabled.

Immutable object caches

A number of WebGPU objects are immutable once created, and can be expensive to create, like
pipelines. DeviceBase contains caches for these objects so that they are free to create the
second time. This is also useful to be able to compare objects by pointers like BindGroupLayouts
since two BGLs would be equal iff they are the same object.

Format Tables

The frontend has a Format structure that represent all the information that are known about a
particular WebGPU format for this Device based on the enabled features. Formats are precomputed at
device initialization and can be queried from a WebGPU format either assuming the format is a valid
enum, or in a safe manner that doesn't do this assumption. A reference to these formats can be
stored persistently as they have the same lifetime as the Device.

Formats also have an "index" so that backends can create parallel tables for internal information
about formats, like what they translate to in the backing API.

Object factory

Like WebGPU's device object, DeviceBase is an factory with methods to create all kinds of other
WebGPU objects. WebGPU has some objects that aren't created from the device, like the texture view,
but in Dawn these creations also go through DeviceBase so that there is a single factory for each
backend.


Dawn Errors (`docs/dawn/errors.md`)

Dawn Errors

Dawn produces errors for several reasons. The most common is validation errors, indicating that a
given descriptor, configuration, state, or action is not valid according to the WebGPU spec. Errors
can also be produced during exceptional circumstances such as the system running out of GPU memory
or the device being lost.

The messages attached to these errors will frequently be one of the primary tools developers use to
debug problems their applications, so it is important that the messages Dawn returns are useful.

Following the guidelines in document will help ensure that Dawn's errors are clear, informative, and
consistent.

Returning Errors

Since errors are expected to be an exceptional case, it's important that code that produces an error
doesn't adversely impact the performance of the error-free path. The best way to ensure that is to
make sure that all errors are returned from within an if statement that uses the
DAWN_UNLIKELY() macro to indicate that the expression is not expected to evaluate to true. For
example:

c++
if (DAWN_UNLIKELY(offset > buffer.size)) {
  return DAWN_VALIDATION_ERROR("Offset (%u) is larger than the size (%u) of %s."
    offset, buffer.size, buffer);
}

To simplify producing validation errors, it's strongly suggested that the DAWN_INVALID_IF() macro
is used, which will wrap the expression in the DAWN_UNLIKELY() macro for you:

c++
// This is equivalent to the previous example.
DAWN_INVALID_IF(offset > buffer.size, "Offset (%u) is larger than the size (%u) of %s."
    offset, buffer.size, buffer);

// TODO: Cover MaybeError, ResultOrError<T>, DAWN_TRY(_ASSIGN), DAWN_TRY_CONTEXT, etc...

Error message formatting

Errors returned from DAWN_INVALID_IF() or DAWN_VALIDATION_ERROR() should follow these guidelines:

Write error messages as complete sentences. (First word capitalized, ends with a period, etc.)

  • Example: Command encoding has already finished.
  • Instead of: encoder finished

Error messages should be in the present tense.

  • Example: Buffer is not large enough...
  • Instead of: Buffer was not large enough...

When possible any values mentioned should be immediately followed in parentheses by the given value.

  • Example: ("Array stride (%u) is not...", stride)
  • Output: Array stride (16) is not...

When possible any object or descriptors should be represented by the object formatted as a string.

  • Example: ("The %s size (%s) is...", buffer, buffer.size)
  • Output: The [Buffer] size (512) is... or The [Buffer "Label"] size (512) is...

Enum and bitmask values should be formatted as strings rather than integers or hex values.

  • Example: ("The %s format (%s) is...", texture, texture.format)
  • Output: The [Texture "Label"] format (TextureFormat::RGBA8Unorm) is...

When possible state both the given value and the expected value or limit.

  • Example: ("Offset (%u) is larger than the size (%u) of %s.", offset, buffer.size, buffer)
  • Output: Offset (256) is larger than the size (144) of [Buffer "Label"].

State errors in terms of what failed, rather than how to satisfy the rule.

  • Example: Binding size (3) is less than the minimum binding size (32).
  • Instead of: Binding size (3) must not be less than the minimum binding size (32).

Don't repeat information given in context.

  • See next section for details

Error Context

When calling functions that perform validation consider if calling DAWN_TRY_CONTEXT() rather than
DAWN_TRY() is appropriate. Context messages, when provided, will be appended to any validation
errors as a type of human readable "callstack". An error with context messages appears will be
formatted as:

text
<Primary error message.>
 - While <context message lvl 2>
 - While <context message lvl 1>
 - While <context message lvl 0>

For example, if a validation error occurs while validating the creation of a BindGroup, the message
may be:

text
Binding size (256) is larger than the size (80) of [Buffer "View Matrix"].
 - While validating entries[1] as a Buffer
 - While validating [BindGroupDescriptor "Frame Bind Group"] against [BindGroupLayout]
 - While calling CreateBindGroup

// TODO: Guidelines about when to include context

Context message formatting

Context messages should follow these guidelines:

Begin with the action being taken, starting with a lower case. - While will be appended by Dawn.

  • Example: ("validating primitive state")
  • Output: - While validating primitive state

When looping through arrays, indicate the array name and index.

  • Example: ("validating buffers[%u]", i)
  • Output: - While validating buffers[2]

Indicate which descriptors or objects are being examined in as high-level a context as possible.

  • Example: ("validating % against %", descriptor, descriptor->layout)
  • Output: - While validating [BindGroupDescriptor "Label"] against [BindGroupLayout]

When possible, indicate the function call being made as the top-level context, as well as the parameters passed.

  • Example: ("calling %s.CreatePipelineLayout(%s).", this, descriptor)
  • Output: - While calling [Device].CreatePipelineLayout([PipelineLayoutDescriptor]).

Internal Error Classes

There are multiple levels of validation and error handling which happen in Dawn. There is WebGPU API
validation which directly validates the user input. There is backend validation to make sure the
provided user input can execute on a given backend, there is backend error handling (e.g. when a
request to the Vulkan API fails due to Out of Memory).

Within all of this, Dawn may need to execute backend functionality in service of a user API call.
For example, when uploading a texture, Dawn internally may need to allocate a transfer buffer, copy
the user data into that buffer and then upload. (This is what is referred to as reentrant in Dawn).
Any validation error in this reentrant behaviour must be upgraded to a DeviceLost because the
system internally is in an invalid state.

Error Types

Internally Dawn currently supports 6 types of error, they are all listed in src/dawn/native/Error.h
inside the InternalErrorType enumeration.

  1. None -- no error generated
  2. Validation -- An API validation error has occurred
  3. BackendDeviceLost -- A call in the backend has caused the device to be lost. The device is
    now gone, there is no cleanup to do and the device must not be
    used anymore.
  4. Unrecoverable -- There was an error that will cause a device lost. Internal resources which
    use the device will be cleaned up before we intentionally lose the device.
  5. PipelineUncategorized -- An error occurred in the backend (this could be the shader compiler
    unable to complete due to lack of registers) that is not device
    fatal, but the users request could not be handled. This does not
    need to cause a device loss.
  6. OutOfMemory -- There was an out-of-memory caused by the request. This should cause a device
    lost as we are in an inconsistent state, modulo a few specific spec permitted
    places (e.g. Createbuffer/Texture/QueueSet/ResourceTable).

Classes

ErrorData, UnrecoverableError, ValidationError, UnknownError

The content of an error is stored in a primary ErrorData class. That class is then wrapped by the
error classes, UnknownError, UnrecoverableError, ValidationError.

The ErrorData class stores all the actual error information, the type, the message, any other
content. Each of the Error classes stores a unique_ptr to an ErrorData.

In general, code preferably produces a Validation or Unrecoverable error. In the case where we
have both validation and unrecoverable errors being produced, the Unknown error is used.
UnknownError stores both Validation and other types of error types. In general using
UnknownError should be used as a last resort. The code should be structured in such a way that
validation is followed by non-validation errors. There are cases, like in the backends where we
have a "validation sandwich" where we must validate, perform backend operation, validate, perform
operation, and these require UnknownError usage.

The UnknownError provides a way to retrieve the error data as a Validation or Unrecoverable
error but the InternalErrorType must match. It must be Validation to retrieve a Validation
error and it must be something other than Validation for an Unrecoverable error.

An UnknownError can be initialized with either a Validation or Unrecoverable error.

An UnrecoverableError can be initialized with an Unknown error, but if that error was
Validation it will become Unrecoverable.

MaybeValError, MaybeError, MaybeUnknownError && ResultOrValError, ResultOrError, ResultOrUnknownError

The Maybe classes are Result<void, E> types, the ResultOr classes are Result<T, E> types
which wrap the matching error type related to the name. They allow returning errors from methods and
work with the various macros to pass errors up the system.

There are certain conversion restrictions between the result types.

  • A Validation error and a UnrecoverableError can be placed in a MaybeUnknown or
    ResultOrUnknown error.
  • A Validation error can be placed into a MaybeError or ResultOrError but it will convert to
    Unrecoverable and no longer be a validation error.
  • A Unrecoverable error cannot be placed into a Validation error. This will trigger a
    compile error.

Error Adapter

The ErrorAdapter is used to convert between various error types. The adapter will be created
templated on one of the Error classes. This template is then used to determine which of the
conversion methods are available. (The adapter has an operator <type> overload for each of the
Result, Maybe entries and the unique_ptr for each Error type.

Macros

Most of the work with the error classes is done through macros in the src/dawn/native/Error.h file.
Some of the more interesting macros are:

  • DAWN_VALIDATION_ERROR -- Creates a Validation Validation error.

  • DAWN_PIPELINE_UNCATEGORIZED_ERROR -- Makes a PipelineUncategorized Unrecoverable error.

  • DAWN_BACKEND_DEVICE_LOST_ERROR -- Makes a BackendDeviceLost Unrecoverable error.

  • DAWN_UNRECOVERABLE_ERROR -- Makes an Unrecoverable Unrecoverable error.

  • DAWN_OUT_OF_MEMORY_ERROR -- Makes an OutOfMemory Unrecoverable error.

  • DAWN_INVALID_IF -- Creates a validation error if the given condition is false

  • DAWN_PIPELINE_UNCATEGORIZED_IF -- Creates the pipeline error if the condition is false

  • DAWN_UNRECOVERABLE_ERROR_IF -- Creates an unrecoverable error if the condition is false

  • DAWN_TRY -- Will execute the given command. If the command returns a Result set to an error
    it will use the ErrorAdapter to return the correct type for the parent method.

  • DAWN_TRY_ASSIGN -- Similar to the above, but will move the success value out of the Result
    and into the given variable.

Error Sink

Once an error has propagated, the ErrorSink methods are used to consume the error. (The Device,
Instance and EncoderContext are all ErrorSink subclasses). Each of the subclass implements a
ConsumeErrorImpl method which takes an UnknownError and emits it in some fashion.


Dawn External Resources (`docs/dawn/external_resources.md`)

Design Docs


Dawn Fuzzing (`docs/dawn/fuzzing.md`)

Fuzzing Dawn

Building

To enable building of the fuzzers, add use_libfuzzer = true to args.gn.

The fuzzers can be built using the fuzzers target (e.g. ninja fuzzers)

`dawn_wire_server_and_frontend_fuzzer`

The dawn_wire_server_and_frontend_fuzzer sets up Dawn using the Null backend, and passes inputs to
the wire server. This fuzzes the dawn_wire deserialization, as well as Dawn's frontend
validation.

`dawn_wire_server_and_vulkan_backend_fuzzer`

The dawn_wire_server_and_vulkan_backend_fuzzer is like dawn_wire_server_and_frontend_fuzzer but
it runs using a Vulkan CPU backend such as Swiftshader. This fuzzer supports error injection by
using the first bytes of the fuzzing input as a Vulkan call index for which to mock a failure.

Automatic Seed Corpus Generation

Using a seed corpus significantly improves the efficiency of fuzzing. Dawn's fuzzers use interesting
testcases discovered in previous fuzzing runs to seed future runs. Fuzzing can be further improved
by using Dawn tests as a example of API usage which allows the fuzzer to quickly discover and use
new API entrypoints and usage patterns.

Dawn has a CI
builder cron-linux-clang-rel-x64
which runs on a periodic schedule. This bot runs the dawn_end2end_tests and dawn_unittests
using the wire and writes out traces of the commands. This can manually be done by running:
<test_binary> --use-wire --wire-trace-dir=tmp_dir. The output directory will contain one trace
for each test, where the traces are prepended with 0xFFFFFFFFFFFFFFFF. The header is the callsite
index at which the error injector should inject an error. If the fuzzer doesn't support error
injection it will skip the header. [cron-linux-clang-rel-x64] then hashes the output files to
produce unique names and uploads them to the fuzzer corpus directories. Please see the
dawn.py[https://source.chromium.org/chromium/chromium/tools/build/+/main:recipes/recipes/dawn.py]
recipe for specific details.

Regenerating the seed corpus keeps it up to date when Dawn's API or wire protocol changes.


Dawn Infra (`docs/dawn/infra.md`)

Dawn's Continuous Testing Infrastructure

Dawn uses Chromium's continuous integration (CI) infrastructure to continually run tests on changes
to Dawn and provide a way for developers to run tests against their changes before submitting. CI
bots continually build and run tests for batches of new changes (although in practice, it is often
once per submitted CL), and Try bots build and run developers' pending changes before submission.
Dawn uses two different builder setups:

  1. Standalone Dawn checkouts, which directly check out Dawn and its dependencies. This is used to
    provide test coverage of Dawn itself.
  2. Chromium checkouts, which are capable of building Dawn targets. This is used to provide coverage
    of Dawn tests using Chromium's versions of dependencies while also allowing Dawn-related Chromium
    tests to run. For example, most WebGPU CTS testing is done on Chromium builders in order to make
    use of a full Chromium browser.

For additional information on GPU testing in Chromium, please
see [chromium/src]//docs/gpu/gpu_testing_bot_details.md.

Dawn CI/Try Builders

Standalone Dawn builders are defined in the following Starlark files, which are used to generate
files consumed by the builders when
running //infra/config/global/main.star:

  • //infra/config/global/gn_standalone_ci.star, which defines all CI builders which use GN as their buildsystem. This encompasses the majority of Dawn's CI builders.
  • //infra/config/global/gn_standalone_try.star, which defines al try builders which use GN as their buildsystem. This includes both manual-only trybots and the ones automatically added to all CLs. This encompasses the majority of Dawn's try builders.
  • //infra/config/global/legacy_builders.star, which defines all other CI and try builders such as those that use CMake or mirror Chromium builders. Long-term, all builders in this file will either be removed or moved to appropriately named files.

There are additional chromium/try builders, but those are described later in this document.

These bots are defined in both buckets luci.dawn.ci and luci.dawn.try, though their ACL permissions
differ. luci.dawn.ci bots will be scheduled regularly based
on [dawn]//infra/config/global/luci-scheduler.cfg.
luci.dawn.try bots will be triggered on the CQ based
on [dawn]//infra/config/global/commit-queue.cfg.

Build status for both CI and Try builders can be seen at
this console which is generated
from [dawn]//infra/config/global/luci-milo.cfg.

Dawn Build Recipe

There are two recipes for building Dawn/Tint, one for GN
based

builds and one for CMake
based

builds.

The specific instructions on how to build the project are contained in the project repos build
files. These recipe files are primary concerned with coordinating how to build and test the project
for CQ/CI. The high level steps of this process:

  1. Check out the repo
  2. Compile the project (both Dawn & Tint)
    • Depending on the specific environment this may include multiple configurations, i.e. with
      dawn.node or fuzzing enabled in addition to the default build options
  3. Run various tests
    • Including unit tests, end-to-end tests, CTS, and others as appropriate for the specific build
      configuration
  4. Optionally Generate and upload fuzzer corpora
    • This is controlled by running a special set of tests and only runs on one CI builder that is
      run once every 24 hours

Note: For Googlers there is an internal doc (go/dawn-luci-guide) with
details on how to update the build recipes and test them.

Dawn Chromium-Based CI Waterfall Bots

The chromium.dawn waterfall consists
of the bots specified in the chromium.dawn section
of [chromium/src]//testing/buildbot/waterfalls.pyl.
Bots named "Builder" are responsible for building top-of-tree Dawn, whereas bots named "DEPS
Builder" are responsible for building Chromium's DEPS version of Dawn.

The other bots, such as "Dawn Linux x64 DEPS Release (Intel HD 630)" receive the build products from
the Builders and are responsible for running tests. The Tester configuration may specify mixins
from [chromium/src]//testing/buildbot/mixins.pyl
which help specify bot test dimensions like OS version and GPU vendor. The Tester configuration
also specifies test_suites
from [chromium/src]//testing/buildbot/test_suites.pyl
which declare the tests are arguments passed to tests that should be run on the bot.

The parent builders and child testers are configured in Chromium's //infra/config/ files. CI
builders are
defined here
while try builders are
defined here.

Dawn Chromium-Based Tryjobs

This Starlark
file

defines mirrors of Chromium trybots for use as Dawn trybots. This allows Dawn changes to be tested
in tests which require Chromium before actually attempting to roll Dawn into Chromium, such as
running the WebGPU CTS in a full Chromium browser. The chromium_dawn_tryjob entries define
mirrors which are automatically added to all Dawn CLs. These correspond to builder names such as
linux-dawn-rel or mac-dawn-rel. The luci.cq_tryjob_verifier entries define manual-only trybot
mirrors. These are never automatically added, but can be manually added through Gerrit or CL
footers.

Bot Allocation

Dawn shares all of its builder capacity and test hardware with the Chrome GPU team. Pool assignments
for both are defined in this
file

(Google only). A capacity dashboard for these physical machines and GCE instances can be found at
go/chrome-gpu-capacity-dashboard (Google only).

Note: At the time of writing, it is recommended to select "Old UI" at the top right of the
capacity dashboard, as the new UI has some performance issues.

While all builders for a given OS share the same pools (e.g. all Linux trybots share the same pool
of Linux GCE instances), builders typically have max_concurrent_builds set to limit how many
builds can be running at once. This is to smooth out usage spikes and help prevent the physical
test hardware from being overwhelmed.


Dawn Litert Lm Benchmark (`docs/dawn/litert_lm_benchmark.md`)

LiteRT-LM Benchmarks

This document provides instructions for Dawn developers on how to configure, build, and run the
LiteRT-LM benchmarks against Dawn, as well as how to inspect and interpret the benchmark output.

Overview

LiteRT-LM is an on-device Large Language Model (LLM) inference runtime. In Dawn, it benchmarks
real-world WebGPU compute shader execution and pipeline dispatch performance using the
libwebgpu_dawn shared library.

Benchmark Metrics

The benchmark reports several phases of execution. Not all reported numbers are affected by changes
to Dawn:

Prefill Speed (`tokens/sec`)

Measures prompt evaluation throughput while processing input tokens into the KV-cache.

WebGPU relevance: Highly relevant. This phase is GPU compute-bound, exercising matrix
multiplication and attention shaders as well as buffer memory bandwidth.

Decode Speed (`tokens/sec`)

Measures autoregressive generation throughput while predicting output tokens one by one.

WebGPU relevance: Highly relevant. This phase is memory-bandwidth and dispatch-latency bound,
making it a direct measure of Dawn's command submission and dispatch overhead.

Time to First Token (TTFT)

Measures the wall-clock time from submitting the prompt to emitting the first generated token.

WebGPU relevance: Relevant. Dominated by the initial WebGPU prefill execution plus the first
decode step.

Init Executor

Measures the time required to initialize the engine before inference begins.

WebGPU relevance: Relevant. Directly captures WebGPU device acquisition, pipeline layout creation,
and shader compilation times.

Tokenizer and Metadata Operations

Includes Init Tokenizer, Init LLM metadata, and TextToTokenIds.

WebGPU relevance: Not relevant. These are host-side CPU operations (SentencePiece tokenization,
protobuf decoding) that do not execute WebGPU code.

Prerequisites (Windows)

Linux and macOS need no setup beyond gclient sync. Windows builds LiteRT-LM with Bazel and MSVC,
which requires the upstream
prerequisites
:
Visual Studio 2022, Git for Windows, Python 3, and a JDK with JAVA_HOME set.

Two machine settings are also required:

  • Developer Mode (Settings → System → For developers), so Bazel and cargo-bazel can create
    symlinks without elevation. Without it the build fails with os error 1314.
  • LongPathsEnabled set to 1 under HKLM\SYSTEM\CurrentControlSet\Control\FileSystem.

1. Fetch dependencies

LiteRT-LM sources, prebuilt accelerators, and benchmark model weights are managed conditionally
in DEPS to keep checkout sizes small for developers who do not need them.

To enable LiteRT-LM, add "checkout_litert_lm": True to custom_vars in your .gclient file:

python
solutions = [
  {
    "name": ".",
    "url": "https://dawn.googlesource.com/dawn",
    "deps_file": "DEPS",
    "managed": False,
    "custom_vars": {
      "checkout_litert_lm": True,
    },
  },
]

After editing .gclient, sync dependencies:

sh
gclient sync

This fetches Bazel (tools/bazelisk/), the LiteRT-LM source tree (third_party/litert-lm/src/),
and the CIPD package containing model.litertlm and precompiled platform
accelerators (third_party/litert-lm/data/).

2. Build

Generate Build Directory

Use the tools/setup-build script to configure a release build:

sh
./tools/setup-build gn release

This generates a GN build configuration in out/gn-release (and updates the out/active symlink to
point to it) with optimizations enabled and debugging/sanitizer overhead disabled.

Build the Benchmark Target

Build the benchmark executable using autoninja:

sh
autoninja -C out/active litert_lm

How the Build Works

  1. GN first builds Dawn's monolithic shared library:
    • Linux: out/active/libwebgpu_dawn.so
    • macOS: out/active/libwebgpu_dawn.dylib
    • Windows: out/active/webgpu_dawn.dll
  2. GN executes third_party/litert-lm/build_litert_lm.py, which:
    • Configures Bazelisk with the hermetic Clang/LLVM toolchain from Dawn's
      third_party/llvm-build (on Linux/macOS) or Visual Studio toolchain (on Windows).
    • Links against the newly compiled local libwebgpu_dawn and platform prebuilts.
    • Builds the Bazel target //runtime/engine:litert_lm_advanced_main.
    • Copies the resulting litert_lm_advanced_main binary and required shared libraries into
      out/active/.

3. Run

You can run the benchmark binary directly from the root of your Dawn checkout.

Linux

sh
LD_LIBRARY_PATH=out/active ./out/active/litert_lm_advanced_main \
  --benchmark \
  --backend=gpu \
  --model_path=third_party/litert-lm/data/model.litertlm

macOS

sh
DYLD_LIBRARY_PATH=out/active ./out/active/litert_lm_advanced_main \
  --benchmark \
  --backend=gpu \
  --model_path=third_party/litert-lm/data/model.litertlm

Windows

powershell
.\out\active\litert_lm_advanced_main.exe `
  --benchmark `
  --backend=gpu `
  --model_path=third_party\litert-lm\data\model.litertlm

Troubleshooting

LiteRT-LM dependencies not found

Ensure "checkout_litert_lm": True is present under custom_vars in your .gclient file, then run
gclient sync.

GPU accelerator could not be loaded and registered

The prebuilt libLiteRtWebGpuAccelerator cannot find libwebgpu_dawn. Ensure that
LD_LIBRARY_PATH=out/active (Linux) or DYLD_LIBRARY_PATH=out/active (macOS) is set when running
the command.

Symbol lookup errors or sanitizer crashes

Prebuilt LiteRT binaries are not built with sanitizers. Avoid ASan/UBSan configurations when
benchmarking LiteRT-LM and use a clean is_debug = false release build.


Dawn Testing (`docs/dawn/testing.md`)

Testing Dawn

(TODO)

Dawn Perf Tests

For benchmarking with dawn_perf_tests, it's best to build inside a Chromium checkout using the
following GN args:

text
is_official_build = true  # Enables highest optimization level, using LTO on some platforms
use_dawn = true           # Required to build Dawn
use_cfi_icall=false       # Required because Dawn dynamically loads function pointers, and we don't
                          # sanitize them yet.

A Chromium checkout is required for the highest optimization flags. It is possible to build and run
dawn_perf_tests from a standalone Dawn checkout as well, only using GN arg is_debug=false. For
more information on building, please see building.md.

Terminology

  • Iteration: The unit of work being measured. It could be a frame, a draw call, a data upload, a
    computation, etc. dawn_perf_tests metrics are reported as time per iteration.
  • Step: A group of Iterations run together. The number of iterationsPerStep is provided to the
    constructor of DawnPerfTestBase.
  • Trial: A group of Steps run consecutively. kNumTrials are run for each test. A Step in a Trial
    is run repetitively for approximately kCalibrationRunTimeSeconds. Metrics are accumulated
    per-trial and reported as the total time divided by numSteps * iterationsPerStep.
    maxStepsInFlight is passed to the DawnPerfTestsBase constructor to limit the number of Steps
    pipelined.

(See //src/dawn/tests/perf_tests/DawnPerfTest.h
for the values of the constants).

Metrics

dawn_perf_tests measures the following metrics:

  • wall_time: The time per iteration, including time waiting for the GPU between Steps in a
    Trial.
  • cpu_time: The time per iteration, not including time waiting for the GPU between Steps in a
    Trial.
  • validation_time: The time for CommandBuffer / RenderBundle validation.
  • recording_time: The time to convert Dawn commands to native commands.

Metrics are reported according to the format specified at
[chromium]//build/recipes/performance_log_processor.py

Dumping Trace Files

The test harness supports a --trace-file=path/to/trace.json argument where Dawn trace events can
be dumped. The traces can be viewed in Chrome's about://tracing viewer.

Test Runner

//scripts/perf_test_runner.py may be run to continuously run a test and report mean times and variances.

Currently the script looks in the out/Release build directory and measures the wall_time
metric (hardcoded into the script). These should eventually become arguments.

Example usage:

text
scripts/perf_test_runner.py DrawCallPerf.Run/Vulkan__e_skip_validation

Tests

BufferUploadPerf

Tests repetitively uploading data to the GPU using either WriteBuffer or CreateBuffer with
mappedAtCreation = true.

DrawCallPerf

DrawCallPerf tests drawing a simple triangle with many ways of encoding commands,
binding, and uploading data to the GPU. The rationale for this is the following:

  • Static/Multiple/Dynamic vertex buffers: Tests switching buffer bindings. This has
    a state tracking cost as well as a GPU driver cost.
  • Static/Multiple/Dynamic bind groups: Same rationale as vertex buffers
  • Static/Dynamic pipelines: In addition to a change to GPU state, changing the pipeline
    layout incurs additional state tracking costs in Dawn.
  • With/Without render bundles: All of the above can have lower validation costs if
    precomputed in a render bundle.
  • Static/Dynamic data: Updating data for each draw is a common use case. It also tests
    the efficiency of resource transitions.

LiteRT-LM Benchmarks

See litert_lm_benchmark.md for instructions on configuring, building, and
running the LiteRT-LM benchmarks with Dawn.

Testing with the CTS

There are various ways to test dawn against the WebGPU CTS.

With dawn.node

See src/dawn/node/README.md.

With a Chromium build manually

Go to https://gpuweb.github.io/cts/standalone/
or clone the CTS repo and serve it locally

sh
git clone https://github.com/gpuweb/cts.git
cd cts
npm ci
npm start

Then open http://localhost:8080/standalone.

On the CQ

You can run a specific version of the CTS with top of tree Dawn in Chromium.
First, upload the version you want to test to your own github fork of the CTS.
Note the commit-hash. Then run:

sh
./tools/run cts roll --max-attempts 0 --repo <repo-url> --revision <commit-hash>

Example:

sh
./tools/run cts roll --max-attempts 0 --repo https://github.com/greggman/cts.git --revision 1b3173b36a1084917e0ff29e5f40c6290b6f8adc

The roll script take a couple of minutes as it downloads that version of the CTS and
creates a roll patch. Once the roll script says it's waiting on the bots to finish
you can kill (Ctrl-C) the script. The CQ will continue to run the tests.

Filtering - running less than the full CTS

You can also specify a CTS query with --test-query <query>, for example, test all the
WGSL builtins ... --test-query "webgpu:shader,execution,expression,call,builtin,*".
You can also filter tests with a glob --test-filter <glob>, for example, only run tests
with BindGroup in the test name ... --test-filter "*BindGroup*".

See tools/src/cmd/cts/roll/roll.go for other options.

Testing with a different version of Dawn

The roll is just a normal CL on gerrit so, after creating a roll CL, follow the instructions
on gerrit to download that CL. Apply any dawn changes. Then re-upload with
git cl upload --force --bypass-hooks and start a dry run on gerrit.


Tint Arch (`docs/tint/arch.md`)

Tint Architecture

text
/* Detailed source-code truncated for AI context efficiency. */

Reader

Readers are responsible for parsing a shader program and populating a
ProgramBuilder with the parsed AST and symbol information.

The WGSL reader is a recursive descent parser. It closely follows the WGSL
grammar in the naming of the parse methods.

ProgramBuilder

A ProgramBuilder is the interface to construct an immutable Program.
There are a large number of helper methods for simplifying the creation of the
AST nodes. A ProgramBuilder can only be used once, and must be discarded after
the Program is constructed.

A Program is built from the ProgramBuilder via a call to
resolver::Resolve(), which will perform validation and semantic analysis.
The returned program will contain the semantic information which can be obtained
by calling Program::Sem().

At any time before building the Program, ProgramBuilder::IsValid() may be
called to ensure that no error diagnostics have been raised during the
construction of the AST. This includes parser syntax errors, but not semantic
validation which happens during the Resolve phase.

If further changes to the Program are needed (say via a Transform) then a
new ProgramBuilder can be produced by cloning the Program into a new
ProgramBuilder.

Unlike Programs, ProgramBuilders are not part of the public Tint API.

AST

The Abstract Syntax Tree is a directed acyclic graph of ast::Nodes which
encode the syntactic structure of the WGSL program.

The root of the AST is the ast::Module class which holds each of the declared
functions, variables and user declared types (type aliases and structures).

Each ast::Node represents a single part of the program's source, and so
ast::Nodes are not shared.

The AST does not perform any verification of its content. For example, the
ast::Array node has numeric size parameter, which is not validated to be
within the WGSL specification limits until validation of the Resolver.

Semantic information

Semantic information is held by sem::Nodes which describe the program at
a higher / more abstract level than the AST. This includes information such as
the resolved type of each expression, the resolved overload of a builtin
function call, and the module scoped variables used by each function.

Semantic information is generated by the Resolver when the Program
is built from a ProgramBuilder.

The sem::Info class holds a map of ast::Nodes to sem::Nodes.
This map is many-to-one - i.e. while a AST node might have a single
corresponding semantic node, the reverse may not be true. For example:
many ast::IdentifierExpression nodes may map to a single sem::Variable,
and so the sem::Variable does not have a single corresponding
ast::Node.

Unlike ast::Nodes, semantic nodes may not necessarily form a directed acyclic
graph, and the semantic graph may contain diamonds.

Types

AST types are regular AST nodes, in that they uniquely represent a single part
of the parsed source code. Unlike semantic types, identical AST types are not
de-duplicated as they refer to the source usage of the type.

Semantic types are constructed during the Resolver phase, and are held by
the Program or ProgramBuilder.

Each sem::Type node uniquely represents a particular WGSL type within the
program, so you can compare type::Type* pointers to check for type
equivalence. For example, a Program will only hold one instance of the
sem::I32 semantic type, no matter how many times an i32 is mentioned in the
source program.

WGSL type aliases resolve to their target semantic type. For example, given:

wgsl
type MyI32 = i32;
const a : i32 = 1;
const b : MyI32 = 2;

The semantic types for the variables a and b will both be the same
sem::I32 node pointer.

Symbols

Symbols represent a unique string identifier in the source program. These string
identifiers are transformed into symbols within the Readers.

During the Writer phase, symbols may be emitted as strings using a Namer.
A Namer may output the symbol in any form that preserves the uniqueness of
that symbol.

Resolver

The Resolver will automatically run when a Program is built.
A Resolver creates the Programs semantic information by analyzing the
Programs AST and type information.

The Resolver will validate to make sure the generated Program is
semantically valid.

Program

A Program holds an immutable version of the information from the
ProgramBuilder along with semantic information generated by the
Resolver.

Program::IsValid() may be called to ensure the program is structurally correct
and semantically valid, and that the Resolver did not report any errors
during validation.

Unlike the ProgramBuilder, a Program is fully immutable, and is part of the
public Tint API. The immutable nature of Programs make these entirely safe
to share between multiple threads without the use of synchronization primitives.

Inspector

The inspectors job is to go through the Program and pull out various pieces of
information. The information may be used to pass information into the downstream
compilers (things like specialization constants) or may be used to pass into
transforms to update the AST before generating the resulting code.

The input Program to the inspector must be valid (pass validation).

Transforms

There maybe various transforms we want to run over the Program.
This is for things like Vertex Pulling or Robust Buffer Access.

A transform operates by cloning the input Program into a new ProgramBuilder,
applying the required changes, and then finally building and returning a new
output Program. As the resolver is always run when a Program is built,
Transforms will always emit a Program with semantic information.

The input Program to a transform must be valid (pass validation).
If the input Program of a transform is valid then the transform must guarantee
that the output program is also valid.

Writers

A writer is responsible for writing the Program in the target shader language.

The input Program to a writer must be valid (pass validation).


Tint Benchmark (`docs/tint/benchmark.md`)

Tint Benchmark

The tint_benchmark executable measures the cost of translating shaders from WGSL to each backend language.
The benchmark uses Google Benchmark, and is integrated into Chromium's performance waterfall.

Benchmark inputs

Shaders used for benchmarking can be provided as either WGSL or SPIR-V.
The shaders are embedded in the binary at build time in order to avoid runtime dependencies.
A Python script is used to generate a header file that contains all of the benchmark shaders, and the macros that register benchmarks with the Google Benchmark harness.

SPIR-V shaders are converted to WGSL as an offline step using Tint, as the SPIR-V reader is not available on the waterfall bots.
The generated WGSL files are checked in to the repo and the same script is used by CQ to check that the generated files are up to date.

The script lists the paths to all of the benchmark shaders, which are either in the end-to-end test directory (test/tint/benchmark/) or provided as external shaders in third_party/benchmark_shaders/.

Adding benchmarks

Files that end with _bench.cc are automatically included in the benchmark binary.
To benchmark a component of Tint against the benchmark shaders, define a function that performs the benchmarking and then register it with the TINT_BENCHMARK_PROGRAMS macro.
For example, the SPIR-V backend benchmark code is in src/tint/lang/spirv/writer/writer_bench.cc.

Other parts of Tint can be benchmarked independently from the benchmark shader corpus by registering the benchmark function with the BENCHMARK macro.

Chromium performance waterfall

The tint_benchmark binary is a dependency of the performance_test_suite_template_base template in chrome/test/BUILD.gn.

The bot_platforms.py script controls which platforms run the benchmark.
The benchmark was added to the waterfall in this CL.

The chrome.perf console shows the status of the builder and tester bots, and can be used to check that changes to the benchmarking setup are working correctly.

Viewing benchmark data

View benchmark data with the perf.luci.app dashboard.
The units for Tint benchmarks are nanoseconds.

The query parameters are:

  • benchmark - select tint_benchmark
  • bot - select the waterfall bot from which to view data
  • test - select the component of Tint to focus on (e.g. a specific backend)
  • subtest_1 - select the benchmark shader to focus on

Tint Compound Statements (`docs/tint/compound_statements.md`)

Compound Statements

Compound statements are statements that can hold other statements.

This document maps the WGSL compound statements to their semantic tree representations.

if statement

WGSL:

text
if (condition_a) {
    statement_a;
} else if (condition_b) {
    statement_b;
} else {
    statement_c;
}

Semantic tree:

text
sem::IfStatement {
    condition_a
    sem::BlockStatement {
        statement_a
    }
    sem::IfStatement {
        condition_b
        sem::BlockStatement {
            statement_b
        }
        sem::BlockStatement {
            statement_c
        }
    }
}

for loop

WGSL:

text
for (initializer; condition; continuing) {
    statement;
}

Semantic tree:

text
sem::ForLoopStatement {
    sem::Statement  initializer
    sem::Expression condition
    sem::Statement  continuing

    sem::LoopBlockStatement {
        sem::Statement statement
    }
}

while

WGSL:

text
while (condition) {
    statement;
}

Semantic tree:

text
sem::WhileStatement {
    sem::Expression condition

    sem::LoopBlockStatement {
        sem::Statement statement
    }
}

loop

WGSL:

text
loop (condition) {
    statement_a;
    continuing {
        statement_b;
    }
}

Semantic tree:

text
sem::LoopStatement {
    sem::Expression condition

    sem::LoopBlockStatement {
        sem::Statement statement_a
        sem::LoopContinuingBlockStatement {
            sem::Statement statement_b
        }
    }
}

switch statement

WGSL:

text
switch (condition) {
    case literal_a, literal_b: {
        statement_a;
    }
    default {
        statement_b;
    }
}

Semantic tree:

text
sem::SwitchStatement {
    sem::Expression condition
    sem::CaseStatement {
        sem::BlockStatement {
            sem::Statement statement_a
        }
    }
    sem::CaseStatement {
        sem::BlockStatement {
            sem::Statement statement_b
        }
    }
}

Tint Coverage Info (`docs/tint/coverage-info.md`)

Generating and viewing Tint code-coverage

Requirements:

  • Host running Linux or macOS
  • vpython3
    (usually provided by depot_tools)
  • Clang toolchain

Building Tint with coverage generation enabled

To generate coverage, you must build with Clang source-based coverage
instrumentation enabled.

Add the following flags to your args.gn:

gn
use_clang_coverage = true
is_component_build = false
is_debug = false
dcheck_always_on = true

If you are building a fuzzer target, also include:

gn
optimize_for_fuzzing = false
use_clang_modules = false
use_libfuzzer = true

Using CMake

Add the additional -DDAWN_EMIT_COVERAGE=1 CMake flag.

To use the coverage.py script's automated building features, you
must use the Ninja generator:

bash
cmake -G Ninja -DDAWN_EMIT_COVERAGE=1 ..

If you use a different generator (like Make), you must build your
targets manually and use the --no-compile flag with the script.

Generating Coverage Reports

Use the tools/code_coverage/coverage.py script to run your targets
and generate reports. (This is the Chromium maintained script that
replaces the previous Tint only script,
tools/tint-generate-coverage).

1. Generate LCOV for VSCode

To use the VSCode Coverage
Gutters

extension, generate an LCOV tracefile:

bash
vpython3 tools/code_coverage/coverage.py tint_unittests \
    -b out/gn-coverage \
    -o coverage_report \
    --format lcov \
    -c 'out/gn-coverage/tint_unittests'

The resulting file will be located at
coverage_report/linux/coverage.lcov.

2. Generate an HTML Report

To generate a browsable HTML report with line-by-line coverage:

bash
vpython3 tools/code_coverage/coverage.py tint_unittests \
    --no-component-view \
    -b out/gn-coverage \
    -o coverage_report \
    --format html \
    -c 'out/gn-coverage/tint_unittests'

Open coverage_report/linux/index.html in your browser.

3. Advanced: Fuzzers with Dynamic Objects (Mesa/Vulkan)

If you are running a fuzzer that dynamically loads a Vulkan driver
(like Mesa's lvp), you need to explicitly include the driver object
and map its source paths:

bash
vpython3 tools/code_coverage/coverage.py tint_ir_fuzzer \
    --no-component-view \
    -b out/gn-coverage \
    -o coverage_report \
    --format html \
    -a out/gn-coverage/libvulkan_lvp.so \
    --path-equivalence=/third_party/mesa,$(pwd)/third_party/mesa \
    --path-equivalence=$(pwd)/out/gn-coverage/src,$(pwd)/out/gn-coverage/gen/third_party/mesa/mesa_build_setup/src \
    -i '\/usr\/.*' \
    -c 'out/gn-coverage/tint_ir_fuzzer -max_total_time=30'
  • -a: Adds the dynamically loaded driver as object source to the
    coverage calculation.
  • --path-equivalence: Maps paths recorded in the binary (which may
    be absolute or relative to build artifacts) to your local
    checkout.
  • Multiple --path-equivalence flags can be provided to handle
    different source roots (e.g., standard sources vs. generated
    files).
  • -i '\/usr\/.* is used to not include system files that are
    outside the source tree.

Troubleshooting

403 error when generating HTML reports

If you see something like this:

text
/* Detailed source-code truncated for AI context efficiency. */

then the likely cause is missing --no-component-view from the flags.

The component view functionality depends on the mapping of directories
in a Chromium checkout to components in the issue tracker, so
misbehaves when run in a Dawn checkout. Fixing this would add
complexity upstream for a view that doesn't provide any useful
information in the report, since it would just tell you all of the
code in the Dawn repo belongs to either the Dawn or Tint component.


Tint Diagnostics Guide (`docs/tint/diagnostics_guide.md`)

Tint diagnostic style guide

This guide provides a set of best practices when writing code that emits
diagnostic messages in Tint. These diagnostics are messages presented to the
user in case of error or warning.

The goal of this document is to have our diagnostic messages be clear and
understandable to our users, so that problems are easy to fix, and to try and
keep a consistent style.

Message style

  • Start diagnostic messages with a lower-case letter
  • Try to keep the message to a single sentence, if possible
  • Do not end the message with punctuation (full stop, exclamation mark, etc)

Don't:

text
shader.wgsl:7:1 error: Cannot take the address of expression.

Do:

text
shader.wgsl:7:1 error: cannot take the address of expression

Justification:

Succinct messages are more important than grammatical correctness.
This style matches the style found in most other compilers.

Prefer to use a `Source` location instead of quoting the code in the message

Don't:

text
shader.wgsl:5:7 error: cannot multiply 'expr_a * expr_b' with types i32 and f32

var res : f32 = expr_a * expr_b
                ^^^^^^^^^^^^^^^

Do:

text
shader.wgsl:5:7 error: cannot multiply types i32 and f32

var res : f32 = expr_a * expr_b
                ^^^^^^^^^^^^^^^

Justification:

The highlighted line provides even more contextual information than the quoted
source, and duplicating this information doesn't provide any more help to the
developer.
Quoting single word identifiers or keywords from the source is not discouraged.

Don't:

text
shader.wgsl:5:11 error: type cannot be used in address space 'storage' as it is non-host-shareable

    cond : bool;
           ^^^^

Do:

text
shader.wgsl:5:11 error: type cannot be used in address space 'storage' as it is non-host-shareable

    cond : bool;
           ^^^^

shader.wgsl:8:4 note: while instantiating variable 'StorageBuffer'

var<storage> sb : StorageBuffer;
             ^^

Justification:

To properly understand some diagnostics requires looking at more than a single
line.
Multi-source links can greatly reduce the time it takes to properly
understand a diagnostic message.
This is especially important for diagnostics raised from complex whole-program
analysis, but can also greatly aid simple diagnostics like symbol collision errors.

Use simple terminology

Don't:

text
shader.wgsl:7:1 error: the originating variable of the left-hand side of an assignment expression must not be declared with read access control.

Do:

text
shader.wgsl:7:1 error: cannot assign to variable with read access control

x.y = 1;
^^^^^^^

shader.wgsl:2:8 note: read access control declared here

var<storage, read> x : i32;
             ^^^^

Justification:

Diagnostics will be read by Web developers who may not be native English
speakers and are unlikely to be familiar with WGSL specification terminology.
Too much technical jargon can be intimidating and confusing.
Diagnostics should give enough information to explain what's wrong, and most
importantly, give enough information so that a fix actionable.

Caution: Be careful to not over simplify. Use the specification terminology
if there's potential ambiguity by not including it.


Tint End To End Tests (`docs/tint/end-to-end-tests.md`)

Tint end-to-end tests

This repo contains a large number of end-to-end tests at <dawn>/test/tint.

Test files

Test input files have either the .wgsl, .spv or .spvasm file extension.

Each test input file is tested against each of the Tint backends. There are <number-of-input-files> × <number-of-tint-backends> tests that are performed on an unfiltered end-to-end test run.

Each backend test can have an expectation file. This expectation file sits next to the input file, with a .expected.<format> extension. For example the test test/foo.wgsl would have the HLSL expectation file test/foo.wgsl.expected.hlsl.

An expectation file contains the expected output of Tint, when passed the input file for the given backend.

If the first line of the expectation file starts SKIP, then the test will be skipped instead of failing the end-to-end test run. It is good practice to include after the SKIP a reason for why the test is being skipped, along with any additional details, such as compiler error messages.

Running

To run the end-to-end tests use the ./tools/run tests script, passing the path to the tint executable with the --tint (defaulting to ./out/active/tint):

text
./tools/run tests

You can pass a list of globs, directories or file paths as extra arguments to specify what tests you
want to run.

You can pass --help to see the full list of command line flags.
The most commonly used flags are:

flag description
--format Filters the tests to the particular backend.Example: --format hlsl will just test the HLSL backend.
--generate-expected Generate expectation files for the tests that previously had no expectation file, or were marked as SKIP but now pass.
--generate-skip Generate SKIP expectation files for tests that are not currently passing.

Authoring guidelines

Each test should be as small as possible, and focused on the particular feature being tested.

Use sub-directories whenever possible to group similar tests, and try to keep the pattern of directories as consistent as possible between different tests. This helps filter tests with glob patterns.


Tint Experimental Extensions (`docs/tint/experimental_extensions.md`)

Experimental extensions

Sometimes a language feature proposed for WGSL requires experiementation
to prove its worth. Tint needs to support these, in general to enable
that experimentation.

The steps for doing so are:

  1. Choose a name for the feature, to be used in an enable directive.
    An experimental extension should use prefix of google_experimental_
    Example:

    enable google_experimental_f16;

  2. Write down what the feature is supposed to mean.
    This informs the Tint implementation, and tells shader authors what
    has changed.
    Ideally, this will take the form of one of the following:

    • A PR against the WGSL spec.

    • A description of what the contents of that PR would be, committed
      as a document in this Tint repository.

  3. File a tracking bug for adding the feature.
    Note: Should the Tint repo have a label for experimental features?

  4. File a tracking bug for removing the feature or converting it to
    non-experimental.

  5. Write a plan for removal of the experiment.

    • Ideally, this plan is committed to this repository, especially the
      description of public activities and commitments. However, we recognize
      that some internal goals or metrics may be sensitive, and can be hidden.
    • The plan is about process, not technical details. It should include:
      • Who is the point of contact for this feature? The point of contact
        is responsible when the feature causes an issue or gets in the way.
      • What is your target date for declaring the experiment a success or
        failure. In Chrome an experiment must be shipped or removed, in
        finite time.
      • What experience are you hoping to gain? Do you have target metrics?
      • What approvals, if any, do you need from W3C? What is your plan to
        present your case to W3C?
      • The bug tracking removal of the experiment.

Tint Fuzzers (`docs/tint/fuzzers.md`)

Tint Fuzzers

Tint currently has two fuzzer target executables:
tint_wgsl_fuzzer which takes WGSL source code
as input and tint_ir_fuzzer which takes a
protobuf binary file as input.

Both fuzzers are implemented using
libFuzzer, and are
continuously run by Chromium's ClusterFuzz infrastructure.

Fuzzer targets are automatically found via gn refs. See
tint.gni for the core fuzzer target
rules.

Tint's fuzzers are implemented as functions registered with the
macros:

Building

The tint_wgsl_fuzzer target can be built with either CMake or GN:

  • CMake: Define TINT_BUILD_FUZZERS=1 (pass -DTINT_BUILD_FUZZERS=1
    to cmake)
  • GN: Define use_libfuzzer = true in args.gn.

Building tint_ir_fuzzer requires additional flags to be set:

  • CMake: Define TINT_BUILD_IR_BINARY=1 (pass
    -DTINT_BUILD_IR_BINARY=1 to cmake)
  • GN: Define tint_build_ir_binary = true in args.gn.

Running fuzzers

Local fuzzing

The tint_wgsl_fuzzer and
tint_ir_fuzzer executables accept the standard
libFuzzer command line
arguments
with
extended command line arguments
described below.

There's also a helper tool to run the fuzzers locally:

  • To run the local fuzzers across the full number of CPU threads
    available on the system, seeded with a corpus based on
    test/tint, and using the dictionary in
    src/tint/cmd/fuzz/wgsl/dictionary.txt run:

    tools/run fuzz run

  • To check that all the test files, test/tint by
    default, pass the fuzzers without crashing and then exit, run:

    tools/run fuzz check

    Note: This is run by Dawn's CQ to check that fuzzers aren't
    accidentally broken.

  • To run the local fuzzers using a generated corpus of .wgsl files:

    tools/run fuzz run -inputs out/Fuzzer/wgsl_corpus

Note: The above commands will run in the default WGSL mode only. If
you want to run using the IR fuzzer, you need to pass in --ir to run
in IR mode.

Examples of IR mode:

bash
tools/run fuzz run -ir
tools/run fuzz run -ir -inputs out/Fuzzer/ir_corpus
tools/run fuzz check -ir

-ir mode without any provided inputs will convert the default WGSL
inputs from test/tint on-the-fly to .tirb
files. A manually provided input of .tirb files can be provided. There
is currently no facility to detect that an explicit input directory
contains .wgsl files that need converting to .tirb files.

See below for how to generate the corpus locally

Generating fuzzer corpora

Running fuzzers without any inputs is relatively inefficient, since
the fuzzing algorithm has to discover the structure of the input
format via generating random inputs. This can be alleviated by
providing a corpus of examples to start from. The fuzzer will not be
restricted by this initial corpus, since it will mutate and recombine
the examples to find new and interesting inputs.

The helper tool is capable of producing appropriate corpora from test
files via the generate subcommand. generate requires you to also pass
in -out, since otherwise the output would go to an ephemeral tmp
directory and not be useable.

tint_wgsl_fuzzer

To generate a corpus based on the default test files from
test/tint no additional flags are needed

bash
tools/run fuzz generate -out out/Fuzzer/wgsl_corpus

-inputs can be used to provide custom inputs to the tool. It will
use any .wgsl files found in the inputs that don't also include the
string .expected. in their name.

bash
tools/run fuzz generate -inputs my_super_cool_tests/ -out out/Fuzzer/wgsl_corpus

-out will overwrite existing files that collide with the output file
names, but does not create the output directory or remove its
contents, so that you can merge multiple input sources together via
multiple calls to the tool.

The corpus can be used with the above helper tool via the -inputs
flag or may be directly passed into a manual invocation of
tint_wgsl_fuzzer.

An example of generating and then using a custom WGSL corpus:

bash
autoninja -C out/Fuzzer fuzzer_corpus_tools  # Guarantees the fuzzers and another tooling is present
tools/run fuzz generate -inputs my_super_cool_tests/ -out out/Fuzzer/wgsl_corpus

# And then either
out/Fuzzer/tint_wgsl_fuzzer -dict=src/tint/cmd/fuzz/wgsl/dictionary.txt out/Fuzzer/wgsl_corpus

# or

tools/run fuzz run -inputs out/Fuzzer/wgsl_corpus

tint_ir_fuzzer

IR corpus generation requires the ir_fuzz_as binary to be present in
the build directory. This directory is out/active by default, but
can be manually specified via -build. The GN target
fuzzer_corpus_tools can be used to build this binary and any of the
other binaries that the helper tool may need.

The corpus generation process for a IR corpus is the same as above,
except using the -ir flag.

The inputs are expected to be .wgsl files like the WGSL generation, but
the output will .tirb files. Similarly to the WGSL mode generation
inputs with .expected. will be filtered out.

Generating a corpus based on the default WGSL tests:

bash
tools/run fuzz generate -ir -out out/Fuzzer/wgsl_corpus

Generating a corpus based on custom WGSL files:

bash
tools/run fuzz generate -ir -input my_super_cool_tests/ -out out/Fuzzer/wgsl_corpus

An example of generating and using a custom IR corpus:

bash
autoninja -C out/Fuzzer fuzzer_corpus_tools  # Guarantees the fuzzers and another tooling is present
tools/run fuzz generate -ir -inputs my_super_cool_tests/ -out out/Fuzzer/ir_corpus

# And then either
out/Fuzzer/tint_ir_fuzzer out/Fuzzer/ir_corpus

# or

tools/run fuzz run -ir -inputs out/Fuzzer/ir_corpus

Minimizing the corpus

Minimizing a fuzzer corpus is no longer supported as part of the
tooling. It was rarely used, since ClusterFuzz does it automatically,
and added significant complexity to the tooling. If you need to
minimize a corpus manually please consult the libFuzzer documentation
for details

Writing fuzzers

Registering a new `tint::Program` fuzzer

  1. Create a new source file with a _fuzz.cc suffix.

  2. #include "src/tint/cmd/fuzz/wgsl/fuzz.h"

  3. Define a function in a (possibly nested) anonymous namespace with
    one of the signatures:

    • void MyFuzzer(const tint::Program& program /*, ...additional fuzzed parameters... */) {
    • void MyFuzzer(const tint::Program& program, const tint::fuzz::wgsl::Context& context /*, ...additional fuzzed parameters... */) {

    The optional context parameter holds information about the
    Program and the environment used to run the fuzzers.

    Note: Any number of additional fuzzer-populated parameters can be
    appended to the function signature.

  4. Implement your fuzzer function, using TINT_ICE() to catch invalid
    state. Return early if the fuzzer cannot handle the input.

  5. At the bottom of the file, in the global namespace, register the
    fuzzer with: TINT_WGSL_PROGRAM_FUZZER(MyFuzzer);

  6. Use tools/run gen build to generate the build files for this new
    fuzzer.

Example:

c++
#include "src/tint/cmd/fuzz/wgsl/fuzz.h"

namespace tint::my_namespace {
namespace {

bool CanRun(const tint::Program& program) {
    if (program.AST().HasOverrides()) {
        return false;  // Overrides are not supported.
    }
    return true;
}

void MyWGSLFuzzer(const tint::Program& program, bool a_fuzzer_provided_value) {
    if (!CanRun(program)) {
        return;
    }

    // Check something with program.
}

}  // namespace
}  // namespace tint::my_namespace

TINT_WGSL_PROGRAM_FUZZER(tint::my_namespace::MyWGSLFuzzer);

Registering a new `tint::core::ir::Module` fuzzer

  1. Create a new source file with a _fuzz.cc suffix.

  2. #include "src/tint/cmd/fuzz/ir/fuzz.h"

  3. Define a function in a (possibly nested) anonymous namespace with
    the signature:

    • void MyFuzzer(core::ir::Module& module /*, ...additional fuzzed parameters... */) {

    Note: Any number of additional fuzzer-populated parameters can be
    appended to the function signature.

  4. Implement your fuzzer function, using TINT_ICE() to catch invalid
    state. Return early if the fuzzer cannot handle the input.

  5. At the bottom of the file, in the global namespace, register the
    fuzzer with: TINT_IR_MODULE_FUZZER(MyFuzzer);

[Note: Often different capabilities need to be enabled before/after
a fuzzer function runs to ensure validation passes, these can be
declared using pre_capabilities and post_capabilities when
registering the fuzzer ]

  1. Use tools/run gen build to generate the build files for this new
    fuzzer.

Example:

c++
#include "src/tint/cmd/fuzz/ir/fuzz.h"

namespace tint::my_namespace {
namespace {

void MyIRFuzzer(core::ir::Module& module) {
    // Do something interesting with module.
}

}  // namespace
}  // namespace tint::my_namespace

TINT_IR_MODULE_FUZZER(tint::my_namespace::MyIRFuzzer);

Additional fuzzer data

WGSL and IR fuzzer functions can also declare any number of additional
parameters, which will be populated with fuzzer provided data. These
additional parameters must come at the end of the signatures described
above, and can be of the following types:

  • Any integer, float or bool type.

  • Any structure reflected with TINT_REFLECT.

    Note: It's recommended to use a const reference, for these to
    avoid pass-by-value overheads.

  • Any enum reflected with TINT_REFLECT_ENUM_RANGE.

Executable targets

Tint has two fuzzer executable targets:

`tint_wgsl_fuzzer`

tint_wgsl_fuzzer accepts WGSL textual
input
and parses line
comments (//) as a base-64 binary encoded data stream for the
additional fuzzer parameters.

The entry point for the fuzzer lives at
src/tint/cmd/fuzz/wgsl/main_fuzz.cc.

Extended command line arguments

On top of the standard libFuzzer command line
arguments
, the fuzzer
supports the following extended command line arguments:

  • --help: lists the command line arguments.
  • --filter=<name>: only runs the fuzzer functions that contain the
    given string in its name.
  • --concurrent: each of the fuzzer functions will be run on a
    separate, concurrent thread. This potentially offers performance
    improvements, and also tests for concurrent execution.
  • --verbose : prints verbose information about what the fuzzer is
    doing.
  • --dump : prints shader source, including input WGSL, and generated
    HLSL, MSL, and GLSL.

Behavior

The tint_wgsl_fuzzer will do the following:

  • Base-64 decode the line comments data from the WGSL source, used to
    populate the additional fuzzer parameters.
  • Parse and resolve the WGSL input, and will early-return if there are
    any parser errors.
  • Invoke each of the fuzzer functions registered with a call to
    TINT_WGSL_PROGRAM_FUZZER()
  • Automatically convert the Program to an IR module and run the
    function for each function registered with
    TINT_IR_MODULE_FUZZER(). Note: The Program is converted to an IR
    module for each registered IR fuzzer as the module is mutable.

`tint_ir_fuzzer`

tint_ir_fuzzer accepts binary protocol
buffer
inputs and uses
libprotobuf-mutator
for mutating this binary format directly.

The suffix .tirb is sometimes used for IR fuzz test case files,
which helps our tooling infer the format. The fuzzer itself does not
generate/depend on this suffix though, instead using the libFuzzer
standard prefixes, i.e. crash-... and slow-..., when needed.

The input protobuf is defined in
src/tint/utils/protos/ir_fuzz/ir_fuzz.proto
is a composite of two elements, a protobuf
src/tint/utils/protos/ir/ir.proto
that defines the actual IR for the test case, and an opaque binary
blob that includes the additional fuzzer
parameters
akin to tint_wgsl_fuzzer.

The entry point for the fuzzer lives at
src/tint/cmd/fuzz/ir/main_fuzz.cc.

Extended command line arguments

This fuzzer accepts the same set of flags as tint_wgsl_fuzzer (both
libFuzzer and extended), expect --dump which is currently not
supported.

Behavior

The tint_ir_fuzzer will do the following:

  • Decode the binary encoded protobuf into a Program for the
    IR module and a binary blob that will be passed into each function
    as options. If the input cannot be decoded due to being invalid or
    containing select constructs that can never occur in real world
    inputs, then the fuzzer will early return.
  • Validate that decoded IR module, and will early-return if there are
    any errors.
  • For each fuzzer function registered via TINT_IR_PROGRAM_FUZZER(),
    make a copy of the IR module, since it is mutable, and then invoke
    the fuzzer function.
  • Validate that outputted IR module state, and raise an error if it
    fails, since this indicates a functional issue with a transform or
    the initial validation missed an illegal state.

Working with test cases

Since the input test cases for tint_ir_fuzzer are in a non-human
readable format, there is additional tooling needed for working with
them.

ir_fuzz_dis

For displaying the IR contents of a test case file (i.e. crash-...,
or foo.tirb), there is a disassembler ir_fuzz_dis, which is the
easiest way to dump out the contents of a test case. This disassembler
supports a subset of the Tint CLI for outputting in various formats
and to files, etc.

The disassembler has two significant known limitations though.

First, it does not dump out the binary options blob. This is because
this data is interpreted on a per-fuzzer function basis, so there
isn't a general human readable interpretation of its contents. If you
need to understand how the binary blob is being handled by a fuzzer
you will either need to add logging or use a debugger.

The other known limitation is that this disassembler depends on Tint's
IR printing code, which assumes well formed inputs. Sometimes it will
fail to output anything, or more subtly not output values it did't
expect. For example if an instruction only expects 2 params and there
are actually 3 in the IR, then the disassembler may only print the
first two. The validator should catch these issues and give you useful
warnings, but since it is a WIP itself there may be omissions in its
implementation.

protoc

Due to the second limitation on the disassembler, there are times
where you will need to dump the raw contents of the binary protocol
buffer in a textual format. This can be done using the tool protoc
which is part of libprotobuf and either installed via a system
package or built as part building the fuzzers. A full tutorial on
using protoc is beyond the scope of this document, see protobuf
docs
for more
details, but the basic invocation looks like this:

bash
out/Fuzzer/protoc --decode tint.cmd.fuzz.ir.pb.Root ./src/tint/utils/protos/ir_fuzz/ir_fuzz.proto < ./input.tirb

The important thing to note is the use of --decode tint.cmd.fuzz.ir.pb.Root to decode just the IR portion of the input,
but still needing to supply top-level ir_fuzz.proto as the format.

ir_fuzz_as

For generating binary test cases a rudimentary assembler,
ir_fuzz_as, has been implemented.

It is primarily used for converting the Tint test shaders into a seed
corpus for tint_ir_fuzzer, but can be used to convert user supplied
shaders into IR tests cases.

Similar to the disassembler it only operates on the shader/IR portion
of the test case format, and does not support embedding/manipulating
the binary blob portion. (There is currently no roundtrip workflow for
taking a test case binary, unpacking it, modifying it, and repacking
it with original blob, other then manually unpacking and packing
protobufs, which is left as an exercise to the reader).

It also suffers from the same limitation of the disassembler with
regards to not handling malformed inputs particularly well, since it
depends on the general Tint CLI parsing/printing infrastructure.

It is capable of dumping raw text of the IR protobuf that it
generates, which can be useful for understanding how the IR is being
encoded/decoded, since you can take a snippet of valid WGSL and dump
out what the protobuf looks like for it.

Debugging

To debug a specific registered fuzzer function, one strategy is to add
a TINT_ICE call at the top of the function, and then run the fuzzer
with -filter <name> to have it only run that specific fuzzer. When
the function is called, the libfuzzer harness will emit a crash file
that can be used as input on subsequent runs. Remove the TINT_ICE
and run the fuzzer again using this crash file.

For example, if we wish to debug tint::msl::writer::IRFuzzer, we
would first insert a TINT_ICE at the top:

c++
Result<SuccessType> IRFuzzer(core::ir::Module& module,
                             const fuzz::ir::Context& context,
                             Options options) {
    TINT_ICE() << "Crash";
    // Comment out the rest of the body to avoid unreachable code warnings
}

Build and run the fuzzer, filtering in this function:

bash
autoninja -C out/libfuzz tint_wgsl_fuzzer
out/libfuzz/tint_wgsl_fuzzer -filter=tint::msl::writer::IRFuzzer

It can take a little while before libfuzzer generates a valid input
WGSL, but eventually it will call into the function and crash on the
ICE:

text
...
#71607  NEW    cov: 3633 ft: 8316 corp: 1045/8248b lim: 25 exec/s: 2469 rss: 158Mb L: 8/25 MS: 2 ShuffleBytes-PersAutoDict- DE: "true"-
#71669  NEW    cov: 3633 ft: 8317 corp: 1046/8266b lim: 25 exec/s: 2471 rss: 158Mb L: 18/25 MS: 2 CMP-ChangeByte- DE: "if"-
#71697  REDUCE cov: 3633 ft: 8317 corp: 1046/8264b lim: 25 exec/s: 2472 rss: 158Mb L: 6/25 MS: 3 PersAutoDict-ChangeBit-EraseBytes- DE: "\001\002"-
ICE while running fuzzer: 'tint::msl::writer::IRFuzzer'
..\..\src\tint\lang\msl\writer\writer_fuzz.cc:63 internal compiler error: Crash
==25204== ERROR: libFuzzer: deadly signal
NOTE: libFuzzer has rudimentary signal handlers.
      Combine libFuzzer with AddressSanitizer or similar for better crash reports.
SUMMARY: libFuzzer: deadly signal
MS: 1 PersAutoDict- DE: "or"-; base unit: b34d87c378ebbbfbfd475303dcc75d1d1b2a7c7a
0x2f,0x2f,0x33,0x33,0x33,0x33,0x6f,0x72,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x33,0x2a,0x2a,0x30,0x32,
//3333or3333333333**02
artifact_prefix='./'; Test unit written to ./crash-21563a85afd5322d9e17c1c43fd3d4029778d6e7
Base64: Ly8zMzMzb3IzMzMzMzMzMzMzKiowMg==

Note that the second to last line specifies that the input test was
written to a file. Now we can remove the TINT_ICE and run the fuzzer
with just this file as input:

bash
out/libfuzz/tint_wgsl_fuzzer ./crash-21563a85afd5322d9e17c1c43fd3d4029778d6e7

Profiling

It is possible to profile the fuzzers using callgrind from
valgrind. Profiling reports from callgrind
can be processed using callgrind_annotate to get human readable text
or visualized using
KCachegrind.

Note: callgrind is not a sampling profiler (e.g. perf), which
means it traces all the calls during the run which produces more
complete data about the execution, but at the cost of causing a
significant (i.e. orders of magnitude) performance hit. If you are
looking for a fast, approximate profiling tool I would recommend
looking at using perf.

Prerequisites

System

You will need to have valgrind (and optionally KCachegrind) installed
on your machine. For APT based machines this looks like:

bash
sudo apt get valgrind

# Optionally for visualization
sudo apt get kcachegrind

Note: If you are not running KDE or another QT based DE,
installing KCachegrind will likely pull in a bunch of framework
dependencies. Alternatives include: QCachegrind which is a lighter
weight version of KCachegrind, and
gprof2dot which supports
outputting a variety of profiling formats to Graphviz dot files

Build Configuration

Because profiling requires symbol information and is going to be doing
a bunch of call interception, there are some build flags that must be
enabled and some that must be disabled in your args.gn

gn
# If not enabled, profiling will still run, the trace will just be
# empty
is_debug = true

# Technically will run with sanitizers on, but execution will likely
# halt early due to the sanitizers triggering on the profiling
# hooks. These flags are off by default in args.gn, but if you are
# working with fuzzers regularly, you may have them enabled for your
# specific build
is_asan = false
is_msan = false
is_<any other san> = false

Example Workflow

The steps for profiling a hypothetical WGSL fuzzer test case that is
causing timeouts.

After doing whatever other triage I normally would do and determining
that this is a performance bottleneck problem that requires
profiling. The first step will be to actually generate the profiling
trace.

bash
valgrind \
    --tool=callgrind \
    --trace-children=yes \
    --dump-instr=yes \
    --collect-jumps=yes \
    out/gn-callgrind/tint_wgsl_fuzzer triage/really_slow.wgsl
  • --trace-children=yes is required for valgrind to follow flow
    across forks. Without this you will just get a trace of the libfuzzer
    framework
  • --dump-instr=yes and --collect-jumps=yes are needed to generate
    proper data attributing time up to parents. Without these flags each
    function call will be only assessed the time actually spent in the
    function's code and not include the time of anything it calls into.

Note: This profiling can be very slow, this is a start executing
and go get a snack type of task.

After the profiling is done, I manually process the output, since the
paths to files in report will be relative to the binary location, i.e
out/gn-callgrind, so will include a leading ../../. The original
output file will be something like callgrind.out. where
is the process id for the specific invocation.

bash
sed -i 's#\./\.\./\.\./#./#g' callgrind.out.<pid> > callgrind.out.fixed.<pid>

And then to visualize and start inspecting the trace

bash
kcachegrind callgrind.out.fixed.<pid>

As mentioned you can use callgrind_annotate, which is installed as
part of valgrind, to pull out information like the N most expensive
functions. The man page for callgrind_annotate is relatively brief,
so I would suggest using your favourite search engine to find examples
of specific usages.


Tint Fuzzing Experiments (`docs/tint/fuzzing-experiments.md`)

Tint Fuzzing Experiments

The Tint fuzzing experiment/benchmarking framework is designed to
automate the process of comparing changes to the Tint fuzzers. It
measures hardware performance, fuzzer coverage percentage rates, and
test case execution rates to come up with abstract performance scores
to compare versions of the fuzzers.

This framework is part of the general fuzz utility for Tint found in
./tools/src/cmd/fuzz.

This document explains the high-level architecture of the experiments
framework, the environment prerequisites, and a step-by-step
walkthrough of how to define, execute, and process an
experiment. Along with a reference section at the end for the
experiment configuration JSON format,


Prerequisites

Running fuzzing experiments requires a local Dawn repository and a GN
build directory configured for fuzzing and coverage.

To support correct instrumentation, your GN build (args.gn) must
include at least the following flags:

gn
use_libfuzzer = true
tint_build_wgsl_reader = true
use_clang_coverage = true
optimize_for_fuzzing = false  # Cannot be used with coverage enabled

Additional flags must be enabled depending on the fuzzers selected:

  • IR Fuzzers (tint_ir_fuzzer, tint_ir_mesa_fuzzer ): Require
    tint_build_ir_binary = true and tint_has_protobuf = true.
  • Mesa Fuzzers (tint_wgsl_mesa_fuzzer, tint_ir_mesa_fuzzer):
    Require tint_build_mesa = true and
    tint_build_fuzzer_vulkan_support = true.

The tool will produce an error if the flags are not correctly
configured. The tool does not support using a pre-built version of the
Tint fuzzers, nor the CMake builds.

Note: For the most applicable results it is recommended that all
backends are turned on (tint_build_hlsl_writer = true,
tint_build_msl_writer = true, tint_build_spv_writer = true)

Note: Though the fuzzers can be built and run with sanitizers
turned on (is_asan = true, etc), this is not recommended, because
the sanitizers tend to dominate the runtime of the execution, so
produce substantially lower quality data


Defining an Experiment

An experiment is represented by a dedicated directory containing:

  1. An experiment.json configuration file.
  2. A corpora/ directory holding the seed corpora for the various
    fuzzing modes.

Directory Structure

directory
my_experiment/
├── experiment.json
└── corpora/
    ├── wgsl_seed/
    │   ├── shader1.wgsl
    │   └── shader2.wgsl
    └── empty/

Writing `experiment.json`

The experiment.json defines variables like the commit hash to check
out, specific fuzzers to test, and duration of the experiments.

Here is an example setup for comparing fuzzer performance using both a
standard corpus and an empty directory as starting points:

json
{
    "name": "example",
    "hash": "4c2395a860ab76b44f7256c25281dfd3a3680192",
    "fuzzers": ["tint_wgsl_fuzzer", "tint_ir_fuzzer"],
    "burnin_enabled": false,
    "normalization_duration": 10,
    "normalization_iterations": 2,
    "wgsl_normalization_corpus": "wgsl_seed",
    "ir_normalization_corpus": "empty",
    "wgsl_corpora": [
        { "name": "wgsl_full", "path": "wgsl_seed" },
        { "name": "wgsl_empty", "path": "empty" }
    ],
    "ir_corpora": [
        { "name": "ir_empty", "path": "empty" }
    ],
    "default_iterations": 5,
    "durations": [
        {"runs": 10000},
        {"runs": 50000},
        {"runs": 100000}
    ]
}
  • name is just a user visible title for this experiment which will
    appear in reports/logging, but has no semantic meaning
  • hash is the specific version of the Dawn repo that will be checked
    out to build the fuzzers. (The tool will be built and run from what
    ever hash the repo is at when you call it, not this version)
  • fuzzers are the specific fuzzer binaries to test
  • burnin_enabled, normalization_duration, and
    normalization_iterations are turned down or off in this example so
    that the setup phases run fast, but these should be removed or
    increased when generating statistically valid data
  • *_normalization_corpus are the corpora to use when establishing
    baselines for normalizing performance numbers between
    machines/environments
  • *_corpora are the various starting corpora to experiment using
  • default_iterations specifies how many times to run each experiment
    if not explicitly overridden
  • durations specifies the sets experiments to run, broadly you will
    want to specify a spread of values here to get a reasonable
    graph performance over time, since the framework doesn't
    sample these values during the experiment run

Full details on the various options are detailed below.

Note: The number of actual runs performed will be # of fuzzers X
# of corpora X # of durations X # of iterations


Running an Experiment

To start the experiment on your system, execute the fuzz helper with
the experiment subcommand:

bash
tools/run fuzz experiment -build out/fuzzers my_experiment/

where out/fuzzers is a GN configured build with the correct values
set in args.gn

Note: experiment does support the -j flag for running
concurrent operations, but defaults it to 1. This is because
saturating the RAM or CPU in the execution environment (e.g. -j max num of cores) can significantly impact the results. It is recommended
to experiment with a mini-version of an experiment.json to determine
an appropriate value to use, (try num of cores / 2 or / 4 as a
starting point)

Execution Pipeline

The framework guides execution through the following phases:

  1. Validation & Sync: The tool checks the active GN arguments in
    your build directory to ensure they match the requirements. It then
    checks out the requested commit hash (hash field) and runs
    gclient sync automatically. (The state of repo should be returned
    to the original state)
  2. Binary Preparation: It builds the selected fuzzer binaries and
    copies them, alongside their dynamic library dependencies and
    required LLVM tools (llvm-profdata, llvm-cov), into
    my_experiment/bin/.
  3. Burn-in (Optional): If enabled, it executes multiple parallel
    workloads for 5 minutes (burnin_duration & burnin_enabled) to
    bring the physical host's CPU to a thermal steady state. This
    prevents throttling and normalization profiling skews during the
    experiment.
  4. Normalization: It executes multiple normalization profiling
    runs for each fuzzer against its designated normalization corpus
    (controlled by normalization_duration and
    normalization_iterations). The resulting average execution rate
    (runs/sec) and standard error (SEM) are saved to
    normalization_scores.json and are used to compute "Normalized CPU
    Seconds" for each fuzzer. All individual profiling runs are
    archived in normalization_iterations.csv.
  5. Task Execution: It calculates the Cartesian product of
    experiments (Fuzzer × Corpus × Duration × Iteration). These are run
    concurrently across available CPU cores (configurable via
    -j). Results are saved directly under
    my_experiment/results/. Each task directory stores a state.json
    file, captured logs, mutated corpus files, and .profraw coverage
    profiles.

Processing and Analyzing Results

Once execution has completed, you can aggregate and process results
using the analyze subcommand:

bash
tools/run fuzz analyze my_experiment/

Analysis Pipeline

  1. Coverage Generation: The analyzer locates the .profraw file
    for each completed iteration, merges them using llvm-profdata,
    and executes coverage.py to produce standard .lcov coverage
    logs.
  2. Component Mapping: It parses the .lcov profiles and
    aggregates line coverage metrics separately for Tint
    (src/tint/ and src/utils/), Mesa (third_party/mesa/), and
    DXC (third_party/directx-headers/).
  3. Statistics Computation: It computes arithmetic means and
    standard errors for:
    • Coverage percentage
    • Normalized CPU seconds
    • Coverage rates (% of lines covered per CPU second, using
      quadrature error propagation)
  4. Report Export:
    • raw_iteration_data.csv: A flat table of raw execution counts,
      actual runtimes, and coverage hits for every single iteration.
    • calculated_statistics.csv: A flat table of calculated
      statistics for every experiment category (Fuzzer × Corpus).
    • experiment_report.md: A human-readable Markdown summary report.

Appendix: `experiment.json` Reference

The following sections define the full configuration schema for
experiment.json.

Root Attributes

Field Type Description
name string A human-readable identifier for the experiment, used in the logging/reports.
hash string The Git commit hash checkout from which the binaries should be compiled.
fuzzers array of strings Fuzzer target names to test(Supported values: "tint_wgsl_fuzzer", "tint_ir_fuzzer", "tint_wgsl_mesa_fuzzer", "tint_ir_mesa_fuzzer").
timeout integer optional Timeout limit in seconds for a single fuzzer execution on a test case in libFuzzer (defaults to 5).
burnin_enabled boolean optional If true, launches parallel workloads to warm up the machine before benching (defaults to true).
burnin_duration integer optional Target duration in seconds for the initial thermal burn-in (defaults to 300).
normalization_duration integer optional Execution duration in seconds for each iteration of the normalization microbenchmarking phase (defaults to 60).
normalization_iterations integer optional Number of iterations to perform for normalization microbenchmarking (defaults to 5).
wgsl_normalization_corpus string Directory path relative to the root corpora/ to use when performing normalization on WGSL fuzzers.
ir_normalization_corpus string Directory path relative to the root corpora/ to use when performing normalization on IR fuzzers.
wgsl_corpora array of CorpusDef Corpora definitions available to run with WGSL fuzzers.
ir_corpora array of CorpusDef Corpora definitions available to run with IR fuzzers.
default_iterations integer The default number of times to repeat every Fuzzer/Corpus/Duration combination if not otherwise specified.
durations array of DurationDef The target run lengths defining the experiment stopping criteria.

`CorpusDef` Format

Field Type Description
name string A human-readable identifier for the corpus, used in the logging/reports.
path string Directory path relative to the root corpora/ directory.

`DurationDef` Format

Note: Must specify exactly one and only one of seconds or
runs.

Note: Because of how libFuzzer operate seconds and runs are
approximate values. Fuzzing should run for at least this limit, but
will normally be a little over.

Field Type Description
seconds integer (optional) Number of seconds to run for (maps to -max_total_time).
runs integer (optional) Number of inputs/test cases to run for(maps to -runs).
iterations integer (optional) Override the experiment default_iterations specifically for this duration target. Useful for manually load balancing very long and very short durations

Tint Gen (`docs/tint/gen.md`)

Code generation

Tint makes use of code generation tooling to emit source code and build files
used to build Tint.

All code is generated with ./tools/run gen.

The generator uses heavy use of Go's text/template
package. This is augmented with the functions declared in tools/src/template/template.go, and the data types provided by the tooling.

Build file generation

The generator will scan all the Tint source directories to produce BUILD.gn
and BUILD.cmake files. These files contain the per-directory targets,
conditionals and inter-target dependency information which is labour-intensive
to maintain by hand.

The bulk of the build generator logic is in the file: tools/src/cmd/gen/build/build.go

Targets

There are 8 kinds of build target:

  • cmd targets are executables.
  • lib targets are libraries used in production code, and can also be used as
    a dependency by any other target kind .
  • test targets are libraries used by Tint unittests. Must not be used by
    production code.
  • test_cmd are test executables.
  • bench targets are libraries used by Tint benchmarks. Must not be used by
    production code.
  • bench_cmd are benchmark executables.
  • fuzz targets are libraries used by Tint fuzzers. Must not be used by
    production code.
  • fuzz_cmd are fuzzer executables.

The build generator uses a file naming convention based on the file name before the extension to classify each source file to a single target kind.

  • Source files named test or with a _test suffix are classed as test library targets.
    Example: parser_test.cc.
  • Source files named bench or with a _bench suffix are classed as bench library targets.
    Example: writer_bench.cc.
  • Source files named fuzz or with a _fuzz suffix are classed as fuzz library targets.
    Example: writer_fuzz.cc.
  • Source files with the name main are classed as executable targets.
    These typically exist under src/tint/cmd.
    Example: cmd/tint/main.cc.
  • Source files with the name main_test are classed as test executable targets.
    These typically exist under src/tint/cmd/test.
    Example: cmd/test/main_test.cc.
  • Source files with the name main_bench are classed as benchmark executable targets.
    These typically exist under src/tint/cmd/bench.
    Example: cmd/benchmark/main_bench.cc.
  • Source files with the name main_fuzz are classed as fuzzer executable targets.
    These typically exist under src/tint/cmd/fuzz.
    Example: cmd/benchmark/main_bench.cc.
  • All other files are considered lib targets.
    Example: parser.cc.

Each source directory can have at most one lib, test, test_main, bench, bench_main or cmd
target.

The graph of target dependencies must be acyclic (DAG).

Target dependencies are automatically inferred from #includes made by the source files.
Additional target dependencies can be added with the use of a BUILD.cfg file.

External dependencies

All external dependencies must be declared in src/tint/externals.json.

The syntax of this file is:

json
{
    "external-target-name": {
        "IncludePatterns": [
          /*
            A list of #include path patterns that refer to this external target.
            You may use the '*' wildcard for a single directory, or '**' as a multi-directory wildcard.
          */
          "myexternal/**.h",
        ],
        /* An optional build condition expression to wrap all uses of this external dependency */
        "Condition": "expression",
    },
    /* Repeat the above for all external targets */
}

`GEN_BUILD` directives

Source and build files can be annotated with special directives in comments to control the build file generation.

Directive Description
GEN_BUILD:IGNORE_FILE Add to a source file to have the file ignored by the generator Example: // GEN_BUILD:IGNORE_FILE
GEN_BUILD:IGNORE_INCLUDE Apply to the end of a #include in a source file to ignore this include for dependency analysis Example: #include "foo/bar.h" // GEN_BUILD:IGNORE_INCLUDE
GEN_BUILD:CONDITION(cond) Applies the build condition for this source file. Example: // GEN_BUILD:CONDITION(is_linux)
GEN_BUILD:DO_NOT_GENERATE Prevents the BUILD.* file from being generated. Example: # GEN_BUILD:DO_NOT_GENERATE

`BUILD.cfg` files

A source directory may have an optional BUILD.cfg JSON file. The syntax of this file is:

json
{
  /* Build condition to apply to all targets of this directory. */
  "Condition": "cond",
  /* Options for the 'lib' target */
  "lib": { /* see TargetConfig */ },
  /* Options for the 'test' target */
  "test": { /* see TargetConfig */ },
  /* Options for the 'bench' target */
  "bench": { /* see TargetConfig */ },
  /* Options for the 'cmd' target */
  "cmd": { /* see TargetConfig */ },
}

All fields are optional.

The syntax of TargetConfig is:

json
{
  /* An override for the output file name for the target */
  "OutputName": "name",
  /* An additional condition for building this target */
  "Condition": "cond",
  "AdditionalDependencies": {
    "Internal": [
      /*
        A list of target name patterns that should in added as dependencies to this target.
        You may use the '*' wildcard for a single directory, or '**' as a multi-directory wildcard.
      */
    ],
    "External": [
      /*
        A list of external targets that should in added as dependencies to this target.
        Must match an external dependency declared in src/tint/externals.json
      */
    ]
  },
}

All fields are optional.

Templates

The build generator will emit a BUILD.ext file in each source directory, for each BUILD.ext.tmpl file found in tools/src/cmd/gen/build.

The template will be passed the Directory as $.


Tint Intrinsic Definition Files (`docs/tint/intrinsic_definition_files.md`)

Intrinsic definition files

Tint uses intrinsic definition to generate intrinsic tables, end-to-end tests and C++ enums.

These definition files have a .def extension and exist under the language subdirectories of src/tint/lang/.
For example, the WGSL definition file can be found at src/tint/lang/wgsl/wgsl.def.

A definition file can contain the following declarations:

Imports

Syntax: import " project-relative-path "

An import is similar to a C++ #include statement. It imports the content of the project-relative
file into this definition file so that declarations can be shared between languages.

Example: import "src/tint/lang/core/access.def"

Enumerators

An named enumerator of symbols can be declared with enum.

Example:

c
enum diagnostic_severity {
  error
  warning
  info
  off
}

These enum declarations can be used by templates to generate an equivalent C++ enum, along with a parser and printer helper method, using the helper template src/tint/utils/templates/enums.tmpl.inc.
See src/tint/lang/wgsl/enums.h.tmpl as an example.

Enum declarations can also be used as constraints on type template parameters and overload template parameters.

Types

Syntax: type name [ < type-or-number-templates > ]

Type declarations form the basic primitives are used to match intrinsic overloads. A type can be referenced by type matchers and by builtin overloads.

The simplest type declaration has the form: type name

A type can be templated with any number of sub-types and numbers, using a <...> suffix.

Examples:

Example Description
type my_type Declares a type called my_type
type vec3<T> Declares a type called vec3 which accepts a templated sub-type
type vec<N: num, T> Declares a type called vec which accepts a templated number N and sub-type T

Annotations

Types may be annotated with @precedence(N) to prioritize which type will be picked when multiple types of a matcher match. This is typically used to ensure that abstract numerical types materialize to the concrete type with the lowest conversion rank. Types with higher precedence values will be matched first.

Example:

ts
@precedence(5) type ia // This is preferred over all others
@precedence(4) type fa
@precedence(3) type i32
@precedence(2) type u32
@precedence(1) type f32
@precedence(0) type f16

Types may also be annotated with @display("string") to customize how the type is printed in diagnostic message. The string may contain curly-brace references to the templated types.

Example:

ts
@display("vec{N}<{T}>") type vec<N: num, T>

Would display as vec2 if N=2 and T=i32.

Type Matchers

Syntax: matcher name : type_1 [ | type_2 [ ... | type_n ] ]

A type matcher can be used by overloads to match one or more types.

Overload declarations

The overload declarations declare all the built-in functions, Value constructors, type
conversions and unary and binary operators supported by the target language.

An overload declaration can declare a simple static-type declarations, as well as overload
declarations that can match a number of different argument types via the use of template types,
template numbers and type matchers.

All overload declarations share the same declaration patterns, but the kind of declaration has a different prefix.

For example: fn isInf(f32) -> bool declares an overload of the function isInf that accepts a single parameter of type f32 and returns a bool.

Parameters

All overload declarations use the same syntax for a parameter.

A parameter can be named or unnamed.

Unnamed parameters just use the name of constraint (type, type-matcher or template parameter).
Named parameters have a name and constraint, separated with a colon. Example fn F(count : i32)

Code Description
fn F(i32) function F has a single unnamed parameter, of type or matcher i32
fn F(count: i32) function F has a single parameter named count, of type or matcher i32

Note: Parameters may use a type-matcher directly, but for wgsl.def it is encouraged to use an implicit template parameter. This helps the readability of the diagnostics produced. For example, instead of fn F(scalar), prefer: fn F[S: scalar](S).

Templates

Builtin function overload declarations can support explicit and implicit template parameters.

Each template parameter has the syntax: name [ : constraint ]
Where the optional constrant can be a type, type matcher, or : num for a number template.

Explicit template parameters:

  • Are optional in the overload declaration.
  • Have the syntax: < template_parameter_list >
  • Must be declared before implicit template parameters (if declared) and parameter list.
  • Must be provided by the call of the function, with exactly one template argument provided for each template parameter.
  • May reference implicit template parameters.
  • Can only be declared on Builtin Functions.
  • Do not support number templates.

Implicit template parameters:

  • Are optional in the overload declaration.
  • Are inferred from the arguments passed to the intrinsic.
  • Have the syntax: [ template_parameter_list ]
  • Must be declared after explicit template parameters (if declared), and before the parameter list.
  • Cannot reference explicit template parameters.
  • Support number templates.

Examples:

ts
// A function overload that has no explicit or implicit template parameters
fn F(i32) -> i32
ts
// A function overload that accepts an explicit template parameter 'T', which matches any type.
// The overload takes a single parameter of type 'T' and returns a value of type 'T'
fn F<T>(T) -> T
ts
// A function overload that accepts an implicit (inferred) template parameter 'T', which must be of
// the type or type-matcher 'scalar'.
// The overload takes a single parameter of type 'T' and returns a value of type 'T'
fn F[T: scalar](T) -> T
ts
// A function overload that accepts an explicit template parameter 'T', which must be of a 'vec'
// type of the inferred element type 'E' and number 'N'.
// 'E' is an implicit template type parameter, and must match the type or type-matcher 'scalar'
// 'N' is an implicit template number parameter.
// The overload takes a single parameter of type 'T' and returns a value of type 'T'
fn F< T: vec<N, E> >[E: scalar, N: num](T) -> T

Matching algorithm for a single overload

The goal of overload matching is to compare a function call's arguments and any
explicitly provided template types in the program source against an overload
declaration, to determine if the call satisfies the form and type constraints
of the overload. If the call matches an overload, then the overload is added
to the list of 'overload candidates' used for overload resolution (described below).

Prior to matching an overload, all template types are undefined.

Implicit template types are first defined with the type of the leftmost argument
that matches against that template type name. Subsequent arguments that attempt
to match against the template type name will either reject the overload or
refine the template, in one of 3 ways:

  • (a) Fail to match, causing the overload to be immediately rejected.
  • (b) Match the existing implicit template type, either exactly or via implicit
    conversion, and overload resolution continues.
  • (c) Match via implicit conversion of the currently defined template type
    to the argument type. In this situation, the template type is refined
    with the more constrained argument type, and overload resolution
    continues.

To better understand, let's consider the following hypothetical overload declaration:

ts
matcher scalar: f32 | i32 | u32 | bool
fn foo[T: scalar](T, T)

Where:

Symbol Description
T is the template type name
scalar is a matcher for the types f32, i32, u32 or bool
[T: scalar] declares the implicit template parameter T, with the constraint that T must match one of f32, i32, u32 or bool.

The process for resolving this overload is as follows:

  1. The overload resolver begins by attempting to match the parameter types from left to right.
    The first argument type is compared against the parameter type T.
    As the implicit template type T has not been defined yet, T is defined as the type of the first argument.
    There's no verification that the T type is a scalar at this stage.
  2. The second argument is then compared against the second parameter.
    As the template type T is now defined as the first argument type, the second argument type is
    compared against the type of T. Depending on the comparison of the argument type to the
    template type, either the actions of (a), (b) or (c) from above will occur.
  3. If all the parameters matched, constraints on the template types need to be checked next.
    If the defined type does not match the constraint, then the overload is no longer considered.

This algorithm for matching a single overload is less general than the algorithm described in the
WGSL spec, but it makes the same decisions because the overloads defined by WGSL are monotonic
in the sense that once a template parameter has been refined, there is never a need to backtrack
and un-refine it to match a later argument.

The algorithm for matching template numbers is similar to matching template types, except numbers
need to exactly match across all uses - there is no implicit conversion. Template numbers may
match integer numbers or enumerators.

Overload resolution for candidate overloads

If multiple candidate overloads match a given set of arguments, then a final overload resolution
pass needs to be performed. The arguments and overload parameter types for each candidate overload
are compared, following the algorithm described at: https:www.w3.org/TR/WGSL/#overload-resolution-section

If the candidate list contains a single entry, then that single candidate is picked, and no
overload resolution needs to be performed.

If the candidate list is empty, then the call fails to resolve and an error diagnostic is raised.

Common attributes

Intrinsics that can be used in constant evaluation expressions are annotated with @const or @const( name ).

The templates use this attribute to associate the overload with a function in src/tint/lang/core/constant/eval.h.

Builtin functions

Syntax: fn name [ < explicit-template-parameters > ] [ [ implicit-template-parameters ] ]( parameters ) -> return_type

Builtin function overloads support both implicit and explicit template parameters.

Builtin functions may be annotated with @must_use to prevent the function being used as a call-statement in WGSL.

Examples

Code Description
fn F() Function called F. No template types or numbers, no parameters, no return value`
fn F() -> R Function with R as the return type
fn F(f32, i32) Two fixed-type, anonymous parameters
fn F(U : f32) Single parameter with name U
fn F[T](T) Single parameter of unconstrained implicit template type T (any type)
fn F[T: scalar](T) Single parameter of constrained implict template type T (must match scalar)
fn F[T: fiu32](T) -> T Single parameter of constrained implicit template type T (must match fiu32)Return type is of the parameter type
fn F[T, N: num](vec<N, T>) Single parameter of vec type with template number N and element template type T
fn F[A: access](texture_storage_1d<f32_texel_format, A>) Single parameter of type texture_storage_1d type with implicit template number A constrained to the enum access, and a texel format that that must match f32_texel_format

Value constructors

Syntax: ctor type-name [ [ implicit-template-parameters ] ]( parameters ) -> return_type

Value constructors construct a value of the given type.

Value conversions

Syntax: conv type-name [ [ implicit-template-parameters ] ]( parameters ) -> return_type

Value conversions convert from one type to another.

Operators

Unary operator syntax: op operator [ [ implicit-template-parameters ] ]( parameter ) -> return_type

Binary operator syntax: op operator [ [ implicit-template-parameters ] ]( lhs-parameter , rhs-parameter ) -> return_type


Tint Ir Extensions (`docs/tint/ir-extensions.md`)

IR Extensions

The IR can be extended by the various backends as needed. This can be done through
custom transforms, capabilities, types, intrinsics or through subclasses of other values.

Transforms

In a few cases we need to make transforms on the IR which are specific to a given backend.
In these cases the transforms live in the writer/raise folder for the backend (e.g.
lang/msl/writer/raise/). These transforms are then added into the raise.cc file to run the
transform when necessary.

Each transform defines its own configuration if necessary. This is typically done by defining a
struct in the transform .h file with a naming matching the transform with a Config suffix.
(e.g. ModuleConstant transform has a ModuleConstantConfig structure). When the transform is
called in raise the configuration will be created from the generator configuration.

Transforms have a set of Capabilities they support. Often the list of supported capabilities is
added to the .h file so they can be shared with a fuzzer for the transform. The set of
capabilities is typically named k + transform name + Capabilities (e.g. ModuleConstant has
kModuleConstantCapabilities). The set of capabilities should be kept as minimal as possible for
the transform. The capabilities are used to control what extra features the IR validator will allow.

Transforms all follow a similar pattern of a free function named after the transform and then a
State struct with a Process method. The free function will run the validator if necessary and
then call the State::Process method.

Intrinsics

Each backend has a .def file listing the intrinsics for that backend (e.g. lang/msl/msl.def).
These custom intrinsics can be types or instructions. See
intrinsic_definition_files.md.

Instructions

The intrinsic instructions typically match the signature of the backend, so may take values which
don't make sense coming from core IR or WGSL. (For instance, the GroupNonUniform SPIR-V
instructions take a scope parameter which doesn't exist, and isn't used, but is there to match the
API on the SPIR-V side.

The naming of the intrinsic is set to match the casing of the backend instruction. So,
group_non_uniform_s_min turns into spirv::BuiltinFn::kGroupNonUniformSMin which matches to the
OpGroupNonUniformSMin instruction in SPIR-V.

As instructions are added they need to be added to the printer.cc file for the backend in order to
emit the intrinsic. The instruction also needs to be added to the builtin_fn.cc.tmpl file for the
backend to add to the GetSideEffects switch. This lists if there are side effects to the
instruction which need to be accounted for when doing instruction inlining.

When these instructions are added to the IR they are done using the BuiltinCall class for that
backend. For example, to call the kGroupNonUniformBroadcast intrinsic in the SPIR-V backend we
call:

text
b_.Call<spirv::ir::BuiltinCall>(ty.u32(), spirv::BuiltinFn::kGroupNonUniformBroadcast, Vector{id}))

Types

There are a few places to modify to create a new type. First, create the type .h and .cc files
in the backend type/ folder. The type will inherit from CastableBase<NewType, Type> where
NewType is the name of the new type. At a minimum the class needs to override:

text
bool Equals(const UniqueNode& other) const override;

/// @returns the friendly name for this type
std::string FriendlyName() const override;

/// @param ctx the clone context
/// @returns a clone of this type
NewType* Clone(core::type::CloneContext& ctx) const override;

The class can also override:

text
/// @returns the size in bytes of the type. This may include tail padding.
/// @note opaque types will return a size of 0.
virtual uint32_t Size() const;

/// @returns the alignment in bytes of the type. This may include tail
/// padding.
/// @note opaque types will return a size of 0.
virtual uint32_t Align() const;

The Align and Size overrides are only necessary if the default of size 0 and align 0 do not work
for the new type.

The type can then be added to the .def file for the backend as type new_type. In order to use
the type in the type matches the type_matches.h file for the backend needs to be updated to have a
MatchNewType method and a BuildNewType method. The arguments end up matching the values passed
to the type implicit template parameters.

The new types can be created in the type manager by calling ty.Get<spirv::types::NewType>(),
providing any needed arguments for the type constructor.

Enums

When defining custom enums in a given backend an extra attribute is needed to set the correct
namespace for the enum, otherwise it ends up in the core IR namespace. In the SPIR-V backend we want
the namespace to be tint::spirv::type so we declare enums as:

text
@ns("spirv::type") enum arrayed {
  NonArrayed
  Arrayed
}

The @ns("spirv::type") will put the enum into the correct namespace when emitted.


Tint Ir (`docs/tint/ir.md`)

Intermediate Representation

As Tint has grown the number of transforms on the AST has grown. This
growth has lead to several issues:

  1. Transforms rebuild the AST and SEM which causes slowness
  2. Transforming in AST can be difficult as the AST is hard to work with

In order to address these goals, an IR was introduced into Tint. The IR
is mutable, it holds the needed state in order to be transformed. The IR
is also translatable back into AST. It will be possible to generate an
AST, convert to IR, transform, and then rebuild a new AST. This
round-trip ability provides a few features:

  1. Easy to integrate into current system by replacing AST transforms
    piecemeal
  2. Easier to test as the resulting AST can be emitted as WGSL and
    compared.

The IR helps with the complexity of the AST transforms by limiting the
representations in the IR form. For example, instead of for, while
and loop constructs there is a single loop construct. alias and
const_assert nodes are not emitted into IR. Dead code maybe eliminated
during IR construction.

As the IR can convert into AST, we could potentially simplify the
SPIRV-Reader by generating IR directly. The IR is closer to what SPIR-V
looks like, so maybe a simpler transform.

Design

The IR is composed of three primary concepts, blocks, values, and
instructions.

The blocks provide lists of instructions which are executed
sequentially. An instruction may contain blocks, value operands
and value results. A value is a block argument, function parameter,
constant result, or a result computed by an instruction.

Each block ends in a terminator expression. There are a few blocks
which maybe empty, if-false block, loop-initializer and
loop-continuing. Those blocks are optional. The terminators may hold
values which are returned out of the block being terminated.

Control flow is handled through ControlInstructions. These are the
if, switch and loop instructions. Each control instruction
contains blocks for the various control flow paths.

Transforming from AST to IR and back to AST is a lossy operation.
The resulting AST when converting back may not be the same as the
AST being provided. This transformation is intentional as it greatly
simplifies the number of things to consider in the IR. For instance:

  • No alias nodes
  • No const_assert nodes

Code Structure

The code is contained in the src/tint/lang/core/ir folder and is broken down
into several classes. Note, the IR is a Tint internal representation
and these files should never appear in the public API.

Builder

The Builder class provides useful helper routines for creating IR
content. The Builder references an ir::Module.

Module

The top level of the IR is the Module. The module stores a list of
functions, the constant manager, the type manager, allocators and various other
bits of information needed by the IR.

A free wgsl::reader::ProgramToIR method is provided to convert from a
Program to an ir::Module. A similar wgsl::writer::IRToProgram free method is
provided for the reverse conversion.

Disassembly

An IR module can be emitted as IR assembly instructions using the disassembler. This
disassembly can be done by calling core::ir::Disassembler() and providing the IR
module to disassemble.

Transforms

A transform system is available for IR. Each IR transform is a standalone process. There
is no transform manager. Data is provided to the transforms through their configuration.
Transforms mutate the IR in place, and can produce auxiliary information through their result if needed.

The common IR transforms live in src/tint/lang/core/ir/transform. These transforms
are for use by the various IR generator backends.

Each backend has custom transforms as needed. They live in the raise/ folder of the
specific backend.

The IR has a set of Capabilities which can be added/removed as transforms are
executed. The transforms know which capabilities they support and will ICE if
an unexpected capability is seen.

Validation

The IR contains a validator. The validator checks for common errors
encountered when building IR. The validator is not run in production
as any error is an internal coding error. The validator is executed by
the generators before starting, and after each transform executes in
debug mode.

Naming

The instructions are internal to the IR, and the values in the IR do not
contain names. The IR module has a symbol table which can be used to
retrieve names for the instructions and values but those names may not
be the names provided originally by the AST.

Control Flow

Block

A block contains the instruction lists for a section of code. A block
always ends in a terminator instruction.

The instructions in a block can be walked in linear fashion. The
control instructions do not need to be entered to walk a blocks
instructions.

Control Instructions

The control instructions provide containers for other blocks. These
include the if, loop and switch instruction. A control instruction
is a self contained scope on top of the containing block scope.

Control Instruction -- If

The if instruction is an if-else structure. There are no else-if
entries, they get moved into the else of the if. The if instruction
has two internal blocks, the True and False blocks.

A if instruction will always have a True target. The False target
maybe empty.

The sub-blocks under the if can be terminated with ExitIf,
ExitSwitch, ExitLoop, Continue or Return.

Control Instruction -- Loop

All of the loop structures in AST merge down to a single IR loop
instruction. The loop contains the Body, which is required and
optional Initializer and Continuing blocks.

The loop body can be terminated with a ExitLoop, Continue,
NextIteration or Return.

The loop continuing can be terminated with a NextIteration, or a
BreakIf terminator.

The loop initializer is always terminated with a NextIteration.

A while loop is decomposed as listed in the WGSL spec:

text
while (a < b) {
  c += 1;
}

becomes:

text
loop {
  if (!(a < b)) {
    break;
  }
  c += 1;
}

A for loop is decomposed as listed in the WGSL spec:

text
for (var i = 0; i < 10; i++) {
  c += 1;
}

becomes:

text
var i = 0;
loop {
  if (!(i < 10)) {
    break;
  }

  c += 1;

  continuing {
    i++;
  }
}

Control Instruction -- Switch

The switch instruction has a block for each of the case/default labels.

The switch case blocks can be terminated with an ExitSwitch,
Continue, or Return.

Expressions

All expressions in IR are single operations. There are no complex
expressions. Any complex expression in the AST is broken apart into the
simpler single operation components.

text
var a = b + c - (4 * k);

becomes:

text
%0:i32 = add %b, %c
%1:i32 = mul 4u, %k
%2:i32 = sub %0, %1
%v:ptr<function, i32, read_write> = var %2

This also means that many of the short forms i += 1, i++ get
expanded into the longer form of i = i + 1.

Short-Circuit Expressions

The short-circuit expressions (e.g. a && b) are convert into an if
structure control flow.

text
let c = a() && b()

becomes

text
let c = a();
if (c) {
  c = b();
}

Values

There are several types of values used in the SSA form.

  1. Constant Value
  2. InstructionResult Value
  3. Function Value
  4. FunctionParam Value
  5. BlockParam Value

Constant Value

All values in IR are concrete, there are no abstract values as
materialization has already happened. Each constant holds a lower level
constant::Value from the src/tint/lang/core/ir/constant system.

InstructionResult Value

The InstructionResult is the result of an instruction. The
temporaries are created as complex expressions are broken down into
pieces. The result tracks the usage for the value so you can determine
which instructions use this value. The InstructionResult also points
back to its Source instruction.

Function Value

All Functions are values in the IR. This allows the function to be
provided as a Value argument to things like the Call instructions.

FunctionParam Value

The function param values are used to store information on the values
being passed into a function call.

BlockParam Values

The BlockParam values are used to pass information for a
MultiInBlock (e.g. Loop body, and loop continuing).

There are no Phi instructions in the IR. Instead, BlockParams are used.
Instead of an OpPhi instruction which says which values came from
which parent blocks. Instead, values are passed into the blocks themselves
from the terminator of the previous block.

For example, block params can provide values into a loop, through the body
and through the continuing block.

text
loop {
    $B1: {  # initializer
      next_iteration 2, 4
    }
    $B2:(%1:i32, %2:i32) {  # body
      %3:i32 = add %1, %2
      continue %3
    }
    $B3:(%4:i32) {  # continuing
      %5:bool = lt %4, 10
      break-if %5
      next_iteration 6, 7
    }
}

Alternatives Considered

Instead of going to a custom IR there are several possible other roads
that could be travelled.

Mutable AST

Tint originally contained a mutable AST. This was converted to immutable
in order to allow processing over multiple threads and for safety
properties. Those desires still hold, the AST is public API, and we want
it to be as safe as possible, so keeping it immutable provides that
guarantee.

Multiple Transforms With One Program Builder

Instead of generating an immutable AST after each transform, running
multiple transforms on the single program builder would remove some of
the performance penalties of going to and from immutable AST. While this
is true, the transforms use a combination of AST and SEM information.
When they transform they do not create new SEM information. That
means, after a given transform, the SEM is out of date. In order to
re-generate the SEM the resolver needs to be rerun. Supporting this
would require being very careful on what transforms run together and
how they modify the AST.

Adopt An Existing IR

There are already several IRs in the while, Mesa has NIR, LLVM has
LLVM IR. There are others, adopting one of those would remove the
requirements of writing and maintaining our own IR. While that is true,
there are several downsides to this re-use. The IRs are internal to the
library, so the API isn't public, LLVM IR changes with each iteration of
LLVM. This would require us to adapt the AST -> IR -> AST transform for
each modification of the IR.

They also end up being lower level then is strictly useful for us. While
the IR in Tint is a simplified form, we still have to be able to go back
to the high level structured form in order to emit the resulting HLSL,
MSL, GLSL, etc. (Only SPIR-V is a good match for the lowered IR form).
This transformation back is not a direction other IRs maybe interested
in so may have lost information, or require re-determining (determining
variables from SSA and PHI nodes for example).

Other technical reasons are the maintenance of BUILD.gn and CMake files
in order to integrate into our build systems, along with resulting
binary size questions from pulling in external systems.


Tint Layering (`docs/tint/layering.md`)

Tint Source Layering

The BUILD.gn is setup with small source units for various components
in Tint. The hierarchy of sources almost matches to folder structure in
tint (except for base which is a mix of things in src/tint and
src/tint/util.

text
/* Detailed source-code truncated for AI context efficiency. */

Tint Origin Trial Changes (`docs/tint/origin-trial-changes.md`)

Tint changes during Origin Trial

Changes for M112

Breaking changes

  • Most builtin functions that return a value can no longer be used as a call statement. tint:1844
  • The sig member of the return type of frexp() has been renamed to fract. tint:1766
  • Calling a function with multiple pointer arguments that alias each other is now a error. tint:1675
  • type deprecation has been removed. alias must be used now. tint:1812
  • static_assert deprecation has been removed. const_assert must now be used. tint:1807

Changes for M111

New features

  • The workgroupUniformLoad builtin function is now supported. tint:1780
  • The diagnostic directive and @diagnostic attribute are now supported. tint:1809
    • The attribute is currently only supported on function declarations.

Breaking changes

  • You may need to add parentheses to less-than or greater-than binary expressions that now parse as template lists. For example a(b<c, d>e) will need parentheses around b<c or d>e. tint:1810.
  • Uniformity analysis failures are now an error tint:880
    • The derivative_uniformity diagnostic filter can be used to modify the severity if needed.

Deprecated Features

  • The keyword to alias a type has been renamed from type to alias. tint:1812
  • static_assert has been renamed to const_assert. tint:1807

Changes for M110

Breaking changes

  • The textureSampleLevel() overload for texture_external has been removed. Use textureSampleBaseClampToEdge(). tint:1671

Deprecated Features

  • The sig member of the return type of frexp() has been renamed to fract. tint:1757
  • Calling a function with multiple pointer arguments that alias each other is now a warning, and
    will become an error in a future release. tint:1675

Changes for M109

Breaking changes

  • textureDimensions(), textureNumLayers() and textureNumLevels() now return unsigned integers / vectors. tint:1526
  • The @stage attribute has been removed. The short forms should be used
    instead (@vertex, @fragment, or @compute). tint:1503
  • Module-scope let is now an error. Use module-scope const instead. tint:1580
  • Reserved words are now an error instead of a deprecation. tint:1463
  • You may no longer use pointer parameters in workgroup address space. tint:1721

New features

  • Uniformity analysis failures are warnings again tint:1728
  • You can now call texture builtins with a mix of signed and unsigned integer arguments. tint:1733

Changes for M108

New features

  • textureSampleBaseClampToEdge() has been implemented. tint:1671

Deprecated Features

  • The external_texture overload of textureSampleLevel() has been deprecated. Use textureSampleBaseClampToEdge() instead. tint:1671

Fixes

  • Constant evaluation of type conversions where the value exceeds the limits of the target type have been fixed. tint:1707

Changes for M107

New features

Breaking changes

  • Uniformity analysis failures are now an error tint:880
  • Indexing an array, vector or matrix with a compile-time expression that's out-of-bounds is now an error tint:1665

Changes for M106

New features

  • array() constructor can now infer type and count. tint:1628
  • static_assert statement has been added. tint:1625

Deprecated Features

  • The list of reserved words has been sync'd to the WGSL specification. tint:1463

Changes for M105

New features

  • Module-scope var can now infer the storage type, like function-scope var. tint:1584
  • The acosh, asinh, and atanh builtin functions are now supported tint:1465

Breaking changes

  • The smoothStep() builtin has been removed (use smoothstep instead). tint:1483
  • Module-scope let has been replaced with module-scope const. tint:1580
    • Note: Module-scope const does not support structure types. Use var if you need a module-scope structure type.
  • Struct members can no longer be separated with semicolons (use commas instead). tint:1475
  • Single scalar matrix constructors have been removed. These were never part of the WGSL spec. tint:1597

Deprecated Features

  • The @stage attribute has been deprecated. The short forms should be used
    instead (@vertex, @fragment, or @compute). tint:1503

Changes for M104

New features

  • Tint now supports abstract-numerics, removing the need to always suffix unsigned integers with u tint:1504
  • Parsing of @compute, @fragment and @vertex added.

Changes for M103

New features

  • Produce warnings for when calling barriers, textureSample, and derivative
    builtins in non-uniform control flow tint:880
  • Matrix identity constructors and constructors for a single scalar value are now supported tint:1545

Breaking changes

  • Builtin atomicCompareExchangeWeak returns a struct instead of a vec2. tint:1185

Changes for M102

New Features

  • Parentheses are no longer required around expressions for if and switch statements tint:1424
  • Compound assignment statements are now supported. tint:1325
  • Postfix increment and decrement statements are now supported. tint:1488
  • The colon in case statements is now optional. tint:1485

Breaking changes

  • Struct members are now separated by commas. tint:1475
  • The @block attribute has been removed. tint:1324
  • The @stride attribute has been removed. tint:1381
  • Attributes using [[attribute]] syntax are no longer supported. tint:1382
  • The elseif keyword is no longer supported. tint:1289

Deprecated Features

  • The smoothStep() builtin has been renamed to smoothstep(). tint:1483

Changes for M101

New Features

  • Tint now supports unicode identifiers. tint:1437

Breaking changes

  • The isNan(), isInf(), isFinite(), and isNormal() builtins have been removed. tint:1312

Changes for M100

Breaking changes

  • The @interpolate(flat) attribute must now be specified on integral user-defined IO. tint:1224
  • The ignore() intrinsic has been removed. Use phoney-assignment instead: ignore(expr); -> _ = expr;.
  • break statements in continuing blocks are now correctly validated.

New Features

  • Module-scope declarations can now be declared in any order. tint:1266
  • The override keyword and @id() attribute for pipeline-overridable constants are now supported, replacing the @override attribute. tint:1403

Changes for M99

Breaking changes

Obviously infinite loops (no condition, no break) are now a validation error.

Deprecated Features

The following features have been deprecated and will be removed in M102:

  • The [[block]] attribute has been deprecated. tint:1324
  • Attributes now use the @decoration syntax instead of the [[decoration]] syntax. tint:1382
  • elseif has been replaced with else if. tint:1289
  • The [[stride]] attribute has been deprecated. tint:1381

New Features

  • Vector and matrix element type can now be inferred from constructor argument types. tint:1334
  • Added builtins degrees() and radians() for converting between degrees and radians. tint:1329
  • let arrays and matrices can now be dynamically indexed. tint:1352
  • Storage and Uniform buffer types no longer have to be structures. tint:1372
  • A struct declaration does not have to be followed by a semicolon. tint:1380

Fixes

  • Fixed an issue where for-loops that contain array or structure constructors in the loop initializer statements, condition expressions or continuing statements could fail to compile. tint:1364

Changes for M98

Breaking Changes

  • Taking the address of a vector component is no longer allowed.
  • Module-scope declarations can no longer alias a builtin name. tint:1318
  • It is now an error to call a function either directly or transitively, from a loop continuing block, that uses discard. tint:1302

Deprecated Features

  • The isNan(), isInf(), isFinite() and isNormal() builtins has been deprecated and will be removed in M101. tint:1312

New Features

  • New texture gather builtins: textureGather() and textureGatherCompare(). tint:1330
  • Shadowing is now fully supported. tint:819
  • The dot() builtin now supports integer vector types.
  • Identifiers can now start with a single leading underscore. tint:1292
  • Control flow analysis has been improved, and functions no longer need to return if the statement is unreachable. tint:1302
  • Unreachable statements now produce a warning instead of an error, to allow WGSL code to be updated to the new analysis behavior. These warnings may become errors in the future gpuweb#2378

Fixes

  • Fixed an issue where using a module-scoped let in a workgroup_size may result in a compilation error. tint:1320

Changes for M97

Breaking Changes

  • Deprecated modf() and frexp() builtin overloads that take a pointer second parameter have been removed.
  • Deprecated texture builtin functions that accepted a read access controlled storage texture have been removed.
  • Storage textures must now only use the write access control.

Deprecated Features

  • The ignore() builtin has been replaced with phony-assignment. gpuweb#2127

New Features

  • any() and all() now support a bool parameter. These simply return the passed argument. tint:1253
  • Call statements may now include functions that return a value (ignore() is no longer needed).
  • The interpolate(flat) attribute can now be specified on integral user-defined IO. It will eventually become an error to define integral user-defined IO without this attribute.
  • Matrix construction from scalar element values is now supported.

Fixes

  • Swizzling of vec3 types in storage and uniform buffers has been fixed for Metal 1.x. tint:1249
  • Calling a function that returns an unused value no longer produces an FXC compilation error. tint:1259
  • abs() fixed for unsigned integers on SPIR-V backend

Changes for M95

New Features

  • The size of an array can now be defined using a non-overridable module-scope constant
  • The num_workgroups builtin is now supported.

Fixes

  • Hex floats: now correctly errors when the magnitude is non-zero, and the exponent would cause overflow. tint:1150, tint:1166
  • Identifiers beginning with an underscore are now correctly rejected. tint:1179

Tint Profiling (`docs/tint/profiling.md`)

Tint Profiling

Profiling is always something of an art form due to the interactions between the compiler and the machine code.
Here is some guidance

  • Profile fully optimized builds. This includes LTO (use_thin_lto = true in args.gn).
  • Fix clock frequencies where possible (usually easier on external devices like ChromeOS/Android)
  • Determinism (this is probably a given for running the tint compiler directly)

See benchmarking.md for tint shader benchmarking setup and existing benchmarking shaders.

Magic Trace

Magic trace is tracing profiler that uses x64 architecture instrumentation (Intel PT) to get full traces without manual or compiler instrumentation (https://github.com/janestreet/magic-trace). It can be built from source or one can use the prebuilt binary directly (assuming a trusted party).
(https://magic-trace.org/)

text
sudo ~/magic_trace/magic-trace run  -full-execution -multi-thread ./out/active/tint -- ./test/tint/benchmark/shadow-fragment.wgsl --format spirv

The resulting trace (default file name 'trace.fxt') can be viewed in perfetto (https://ui.perfetto.dev/)
Example trace:
(https://ui.perfetto.dev/#!/?s=9c2017442ae51f9e7dc6dcf041bce7003ca2095a)

Magic trace can sometimes produce misleading traces due to the tracing mechanism.

  • Missing trace data due to trace size limits
  • Unrealistic (and incorrect) timing in the initial phase of tracing
  • Broken or spliced traces due to internal errors that do not reflect the actual call history

Tint Spirv Input Output Variables (`docs/tint/spirv-input-output-variables.md`)

SPIR-V translation of shader input and output variables

WGSL MR 1315 changed WGSL so
that pipeline inputs and outputs are handled similar to HLSL:

  • Shader pipeline inputs are the WGSL entry point function arguments.
  • Shader pipeline outputs are the WGSL entry point return value.

Note: In both cases, a struct may be used to pack multiple values together.
In that case, I/O specific attributes appear on struct members at the struct declaration.

Resource variables, e.g. buffers, samplers, and textures, are still declared
as variables at module scope.

Vulkan SPIR-V today

SPIR-V for Vulkan models inputs and outputs as module-scope variables in
the Input and Output address spaces, respectively.

The OpEntryPoint instruction has a list of module-scope variables that must
be a superset of all the input and output variables that are statically
accessed in the shader call tree.
From SPIR-V 1.4 onward, all interface variables that might be statically accessed
must appear on that list.
So that includes all resource variables that might be statically accessed
by the shader call tree.

Translation scheme for SPIR-V to WGSL

A translation scheme from SPIR-V to WGSL is as follows:

Each SPIR-V entry point maps to a set of Private variables proxying the
inputs and outputs, and two functions:

  • An inner function with no arguments or return values, and whose body
    is the same as the original SPIR-V entry point.

  • Original input variables are mapped to pseudo-in Private variables
    with the same store types, but no other attributes or properties copied.
    In Vulkan, Input variables don't have initalizers.

  • Original output variables are mapped to pseudo-out Private variables
    with the same store types and optional initializer, but no other attributes
    or properties are copied.

  • A wrapper entry point function whose arguments correspond in type, location
    and builtin attributes the original input variables, and whose return type is
    a structure containing members correspond in type, location, and builtin
    attributes to the original output variables.
    The body of the wrapper function the following phases:

    • Copy formal parameter values into pseudo-in variables.
      • Insert a bitcast if the WGSL builtin variable has different signedness
        from the SPIR-V declared type.
    • Execute the inner function.
    • Copy pseudo-out variables into the return structure.
      • Insert a bitcast if the WGSL builtin variable has different signedness
        from the SPIR-V declared type.
    • Return the return structure.
  • Replace uses of the the original input/output variables to the pseudo-in and
    pseudo-out variables, respectively.

  • Remap pointer-to-Input with pointer-to-Private

  • Remap pointer-to-Output with pointer-to-Private

We are not concerned with the cost of extra copying input/output values.
First, the pipeline inputs/outputs tend to be small.
Second, we expect the backend compiler in the driver will be able to see
through the copying and optimize the result.

Example

glsl
#version 450

    layout(location = 0) out vec4 frag_colour;
    layout(location = 0) in vec4 the_colour;

    void bar() {
      frag_colour = the_colour;
    }

    void main() {
        bar();
    }

Current translation, through SPIR-V, SPIR-V reader, WGSL writer:

groovy
@location(0) var<out> frag_colour : vec4<f32>;
    @location(0) var<in> the_colour : vec4<f32>;

    fn bar_() -> void {
      const x_14 : vec4<f32> = the_colour;
      frag_colour = x_14;
      return;
    }

    @fragment
    fn main() -> void {
      bar_();
      return;
    }

Proposed translation, through SPIR-V, SPIR-V reader, WGSL writer:

groovy
// 'in' variables are now 'private'.
    var<private> frag_colour : vec4<f32>;
    var<private> the_colour : vec4<f32>;

    fn bar_() -> void {
      // Accesses to the module-scope variables do not change.
      // This is a big simplifying advantage.
      const x_14 : vec4<f32> = the_colour;
      frag_colour = x_14;
      return;
    }

    fn main_inner() -> void {
      bar_();
      return;
    }

    // Declare a structure type to collect the return values.
    struct main_result_type {
      @location(0) frag_color : vec4<f32>;
    };

    @fragment
    fn main(

      // 'in' variables are entry point parameters
      @location(0) the_color_arg : vec4<f32>

    ) -> main_result_type {

      // Save 'in' arguments to 'private' variables.
      the_color = the_color_arg;

      // Initialize 'out' variables.
      // Use the zero value, since no initializer was specified.
      frag_color = vec4<f32>();

      // Invoke the original entry point.
      main_inner();

      // Collect outputs into a structure and return it.
      var result : main_outer_result_type;
      result.frag_color = frag_color;
      return result;
    }

Alternately, we could emit the body of the original entry point at
the point of invocation.
However that is more complex because the original entry point function
may return from multiple locations, and we would like to have only
a single exit path to construct and return the result value.

Handling fragment discard

In SPIR-V OpKill causes immediate termination of the shader.
Is the shader obligated to write its outputs when OpKill is executed?

The Vulkan fragment operations are as follows:
(see 6. Fragment operations).

  • Scissor test
  • Sample mask test
  • Fragment shading
  • Multisample coverage
  • Depth bounds test
  • Stencil test
  • Depth test
  • Sample counting
  • Coverage reduction

After that, the fragment results are used to update output attachments, including
colour, depth, and stencil attachments.

Vulkan says:

If a fragment operation results in all bits of the coverage mask being 0,
the fragment is discarded, and no further operations are performed.
Fragments can also be programmatically discarded in a fragment shader by executing one of

text
OpKill.

I interpret this to mean that the outputs of a discarded fragment are ignored.

Therefore, OpKill does not require us to modify the basic scheme from the previous
section.

The OpDemoteToHelperInvocationEXT
instruction is an alternative way to throw away a fragment, but which
does not immediately terminate execution of the invocation.
It is introduced in the [`SPV_EXT_demote_to_helper_invocation](http://htmlpreview.github.io/?https://github.com/KhronosGroup/SPIRV-Registry/blob/master/extensions/EXT/SPV_EXT_demote_to_helper_invocation.html)
extension. WGSL does not have this feature, but we expect it will be introduced by a
future WGSL extension. The same analysis applies to demote-to-helper. When introduced,
it will not affect translation of pipeline outputs.

Handling depth-replacing mode

A Vulkan fragment shader must write to the fragment depth builtin if and only if it
has a DepthReplacing execution mode. Otherwise behaviour is undefined.

We will ignore the case where the SPIR-V shader writes to the FragDepth builtin
and then discards the fragment.
This is justified because "no further operations" are performed by the pipeline
after the fragment is discarded, and that includes writing to depth output attachments.

Assuming the shader is valid, no special translation is required.

Handling output sample mask

By the same reasoning as for depth-replacing, it is ok to incidentally not write
to the sample-mask builtin variable when the fragment is discarded.

Handling clip distance and cull distance

Most builtin variables are scalars or vectors.
However, the ClipDistance and CullDistance builtin variables are arrays of 32-bit float values.
Each entry defines a clip half-plane (respectively cull half-plane)
A Vulkan implementation must support array sizes of up to 8 elements.

How prevalent are shaders that use these features?
These variables are supported when Vulkan features shaderClipDistance and shaderCullDistance
are supported.
According to gpuinfo.org as of this writing, those
Vulkan features appear to be nearly universally supported on Windows devices (>99%),
but by only 70% on Android.
It appears that Qualcomm devices support them, but Mali devices do not (e.g. Mali-G77).

The proposed translation scheme forces a copy of each array from private
variables into the return value of a vertex shader, or into a private
variable of a fragment shader.
In addition to the register pressure, there may be a performance degradation
due to the bulk copying of data.

We think this is an acceptable tradeoff for the gain in usability and
consistency with other pipeline inputs and outputs.

Translation scheme for WGSL AST to SPIR-V

To translate from the WGSL AST to SPIR-V, do the following:

  • Each entry point formal parameter is mapped to a SPIR-V Input variable.
    • Struct and array inputs may have to be broken down into individual variables.
  • The return of the entry point is broken down into fields, with one
    Output variable per field.
  • In the above, builtins must be separated from user attributes.
    • Builtin attributes are moved to the corresponding variable.
    • Location and interpolation attributes are moved to the corresponding
      variables.
  • This translation relies on the fact that pipeline inputs and pipeline
    outputs are IO-shareable types. IO-shareable types are always storable,
    and can be the store type of input/output variables.
  • Input function parameters will be automatically initialized by the system
    as part of setting up the pipeline inputs to the entry point.
  • Replace each return statement in the entry point with a code sequence
    which writes the return value components to the synthesized output variables,
    and then executes an OpReturn (without value).

This translation is sufficient even for fragment shaders with discard.
In that case, outputs will be ignored because downstream pipeline
operations will not be performed.
This is the same rationale as for translation from SPIR-V to WGSL AST.


Tint Spirv Ptr Ref (`docs/tint/spirv-ptr-ref.md`)

SPIR-V translation of WGSL pointers and references

WGSL was updated to have two kinds of memory views: pointers and references.
See https://github.com/gpuweb/gpuweb/pull/1569

In summary:

  • Reference types are never explicitly mentioned in WGSL source.
  • A use of a variable is a value of reference type corresponding
    to the reference memory view of the storage allocated for the
    variable.
  • Let-declared constants can be of pointer type, but not reference
    type.
  • Function parameter can be of pointer type, but not reference type.
  • A variable's store type is never a pointer type, and never a
    reference type.
  • The "Load Rule" allows a reference to decay to the underlying
    store type, by issuing a load of the value in the underlying memory.
  • For an assignment:
    • The right-hand side evaluates to a non-reference type (atomic-free
      plain type).
    • The left-hand side evaluates to a reference type, whose store
      type is the same as the result of evaluating the right hand side.
  • The address-of (unary &) operator converts a reference to a
    pointer.
  • The dereference (unary *) operator converts a pointer to a
    reference.

TODO: Passing textures and samplers to helper functions might be
done by "handler value", or by pointer-to-handle.

Writing SPIR-V from WGSL

The distinction in WGSL between reference and pointer disappears
at the SPIR-V level. Both types map into pointer types in SPIR-V.

To translate a valid WGSL program to SPIR-V:

  • The dereference operator (unary *) is the identity operation.
  • The address-of operator (unary &) is the identity operation.
  • Assignment maps to OpStore.
  • The Load Rule translates to OpLoad.

Reading SPIR-V to create WGSL

The main changes to the SPIR-V reader are:

  • When translating a SPIR-V pointer expression, track whether the
    corresponding WGSL expression is of corresponding WGSL pointer
    type or correspoinding WGSL type.
  • Insert dereference (unary-*) or address-of (unary-&) operators
    as needed to generate valid WGSL expressions.

The choices can be made deterministic, as described below.

The SPIR-V reader only supports baseline functionality in Vulkan.
Therefore we assume no VariablePointers or VariablePointersStorageBuffer
capabilities. All pointers are
SPIR-V logical pointers.
The SPIR-V Universal Validation Rules
specify where logical pointers can appear as results of instructions
or operands of instructions.

Each SPIR-V pointer result expression is a logical pointer, and
therefore is one of:

  • OpVariable: map to the reference type.
  • OpFunctionParameter: map to the pointer type.
  • OpCopyObject:
    • When these only have one use, then these often fold away.
      Otherwise, they map to a a let-declared constant.
    • Map to the pointer type.
  • OpAccessChain, OpInBoundsAccessChain:
    • This could map to either pointer or reference, and adjustments
      in other areas could make it work. However, we recommend mapping
      this to the reference type.
  • OpImageTexelPointer is not supported in WGSL.
    It is used to get a pointer into a storage texture, for use with
    atomic instructions. But image atomics is not supported in
    WebGPU/WGSL.

Each SPIR-V pointer operand is also a logical pointer, and is an
operand to one of:

  • OpLoad Pointer operand:
    • Map to reference, inserting a dereference operator if needed.
  • OpStore Pointer operand:
    • Map to reference, inserting a dereference operator if needed.
  • OpStore Pointer operand:
  • OpAccessChain, OpInBoundsAccessChain Base operand:
    • WGSL array-access and subfield access only works on references.
    • Map to reference, inserting a dereference operator if needed.
  • OpFunctionCall function argument pointer operands
    • Function operands can't be references.
    • Map to pointer, inserting an address-of operator if needed.
  • OpAtomic instruction Pointer operand
    • These map to WGSL atomic builtins.
    • Map to pointer, inserting an address-of operator if needed.
    • Note: As of this writing, the atomic instructions are not supported
      by the SPIR-V reader.
  • OpCopyObject source operand
    • This could have been mapped either way, but it's easiest to
      map to pointer, to match the choice for OpCopyObject result type.
    • Map to pointer, inserting an address-of operator if needed.
  • OpCopyMemory, source and destination operands
    • This acts as an assignment.
    • Map both source and destination to reference, inserting dereference
      operators if needed.
    • Note: As of this writing, OpCopyMemory is not supported by the
      SPIR-V reader.
  • Extended instruction set instructions Modf and Frexp
    • These map to builtins.
    • Map the pointer operand to pointer, inserting an address-of
      operator if needed.

Tint Style Guide (`docs/tint/style_guide.md`)

Tint style guide

  • Generally, follow the Chromium style guide for C++
    which itself is built on the Google C++ style guide.

  • Overall try to use the same style and convention as code around your change.

  • Code must be formatted. Use clang-format with the provided .clang-format
    file. The tools/format script runs the formatter.

  • Code should not have linting errors.
    The tools/lint script runs the linter. So does git cl upload.

  • Do not use C++ exceptions

  • Do not use C++ RTTI.
    Instead, use tint::Castable::As<T>() from
    src/castable.h

  • Generally, avoid assert. Instead, issue a diagnostic
    and fail gracefully, possibly by returning an error sentinel value.
    Code that should not be reachable should call TINT_UNREACHABLE macro
    and other internal error conditions should call the TINT_ICE macro.
    See src/debug.h

  • Use type as part of a name only when the name refers to a type
    in WGSL or another shader language processed by Tint. If the concept you are
    trying to name is about distinguishing between alternatives, use kind instead.

  • Forward declarations:

    • Use forward declarations where possible, instead of using #include's.
    • Place forward declarations in their own un-nested namespace declarations.
      Example:
      to forward-declare struct X in namespace A and struct Y
      in namespace A::B, you'd write:
      c++
      // Forward declarations
      namespace A {
        struct X;
      }  // namespace A
      namespace A::B {
        struct Y;
      }  // namespace A::B
      
      // rest of the header code is declared below ...

Compiler support

Tint requires C++20.

Tint uses the Chromium build system and will stay synchronized with that system.
Compiler configurations beyond that baseline is on a best-effort basis.
We strive to support recent GCC and MSVC compilers.

Test code

We might relax the above rules for test code, since test code
shouldn't ship to users.

However, test code should still be readable and maintainable.

For test code, the tradeoff between readability and maintainability
and other factors is weighted even more strongly toward readability
and maintainability.

Infra/Tooling

Tint has infra scripts and tooling written in two primary languages
Python and Go.

The general advice when making changes to existing code is to use the
language that the existing implementation uses, since there is no
ongoing effort to migrate to a single common language, i.e. don't go
rewriting things just for the sake of rewriting things.

If you are looking at writing a new tool or script, where and how this
code is going to be used should guide your choice.

For example if it is a standalone tool that primarily lives in the
Dawn repo, especially for something like a CLI dev tool, etc, the
preference is to use Go, since there is a substantial existing body of
support code already.

If it is something that needs to directly interface with Chromium
tooling, e.g. interacts with GN or other support libraries/tools will
be accessing it, the preference would be to use Python.

That being said for many Chromium infrastructure tasks, i.e. running
in a build recipe, the integration there is via exec, even for Python
scripts. For integration there is no significant difference between
call into a Python script vs a Go one.

The primary driver in this case to using Python instead of Go would be
either something else written in Python that needs to import your code
as a library (and not just treat it like a standalone binary) or your
code needs to import Chromium specific Python code/libraries.

Often tooling for Tint does double duty, being a CLI for devs and also
how the bots do a similar task. Due to the above considerations,
i.e. existing support for CLI in Go and needing to fork regardless of
the language, the preference in these cases tends to be use Go if
possible.

Another way to view the decision is looking at the number of execs or
similar calls that need to occur. If you are designing something and
seeing that your code is going to be ping ponging between the two
languages via execs, it is probably worthwhile seeing if it is
feasible to implement everything as a library in one language.


Tint Testing (`docs/tint/testing.md`)

Testing Tint

Tint has multiple levels of testing:

  • Unit tests, using the Googletest framework. These are the lowest level tests,
    comprehensively checking functionality of internal functions and classes.
    The test code is inside the Tint source tree in files with names ending in
    _test.cc

    To run them, build the tint_unittests target to produce the tint_unittests
    executable, and then run the executable.

    The tint_unittests executable supports all the standard Googletest command
    line options, e.g. using filters to run only a subset of the tests.
    See Running Test Programs: Advanced
    Options

  • End-to-end tests. These test the whole compiler flow, translating source
    shaders to output shaders in various languages, and optionally checking
    the text of diagnostics. See Tint end-to-end tests.

  • WebGPU Conformance Test Suite (CTS). The WebGPU CTS has both validation and
    execution tests, in the webgpu:shader hierarchy.
    See https://github.com/gpuweb/cts
    All test can be run in Chrome, and many tests can be run via dawn.node,
    the Dawn bindings for NodeJS.


Tint Translations (`docs/tint/translations.md`)

Translations

This document attempts to document how WGSL translates into the various backends
for the cases where the translation is not a direct mapping.

Access Control

HLSL

  • ReadOnly -> ByteAddressBuffer
  • ReadWrite -> RWByteAddressBuffer

MSL

  • ReadOnly -> const

SPIR-V

There are two ways this can be achieved in SPIR-V. Either the variable can be
decorated with NonWritable or each member of the struct can be decorated with
NonWritable. We chose to go the struct member route.

  • The read-only becomes part of the type in this case. Otherwise, you are
    treating the readonly type information as part of the variable which is
    confusing.
  • Treating the readonly as part of the variable means we should be
    deduplicating the types behind the access control, which causes confusing
    with the type_names and various tracking systems within Tint.

Builtin Decorations

Name SPIR-V MSL HLSL
position SpvBuiltInPosition position SV_Position
vertex_index SpvBuiltInVertexIndex vertex_id SV_VertexID
instance_index SpvBuiltInInstanceIndex instance_id SV_InstanceID
front_facing SpvBuiltInFrontFacing front_facing SV_IsFrontFacing
frag_coord SpvBuiltInFragCoord position SV_Position
frag_depth SpvBuiltInFragDepth depth(any) SV_Depth
local_invocation_id SpvBuiltInLocalInvocationId thread_position_in_threadgroup SV_GroupThreadID
local_invocation_index SpvBuiltInLocalInvocationIndex thread_index_in_threadgroup SV_GroupIndex
global_invocation_id SpvBuiltInGlobalInvocationId thread_position_in_grid SV_DispatchThreadID

Builtins Methods

Name SPIR-V MSL HLSL
abs GLSLstd450FAbs or GLSLstd450SAbs fabs or abs abs
acos GLSLstd450Acos acos acos
all SpvOpAll all all
any SpvOpAny any any
arrayLength SpvOpArrayLength
asin GLSLstd450Asin asin asin
atan GLSLstd450Atan atan atan
atan2 GLSLstd450Atan2 atan2 atan2
ceil GLSLstd450Ceil ceil ceil
clamp GLSLstd450NClamp or GLSLstd450UClamp or GLSLstd450SClamp clamp clamp
cos GLSLstd450Cos cos cos
cosh GLSLstd450Cosh cosh cosh
countOneBits SpvOpBitCount popcount countbits
cross GLSLstd450Cross cross cross
determinant GLSLstd450Determinant determinant determinant
distance GLSLstd450Distance distance distance
dot SpOpDot dot dot
dpdx SpvOpDPdx dpdx ddx
dpdxCoarse SpvOpDPdxCoarse dpdx ddx_coarse
dpdxFine SpvOpDPdxFine dpdx ddx_fine
dpdy SpvOpDPdy dpdy ddy
dpdyCoarse SpvOpDPdyCoarse dpdy ddy_coarse
dpdyFine SpvOpDPdyFine dpdy ddy_fine
exp GLSLstd450Exp exp exp
exp2 GLSLstd450Exp2 exp2 exp2
faceForward GLSLstd450FaceForward faceforward faceforward
floor GLSLstd450Floor floor floor
fma GLSLstd450Fma fma fma
fract GLSLstd450Fract fract frac
frexp GLSLstd450Frexp
fwidth SpvOpFwidth fwidth fwidth
fwidthCoarse SpvOpFwidthCoarse fwidth fwidth
fwidthFine SpvOpFwidthFine fwidth fwidth
inverseSqrt GLSLstd450InverseSqrt rsqrt rsqrt
ldexp GLSLstd450Ldexp
length GLSLstd450Length length length
log GLSLstd450Log log log
log2 GLSLstd450Log2 log2 log2
max GLSLstd450NMax or GLSLstd450SMax or GLSLstd450UMax fmax or max max
min GLSLstd450NMin or GLSLstd450SMin or GLSLstd450UMin fmin or min min
mix GLSLstd450FMix mix mix
modf GLSLstd450Modf
normalize GLSLstd450Normalize normalize normalize
pow GLSLstd450Pow pow pow
reflect GLSLstd450Reflect reflect reflect
reverseBits SpvOpBitReverse reverse_bits reversebits
round GLSLstd450Round round round
select SpvOpSelect select
sign GLSLstd450FSign sign sign
sin GLSLstd450Sin sin sin
sinh GLSLstd450Sinh sinh sinh
smoothstep GLSLstd450SmoothStep smoothstep smoothstep
sqrt GLSLstd450Sqrt sqrt sqrt
step GLSLstd450Step step step
tan GLSLstd450Tan tan tan
tanh GLSLstd450Tanh tanh tanh
trunc GLSLstd450Trunc trunc trunc

Types

Sampler Types

WGSL SPIR-V MSL HLSL
sampler OpTypeSampler sampler SamplerState
sampler_comparison OpTypeSampler sampler SamplerComparisonState

Texture Types

WGSL SPIR-V MSL HLSL
texture_1d<type> OpTypeImage 1D Sampled=1 texture1d<type, access::sample> Texture1D
texture_2d<type> OpTypeImage 2D Sampled=1 texture2d<type, access::sample> Texture2D
texture_2d_array<type> OpTypeImage 2D Arrayed=1 Sampled=1 texture2d_array<type, access::sample> Texture2DArray
texture_3d<type> OpTypeImage 3D Sampled=1 texture3d<type, access::sample> Texture3D
texture_cube<type> OpTypeImage Cube Sampled=1 texturecube<type, access::sample> TextureCube
texture_cube_array<type> OpTypeImage Cube Arrayed=1 Sampled=1 texturecube_array<type, access::sample> TextureCubeArray
texture_multisampled_2d<type> OpTypeImage 2D MS=1 Sampled=1 texture2d_ms<type, access::sample> Texture2D
texture_depth_2d OpTypeImage 2D Sampled=1 depth2d<float, access::sample> Texture2D
texture_depth_2d_array OpTypeImage 2D Arrayed=1 Sampled=1 depth2d_array<float, access::sample> Texture2DArray
texture_depth_cube OpTypeImage Cube Sampled=1 depthcube<float, access::sample> TextureCube
texture_depth_cube_array OpTypeImage Cube Arrayed=1 Sampled=1 depthcube_array<float, access::sample> TextureCubeArray
texture_depth_multisampled_2d OpTypeImage 2D MS=1 Sampled=1 depth2d<float, access::sample> Texture2DMSArray
texture_storage_1d<image_storage_type> OpTypeImage 1D Sampled=2 texture1d<type, access::read> RWTexture1D
texture_storage_2d<image_storage_type> OpTypeImage 2D Sampled=2 texture2d<type, access::read> RWTexture2D
texture_storage_2d_array<image_storage_type> OpTypeImage 2D Arrayed=1 Sampled=2 texture2d_array<type, access::read> RWTexture2DArray
texture_storage_3d<image_storage_type> OpTypeImage 3D Sampled=2 texture3d<type, access::read> RWTexture3D
texture_storage_1d<image_storage_type> OpTypeImage 1D Sampled=2 texture1d<type, access::write> RWTexture1D
texture_storage_2d<image_storage_type> OpTypeImage 2D Sampled=1 texture2d<type, access::write> RWTexture2D
texture_storage_2d_array<image_storage_type> OpTypeImage 2D Arrayed=1 Sampled=2 texture2d_array<type, access::write> RWTexture2DArray
texture_storage_3d<image_storage_type> OpTypeImage 3D Sampled=2 texture3d<type, access::write> RWTexture3D

Short-circuting

HLSL

TODO(dsinclair): Nested if's

SPIR-V

TODO(dsinclair): Nested if's

Address spaces

TODO(dsinclair): do ...

Storage buffers

HLSL

TODO(dsinclair): Rewriting of accessors to loads

Loop blocks

HLSL

TODO(dsinclair): Rewrite with bools

MSL

TODO(dsinclair): Rewrite with bools

Input / Output address spaces

HLSL

TODO(dsinclair): Structs and params

MSL

TODO(dsinclair): Structs and params

Discard

HLSL

  • discard

MSL

  • discard_fragment()

Specialization constants

HLSL

text
#ifndef WGSL_SPEC_CONSTANT_<id>
-- if default provided
#define WGSL_SPEC_CONSTANT_<id> default value
-- else
#error spec constant required for constant id
--
#endif
static const <type> <name> = WGSL_SPEC_CONSTANT_<id>

MSL

@function_constant()


Tint Uniformity Analysis (`docs/tint/uniformity_analysis.md`)

Tint: Uniformity Analysis

This document provides a high-level overview of Uniformity Analysis in Tint. It is intended for developers who need to understand or modify the uniformity analysis pass at: src/tint/lang/wgsl/resolver/uniformity.cc.

What is Uniformity?

In GPU programming, "uniformity" refers to whether a value is guaranteed to be the same across all threads executed in a group, like a workgroup, subgroup or quad. There are two concepts necessary to understand the analysis of uniformity of a WebGPU program: uniform values and uniform control flow.

Values

Uniform Value: For a given value in a GPU program, if every thread in the workgroup sees the same value, that value is considered uniform. Examples include static constants or values from a uniform buffer.

Non-Uniform Value: If different threads may see different values, that value is considered non-uniform. Examples include @builtin(local_invocation_id), a textureLoad() from a non-uniform index, or values from a read_write storage buffer.

Control Flow

Uniform Control Flow: If every thread in the workgroup is guaranteed to be executing the same statement at the same time, the control flow is uniform.

Non-Uniform Control Flow: If different threads may be executing different statements, the control flow is non-uniform. For example, inside an if block where the condition was based on a non-uniform value like the thread id. Some GPU operations, like barriers, must only be executed in uniform control flow.

What is Uniformity Analysis?

Uniformity Analysis is a static compiler pass that proves that certain operations are performed in uniform control flow. If it cannot prove that uniformity is maintained everywhere that uniformity is required, it will result in a compilation error. By necessity, it is a conservative compile-time analysis, meaning it will produce false negatives by rejecting WGSL that would actually be uniform at runtime, but it errs on the side of caution to ensure that all accepted shaders are reasonably guaranteed to be safe and correct.

Why do we need it?

WebGPU requires uniformity analysis for two main reasons:

  1. Correctness: Operations like textureSample() require the GPU to calculate the differences between neighboring pixels in a quad. If the threads in a quad diverge (i.e., some enter an if, others don't), the derivatives become undefined, leading to visual artifacts. Requiring textureSample() to be called in uniform control flow helps graphics developers write functionally correct shaders.

  2. Safety: Synchronization primitives like workgroupBarrier() require all threads in the workgroup to reach the barrier together. If the barrier is inside a non-uniform if block, some threads might never reach it. In traditional GPU programming models, this would typically result in undefined behavior, and might lead to a GPU hang. WebGPU is designed to execute untrusted web content, so additional hardening against these negative outcomes is strictly necessary because that execution happens in a shared GPU context with less process isolation than other tightly sandboxed web APIs typically have.

High-Level Implementation

Graph Construction

Tint implements uniformity analysis by building a Directed Dependency Graph.

The analysis uses two main types of graph nodes:

  • Value Nodes: Represent the uniformity of an expression or variable.
  • Control Flow (CF) Nodes: Represent the uniformity of the current execution path.

An edge in the uniformity dependency graph from A -> B signifies that the uniformity of node A is dependent on the uniformity of node B. This means if B is non-uniform, then A must also be considered non-uniform. The uniformity analysis pass builds a graph of these nodes and edges, and when non-uniform values are encountered, it can be determined which parts of control flow they affect.

Example

wgsl
@compute @workgroup_size(8)
fn main(@builtin(local_invocation_id) local_id : vec3u) {
  let u = 1u;
  let n = local_id.x;
  let result = u + n;
}

In the example above, local_id is a non-uniform value, represented by the edge in the graph below from main.local_id_ident_expr -> main.MayBeNonUniform. The result is a binary expression dependent on a uniform value (u) and a non-uniform value (n). This is represented by the edges from main.binary_expr_result -> main.u_ident_expr and main.binary_expr_result -> main.n_ident_expr.

Dependency Graph (Tint):

You can think of non-uniformity like an "infection". If you reversed the direction of all edges in the graph, it would represent the pathways through which the non-uniformity infection spreads. For illustration purposes, the arrows are reversed in the graph below and infected nodes are highlighted in red, showing how the MayBeNonUniform infection spreads from local_id, to n, to binary_expr_result.

Infection Flow:

There's nothing wrong with this example shader though, it compiles successfully in Tint. Shaders can have non-uniform values and non-uniform control flow, as long as they're used in a valid way conformant with the WGSL spec.

Validation

With such a graph built, Tint can now assess the validity of operations like textureSample or workgroupBarrier. When a function that requires uniform control flow is encountered in the AST, the analyzer checks the current Control Flow Node. If that node can be traced back to a non-uniform source, then the analysis cannot guarantee that the operation was called in uniform control flow and validation fails.

Example: Valid Control Flow (Uniform)

In this example, the if condition is a constant, which is uniform. Every thread in the workgroup is guaranteed to enter the if block and reach the barrier together.

wgsl
@compute @workgroup_size(64)
fn main() {
  const uniform_cond = true;
  if (uniform_cond) {
    workgroupBarrier(); // Valid: Uniform control flow
  }
}

This shader passes validation and compiles successfully in Tint. In the graph below, note that the main.RequiredToBeUniform_Error node cannot reach any MayBeNonUniform values.

Example: Invalid Control Flow (Non-Uniform)

In this example, the if condition depends on local_id.x, which is non-uniform. Threads will diverge, meaning some will reach the barrier while others do not. This is a validation error.

wgsl
@compute @workgroup_size(64)
fn main(@builtin(local_invocation_id) local_id : vec3u) {
  if (local_id.x > 0u) {
    workgroupBarrier(); // Error: Divergent control flow
  }
}

Tint Compiler Output:

text
test.wgsl:4:5 error: 'workgroupBarrier' must only be called from uniform control flow
    workgroupBarrier();
    ^^^^^^^^^^^^^^^^

test.wgsl:3:3 note: control flow depends on possibly non-uniform value
  if (local_id.x > 0u) {
  ^^

test.wgsl:3:7 note: builtin 'local_id' may be non-uniform
  if (local_id.x > 0u) {
      ^^^^^^^^

In the graph below, note that the main.RequiredToBeUniform_Error node can reach main.MayBeNonUniform.

How to Generate Diagrams

Tint has a built-in feature to dump the dependency graph as a Graphviz (DOT) file, which was used to generate the example diagrams in this doc.

To generate these diagrams yourself to help understand the analysis, follow these steps:

  1. Download and install Graphviz: https://graphviz.org/download/
  2. Enable the dump flag in src/tint/lang/wgsl/resolver/uniformity.cc, by changing #define TINT_DUMP_UNIFORMITY_GRAPH 0 to 1.
  3. Rebuild Tint.
  4. Since this dumps the DOT file contents to stdout, you should be able to redirect it to a file with the following command:
    bash
    ./path/to/tint -o /dev/null shader.wgsl > graph.dot
  5. Render the image:
    bash
    dot -Tsvg graph.dot -o graph.svg

Further Reading

The official WGSL specification contains all uniformity rules: §15.2. Uniformity.


Tint Unsafe Buffer Usage (`docs/tint/unsafe_buffer_usage.md`)

Remaining -Wunsafe-buffer-usage Suppressions in Tint

This document summarizes and explains the remaining
-Wunsafe-buffer-usage and -Wunsafe-buffer-usage-in-container
suppressions in the Tint codebase.

Categorization of Suppressions

1. Localization via `tint::Copy`

The tint::Copy utility in src/tint/utils/memory/copy.h
encapsulates memcpy calls.

  • Rationale: memcpy is intrinsically unsafe but necessary for
    performance-critical byte copying. By using tint::Copy, we
    localize the suppressions to a single file and provide a safer API
    that accepts std::span.
  • Affected Files: src/tint/utils/memory/copy.h

2. Two-Parameter `std::span` Constructors

Clang's -Wunsafe-buffer-usage-in-container warning is triggered by
std::span(ptr, size).

  • Rationale: This is often unavoidable when interfacing with older
    APIs (like std::string_view::data()) or when reinterpreting byte
    buffers (e.g., std::span<std::byte> to std::span). We
    prefer this over raw pointer arithmetic as it still provides a
    bound-checked view for subsequent operations.
  • Affected Files:
    • src/tint/utils/bytes/buffer_reader.h
    • src/tint/utils/text/unicode.cc

3. Tight Loops and Performance-Critical Indexing

In some performance-critical code (like UTF-8 decoding), we use
UNSAFE_BUFFER_USAGE_IN_CONTAINER to allow standard indexing after
manual bounds checks.

  • Rationale: While span::at() or other methods might be safer,
    standard indexing [] is preferred in certain Tint utilities for
    consistency and to avoid the overhead of redundant checks where we
    have already validated the size.
  • Affected Files:
    • src/tint/utils/text/unicode.cc
    • src/tint/utils/containers/vector.h

4. External API Interfacing

Code that interfaces with C-style external APIs (like
std::from_chars or terminal IO) requires raw pointers.

  • Rationale: std::from_chars requires a pointer to
    one-past-the-end of the buffer. This pattern is inherently flagged
    but necessary to use the standard library feature.
  • Affected Files:
    • src/tint/lang/wgsl/reader/parser/lexer.cc (for line_end())
    • src/tint/utils/system/terminal_posix.cc

5. Custom Containers

Some foundational Tint containers (Vector, HashMap,
BlockAllocator) still contain suppressions.

  • Rationale: These are complex, low-level memory management
    utilities where unsafe buffer usage is part of the implementation of
    the container itself. Refactoring these requires deep architectural
    changes beyond the scope of this effort.
  • Affected Files:
    • src/tint/utils/containers/vector.h
    • src/tint/utils/containers/hashmap_base.h
    • src/tint/utils/memory/block_allocator.h
    • src/tint/utils/memory/bump_allocator.h

Dawn Features Adapter Options (`docs/dawn/features/adapter_options.md`)

Adapter Options (Unstable!)

wgpu::RequestAdapterOptions may be passed to wgpuInstanceRequestAdapter to request an adapter.

Dawn provides a few chained extension structs on RequestAdapterOptions to customize the behavior.
Currently, the WGPUInstance doesn't provide a way to query support for these features, so these
features may not be suitable for general use yet. Currently, they are used in Dawn's testing, and
in Chromium-specific integration with Dawn.

DawnTogglesDescriptor can also be chained on RequestAdapterOptions to provide the required adapter toggles. However, the final adapter toggles state also depends on instance toggles inheritance and adapter toggles validation/default-setting.

dawn::native::Instance::EnumerateAdapters is a Dawn native-only API that may be used to synchronously
get a list of adapters according to the RequestAdapterOptions. The members are treated as follows:

  • RequestAdapterOptions::compatibleSurface is ignored.
  • RequestAdapterOptions::featureLevel all returned adapters must support the features and limits in the requested feature level. Devices created from the adapter will default to the capabilities of this feature level.
  • RequestAdapterOptions::powerPreference adapters are sorted according to powerPreference such that
    preferred adapters are at the front of the list. It is a preference - so if
    wgpu::PowerPreference::LowPower is passed, the list may contain only integrated GPUs, fallback adapters, or a mix of everything. Implementations should try to avoid returning any discrete GPUs when low power is requested if at least one integrated GPU is available.
  • RequestAdapterOptions::backendType filters adapters such that only those on a particular backend are discovered. If WGPURequestAdapterType_Undefined is passed, all backends may be discovered.
  • RequestAdapterOptions::forceFallbackAdapter all returned adapters must be fallback adapters.

If no options are passed to EnumerateAdapters, then it is as if the default RequestAdapterOptions are passed.

`RequestAdapterOptionsGetGLProc`

When discovering adapters on the GLES backend, Dawn uses the provided RequestAdapterOptionsGetGLProc::getProc method to load GL procs. RequestAdapterOptionsGetGLProc::display indicates the EGLDisplay on which to create an adapter. If display is EGL_NO_DISPLAY, the current display will be used. This extension struct does nothing on other backends.

`RequestAdapterOptionsAngleVirtualizationGroup`

When dawn runs on top of angle, you can set angleVirtualizationGroup to a virtualization group number to be used with EGL_ANGLE_context_virtualization extensions. If this struct isn't provided,
the EGL_DONT_CARE value is used.

`RequestAdapterOptionsLUID`

When discovering adapters on D3D11 and D3D12, Dawn only discovers adapters matching the provided RequestAdapterOptionsLUID::adapterLUID. This extension struct does nothing on other backends.

`RequestAdapterOptionsD3D11Device`

When discovering adapter on D3D11, Dawn creates an adapter matching the provided RequestAdapterOptionsD3D11Device::device, and wgpu::Device created from the adapter will share the same D3D11 device from RequestAdapterOptionsD3D11Device::device. This extension struct does nothing on other backends.

`RequestAdapterOptionsVulkanUUIDs`

When discovering adapters on Vulkan, Dawn only discovers adapters matching the provided RequestAdapterOptionsVulkanUUIDs::driverUUID/deviceUUID. This extension struct does nothing on other backends.

`DawnTogglesDescriptor`

When discovering adapters, Dawn will use chained DawnTogglesDescriptor as required adapter
toggles. The final toggles state of each result adapter depends on the inheritance of instance
toggles state, the backend-specific validation and the backend-specific default setting.


Dawn Features Adapter Properties (`docs/dawn/features/adapter_properties.md`)

Adapter Properties

Memory Heaps

wgpu::FeatureName::AdapterPropertiesMemoryHeaps allows querying memory heap information from the adapter.

wgpu::AdapterPropertiesMemoryHeaps may be chained on wgpu::AdapterInfo in a call to wgpu::Adapter::GetInfo or wgpu::Device::GetAdapterInfo in order to query information about the memory heaps on the adapter.
The implementation will write out the number of memory heaps and information about each heap.

If wgpu::FeatureName::AdapterPropertiesMemoryHeaps is not available, the struct will not be populated.

Adds wgpu::HeapProperty which is a bitmask describing the type of memory a heap is. Valid bits:

  • DeviceLocal
  • HostVisible
  • HostCoherent
  • HostUncached
  • HostCached

Note that both HostUncached and HostCached may be set if a heap can allocate pages with either cache property.

Adds wgpu::MemoryHeapInfo which is a struct describing a memory heap.

text
struct MemoryHeapInfo {
    HeapProperty properties;
    uint64_t size;
};

wgpu::MemoryHeapInfo::size is the size that should be allocated out of this heap. Allocating more than this may result in poor performance or may deterministically run out of memory.

DRM properties

wgpu::FeatureName:AdapterPropertiesDRM, currently only available on Vulkan (but could be added on OpenGL / ES), allows querying Linux DRM information about the adapter.

wgpu::AdapterPropertiesDRM may be chained on wgpu::AdapterInfo in a call to wgpu::Adapter::GetInfo or wgpu::Device::GetAdapterInfo in order to query DRM information on the adapter.

D3D

wgpu::FeatureName::AdapterPropertiesD3D allows querying D3D information from the adapter.

wgpu::AdapterPropertiesD3D may be chained on wgpu::AdapterInfo in a call to wgpu::Adapter::GetInfo or wgpu::Device::GetAdapterInfo in order to query D3D information on the adapter.

Adds wgpu::AdapterPropertiesD3D which is a struct describing the D3D adapter.

text
struct AdapterPropertiesD3D {
    uint32_t shaderModel;  // The D3D shader model
};

Vulkan

wgpu::FeatureName::AdapterPropertiesVk allows querying Vulkan information from the adapter.

wgpu::AdapterPropertiesVk may be chained on wgpu::AdapterInfo in a call to wgpu::Adapter::GetInfo or wgpu::Device::GetAdapterInfo in order to query Vulkan information on the adapter.

Adds wgpu::AdapterPropertiesVk which is a struct describing the Vulkan adapter.

text
struct AdapterPropertiesVk {
    uint32_t driverVersion;  // The Vulkan driver version
};

Likewise dawn::native::vulkan::AdapterPropertiesVulkanUUIDs may be chained on wgpu::AdapterInfo to query the following:

text
struct AdapterPropertiesVulkanUUIDs {
    std::array<uint8_t, VK_UUID_SIZE> deviceUUID;
    std::array<uint8_t, VK_UUID_SIZE> driverUUID;
};

WebGPU

wgpu::FeatureName::AdapterPropertiesWGPU allows querying inner backend information of the WebGPUBackend from the adapter.

wgpu::AdapterPropertiesWGPU may be chained on wgpu::AdapterInfo in a call to wgpu::Adapter::GetInfo or wgpu::Device::GetAdapterInfo in order to query WebGPU backend information on the adapter.

Adds wgpu::AdapterPropertiesWGPU which is a struct describing the WebGPU adapter.

text
struct AdapterPropertiesWGPU {
    wgpu::BackendType backendType;  // The inner backend type
};

Dawn Features Angle Texture Sharing (`docs/dawn/features/angle_texture_sharing.md`)

ANGLE Texture Sharing

Available only on the OpenGL ES backend when running on top of ANGLE.
When a device is created with this extension, it is added to the EGL_DISPLAY_TEXTURE_SHARE_GROUP_ANGLE which is a display-wide share context, but only for texturs.

When enabled, dawn::native::opengl::ExternalImageDescriptorGLTexture and dawn::native::opengl::WrapExternalGLTexture become available which can import a GL texture into a wgpu::Device with a GL texture ID.

See the spec for EGL_ANGLE_display_texture_share_group.


Dawn Features Buffer Map Extended Usages (`docs/dawn/features/buffer_map_extended_usages.md`)

Buffer Map Extended Usages

Overview:

  • The wgpu::Feature::BufferMapExtendedUsages feature allows creating a buffer with wgpu::BufferUsage::MapRead and/or wgpu::BufferUsage::MapWrite and any other wgpu::BufferUsage.
  • wgpu::Feature::BufferMapExtendedUsages is a superset of BufferMapWriteExtendedUsages. Enabling BufferMapExtendedUsages on a device implicitly enables BufferMapWriteExtendedUsages as well.

Example Usage:

text
wgpu::BufferDescriptor descriptor;
descriptor.size = size;
descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::Uniform;
wgpu::Buffer uniformBuffer = device.CreateBuffer(&descriptor);

uniformBuffer.MapAsync(wgpu::MapMode::Write, 0, size,
   [](WGPUBufferMapAsyncStatus status, void* userdata)
   {
      wgpu::Buffer* buffer = static_cast<wgpu::Buffer*>(userdata);
      memcpy(buffer->GetMappedRange(), data, sizeof(data));
   },
   &uniformBuffer);

Dawn Features Buffer Map Write Extended Usages (`docs/dawn/features/buffer_map_write_extended_usages.md`)

Buffer Map Write Extended Usages

Background

By default WebGPU only allows wgpu::BufferUsage::MapWrite to be combined with
wgpu::BufferUsage::CopySrc. Uploading data that the GPU consumes as a uniform, vertex, index,
storage or indirect buffer therefore requires a separate staging buffer and an explicit copy on
every update.

The wgpu::FeatureName::BufferMapWriteExtendedUsages feature removes this restriction: it allows
creating a buffer with wgpu::BufferUsage::MapWrite combined with any other wgpu::BufferUsage,
except wgpu::BufferUsage::MapRead. This makes it possible to write directly into a mappable
buffer that is also GPU-usable, eliminating the staging buffer and the extra copy.

The feature works on the GPUs with either cache-coherent or non-cache-coherent Unified
Memory Architecture (UMA), where we can effectively upload data from CPU to GPU through mapped
pointer, and directly access the data on GPU without any extra copies.

This is a subset of the more permissive
BufferMapExtendedUsages feature, which additionally allows
wgpu::BufferUsage::MapRead to be combined with arbitrary usages but is only available on a
narrower set of hardware (cache-coherent UMA). Because it is a superset, enabling
BufferMapExtendedUsages on a device implicitly enables BufferMapWriteExtendedUsages as well.

Target API support

The table below summarizes the requirement to enable this feature on D3D12, Metal and Vulkan:

Backend Requirement to enable Backing memory for write-only mapping
Direct3D 12 D3D12_FEATURE_DATA_ARCHITECTURE.UMA == TRUE Custom heap (D3D12_HEAP_TYPE_CUSTOM) in the L0 system memory pool (D3D12_MEMORY_POOL_L0), with CPU page property D3D12_CPU_PAGE_PROPERTY_WRITE_BACK on cache-coherent UMA or D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE on non-cache-coherent UMA.
Metal [MTLDevice hasUnifiedMemory] == YES MTLResourceStorageModeShared allocation with MTLResourceCPUCacheModeWriteCombined; no didModifyRange: / synchronization needed.
Vulkan A memory type exposing VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT. A HOST_COHERENT (write-combined) memory type; VK_MEMORY_PROPERTY_HOST_CACHED_BIT is not required, so non-cache-coherent UMA GPUs are supported.

Proposal

API

New feature name wgpu::FeatureName::BufferMapWriteExtendedUsages. It is currently experimental
(see crbug.com/386255678).

Once enabled, wgpu::BufferUsage::MapWrite can be combined with any other wgpu::BufferUsage
except wgpu::BufferUsage::MapRead.

Example Usage

c++
wgpu::BufferDescriptor descriptor;
descriptor.size = size;
// MapWrite can now be combined with any usage other than MapRead.
descriptor.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::Uniform;
wgpu::Buffer uniformBuffer = device.CreateBuffer(&descriptor);

uniformBuffer.MapAsync(wgpu::MapMode::Write, 0, size,
    wgpu::CallbackMode::AllowSpontaneous,
    [uniformBuffer, size](wgpu::MapAsyncStatus status, wgpu::StringView) {
        if (status == wgpu::MapAsyncStatus::Success) {
            memcpy(uniformBuffer.GetMappedRange(), data, size);
            uniformBuffer.Unmap();
        }
    });

Validation

  • The feature must be enabled on the device before creating a buffer
    that combines wgpu::BufferUsage::MapWrite with non-wgpu::BufferUsage::CopySrc usages.
  • wgpu::BufferUsage::MapRead cannot be combined with other GPU usages under this feature.

Notes and Limitations

  • Reading back from a buffer created with this feature on CPU (for example via memcpy out of the
    mapped range) may be extremely slow because the underlying memory is typically uncached on CPU.

Dawn Features Core Features And Limits (`docs/dawn/features/core_features_and_limits.md`)

Core Features And Limits

When enabled on a device, lifts all Compatibility mode restrictions (features and limits) to Core.

Core-defaulting adapters always support the "core-features-and-limits" feature. It is automatically enabled on devices created from such adapters.

Compatibility-defaulting adapters may support the "core-features-and-limits" feature. It may be requested on devices created from such adapters.

For more details see WebGPU compatibility mode proposal


Dawn Features D3d11 Multithread Protected (`docs/dawn/features/d3d11_multithread_protected.md`)

D3D11 multithread protected

Optional feature available on the D3D11 backend that when enabled makes all calls to the ID3D11DeviceContext happen inside a ID3D11Mulithread scope.
This ensure that the D3D11 device can be used concurrently by other components, in particular ANGLE in Chromium.

The initial tracking bug was crbug.com/dawn/1927.


Dawn Features Dawn Allow Undefined Load Store Op (`docs/dawn/features/dawn_allow_undefined_load_store_op.md`)

Dawn Allow Undefined Load Store Op

The dawn-allow-undefined-load-store-op feature allows wgpu::LoadOp::Undefined and
wgpu::StoreOp::Undefined to be passed for render pass attachments that would otherwise require
an explicit load/store op:

  • wgpu::LoadOp::Undefined
    • This will be either Load or Clear depending on what the driver sees as the best performing
      option.
  • wgpu::StoreOp::Undefined
    • This will be either Store or Discard depending on what the driver sees as the best performing
      option.

Dawn Features Dawn Device Allocator Control (`docs/dawn/features/dawn_device_allocator_control.md`)

Dawn Device Allocator Control (experimental)

This feature allows wgpu::DawnDeviceAllocatorControl to be chained on wgpu::DeviceDescriptor. If
wgpu::DawnDeviceAllocatorControl is chained and wgpu::FeatureName::DawnDeviceAllocatorControl is
not in requiredFeatures then creating the device will fail with a validation error.

It is available in the Vulkan backend.


Dawn Features Dawn Internal Usages (`docs/dawn/features/dawn_internal_usages.md`)

Dawn Internal Usages

The dawn-internal-usages feature allows adding additional usage which affects how a texture is allocated, but does not affect normal frontend validation.

Adds WGPUDawnTextureInternalUsageDescriptor for specifying additional internal usages to create a texture with.

Example Usage:

text
wgpu::DawnTextureInternalUsageDescriptor internalDesc = {};
internalDesc.internalUsage = wgpu::TextureUsage::CopySrc;

wgpu::TextureDescriptor desc = {};
// set properties of desc.
desc.nextInChain = &internalDesc;

device.CreateTexture(&desc);

Adds WGPUDawnEncoderInternalUsageDescriptor which may be chained on WGPUCommandEncoderDescriptor. Setting WGPUDawnEncoderInternalUsageDescriptor::useInternalUsages to true means that internal resource usages will be visible during validation. ex.) A texture that has WGPUTextureUsage_CopySrc in WGPUDawnEncoderInternalUsageDescriptor::internalUsage, but not in WGPUTextureDescriptor::usage may be used as the source of a copy command.

Example Usage:

text
wgpu::DawnEncoderInternalUsageDescriptor internalEncoderDesc = { true };
wgpu::CommandEncoderDescriptor encoderDesc = {};
encoderDesc.nextInChain = &internalEncoderDesc;

wgpu::CommandEncoder encoder = device.CreateCommandEncoder(&encoderDesc);

// This will be valid
wgpu::TexelCopyTextureInfo src = {};
src.texture = texture;
encoder.CopyTextureToBuffer(&src, ...);

One use case for this is so that Chromium can use an internal copyTextureToTexture command to implement copies from a WebGPU texture-backed canvas to other Web platform primitives when the swapchain texture was not explicitly created with CopySrc usage in Javascript.


Dawn Features Dawn Load Resolve Texture (`docs/dawn/features/dawn_load_resolve_texture.md`)

Dawn Load Resolve Texture

The dawn-load-resolve-texture feature allows a render pass to expand a resolve attachment's pixels into the MSAA attachment's as part of the loading operation.

Additional functionalities:

  • Adds wgpu::LoadOp::ExpandResolveTexture enum value to specify that the MSAA attachment will load the pixels from its corresponding resolve texture. This is cheaper than wgpu::LoadOp::Load which will load the existing pixels of the MSAA attachment itself.
  • Adds wgpu::ColorTargetStateExpandResolveTextureDawn as chained struct for wgpu::RenderPipelineDescriptor::FragmentState::ColorTargetState. It has enabled flag to indicate that the render pipeline is going to be used in a render pass with ExpandResolveTexture load op in the respective color attachment.

Example Usage:

text
/* Detailed source-code truncated for AI context efficiency. */

Notes:

  • If a resolve texture is used in a wgpu::LoadOp::ExpandResolveTexture operation, it must have wgpu::TextureUsage::TextureBinding usage.
  • The wgpu::ColorTargetStateExpandResolveTextureDawn chained struct controls the compatibility between a render pipeline and a render pass:
  • If the chained struct is not included in any wgpu::RenderPipelineDescriptor::FragmentState::ColorTargetState then the render pipeline can only be used on any render pass not using any ExpandResolveTexture load op. Whether the render pass has any resolve target doesn't matter.
  • If the chained struct is included in some color targets but none of their enabled boolean flags are true, then it's the same as above case.
  • If at least one included chained struct has enabled = true, then the compatibility's requirements are stricter:
    • If render pipeline's color target i has wgpu::ColorTargetStateExpandResolveTextureDawn.enabled = true, then the compatible render pass must have ExpandResolveTexture load op on attachment i.
    • If render pipeline's color target i has wgpu::ColorTargetStateExpandResolveTextureDawn.enabled = false, then the compatible render pass's attachment i must have a resolve target and its load op must not be ExpandResolveTexture.
    • If render pipeline's color target i has no wgpu::ColorTargetStateExpandResolveTextureDawn included, then the compatible render pass's attachment i must not have any resolve target.
  • Currently the ExpandResolveTexture LoadOp only works on color attachment, this could be changed in future.
  • The texture is not supported if it is not resolvable by WebGPU standard. This means this feature currently doesn't work with integer textures.
  • Using ExpandResolveTexture load op on a multiplanar texture's view is not supported currently.

Dawn Features Dawn Native (`docs/dawn/features/dawn_native.md`)

Dawn Native

The dawn-native feature enables additional functionality that is supported only
when the WebGPU implementation is dawn_native.

Additional functionality:

  • dawn::native::DawnInstanceDescriptor may be chained on wgpu::InstanceDescriptor on instance creation to pass in the platform and additional search paths, set backend validation level, and receive instance level errors.

  • wgpu::DawnWGSLBlocklist may be chained on wgpu::InstanceDescriptor on instance creation to block some WGSL features.

  • wgpu::DawnCacheDeviceDescriptor may be chained on wgpu::DeviceDescriptor on device creation to enable cache options such as isolation keys and pass in load and store functions.

  • Synchronous adapter.CreateDevice(const wgpu::DeviceDescriptor*) may be called.

Notes:

  • Enabling this feature in the wgpu::DeviceDescriptor does nothing, but
    its presence in the Adapter's set of supported features means that the additional functionality is supported.

Dawn Features Dawn Partial Load Resolve Texture (`docs/dawn/features/dawn_partial_load_resolve_texture.md`)

Dawn partial Load Resolve Texture

The dawn-partial-load-resolve-texture feature is an extension to dawn-load-resolve-texture, which in addition allows to specify a rect sub-region of texture, where load and resolve will take effect only. The feature can't be available unless dawn-load-resolve-texture is available.

Additional functionalities:

  • Adds wgpu::RenderPassDescriptorResolveRect as chained struct for wgpu::RenderPassDescriptor. It defines an expanding rect of {colorOffsetX, colorOffsetY, width, height} and a resolving rect {resolveOffsetX, resolveOffsetY, width, height} to indicate that expanding and resolving are only performed partially on the texels within this rect region of texture. The texels outside of the rect are not impacted.

Example Usage 1, ExpandResolveTexture and RenderPassDescriptorResolveRect:

When the load operation is set to wgpu::LoadOp::ExpandResolveTexture:

  1. Expand the resolve texture's region {resolveOffsetX, resolveOffsetY, width, height} into the MSAA texture's region {colorOffsetX, colorOffsetY, width, height}.
  2. Optional user draws to the MSAA texture.
  3. Resolve the MSAA texture's region {colorOffsetX, colorOffsetY, width, height} into the resolve texture's region {resolveOffsetX, resolveOffsetY, width, height}.
text
/* Detailed source-code truncated for AI context efficiency. */

Example Usage 2, Clear and RenderPassDescriptorResolveRect:

Similar to the above example, but using wgpu::LoadOp::Clear:

  1. Clear the entire MSAA texture using the clearValue.
  2. Optional user draws to the MSAA texture.
  3. Resolve the MSAA texture's region {colorOffsetX, colorOffsetY, width, height} into the resolve texture's region {resolveOffsetX, resolveOffsetY, width, height}.
text
// Create another render pass with "Clear" LoadOp.
wgpu::RenderPassDescriptor renderPassDesc2 = ...;
renderPassDesc2.colorAttachments[0].view = msaaTexture.CreateView();
renderPassDesc2.colorAttachments[0].resolveTarget
 = resolveTexture.CreateView();
renderPassDesc2.colorAttachments[0].loadOp = wgpu::LoadOp::Clear;
renderPassDesc2.colorAttachments[0].clearValue = clearValue;

wgpu::RenderPassDescriptorResolveRect rect{};
rect.colorOffsetX = rect.colorOffsetY = 1;
rect.resolveOffsetX = rect.resolveOffsetY = 1;
rect.width = rect.height = 32;
renderPassDesc2.nextInChain = &rect;

Example Usage 3, Load and RenderPassDescriptorResolveRect:

Similar to the first example, but using wgpu::LoadOp::Load:

  1. Load the MSAA texture.
  2. Optional user draws to the MSAA texture.
  3. Resolve the MSAA texture's region {colorOffsetX, colorOffsetY, width, height} into the resolve texture's region {resolveOffsetX, resolveOffsetY, width, height}.
text
// Create another render pass with "Load" LoadOp.
wgpu::RenderPassDescriptor renderPassDesc2 = ...;
renderPassDesc2.colorAttachments[0].view = msaaTexture.CreateView();
renderPassDesc2.colorAttachments[0].resolveTarget
 = resolveTexture.CreateView();
renderPassDesc2.colorAttachments[0].loadOp = wgpu::LoadOp::Load;

wgpu::RenderPassDescriptorResolveRect rect{};
rect.colorOffsetX = rect.colorOffsetY = 1;
rect.resolveOffsetX = rect.resolveOffsetY = 1;
rect.width = rect.height = 32;
renderPassDesc2.nextInChain = &rect;

Notes:

  • In case that the target size of a render pass is very large, the cost of using wgpu::LoadOp::ExpandResolveTexture can be rather expensive, as it always assumes full-size expand and resolve. More commonly in reality, each frame we only need to re-draw a small damage region, of which UI frameworks usually have the knowledge, instead of the full window, or webpage. This feature aims to eliminate the waste by doing partial expand and resolve with the hint of wgpu::RenderPassDescriptorResolveRect, the actual damage region.
  • This feature is also useful to some scenarios where the applications want to use a smaller MSAA texture to render to a larger single sampled texture.
  • If the color attachment's loadOp is Load or Clear, then RenderPassDescriptorResolveRect will be ignored in the loading step. It will still be used in the resolving step to do a partial resolve.
  • The feature currently is only available on dawn d3d11 backend. Internally, both expand and resolve are implemented with a dedicated wgpu::RenderPipeline. wgpu::RenderPassEncoder::APISetScissorRect is used to set the scissor rect to wgpu::RenderPassDescriptorResolveRect, when using the pipeline. The major difference is that expand lives in the original render pass, while resolve requires a separate one.

Dawn Features Dawn Texel Copy Buffer Row Alignment (`docs/dawn/features/dawn_texel_copy_buffer_row_alignment.md`)

Dawn Texel Copy Buffer Row Alignment

The dawn-texel-copy-buffer-row-alignment feature exposes the alignment restriction of byetsPerRow in wgpu::TexelCopyBufferLayout. Each backend may have its own min alignment value. Without this feature, the alignment must be 256 for all backends.

Additional functionalities:

  • Adds wgpu::DawnTexelCopyBufferRowAlignmentLimits as chained struct for wgpu::SupportedLimits. It has a member minTexelCopyBufferRowAlignment to indicate the alignment limit of the current device.

Notes:

  • Even with this feature enabled, the alignment clients actually use, still needs to respect 'bytes-per-texel-block', and should be the max of them.
  • The feature currently is only available on D3D11 backend.

Dawn Features Flexible Texture Views (`docs/dawn/features/flexible_texture_views.md`)

Flexible Texture Views

In Compat Mode, a texture by default:

  • Must be created with texture view dimension specified.
  • Cannot use different views in the same draw call.
  • Cannot create a 2D view of a 2DArray texture.

The FlexibleTextureViews feature removes the above restrictions.


Dawn Features Format Capabilities (`docs/dawn/features/format_capabilities.md`)

Format Capabilities

wgpu::FeatureName::DawnFormatCapabilities

Allows querying Adapter::GetFormatCapabilities. This API doesn't yet provide WebGPU related
information about the texture format, but will in the future.

See: https://github.com/webgpu-native/webgpu-headers/issues/283

wgpu::FeatureName::DawnDrmFormatCapabilities

Allows querying Adapter::GetFormatCapabilities with a DawnDrmFormatCapabilities chained struct, which
will be populated with DRM info including format modifiers and memory plane counts.

Requires the wgpu::FeatureName::DawnFormatCapabilities flag to be enabled as well.


Dawn Features Framebuffer Fetch (`docs/dawn/features/framebuffer_fetch.md`)

Framebuffer Fetch (experimental)

This extension enables support for the
chromium_experimental_framebuffer_fetch
WGSL extension.

The extension is experimental and might change for example to gain new validation rules
(with extension struct) in the future.

It is available on tiler Metal GPUs and Vulkan with the InputAttachment capability.

Validation

In Device::CreateRenderPipeline or Device::CreateRenderPipelineAsync:

  • For each @color(N) in : T fragment in:
    • color target N must exist
    • color target N's format must match T in both component count and base type

WGSL

Validation

  • Requires enable chromium_experimental_framebuffer_fetch
  • Requires fragment shader
  • Requires T to be i32, u32, f32, f16 or vec{2,3,4}{i,u,f,h}.
  • Requires N to be unique per color
  • The N value must be in the range of [0..7]

Metal

  • @color(N) entries translate directly to [[color(N)]] annotations on the input data.

Vulkan

  • Requires a group, binding value provided for each color N value from Host

  • Add the OpCapability InputAttachment

  • For each color(N) entry

    • Create an OpTypeImage %type SubpassData 0 0 0 2 Unknown where %type is f32, i32 or u32. For f16, the %type will be f32 and the OpImageRead will be decorated with RelaxedPrecision.

    • Create a OpTypePointer UniformConstant %image

    • Create a OpVariable %ptr_uniform_image UniformConstant

    • Decorate the var with OpDecorate %inputVar InputAttachmentIndex N

    • We will then replace the value of the input variable with the value read from the attachment

      • OpImageRead %v4float %img %zero where %zero is OpConstantComposite %v2int %int_0 %int_0
      • For f16, the result of the RelaxedPrecision-decorated OpImageRead is converted to f16.
  • For Dawn, this would probably map to creating the image in ShaderIO and then calling the
    image_read intrinsic for SPIR-V


Dawn Features Host Mapped Pointer (`docs/dawn/features/host_mapped_pointer.md`)

Host Mapped Pointer (Experimental!)

Overview

Enable this feature by requesting wgpu::FeatureName::HostMappedPointer on device creation.

Allows creation of buffers from host-mapped pointers. These may be pointers from shared memory allocations, memory-mapped files, etc. The created buffer does not own a reference to the underlying memory so any use of it will become undefined if the host memory is unmapped or freed.

Requirements

  • wgpu::FeatureName::HostMappedPointer must be supported and enabled.
  • Both the address of the pointer and the size of the allocation that the pointer refers to must be aligned to OS-specific requirements. This is 4Kb on Mac / Linux and 64Kb on Windows.
  • None of the mapping APIs may actually be called on the buffer since it is effectively persistently mapped.
  • On Windows, the pointer must point to the start of the virtual or mapped memory. It will be invalid to pass a pointer returned from MapViewOfFile at a non-zero offset.

Example Usage

c++
// Memory map a file.
void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);

wgpu::BufferHostMappedPointer hostMappedDesc;
hostMappedDesc.pointer = ptr;

// Use the dispose callback to be notified when the buffer is destroyed and no
// longer in use on the GPU. After this point, it is safe to unmap the memory.
hostMappedDesc.disposeCallback = [](void* userdata) {
  // Unmap and close the file.
  auto* data = reinterpret_cast<std::tuple<int, void*, size_t>*>(userdata);
  munmap(std::get<1>(*data), std::get<2>(*data));
  close(std::get<0>(*data));
  delete data;
};
// Pass the fd, ptr, and size as the userdata so we can free the
// memory later.
hostMappedDesc.userdata = new std::tuple<int, void*, size_t>(fd, ptr, size);

wgpu::BufferDescriptor bufferDesc;
bufferDesc.usage = wgpu::BufferUsage::CopySrc;
bufferDesc.size = size;
bufferDesc.nextInChain = &hostMappedDesc;

wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);

It's expected that the memory pages from wrapped host allocations and mapped files are made resident and then pinned at the time of buffer creation. Thus, the memory should not be paged out when used on the GPU. Explicitly requesting that it be decommitted, for example using madvise with MADV_DONTNEED or VirtualFree with MEM_DECOMMIT is undefined behavior.

TODO(crbug.com/dawn/2018):

  • Investigate adding a chained struct for importing from a Windows file HANDLE
  • Expose alignment as a proper WebGPU limit.
  • Figure out interaction with buffer mapping. These types of buffers are essentially persistently mapped.
  • Flesh out if/when the dispose callback is called when there is a validation, out-of-memory, or internal error on creation.

Dawn Features Implicit Device Synchronization (`docs/dawn/features/implicit_device_synchronization.md`)

Implicit Device Synchronization

Makes most public API methods implicitly lock the device, making them safe to call in a multi-threaded environment.
All commands for the wgpu::Device and child objects are safe, except command encoding which doesn't get implicit synchronization.

Note that this extension is quite heavy-handed in terms of synchronization, and should eventually disappear in favor of proper fine-grained and efficient synchronization in Dawn.
Also there are likely still cases where Dawn's reentrancy is not handled properly and could cause deadlocks with this extension.

The initial tracking bug was https://crbug.com/dawn/1662.


Dawn Features Msaa Render To Single Samples (`docs/dawn/features/msaa_render_to_single_samples.md`)

MSAA Render To Single Sampled/Multisampled Render To Single Sampled

The msaa-render-to-single-sampled feature (MSRTSS) allows a render pass to include single-sampled attachments while rendering is done with a specified number of samples. When this feature is used, the client doesn't need to explicitly allocate any multi-sampled color textures.

Additional functionalities:

  • Adds wgpu::DawnRenderPassSampleCount as chained struct for wgpu::RenderPassDescriptor to specify number of samples to be rendered for the respective single-sampled attachment.

Example Usage:

text
// Create texture with RenderAttachment usage.
wgpu::TextureDescriptor desc = ...;
desc.sampleCount = 1;
desc.usage = wgpu::TextureUsage::RenderAttachment;

auto texture = device.CreateTexture(&desc);

// Create a render pipeline
wgpu::RenderPipelineDescriptor pipelineDesc = ...;
auto pipeline = device.CreateRenderPipeline(&pipelineDesc);

// Create a render pass with "implicit multi-sampled" enabled.
wgpu::DawnRenderPassSampleCount renderPassSampleCount;
renderPassSampleCount.sampleCount = 4;

wgpu::RenderPassDescriptor renderPassDesc = ...;
renderPassDesc.colorAttachments[0].view = texture.CreateView();
renderPassDesc.nextInChain = &renderPassSampleCount;

auto renderPassEncoder = encoder.BeginRenderPass(&renderPassDesc);

renderPassEncoder.SetPipeline(pipeline);
renderPassEncoder.Draw(3);
renderPassEncoder.End();

Notes:

  • To trigger MSRTSS load or store behavior, a single-sampled color attachment must be attached to a render pass with an explicit sample count given by the wgpu::DawnRenderPassSampleCount chained struct. The resolveTarget field of the the color attachment must be nullptr.
  • Only colorAttachment zero may be used.
  • If a multisampled depth/stencil attachment is specified, it must have TextureUsage::TransientAttachment, its sample count must match the sampleCount specified by the wgpu::DawnRenderPassSampleCountchained struct and theresolveTargetfield of the attachment must benullptr`
  • If a texture is not resolvable by WebGPU standard it cannot be used as an MSRTSS color attachment. This means this feature currently doesn't work with integer textures.

Dawn Features Multi Draw Indirect (`docs/dawn/features/multi_draw_indirect.md`)

Multi Draw Indirect

The multi-draw-indirect feature allows issuing multiple draws with a single command. This is useful for rendering thousands if not millions of objects in GPU driven rendering.

It introduces the following new commands:

  • wgpu::CommandEncoder::multiDrawIndirect(wgpu::Buffer indirectBuffer, uint64_t indirectOffset, uint32_t maxDrawCount, wgpu::Buffer countBuffer, uint64_t countOffset)
  • wgpu::CommandEncoder::multiDrawIndexedIndirect(wgpu::Buffer indirectBuffer, uint64_t indirectOffset, uint32_t maxDrawCount, wgpu::Buffer countBuffer, uint64_t countOffset)

This feature aims to overcome the shortcomings of drawIndirect and drawIndexedIndirect commands, which can only issue a single draw call at a time from a certain region of a buffer.
In GPU driven rendering, the compute passes can generate the thousands of draw calls and specify how many draw calls to issue, which can be stored in a buffer and then executed.
With drawIndirect and drawIndexedIndirect, the CPU has to issue every single draw call and if the number of draw calls is not known ahead of time, the CPU has to read back the number of draw calls from the GPU, which can be a performance bottleneck.
The alternative is to issue maxDrawCount commands and zero out the draw calls that are not needed, but that wastes CPU and GPU resources.
The bottleneck can be avoided by using this feature.

Usage

wgpu::FeatureName::MultiDrawIndirect feature must be enabled to use the commands. Most desktop GPUs support this feature.

cpp
// Create a buffer to store the draw calls, and the number of draw calls which is a uint32_t.
// Number of draw calls can be stored in another buffer with indirect usage.
wgpu::BufferDescriptor drawCallsBufferDesc = {
	.size = sizeof(DrawCall) * 100 + sizeof(uint32_t),
	.usage = wgpu::BufferUsage::Indirect | wgpu::BufferUsage::Storage,
};
wgpu::Buffer drawCallsBuffer = device.CreateBuffer(&drawCallsBufferDesc);

// Fill the buffer with draw calls and the uint32_t specifying the number of draw calls ...
wgpu::Buffer vertexBuffer = ...;
wgpu::BindGroup bindGroup = ...;
wgpu::CommandEncoder encoder = ...;
wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(...);

// In the render pass, issue the draw calls
{
	...
	renderPassEncoder.SetBindGroup(...);
	renderPassEncoder.SetPipeline(...);
	renderPassEncoder.SetVertexBuffer(0, vertexBuffer);
	renderPassEncoder.MultiDrawIndirect(drawCallsBuffer, 0, 32, drawCallsBuffer, 1024);
	...
}

renderPassEncoder.EndPass();

Restrictions

  • The number of draw calls executed is min(maxDrawCount, countBuffer), where countBuffer is the number of draw calls specified in the buffer with the offset.
  • The indirect buffer must be created with wgpu::BufferUsage::Indirect usage and be large enough to store maxDrawCount draw calls.
  • If wgpu::FeatureName::IndirectFirstInstance is not enabled, the firstInstance field of every draw call encoded in the indirect buffer must be 0, otherwise the draw call with a non-zero firstInstance will be skipped.

Dawn Features Multi Planar Formats (`docs/dawn/features/multi_planar_formats.md`)

Multi Planar Formats

Video is often decoded to formats with a full resolution luminance image and a lower resolution chrominance image.
This is represented in GPU APIs with multi-planar color texture formats where plane 0 is luminance, plane 1 is chrominance and plane 2 an optional alpha channel. It is important to support these formats in Dawn because in combination with wgpu::ExternalTexture they can provide 0-copy access to video data with less memory bandwidth than if it were decoded to RGBA8Unorm textures.

Dawn has a number of optional features that give access to some of these formats and texture capabilities.
Note however that Dawn doesn't specify the semantics of the planes (luminance vs. chrominance), but instead just exposes multiple planes with varying sizes and formats.

Multi planar formats introduce new wgpu::TextureAspect values Plane0Only, Plane1Only and Plane2Only to allow using a single aspect of multi planar textures in TextureView creation or copy operations. All multi planar formats support wgpu::TextureUsage::TextureBinding and wgpu::TextureUsage::CopySrc without any need for another optional feature (and cannot be created directly, only imported with SharedTextureMemory).

TODO(dawn:551): It seems these aspects enums are always allowed. Maybe they should be gated on the base feature?

`DawnMultiPlanarFormats`

This feature adds support for "NV12", a common format for decoded SDR videos.
It is composed of:

  • a full res R8Unorm plane 0 (luminance).
  • a 1/4 res RG8Unorm plane 1 (chrominance).

For historical reasons the feature is called DawnMultiPlanarFormats.
This format is called wgpu::TextureFormat::R8BG8Biplanar420Unorm.

TODO(dawn:551): Rename to MultiPlanarFormatNV12 ?

`MultiPlanarFormatP010`

This feature adds support for "P010", a common format for decoded HDR videos.
It is composed of:

  • a full res R16Unorm plane 0 (luminance) with only the first 10 bits used in the decoding of the videos.
  • a 1/4 res RG16Unorm plane 1 (chrominance) with only the first 10 bits of each channel used in the decoding of the videos.

This format is called wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm.

TODO(dawn:551) is it possible to merge this feature with the nv12 feature?

`MultiPlanarFormatNV12A` (Experimental!)

This feature adds support for "NV12A", a format for decoded SDR videos with an alpha channel.
It is composed of:

  • a full res R8Unorm plane 0 (luminance).
  • a 1/4 res RG8Unorm plane 1 (chrominance).
  • a full res R8Unorm plane 2 (alpha).

This format is called wgpu::TextureFormat::R8BG8A8Triplanar420Unorm.

At the moment it is only available on macOS >= 10.15, iOS >= 13.0.

`MultiPlanarFormatNV16` (Experimental!)

This feature adds support for "NV16", a common format for 8-bit decoded 4:2:2 videos.
It is composed of:

  • a full res R8Unorm plane 0 (luminance).
  • a 1/2 res RG8Unorm plane 1 (chrominance).

This format is called wgpu::TextureFormat::R8BG8Biplanar422Unorm.

At the moment it is only available on macOS >= 11.0, iOS >= 14.0.

`MultiPlanarFormatNV24` (Experimental!)

This feature adds support for "NV24", a common format for 8-bit decoded 4:2:2 videos.
It is composed of:

  • a full res R8Unorm plane 0 (luminance).
  • a full res RG8Unorm plane 1 (chrominance).

This format is called wgpu::TextureFormat::R8BG8Biplanar444Unorm.

At the moment it is only available on macOS >= 11.0, iOS >= 14.0.

`MultiPlanarFormatP210` (Experimental!)

This feature adds support for "P210", a common format for 10-bit decoded 4:2:2 videos.
It is composed of:

  • a full res R16Unorm plane 0 (luminance) with only the first 10 bits used in the decoding of the videos.
  • a 1/2 res RG16Unorm plane 1 (chrominance) with only the first 10 bits of each channel used in the decoding of the videos.

This format is called wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm.

At the moment it is only available on macOS >= 10.13, iOS >= 11.0.

`MultiPlanarFormatP410` (Experimental!)

This feature adds support for "P410", a common format for 10-bit decoded 4:4:4 videos.
It is composed of:

  • a full res R16Unorm plane 0 (luminance) with only the first 10 bits used in the decoding of the videos.
  • a full res RG16Unorm plane 1 (chrominance) with only the first 10 bits of each channel used in the decoding of the videos.

This format is called wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm.

At the moment it is only available on macOS >= 10.13, iOS >= 11.0.

`MultiPlanarFormatExtendedUsages`

Adds the ability to create multi-planar textures with device.CreateTexture() and the wgpu::TextureUsage::CopyDstusage.

`MultiPlanarRenderTargets`

Adds the wgpu::TextureUsage::RenderAttachment usage to multi planar formats.


Dawn Features Norm16 Texture Formats (`docs/dawn/features/norm16_texture_formats.md`)

Norm16 texture formats

Adds support for the following norm16 formats with CopySrc|CopyDest|RenderAttachment|TextureBinding usages, multisampling and resolving capabilities:

  • wgpu::TextureFormat::R16Unorm
  • wgpu::TextureFormat::RG16Unorm
  • wgpu::TextureFormat::RGBA16Unorm

The initial tracking bug was https://crbug.com/dawn/1982.


Dawn Features Opaque Ycbcr Android For External Texture (`docs/dawn/features/opaque_ycbcr_android_for_external_texture.md`)

`OpaqueYCbCrAndroid` for external textures.

Google-internal design doc

Context

WebGPU is used on the Web and in native to do GPU-accelerated video processing.
Video streams are usually coming from some system component (HW video decoder, camera hardware) and encoded in some YUV format (as a basic form of compression because the human eye is more sensitive to variation in luminance than chrominance).
For performance and power-efficiency reasons, it is important that Dawn/WebGPU (and the applications using it) be able to directly sample YUV data in the shader instead of first decompressing to RGB as both the decompression and the sampling of expanded RGB data cause additional memory traffic than necessary.

For this purpose, the WebGPU API added the GPUExternalTexture concept that can be created from a HTML <video> element, a WebCodec VideoFrame, etc.
In WGSL shaders, a sampling builtin is used that is polyfilled in Tint to generate all the multiplane sampling, YUV to RGB and colorspace conversion, and additional computations needed to sample 0-copy.
On systems where the Y and UV planes of textures can be sampled separately, the generic Tint polyfill works well, but not on Android because videos are AHardwareBuffers with opaque YCbCr formats.

On Android with AHB with YCbCr formats (which is what's given by camera hardware and video decoders), the Vulkan API requires usage of static samplers and more.
Dawn has a large API in y_cb_cr_vulkan_samplers.md that's mostly passthrough to Vulkan but it is quite inflexible and difficult to use (future Vulkan extension could improve this but would take time to be available everywhere).

Creating `ExternalTexture` from `OpaqueYCbCrAndroid`.

This extension OpaqueYCbCrAndroidForExternalTexture allows creating an ExternalTexture from imported YCbCr AHB, and have sampling "just work".
Internally Dawn recognizes that a pipeline will use an ExternalTexture and uses the same mechanisms as y_cb_cr_vulkan_samplers.md to do 0-copy YUV sampling, recreating BindGroupLayouts with static samplers and recompiling the underlying VkPipeline.
With this functionality it is possible to 0-copy sample YCbCr AHBs with code like:

cpp
// Import the AHB in Dawn, wrap it in an STM first (the object doing access control
// and vending indivial wgpu::Texture views of the AHB), then in a wgpu::Texture and
// finally wrap the wgpu::Texture in the wgpu::ExternalTexture object (that is used
// to represent all the other ways to do YUV 0-copy imports).
wgpu::SharedTextureMemory stm = device.CreateSharedTextureMemory(&descFromAHB);
// descForTexture needs to be compatible with Dawn's reflection in stm.GetProperties()
// of what's allowed with that STM (size, usage, format).
wgpu::Texture tex = stm.CreateTexture(&descForTexture);
wgpu::ExternalTexture et = device.CreateExternalTexture(&descWithPlane0Only);

// Start Dawn's access of the AHB, the application needs to pass in the fences required
// to ensure proper ordering of (GPU-timeline) operations on the AHB.
stm.BeginAccess(&beginAccessWithFencesEtc);

// Render, sampling `et` in pipeline to do 0-copy YUV sampling.

// End Dawn's access of the AHB. Dawn will return fences that must be used to
// to synchronize, on the GPU-timeline, the subsequent accesses on the AHB.
stm.EndAccess(&endAccessDesc);

// Destroy stm, tex, et if they are used for a single frame, or keep them around
// if we know that video decoders reuse AHBs internally.

Validation rules

  • This feature allows using wgpu::TextureFormat::OpaqueYCbCrAndroid but does not enable any use of them except noted below.
  • Texture::CreateView is allowed on OpaqueYCbCrAndroid (without a YCbCrVkDescriptor).
  • These views can be used in ExternalTextureDescriptor::plane0 as the single plane specified (views with a YCbCrVkDescriptor are rejected).
  • These views cannot be used in BindGroups even with YCbCr static samplers.

Dawn Features Pixel Local Storage (`docs/dawn/features/pixel_local_storage.md`)

Pixel Local Storage (PLS)

This features adds support for efficiently reading and writing per-pixel data that's local to a render pass, with correctly synchronized accesses.
On "explicit" tiler GPUs this additional data is allocate in tile storage similarly to attachments, but on other GPUs the same functionality can be implemented using "fragment shader interlock" and read-write storage textures.

Here is the design doc for this feature.

Concepts

A couple new concepts are added:

  • A pixel_local storage class in WGSL that can be used with var pls : u32/MyStruct which declares the pixel local data in the shader.
    It requires a WGSL enable, see chromium_experimental_framebuffer_fetch.

  • Pixel local storage descriptors for both wgpu::PipelineLayoutDescritor and wgpu::RenderPassDescriptor that describe the layout of the PLS inside a render pass, and are part of pipeline / render pass compatibility.
    These descriptors contain both:

    • The total size of the PLS allocation for the render pass.
    • A number of storage attachments which are textures which will have their corresponding texel loaded/stored into a specific offset of the PLS at the start/end of the render pass.
      (note that on explicit tiler GPUs this is exactly what happens, but on others the load/stores are done directly as "storage texture" operations)
    • Implicit PLS slots (the ones not bound to a storage attachment) start with value 0 and are discarded at the end of the pass.
  • A wgpu::TextureUsage::StorageAttachment that allows a texture to be used as a storage attachment in a render pass.

  • The wgpu::RenderPassEncoder::PixelLocalStorageBarrier method for use in the non-coherent PLS extension.

Coherency

The feature comes in two flavors depending on coherency.
The coherent version automatically synchronizes fragment shader accesses to the PLS so that no data race happens (as if there is a critical section between the first and last use of the PLS in an invocation), and enforcing that fragment shaders happen in API order (two fragments invocations from two triangles of the same draw happen in the order of the triangles in the draw).
The non-coherent version allows racy access to the PLS during the whole render pass, but provides a PixelLocalStorageBarrier() that prevents races between fragment invocations before and after the barrier.
In particular, there is no way to prevent racy access to the PLS in the same draw with the non-coherent version of the feature.

New API surface

cpp
// Add to wgpu::FeatureName
wgpu::FeatureName::PixelLocalStorageNonCoherent
wgpu::FeatureName::PixelLocalStorageCoherent

// Add to wgpu::TextureUsage
wgpu::TextureUsage::StorageAttachment

// Can be chained to a RenderPassDescriptor
struct wgpu::RenderPassPixelLocalStorage : wgpu::ChainedStruct {
    uint64_t totalPixelLocalStorageSize;
    size_t storageAttachmentCount;
    wgpu::RenderPassStorageAttachment* storageAttachments;
};
struct wgpu::RenderPassStorageAttachment : wgpu::ChainedStruct {
    wgpu::LoadOp loadOp;
    wgpu::StoreOp storeOp;
    wgpu::Color clearValue;
    wgpu::TextureView storage;
    uint64_t offset;
};

// Can be chained to a PipelineLayoutDescriptor
struct wgpu::PipelineLayoutPixelLocalStorage : wgpu::ChainedStruct {
    uint64_t totalPixelLocalStorageSize;
    size_t storageAttachmentCount;
    wgpu::PipelineLayoutStorageAttachment* storageAttachments;
};
struct wgpu::PipelineLayoutStorageAttachment : wgpu::ChainedStruct {
    wgpu::TextureFormat format;
    uint64_t offset;
};

// Used for non-coherent.
wgpu::RenderPassEncoder::PixelLocalStorageBarrier();

Validation Rules

  • Only the R32Uint, R32Sint and R32Float texture formats can specify wgpu::TextureUsage::StorageAttachment.

  • StorageAttachment textures must be single-sampled.

  • In a PixelLocalStorage descriptor:

    • The totalPixelLocalStorageSize must be less than a small constant (should eventually get into the limits) currently set at 16 and must be a multiple of 4.
    • The offset of each storage attachment must fit in the total size (with the additional size of that PLS slot) and be a multiple of 4.
    • The format of the storage attachments must be one of the texture formats that allows StorageAttachment.
    • Storage attachment views must be for a single subresources (and used at most once in BeginRenderPass).
    • Storage attachment views must have the same size of any other attachment used in the render pass.
    • The load and store operations on storage attachments follow the same rules as for color attachments.
    • Two storage attachments must not collide.
  • The PLS definition between a pipeline and a render pass must match.

  • A render pipeline must us a pixel_local storage class that's compatible with its layout's PLS descriptor.

    • The totalPLSSize must match the size of the pixel_local block.
    • Each slot used for a storage attachment must have a WGSL type that matches that attachment's texture format.
    • Each implicit slot must use the u32 WGSL type.

Dawn Features Render Pass Render Area (`docs/dawn/features/render_pass_render_area.md`)

Render Pass Render Area

The render-pass-render-area feature allows specifying a sub-region of the attachments that the
render pass will use. The "render area" defines the rectangle within which rendering operations,
including load and store operations, must be performed. Rendering operations may still modify texels
outside of the render area.

Additional functionalities:

  • Adds wgpu::RenderPassRenderAreaRect as chained struct for wgpu::RenderPassDescriptor. It
    defines a rect of {x, y, width, height} to indicate that rendering operations are only
    performed on the texels within this rect region of the attachments.

Example Usage:

cpp
wgpu::RenderPassRenderAreaRect rect;
rect.x = 10;
rect.y = 10;
rect.width = 100;
rect.height = 100;

wgpu::RenderPassDescriptor renderPassDesc = ...;
renderPassDesc.nextInChain = &rect;

auto renderPassEncoder = encoder.BeginRenderPass(&renderPassDesc);
// Draw commands here...
renderPassEncoder.End();

Notes:

  • The render area must be contained within the dimensions of the render pass attachments.
  • The render area cannot be empty.
  • Any scissor rect set with wgpu::RenderPassEncoder::SetScissorRect() must be contained by the
    render area rect specified in wgpu::RenderPassRenderAreaRect.
  • The default scissor rect is the render area.
  • The default viewport is unchanged.
  • The actual render area used by the hardware may be slightly larger than the requested area due to
    tile alignment or granularity requirements. Dawn handles this alignment internally by expanding
    the render area to meet the backend's granularity requirements while still encompassing the
    requested region.

Dawn Features Shader Module Compilation Options (`docs/dawn/features/shader_module_compilation_options.md`)

Shader Module Compilation Options

Shader module compilation options may be specified to override Dawn's default compilation behavior.

wgpu::ShaderModuleCompilationOptions may be chained on wgpu::ShaderModuleDescriptor. If it is,
Dawn will use these compilation options instead of its defaults.

`wgpu::ShaderModuleCompilationOptions::strictMath`

Enables or disables strict math. When strict math is disabled, generally the compiler will:

  • Assume no NaNs
  • Assume no Inf
  • Assume no signed 0
  • Use multiplication by reciprocal instead of division
  • Allow algebraic transformations according to associative and distribute properties.

It is implemented only on Metal and D3D.


Dawn Features Shared Buffer Memory (`docs/dawn/features/shared_buffer_memory.md`)

Shared Buffer Memory

Overview

Shared Buffer Memory refers to a superset of features that allow Dawn to import externally allocated buffers.

  • wgpu::FeatureName::SharedBufferMemoryD3D12Resource
  • wgpu::FeatureName::SharedBufferMemoryFromWindowsHandle
  • wgpu::FeatureName::SharedBufferMemoryHostPointer
c++
wgpu::SharedBufferMemoryFooBarDescriptor fooBarDesc = {
  .fooBar = ...,
};
wgpu::SharedBufferMemoryDescriptor desc = {};
desc.nextInChain = &fooBarDesc;

wgpu::SharedBufferMemory memory = device.CreateSharedBufferMemory(&desc);

After creating the memory, the supported wgpu::BufferUsage can be queried.

c++
wgpu::SharedBufferMemoryProperties properties;
memory.GetProperties(&properties);

switch (properties.usage) {
  // ... handle supported usage
}

Then, a buffer can be created from it. For example:

c++
wgpu::BufferDescriptor bufferDesc = {
  // usage must be a subset of the buffer memory's usage
  .usage = properties.usage
};
wgpu::Buffer buffer = memory.CreateBuffer(&bufferDesc);

A buffer can also be created from the shared buffer memory without a wgpu::BufferDescriptor. In this case:

  • The buffer will be created with the same size as the shared buffer memory
  • The buffer will be created with the same buffer usage as the shared buffer memory without wgpu::BufferUsage::MapRead and wgpu::BufferUsage::MapWrite, which means the buffer will only contain GPU-accessible buffer usages.
c++
// Create a buffer from shared buffer memory without a descriptor
wgpu::Buffer buffer = memory.CreateBuffer();

A buffer created from shared buffer memory is not valid to use inside a queue operation until access to the memory is explicitly started using BeginAccess. Access is ended using EndAccess. For example:

c++
wgpu::BufferDescriptor bufferDesc = { ... };
wgpu::Buffer buffer = memory.CreateBuffer(&bufferDesc);

// It is valid to create a bind group and encode commands
// referencing the buffer.
wgpu::BindGroup bg = device.CreateBindGroup({..., buffer, ... });
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePass pass = encoder.BeginComputePass();
// ...
pass.SetBindGroup(0, bg);
// ...
pass.End();
wgpu::CommandBuffer commandBuffer = encoder.Finish();

// Begin/EndAccess must wrap usage of the buffer on the queue.
wgpu::SharedBufferMemoryBeginAccessDescriptor beginAccessDesc = { ... };
memory.BeginAccess(buffer, &beginAccessDesc);

queue.writeBuffer(buffer, ...);
queue.submit(1, &commandBuffer);

wgpu::SharedBufferMemoryEndAccessState endAccessDesc = { ... };
memory.EndAccess(buffer, &endAccessDesc);

Uniform Usage Restriction

Using wgpu:BufferUsage::Uniform with a buffer created from SharedBufferMemory is not allowed due to an alignment restriction on D3D12 when creating a constant buffer view. It is possible this restriction could be removed in the future if additional alignment restrictions are placed on the provided SharedBufferMemory during import.

Mappable Buffers

A buffer created from shared buffer memory cannot be mapped until access to the memory is explicitly started using BeginAccess. The buffer must be unmapped before calling EndAccess.

Concurrent Access

Multiple buffers may be created from the same SharedBufferMemory, but only one buffer may have access to the memory at a time.

SharedBufferMemory Types

SharedBufferMemoryDescriptor accepts different chained structs corresponding to the memory type.

Synchronization

The Begin/End access descriptors are used to pass Shared Fences necessary to synchronize buffer states between Dawn and external code.

When calling BeginAccess, Dawn will wait for any passed shared fences to be signaled before the buffer is used on the GPU. This will ensure any GPU work occurring outside of Dawn is finished before Dawn accesses the memory.

When calling EndAccess, the shared fences for the buffer's last usages will be exported. This allows code external to Dawn to be notified when Dawn's GPU work on the shared memory is complete.


Dawn Features Shared Fence (`docs/dawn/features/shared_fence.md`)

Shared Fence

Overview

A variety of features may be used to externally synchronize usage of resources on the GPU.

  • wgpu::FeatureName::SharedFenceVkSemaphoreOpaqueFD
  • wgpu::FeatureName::SharedFenceSyncFD
  • wgpu::FeatureName::SharedFenceVkSemaphoreZirconHandle
  • wgpu::FeatureName::SharedFenceDXGISharedHandle
  • wgpu::FeatureName::SharedFenceMTLSharedEvent
  • wgpu::FeatureName::SharedFenceEGLSync

TODO(crbug.com/dawn/1745): additional documentation

Work-in-progress: https://docs.google.com/document/d/1uRGL6vE1mSbpWd2v_KU5--RT5EjTXtruwiC7Ri3ZKz4/edit


Dawn Features Shared Texture Memory (`docs/dawn/features/shared_texture_memory.md`)

Shared Texture Memory

Overview

A variety of features may be used to import externally allocated shared texture memory.

  • wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer
  • wgpu::FeatureName::SharedTextureMemoryDmaBuf
  • wgpu::FeatureName::SharedTextureMemoryOpaqueFD
  • wgpu::FeatureName::SharedTextureMemoryZirconHandle
  • wgpu::FeatureName::SharedTextureMemoryDXGISharedHandle
  • wgpu::FeatureName::SharedTextureMemoryD3D11Texture2D
  • wgpu::FeatureName::SharedTextureMemoryIOSurface

Each memory type uses an extension struct chained on wgpu::SharedTextureMemoryDescriptor to create the memory. For example:

c++
wgpu::SharedTextureMemoryFooBarDescriptor fooBarDesc = {
  .fooBar = ...,
};
wgpu::SharedTextureMemoryDescriptor desc = {};
desc.nextInChain = &fooBarDesc;

wgpu::SharedTextureMemory memory = device.CreateSharedTextureMemory(&desc);

After creating the memory, the properties such as the supported wgpu::TextureUsage can be queried.

c++
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);

switch (properties.usage) {
  // ... handle supported usage
}

Then, a texture can be created from it. For example:

c++
wgpu::TextureDescriptor textureDesc = {
  // usage must be a subset of the texture memory's usage
  .usage = properties.usage
};
wgpu::Texture texture = memory.CreateTexture(&textureDesc);

Note: There are restrictions on the configuration that can be requested for
textures created from SharedTextureMemory objects:

  • they must be single-sampled
  • they must be 2D
  • they must have a single mip level
  • they must have an array layer count of 1

Textures created from shared texture memory are not valid to use inside a queue operation until access to the memory is explicitly started using BeginAccess. Access is ended using EndAccess. For example:

c++
wgpu::TextureDescriptor textureDesc = { ... };
wgpu::Texture texture = memory.CreateTexture(&textureDesc);

// It is valid to create a bind group and encode commands
// referencing the texture.
wgpu::BindGroup bg = device.CreateBindGroup({..., texture, ... });
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::ComputePass pass = encoder.BeginComputePass();
// ...
pass.SetBindGroup(0, bg);
// ...
pass.End();
wgpu::CommandBuffer commandBuffer = encoder.Finish();

// Begin/EndAccess must wrap usage of the texture on the queue.
wgpu::SharedTextureMemoryBeginAccessDescriptor beginAccessDesc = { ... };
memory.BeginAccess(texture, &beginAccessDesc);

queue.writeTexture(texture, ...);
queue.submit(1, &commandBuffer);

wgpu::SharedTextureMemoryEndAccessState endAccessDesc = { ... };
memory.EndAccess(texture, &endAccessDesc);

Multiple textures may be created from the same SharedTextureMemory, but only one texture may have access to the memory at a time.

The Begin/End access descriptors are used to pass data necessary to update and synchronize texture state since the texture may have been externally used. Chained structs that are supported or required vary depending on the memory type. These access descriptors communicate the texture's initialized state. They are also used to pass Shared Fences to synchronize access to the texture. When passed in BeginAccess, the texture will wait for the shared fences to pass before use on the GPU, and on EndAccess, the shared fences for the texture's last usage will be exported. Passing shared fences to BeginAccess allows Dawn to wait on externally-enqueued GPU work, and exporting shared fences from EndAccess then allows other external code to wait for Dawn's GPU work to complete.

TODO(crbug.com/dawn/1745): additional documentation

Work-in-progress: https://docs.google.com/document/d/1uRGL6vE1mSbpWd2v_KU5--RT5EjTXtruwiC7Ri3ZKz4/edit


Dawn Features Static Samplers (`docs/dawn/features/static_samplers.md`)

Static Samplers

The static-samplers feature allows specification of samplers statically as
part of the bind group layout.

TODO(crbug.com/dawn/2364): Expand this documentation with examples and
description of semantics (including constraints/validations) as we build out
support


Dawn Features Subgroup Matrix (`docs/dawn/features/subgroup_matrix.md`)

Subgroup Matrix

Background

Subgroup matrices are an abstract matrix data type. Computations on subgroup matrices utilize the SIMD nature of the GPU and are distributed among multiple invocations. Subgroup matrices are a critical primitive for ML workloads: Large matrix-multiply operations can be decomposed into many matrix multiplies over small sub-blocks of the larger matrices. (Note that 1x1 convolutions are mathematically the same as matrix multiply.)

Many GPUs now support this feature, with various parameterizations of block sizes, component data types, and result types. In the following, a typical block matrix multiply operation is:

text
Accum = A * B

or

text
Accum = A * B + Accum


where  Accum is M-row, N-column,


A is M-row K-column,


B is K-row, N-column.

Target API support

SPIR-V/Vulkan

Originally introduced as an Nvidia vendor extension (SPV_NV_cooperative_matrix and VK_NV_cooperative_matrix). NVIDIA presented an overview of the feature and its benefits, in a 2019 blog post and a 2022 presentation, and optimization guide. The functionality has since been standardized as SPV_KHR_cooperative_matrix and VK_KHR_cooperative_matrix.

The Vulkan extension has two feature bits:

  • cooperativeMatrix: basic feature support
  • cooperativeMatrixRobustBufferAccess: robust buffer support for cooperative matrix load and store (usual caveats).

There is a supported stages property in [VkPhysicalDeviceCooperativeMatrixPropertiesKHR](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkPhysicalDeviceCooperativeMatrixPropertiesKHR), but I’ve only ever seen compute supported.

OpTypeCooperativeMatrixKHR represents the abstract type. It parameterized as follows:

  • Component type: Matrix element type (must be a numerical scalar) (Float16/32/64, S/UInt8/16/32/64 currently)
  • Scope: the scope of operations on the type (Device, QueueFamily, Workgroup, or Subgroup)
  • Rows: Number of rows
  • Columns: Number of columns
  • Use: an enum specifying which kind of matrix this is used as:
    • MatrixAKHR: LHS of a multiply (M rows x K columns)
    • MatrixBKHR: RHS of a multiply (K rows x N columns)
    • MatrixAccumulatorKHR: result of multiply-accumulate (M rows x N columns)

SPIR-V can represent a wide variety of cooperative matrices, but actual devices only support a subset of parameterizations. These are queried from the API by calling vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR. It returns a linked list of valid enumerations. Preferred variants are listed earlier in the chain. At least one entry in the list must have power of two values for all of MSize, KSize, and NSize. The enumerations indicate whether they support saturating accumulation. Because the type is abstract it can only be stored in the Function and Private storage classes. Special load and store instructions are used to translate to/from backing memory.

The fundamental operation is OpCooperativeMatrixMulAddKHR. It performs a multiply-accumulate operation. It requires consistent values of scope, M, N, and K for all types. The component types may differ, the operations occur using the result type. Signedness of results and operands is indicated using the Cooperative Matrix Operands (as is saturating accumulation). The result is undefined if saturating accumulation is used, but intermediate results overflow (same as integer dot product).

OpCooperativeMatrixLoadKHR and OpCooperativeMatrixStoreKHR share the following operands:

  • Pointer: a logical pointer into an array (stride is ignored) (limited to Workgroup, StorageBuffer, and PhysicalStorageBuffer storage classes in Vulkan)
  • Memory Layout: an enum for row- or column-majorness
  • Stride: an optional operand indicating the stride between elements (aligned to min(16, align(col/row))). The stride counts the number of elements in the pointee type; it is not a byte count. For example, if the pointee type of the Pointer argument is 2xf16 then a stride of 8 translates to a byte stride of 8x (2x2) bytes.

These operations convert to/from the abstract matrix type and memory. The operands must be uniform values for the scope.

Additional operations:

  • OpCooperativeMatrixLengthKHR: Number of accessible components from an invocation for a given cooperative matrix type.
  • Conversions: All standard conversions are allowed (FToU, UToF, FToS, SToF, U, S, F, bitcast).
  • Arithmetic: Negate, add, sub, mul, div using the opcode for the appropriate component type. Additionally, OpMatrixTimesScalar is also supported.
  • Constants: OpCompositeConstruct and OpConstantComposite can provide a single fill value for a cooperative matrix.

All operations involving a cooperative matrix must only be executed in uniform control flow for scope of the operation.

Cooperative matrices require the Vulkan memory model in SPIR-V.

OpTypeCooperativeMatrixKHR can only be instantiated by a variable in the Function or Private storage classes.

HLSL/Direct3D

Microsoft calls them WaveMatrices, and added them to SM6.8 as experimental.

Google provided feedback via https://github.com/microsoft/hlsl-specs/issues/72

This experimental feature was later withdrawn.

Jul 14, 2025 Chris Bieneman posted a revised proposal for HLSL linalg. See https://github.com/microsoft/hlsl-specs/pull/556

MSL/Metal

Apple calls them simdgroup matrices and support has existed since MSL 2.3. They are limited to relatively few GPUs though (Apple7+ in the feature set). That is iPhone 12+, some newer iPads, and the M1 and later macs.

MSL support a small number of matrix variants:

  • simdgroup_float8x8
  • simdgroup_half8x8
  • simdgroup_bfloat8x8 (MSL 3.1)

The simdgroup_float8x8 and simdgroup_half8x8 types are equivalent to a SPIR-V 8x8 cooperative matrix with Subgroup scope and corresponding component type. Unlike SPIR-V and HLSL, MSL does not specialize the matrices based on the use.

MSL also has more limited supported operations on simdgroup matrices. For load/store/creation:

  • simdgroup_matrix<_T_, _M_, _N_>: Create diagonal matrix with single value.
  • make_filled_simdgroup_matrix<_T_, _M_, _N_>: Create single value filled matrix.
  • simdgroup_load/store: specify stride, layout (via transpose), and an offset (via origin)

Similar to SPIR-V the device or threadgroup pointer points to a matrix component type.

MSL supports multiply and multiply-accumulate (non-saturating) arithmetic operations.

All functions must be called from simdgroup uniform control flow. Presumably load and store parameter values must be simdgroup uniform though I can’t see this called out in the spec.

MSL allows interfaces to be declared as simdgroup_matrix, but no operators exist for them to make them usable. Effectively, this limits them to function storage.

SYCL

SYCL is a single-source CPU/GPU compute language, and may reflect support among GPU hardware, at least as exposed through OpenCL.

SYCL has a corresponding proposal, using the terminology “joint matrix”. See https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/experimental/sycl_ext_oneapi_matrix/sycl_ext_oneapi_matrix.asciidoc

Proposal

WGSL

Enable Extension

Add a new enable extension, chromium_experimental_subgroup_matrix. Enables the declaration of subgroup_matrix types, related built-in functions, and limited use of some scalar types (u8 and i8). Implicitly depends on subgroups. WGSL does not require any subgroups enable, but the API does.

Types

Add new types:

  • subgroup_matrix_left<_T_, _K_, _M_>: M rows x K columns matrix of T components.
  • subgroup_matrix_right<_T_, _N_, _K_>: K rows x N columns matrix of T components
  • subgroup_matrix_result<_T_, _N_, _M_>: M rows x N columns matrix of T components
  • M, N, and K are override-expressions of positive integer values
    • For initial prototyping and feedback, we will restrict them to const-expression. We should finalize const vs. override before full standardization.
    • Similar to arrays, two subgroup matrices are the same if:
      • They are the same base type
      • They have the same component type
      • The have matching row counts such that:
        • Both are equal valued const-expressions, or
        • Both resolve to the same override-declaration
      • The have matching column counts such that:
        • Both are equal valued const-expressions, or
        • Both resolve to the same override-declaration
      • Note: like arrays, types cannot match if the row or column counts use an override-expression that is not equivalent to override identifier.
  • T is the component type and must be one of the entries in the table
    • The scalar shader type is the associated type usable in the shader code for scalar operations and data representation in memory
    • Can be expanded in the future to support more types (e.g. bfloat16) via new enables.
    • The u8 and i8 cases are predeclared types that are not otherwise usable in WGSL. For layout calculations, they are of size 1 byte and have an alignment requirement of 1 byte.
Type Extra Enable Element Stride (bytes) Shader Scalar Type Min Value Max Value
f32 4 f32
f16 f16 2 f16
u32 4 u32
i32 4 i32
u8 1 u32 0 255
i8 1 i32 -128 127

These types are not considered “composite” in the WGSL taxonomy, because they are not decomposible. You can’t reference a sub-vector or a single component. The numeric dimensions must be override-expressions. These types cannot be part of any interface (i.e. they can only be instantiated in Function and Private address spaces). They are plain types (similar to atomics) so that they can be included in composite types. An important use case is to make arrays of these matrices.

It is a pipeline-creation error if any matrix type is not included in a supported GPUSubgroupMatrixConfig, config, on the device:

  • For subgroup_matrix_left:
    • M equals config.M
    • K equals config.K
    • T matches config.componentType
  • For subgroup_matrix_right:
    • K equals config.K
    • N equals config.N
    • T matches config.componentType
  • For subgroup_matrix_result:
    • M equals config.M
    • N equals config.N
    • T matches config.resultType

Why use a “left” and “right” matrix type, instead of a single matrix type like Metal has?

  • D3D and Vulkan both need them.
  • There is no free transpose operation. If you had a free transpose, then you can take advantage of: A* B = transpose(transpose(B)*transpose(A)). I haven’t seen a transpose operation in APIs or hardware specs.

Variables

A variable containing a subgroup matrix can only be instantiated in Function or Private address spaces. (This limitation comes from SPIR-V and Metal). These variables are meant to be used as very temporary scratch space.

Loading and storing

Builtin functions are used to load and store subgroup matrix values from variables in workgroup and storage address spaces. The builtins do two things:

  • Map matrix row and column indices to external memory locations. This mapping has two parameters: majorness (row-major, or column-major), and an integer Stride. Let Base be the byte address of the start of the external matrix, i.e. where the [0,0]’th element of the matrix is stored. Then
    • For row-major:
      • Matrix entry [r,c] maps to the sizeof(T) bytes located at Base + Stride*r*sizeof(T) + sizeof(T)*c
      • Stride >= number of matrix columns.
    • For column-major:
      • Matrix entry [r,c] maps to the sizeof(T) bytes located at Base + Stride*c*sizeof(T) + sizeof(T)*r
      • Stride >= number of matrix rows.
  • Reinterpret data values between the shader scalar type and the external component type T, when those types differ.

For a subgroup_matrix_left/right/result<T, Cols, Rows>, loads and stores are out-of-bounds if the length of the array of the pointer argument is less than Offset + Stride * Rows* Cols.

Attributes

Add an additional error condition to workgroup_size. It is a pipeline-creation error if the x dimension is not a multiple of GPUAdapterInfo.subgroupMaxSize and the shader statically accesses any subgroup matrix.

Note: this is where the requirement on the subgroups feature stems from in practice.

Expressions

Supported expressions:

  • Parenthesization
  • Identifier expressions
  • Indirection
  • Address-of
  • Function call expression
  • Type expressions

Explicitly not supported:

  • Decomposition expressions

Possible future expansion:

  • Arithmetic expressions

Built-in Values

New built-in value: subgroup_id.
Input in compute shaders of type u32.
It is the current invocation's subgroup's ID within the workgroup.

Note: This was not included in the subgroups feature because there was insufficient documentation detailing the mapping in HLSL between workgroups and subgroups. This experiment is not implemented on D3D so the built-in value is available on supported platforms. If guaranteed mapping from Microsoft is provided for D3D, subgroup_id could be added as a language feature on top of the subgroups feature.

Built-in Functions

Calls to these functions:

  • Must only be used in a compute shader stage.
  • Trigger a chromium_experimental.subgroup_matrix_uniformity diagnostic if uniformity analysis cannot prove the call is in subgroup uniform control flow.

Value constructors

Overloads:

rust
@must_use fn T(value : S) -> T
@must_use fn T() -> T

Preconditions:
T is a subgroup matrix type whose shader scalar type is S.

Description:
Create a subgroup matrix filled with value.

When no value is provided, use S().

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove value is a subgroup uniform value.

Load/store functions

See Loading and Storing above.

Overload:

rust
@must_use fn
subgroupMatrixLoad<T>(p : ptr<AS, SA, AM>,
                      offset : u32,
                      col_major : bool,
                      stride : u32) -> T

Preconditions:
T is a subgroup matrix type with shader scalar type S.
SA is an array with type S.
AS is storage or workgroup.
AM is read or read_write.

Description:
Load a subgroup matrix from p, offset elements from the start of the array.

col_major must be an override-expression.

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove p, offset, or stride are subgroup uniform
values.

offset and stride count elements of the component type of T.
Behavior is undefined if stride is less than:

  • The number of rows of T if col_major is true
  • The number of columns of T is col_major is false

Overload:

rust
fn subgroupMatrixStore(p : ptr<AS, SA, AM>,
                       offset : u32,
                       value : T,
                       col_major : bool,
                       stride : u32)

Preconditions:
T is a subgroup matrix type whose scalar shader type is S.
SA is an array with element type S.
AS is storage or workgroup.
AM is write or read_write.

Description:
Store the subgroup matrix value into p, offset elements from the start of the array.

col_major must be an override-expression.

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove p, offset, value, or stride are subgroup
uniform values.

offset and stride count elements of the component type of T.
Behavior is undefined if stride is less than:

  • The number of rows of T if col_major is true
  • The number of columns of T is col_major is false

Matrix arithmetic functions

The operands of a subgroup matrix arithmetic function comprise a supported subgroup matrix configuration if the device has a GPUSubgroupMatrixConfig, config, such that all operand types match as below:

  • For L:
    • M equals config.M
    • K equals config.K
    • T matches config.componentType
  • For R:
    • K equals config.K
    • N equals config.N
    • T matches config.componentType
  • For RT:
    • M equals config.M
    • N equals config.N
    • TR matches config.resultType

Overload:

rust
@must_use fn
subgroupMatrixMultiply<RT>(left : L, right : R) -> RT

Preconditions:
L is a subgroup_matrix_left<T, M, K>.
R is a subgroup_matrix_right<T, K, N>.
RT is a subgroup_matrix_result.

Description:
Matrix multiply.

It is a pipeline-creation error if L, R, and RT do not comprise a supported
subgroup matrix configuration.

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove left or right are subgroup uniform values.

Overload:

rust
@must_use fn
subgroupMatrixMultiplyAccumulate<RT>(left : L,
                                     right : R,
                                     acc : RT) -> RT

Preconditions:
L is a subgroup_matrix_left<T, M, K>.
R is a subgroup_matrix_right<T, K, N>.
RT is a subgroup_matrix_result.

Description:
Matrix multiply add (left * right + acc).

It is a pipeline-creation error if L, R, and RT do not comprise a supported
subgroup matrix configuration.

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove left or right are subgroup uniform values.

Scalar arithmetic functions

These functions are useful for operations such as applying biases to a model.

Overload:

rust
@must_use fn
subgroupMatrixScalarAdd(matrix : M, value : S) -> M

Preconditions:
M is a subgroup matrix type with scalar shader type S.

Description:
Scalar addition.

value is clamped to a valid range for the component type of M and then added to
each element of matrix.

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove matrix or value are subgroup uniform values.

Overload:

rust
@must_use fn
subgroupMatrixScalarSubtract(matrix : M, value : S) -> M

Preconditions:
M is a subgroup matrix type with scalar shader type S.

Description:
Scalar subtraction.

value is clamped to a valid range for the component type of M and then added to
each element of matrix.

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove matrix or value are subgroup uniform values.

Overload:

rust
@must_use fn
subgroupMatrixScalarMultiply(matrix : M, value : S) -> M

Preconditions:
M is a subgroup matrix type with scalar shader type S.

Description:
Scalar multiplication.

value is clamped to a valid range for the component type of M and then added to
each element of matrix.

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove matrix or value are subgroup uniform values.

REMOVED (in an attempt to be safely forward compatible if Metal adds integral components, but no other operators)

Overload:

rust
@must_use fn
subgroupMatrixScalarDivide(matrix : M, value : S) -> M

Preconditions:
M is a subgroup matrix type with scalar shader type S.

Description:
Scalar division.

value is clamped to a valid range for the component type of M and then added to
each element of matrix.

Triggers a chromium_experimental.subgroup_matrix_uniformity diagnostic if
uniformity analysis cannot prove matrix or value are subgroup uniform values.

Uniformity

Subgroup matrices are an abstraction of data spread among the invocations in a subgroup. As such the built-in functions must only be called from subgroup uniform control flow. Additionally, most of the parameter values must also be subgroup uniform values.

WGSL does not currently represent subgroup uniformity and empirical testing shows that implementations do not provide reliable reconvergence. Vulkan backends that implement VK_KHR_shader_subgroup_uniform_control_flow or, even better, VK_shader_maximal_reconvergence would be able to make enabling the analysis at other scopes reasonable, but that is not portable.

For this experimental extension we should add a diagnostic, chromium_experimental.subgroup_matrix_uniformity, that defaults to error. This diagnostic would be triggered based on workgroup uniformity violations (if the control is not off) for all subgroup matrix built-in functions. This allows applications some control. We can gather data about the usefulness of the static analysis for subgroup matrices. It is likely that most use cases would satisfy workgroup uniformity.

Floating-point Accuracy

subgroupMatrixLoad and subgroupMatrixStore should be bit preserving. ULPs are undefined for other operations.

API

New GPUFeatureName chromium-experimental-subgroup-matrix

  • Requires subgroups (for maxSubgroupSize)
  • Vulkan:
    • [vkPhysicalDeviceCooperativeMatrixFeaturesKHR](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkPhysicalDeviceCooperativeMatrixFeaturesKHR)::cooperativeMatrix is VK_TRUE
    • [vkPhysicalDeviceCooperativeMatrixPropertiesKHR](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkPhysicalDeviceCooperativeMatrixPropertiesKHR)::cooperativeMatrixSupportedStages includes VK_SHADER_STAGE_COMPUTE_BIT
    • The supported configurations list is non-empty
    • [vkPhysicalDeviceVulkanMemoryModelFeatures](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkPhysicalDeviceVulkanMemoryModelFeatures)::vulkanMemoryModel is VK_TRUE
    • [subgroupSizeControl](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#features-subgroupSizeControl)feature must be enabled
    • computeFullSubgroups feature must be enabled
    • Vulkan pipelines will need to be compiled with VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT and VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT, or the SPIR-V module must be version 1.6 or later
  • Metal:
    • Family is Apple 7+
  • D3D: no support

New immutable array, subgroupMatrixConfigs, added to GPUAdapterInfo.

json
partial interface GPUAdapterInfo {
  [SameObject] readonly attribute FrozenArray<GPUSubgroupMatrixConfig> subgroupMatrixConfigs;
};

enum GPUSubgroupMatrixComponentType {
  "f32",
  "f16",
  "u32",
  "i32",
  "u8",
  "i8",
};

interface GPUSubgroupMatrixConfig {
  readonly attribute GPUSubgroupMatrixComponentType componentType;
  readonly attribute GPUSubgroupMatrixComponentType resultComponentType;
  readonly attribute unsigned long M;
  readonly attribute unsigned long N;
  readonly attribute unsigned long K;
};

Validation

No specific API validation is necessary; however, it is likely easier to implement the pipeline-creation error checks in Dawn by adding the subgroup matrix configurations to the shader reflection information.

WGSL pipeline-creation checks (repeated for ease of reference):

  • All subgroup matrix types are part of a GPUSubgroupMatrixConfig
  • All subgroup matrix operands in subgroupMatrixMultiply and subgroupMatrixMultipleAccumulate are part of a single GPUSubgroupMatrixConfig
  • The x-dimension of workgroup_size is a multiple of GPUSupportedLimits::maxSubgroupSize

Mapping

Types

Type SPIR-V1,2 MSL3
subgroup_matrix_left<T, K, M> OpTypeCooperativeMatrixKHRMatrixAKHR useM rowsK colsT component type simdgroup_matrix<T, 8, 8>
subgroup_matrix_right<T, N, K> OpTypeCooperativeMatrixKHRMatrixBKHR useK rowsN colsT component type simdgroup_matrix<T, 8, 8>
subgroup_matrix_result<T, N, M> OpTypeCooperativeMatrixKHRMatrixAccumulatorKHR useM rowsN colsT component type simdgroup_matrix<T, 8, 8>
  1. All OpTypeCooperativeMatrixKHR use subgroup scope.
  2. Component type enum maps directly to SPIR-V type (e.g. i8 to OpTypeInt 8 1).
  3. MSL types use the usual mappings (e.g. f32 to float).

Builtin-in Values

Value SPIR-V MSL
subgroup_id SubgroupId simdgroup_index_in_threadgroup

Functions

Function SPIR-V MSL
Value constructors OpCompositeConstruct with appropriate valueconst-/override-expressions could use constant instructions make_filled_simdgroup_matrix
subgroupMatrixLoad OpCooperativeMatrixLoadKHRPointer operand is a direct translation of the WGSL pointer simdgroup_matrix_loadThe WGSL pointer needs translated into the origin operand in MSL
subgroupMatrixStore OpCooperativeMatrixStoreKHRPointer operand is a direct translation of the WGSL pointer simdgroup_matrix_storeThe WGSL pointer needs translated into the origin operand in MSL
subgroupMatrixMultiply OpCooperativeMatrixMulAddKHRC is a zero value matrix matching the result type simdgroup_multiply
subgroupMatrixMultiplyAccumulate OpCooperativeMatrixMulAddKHR simdgroup_multiply_accumulate
subgroupMatrixScalarAdd OpI/FAdd with composite constructed value simdgroup_multiply_accumulate with diagonal matrix for b and filled matrix scalar value for c
subgroupMatrixScalarSubtract OpI/FSub with composite constructed value simdgroup_multiply_accumulate with diagonal matrix for b and filled inverted matrix scalar value for c
subgroupMatrixScalarMultiply OpI/FMul with composite constructed value simdgroup_multiply with diagonal matrix scalar value for b1
subgroupMatrixScalarDivide OpI/S/FDiv with composite constructed scalar or OpMatrixTimesScalar (for float components with reciprocal scalar value) simdgroup_multiply with diagonal matrix reciprocal scalar value for b1
  1. This works because MSL only supports floating-point component types.

Properties

Vulkan

Filter the list returned from vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR such that:

  • A type and B type match
  • C type and result type match
  • A, B, C, and result types are one of (-> API enum):
    • VK_COMPONENT_TYPE_FLOAT32_KHR -> f32
    • VK_COMPONENT_TYPE_FLOAT16_KHR -> f16
    • VK_COMPONENT_TYPE_SINT32_KHR -> i32
    • VK_COMPONENT_TYPE_UINT32_KHR -> u32
    • VK_COMPONENT_TYPE_SINT8_KHR -> i8
    • VK_COMPONENT_TYPE_UINT8_KHR -> u8
  • saturatingAccumulation is false
  • Scope is VK_SCOPE_SUBGROUP_KHR

VK_COMPONENT_TYPE_FLOAT16 will need to be filtered out of the device properties if the shader-f16 feature is not requested.

Metal

Hardcode the following configurations if the feature is supported:

componentType resultComponentType M N K
f32 f32 8 8 8
f16 f16 8 8 8
  1. Filter out f16 from the device properties if shader-f16 is not requested on the device.

Future Expansion

It is likely that more component types can be supported in the future. Metal can already support bfloat16 for example.

We could consider exposing saturating accumulation.


Dawn Features Timestamp Query Inside Passes (`docs/dawn/features/timestamp_query_inside_passes.md`)

Timestamp Query Inside Passes (experimental!)

Adds support for the writeTimestamp call inside render and compute passes.
This method might not always make a lot of sense on tiler GPUs (so basically any modern GPU) where the ordering of operations inside render passes is not strictly like the order of commands recorded (each tile being executed independently),

Adds the following methods:

text
voi wgpu::ComputePassEncoder::writeTimestamp(wgpu::QuerySet querySet, uint32_t queryIndex);
voi wgpu::RenderPassEncoder::writeTimestamp(wgpu::QuerySet querySet, uint32_t queryIndex);

The validation is the following:

  • querySet must be a valid query set.
  • querySet must have been created with wgpu::QueryType::Timestamp.
  • queryIndex must be less than querySet's size.
  • During the wgpu::Queue::Submit for these commands, querySet must not be destroyed.

The initial tracking bug was https://crbug.com/dawn/434.


Dawn Features Transient Attachments (`docs/dawn/features/transient_attachments.md`)

Transient Attachments

Transient attachments allow render pass operations to stay in tile memory,
avoiding VRAM traffic and potentially avoiding VRAM allocation for the textures.

Presence of the transient-attachments feature on an Adapter is a hint
indicating that the TransientAttachment usage will be used by the backend. If
it's not present, the usage is allowed, but it is a no-op.
Enabling this feature on a Device doesn't do anything.

Example Usage:

cpp
wgpu::TextureDescriptor desc;
desc.format = wgpu::TextureFormat::RGBA8Unorm;
desc.size = {1, 1, 1};
desc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::TransientAttachment;

auto transientTexture = device.CreateTexture(&desc);

// Can now create views from the texture to serve as transient attachments, e.g.
// as color attachments in a render pipeline.

Textures with the TransientAttachment usage:

  • Must have exactly the usages RenderAttachment | TransientAttachment.
  • May not remove any usages when creating views.
  • May not be used with load/store in render passes.
  • May not be used as resolve attachments.

Dawn Features Unorm16 Filterable (`docs/dawn/features/unorm16_filterable.md`)

Unorm16 filterable

This optional feature makes R16Unorm, RG16Unorm and RGBA16Unorm filterable.
It is needed to allow Graphite to sample (with filtering) individual planes of HDR YUV formats that have planes with formats R16Unorm and RG16Unorm.


Dawn Features Unorm16 Formats For External Texture (`docs/dawn/features/unorm16_formats_for_external_texture.md`)

Unorm 16 formats for external textures

Chromium needs to create ExternalTextures on YUV HDR videos for 0-copy interop, using formats such as wgpu::TextureFormat::R10X6BG10C6Biplanar420Unorm and friends.
These formats have planes with R/RG16Unorm formats that need to work to import ExternalTextures from video, even if other texture format extensions haven't been enabled.

This feature loosens validation in two specific places to allow using YUV HDR videos without exposing R/RG16Unorm for anything else:

  • Views of planes of YUV HDR textures can be created using the R/RG16Unorm formats even if the formats are not otherwise allowed on the device.
  • Validation of CreateExternalTexture considers R/RG16Unorm to be filterable float, even if they are unfilterable-float (even when enabled with wgpu::FeatureName::TextureFormatsTier1).

Dawn Features Y Cb Cr Vulkan Samplers (`docs/dawn/features/y_cb_cr_vulkan_samplers.md`)

YCbCr Vulkan Samplers

Video playback in Vulkan on Android works via so called YCbCr sampling (note:
this is not the only context in which YCbCr sampling can be used on Android, but
for video playback it must be used). In a nutshell:

  • The AHardwareBuffer containing the video frame has YCbCr information that can
    be queried. This YCbCr information has an opaque "external format" integer
    that must be supplied when creating a sampler to sample this
    AHardwareBuffer. It's possible for this format or other parts of the YCbCr
    info to change from frame to frame (i.e., between AHBs), and hence the
    information must be queried every frame.
  • The client must query that information and use it to create an immutable
    sampler
  • The client must then package that sampler together with the information of
    the sampled image into a combined image sampler that is inserted into the
    VkDescriptorSetLayout
  • The client must insert the sampled image (i.e., an image wrapping the AHB)
    into the VkDescriptorSet
  • The client's SPIR-V must either directly create a SampledImage from the
    combined image sampler or must create sampler and texture variables that both
    point to the combined image sampler entry and then create a SampledImage from
    the sampler and texture variables

Supporting YCbCr Sampling in Dawn: Semantics

A complicating factor of the challenge here is that combined image samplers are
intentionally not a part of the WebGPU specification. Dawn's support for YCbCr
sampling on Android rests on the following features:

  • Static samplers, which allow a client to pass a sampler object
    directly in the BindGroupLayout rather than the BindGroup.
  • The ability to query YCbCr info from SharedTextureMemory
    and create YCbCr samplers from this info (details below)
  • The ability to specify in the BindGroupLayout that a specific static sampler
    entry and texture entry are paired to one another - that sampler will be used
    to sample only that texture, and that texture will be sampled only by that
    sampler. This is done via adding an optional sampledTextureBinding to the
    static sampler entry in the BindGroupLayout.

The y-cb-cr-vulkan-samplers feature allows specification of VkSamplerYcbcrConversionCreateInfo as
part of the static vulkan sampler descriptor. For this purpose, clients
can supply YCbCrVkDescriptor instances when creating samplers and
texture views. Clients can also obtain the YCbCr info for a
SharedTextureMemory instance that was created from an AHardwareBuffer by
querying its properties. Most properties will be created directly from the
corresponding buffer format properties on the underlying AHardwareBuffer. The
two exceptions are as follows:

  • vkChromaFilter: Will be set to FilterMode::Linear iff
    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT is
    present in the AHB format features and FilterMode::Nearest otherwise
  • forceExplicitReconstruction: will be set to true iff
    VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_BIT
    is present in the AHB format features

Implementing YCbCr Sampling via Dawn from the Client's Perspective

Putting the above together, a typical client flow would look like this:

  • Obtain the AHB for a video frame
  • Create a SharedTextureMemory instance from that AHB
  • Query that SharedTextureMemory instance for its YCbCr info
  • Create a YCbCr sampler using that YCbCr info
  • Create a BindGroupLayout that includes that YCbCr sampler with a
    sampledTextureBinding annotation that points to the entry of the texture
    that will be sampled (i.e., where the AHB will be passed). That texture entry
    must have format type EXTERNAL. Note: The client might want to cache created
    BGLs for performance; if so, this caching must be keyed off the YCbCr info.
  • Create WGSL that references the sampler and texture via their respective
    indices in the BindGroupLayout, just as if they were an "ordinary" sampler and
    texture.
  • Create a BindGroup that passes a texture obtained from the SharedTextureMemory
    wrapping the AHB in the texture entry that had been reserved for that texture
    in the BindGroupLayout.

Implementation Details

  • Static samplers are translated to immutable samplers in the Vulkan backend.
  • If the static sampler has a sampledTextureBinding annotation, it is
    translated to a combined image sampler rather than a regular sampler entry.
    The texture information for that combined image sampler is that which the
    client passed in the corresponding texture entry.
  • In BindGroup->VkDescriptorSet translation, the texture is passed at the index
    for the combined image sampler in the VkDescriptorSetLayout.
  • In WGSL->SPIR-V translation, any variables created from the texture entry in
    the BindGroup are translated to point to the combined image sampler in the
    VkDescriptorSet (note that variables created from the sampler entry will
    naturally point to the combined image sampler, since the combined image
    sampler is created at the index for the sampler entry).

Validation

We validate the following:

  • YCbCr samplers must be static samplers
  • YCbCr samplers must have a sampledTextureBinding annotation
  • The sampledTextureBinding annotation must point to a valid entry
  • No two sampler entries can point at the same texture entry
  • Textures sampled by YCbCr samplers must have format type External

Tint Extensions Chromium Experimental Barycentric Coord (`docs/tint/extensions/chromium_experimental_barycentric_coord.md`)

Chromium Experimental Barycentric Coordinates

The chromium_experimental_barycentric_coord is an experimental extension that allows the use of the
barycentric_coord builtin in fragment shaders in WGSL.

Status

This extension is experimental and the syntax is being discussed. No official WebGPU specification
has been written yet.

Availability

The usage is restricted to fragment shaders.

Backend Type Requirements
SPIR-V vec3f VK_KHR_fragment_shader_barycentric. Available as the BaryCoordKHR builtin
HLSL vec3f Requires D3D12, HLSL 6.1. Available as the SV_Barycentrics semantic
GLSL vec3f GLSL_EXT_fragment_shader_barycentric (Requires GLSL 4.50 and later, ESSL 3.2 and later). Available as the gl_BaryCoordEXT builtin
Metal f32, vec2f, or vec3f Requires Metal 2.2 on MacOS or Metal 2.3 on iOS. Available as [[barycentric_coord]], can test with supportsShaderBarycentricCoordinates

Vulkan support unfortunately looks pretty low with 10.76% of devices on GPUInfo.org reporting support. The relevant GL extension doesn't even appear on the site.

Due to the limited availability, this will need a enable statement to be used. For this
experimental extension it would be enable chromium_experimental_barycentric_coord.

Additionally, anywhere that barycentrics are supported they support perspective corrected and non-perspective correct
values are available. Most backends support multiple interpolation modes, but only centroid is available everywhere.

Backend Supported Interpolation Modes
SPIR-V BaryCoordKHR, BaryCoordNoPerspKHR, both can be decorated with the Centroid or Sample interpolation qualifiers
HLSL float3 b : SV_Barycentrics (default, perspective correct), noperspective float3 b : SV_Barycentrics. Interpolation modes like linear, centroid, and sample are supported
GLSL gl_BaryCoordEXT, gl_BaryCoordNoPerspEXT, both can be decorated with the centroid or sample interpolation qualifiers
Metal [[barycentric_coord, center_perspective]] (default, can be omitted), [[barycentric_coord, center_no_perspective]]

Specification

This extension adds a new builtin_value_name entry for barycentric_coord.

An entry is added to the Built-in input and output values table:

Category Value
Name barycentric_coord
Stage fragment
Direction input
Type vec3
Extension chromium_experimental_barycentric_coord
  • The value contains the relative weights for each vertex in the primitive.
  • Fragments in the corners of triangle primitives will have weights of (1,0,0), (0,1,0), and (0,0,1)
  • Fragments at the endpoints of line primitives will have weights of (1,0,0), and (0,1,0). The z component will always be 0.
  • Support for point primitives is unclear (Metal mentions that they will have yz components of (0, 0), HLSL and GLSL make no mention of points.)
  • The value of the three components should add up to approximately 1, but strictly adding up to 1 is not guaranteed.

Example usage

wgsl
@fragment fn fs_main(@builtin(barycentric_coord) bary_coord: vec3f) -> @builtin(color) vec4f {
    return vec4f(bary_coord, 1.0);
}

References


Tint Extensions Chromium Experimental Framebuffer Fetch (`docs/tint/extensions/chromium_experimental_framebuffer_fetch.md`)

Chromium Experimental Framebuffer Fetch

The chromium_experimental_framebuffer_fetch extension adds support for framebuffer inputs to fragment stages in WGSL.
Framebuffer input, or framebuffer fetch, lets a fragment shader read the value of framebuffer attachments for the current fragment.
It synchronizes the read with all previous writes to the same fragment (in API order).

Status

Framebuffer Fetch support in Tint is experimental and might change as we gain implementation experience.
It will also be updated to follow an upstream specification if one is made at some point.
Specification work in the WebGPU group hasn't started.

Pseudo-specification

This adds a new builtin attribute @color which takes a u32 argument and is only allowed on @fragment entry point scalar / vector inputs.

  • Two input variables cannot have the same @color attribute.
  • The use of @color(N) with a multi-sampled color attachment will cause the fragment shader to run once per sample.

Example usage

text
@fragment fn main(@color(0) vec4f previousColor) -> @location(0) vec4f {
    let color = computeThisFragmentColor();
    return myBlend(previousColor, color);
}

To-dos

  • Are f16 types allowed for the @color inputs?

Tint Extensions Chromium Experimental Resource Table (`docs/tint/extensions/chromium_experimental_resource_table.md`)

Chromium Experimental Resource Table

The chromium_experimental_resource_table is an experimental extension that provides different
prototype options to support the WebGPU bindless prototypes.

Status

This extension is experimental and the syntax is being discussed. No official WebGPU specification
has been written yet.

Availability

| Platform | Notes |
| SPIR-V | Requires SPV_EXT_descriptor_indexing extension and RuntimeDescriptorArray capability |
| HLSL | Supported for DXC, not FXC |
| MSL | Supported (TODO determine if there are Metal version restrictions) |
| GLSL | Not supported |

Due to limited availability, this will need an enable statement to be used. For this experimental
extension it would be enable chromium_experimental_resource_table.

Specification

Resource table

The concept of a Resource Table is added to WGSL. This is not a type which can be written, or
an address space, but a data table available to use with the getResource and hasResource
methods. The table is made available by the system and is just accessible.

`hasResource`

text
@must_use fn hasResource<T>(index: I) -> bool
  • I is an i32 or u32
  • T is a format-less storage texture (e.g. texture_storage_2d<f32>), sampled texture, multisampled
    texture, or depth texture.

hasResource returns true if the item a index of the resource table is of type T.

`getResource`

text
@must_use fn getResource<T>(index: I) -> T
  • I is an i32 or u32
  • T is a format-less storage texture (e.g. texture_storage_2d<f32>), sampled texture, multisampled
    texture, or depth texture.

getResource returns the value in the resource table at index of type T.

If index is outside the bounds of the resource table then a default value of type T will be
returned. If the item at index is not of type T then a default value of type T is returned.
Essentially, a value is always returned, it may just be a synthensized default value.

Example usage

Resource Table

text
enable chromium_experimental_resource_table;

const kHouseTexture = 0u;

@fragment fn fs() {
    if (hasResource<texture_2d<f32, filterable>>(kHouseTexture)) {
        let tex = textureLoad(getResource<texture_2d<f32, filterable>>(kHouseTexture));
    }
}

Tint Extensions Chromium Experimental Static Samplers (`docs/tint/extensions/chromium_experimental_static_samplers.md`)

Chromium Experimental Static Samplers

The chromium_experimental_static_samplers extension is an experimental extension that allows the use of static samplers in WGSL.
Static samplers translate to immutable samplers in Vulkan, constexpr samplers in Metal Shading Language, and static samplers in Direct3D 12.

Status

This extension is experimental and the syntax is being discussed. No official WebGPU specification has been written yet.

Design considerations

const or var

@group(X) @binding(Y) const mySampler;
or
@group(X) @binding(Y) var mySampler;

In HLSL and SPIR-V, static samplers need a binding slot, hence the @group(X) @binding(Y) syntax.
Vulkan and Direct3D 12 supply static samplers via the API.
Metal Shading Language declares static samplers with constexpr, which is ready to be used in the shader without Metal API interaction.

Normal samplers in WGSL are declared with var because they have a handle address space, which is read-only.
Using the const keyword for static samplers is sensible, as static samplers are immutable objects.
However, const does not have a handle address space which is required for HLSL and SPIR-V and would require spec changes to support it.

Using var for static samplers is recommended for consistency with the rest of the language and they are already immutable.

Using a struct

wgsl
@group(X) @binding(Y) const mySampler = sampler {
  addressModeU: mirror_repeat, // enumerant
  magFilter: linear,
  lodMinClamp: 1, // abstract float
  ...
};

The sampler is a built-in struct that can be used to define samplers.
Advantages are its readability and similarity to the WebGPU API's GPUSamplerDescriptor struct.

This approach would require a new WGSL feature for initializing struct values.

If the new WGSL feature is introduced, this approach is recommended for readability and intuitiveness.
However, for the initial implementation, it is not desirable to introduce a substantial change in WGSL.

Using a function

wgsl
const clamp_to_edge = 0;
const mirror_repeat = 1;
...
sampler(mirror_repeat, linear, 0.0, 16);
sampler_comparison(mirror_repeat, linear, 0.0, 16);

or

wgsl
sampler<mirror_repeat, linear>(0.0, 16);
sampler_comparison<mirror_repeat, linear>(0.0, 16);

In both cases, sampler is a built-in function that returns a static sampler.
The first approach takes a list of arguments. These arguments are predefined constant values and not enums.
The second approach takes a list of enum template parameters and a list of arguments. The arguments are LOD min and max clamps, because they are not enums.

Both approaches are readable and intuitive but due to the large amount of parameters that can be set on a sampler, the approaches may become hard to read and write.
The approaches do not need any new WGSL features and are sufficient for a prototype implementation.

For the prototype implementation, the first function approach is good for its simplicity.

Pseudo-specification

This extension adds sampler(...) and sampler_comparison(...) functions to return a sampler type.
The functions take integer values that are predefined constants. If a sampler is initialized with any of the two functions, it is considered a static sampler, otherwise it is a normal sampler.
The predefined constant variable names are similar to the WebGPU API enums.

Function restrictions include:

  • The function can only be called to initialize a static sampler.
  • Only built-in functions can return a sampler, user-defined functions cannot.
  • The function can not be used in an expression, only in a declaration.

Order of arguments:

  • addressModeU
  • addressModeV
  • addressModeW
  • magFilter
  • minFilter
  • mipmapFilter
  • compare (only for sampler_comparison(...))
  • lodMinClamp
  • lodMaxClamp

New Constants:

  • Address Mode:
    • WGSL: clamp_to_edge=0, repeat=1, mirror_repeat=2
    • WebGPU equivalent: "clamp-to-edge", "repeat", "mirror-repeat"
  • Filter Mode:
    • WGSL: nearest=0, linear=1
    • WebGPU equivalent: "nearest", "linear"
  • Mipmap Filter Mode:
    • WGSL: mipmap_nearest=0, mipmap_linear=1
    • WebGPU equivalent: "nearest", "linear"
  • Comparison Function:
    • WGSL: never=0, less=1, equal=2, less_equal=3, greater=4, not_equal=5, greater_equal=6, always=7
    • WebGPU equivalent: "never", "less", "equal", "less-equal", "greater", "not-equal", "greater-equal", "always"

Example usage

wgsl
@group(0) @binding(0) var mySampler = sampler(mirror_repeat, clamp_to_edge, mirror_repeat, linear, linear, mipmap_linear, 0.0, 16.0)

@group(0) @binding(1) var myComparisonSampler = sampler_comparison(mirror_repeat, clamp_to_edge, mirror_repeat, linear, linear, mipmap_linear, less, 0.0, 16.0)

Tint Extensions Chromium Internal Input Attachments (`docs/tint/extensions/chromium_internal_input_attachments.md`)

Chromium Internal Input Attachments

The chromium_internal_input_attachments extension adds support for input attachment global variables to WGSL.
This is similar to Vulkan's subpassInput variable.

Status

Input attachments support in Tint is highly experimental and only meant to be used in internal transforms at this stage.
This extension is only relevant to SPIRV backend atm.
Specification work in the WebGPU group hasn't started.

Pseudo-specification

This extension adds:

  • A new input_attachment<T> type in the handle address space, where T is u32, i32 or f32. It's only allowed on module-scoped variable declarations.
    • This will be mapped to OpTypeImage with Dim=SubpassData in SPIRV.
  • A new input_attachment_index(n) attribute where n is a u32 or i32 with a positive value. Unlike binding which specifies the binding point in a BindGroup, this attribute specifies the index of the input attachment in the render pass descriptor. This is required for input_attachment<T> type.
    • This is equivalent to InputAttachmentIndex decoration in SPIRV.
  • A new inputAttachmentLoad builtin function with signature:
    inputAttachmentLoad(input_attachment<T>) -> vec4<T>
    • This will emit OpImageRead function call in SPIRV.
  • All of the above are only available in fragment state.

Example usage

text
@group(0) @binding(0) @input_attachment_index(0)
    var input_tex : input_attachment<f32>;

@fragment fn main() -> @location(0) vec4f {
    return inputAttachmentLoad(input_tex);
}

Tint Extensions Fragment Depth (`docs/tint/extensions/fragment_depth.md`)

Fragment depth (less, greater)

Issue: https://github.com/gpuweb/gpuweb/issues/5342

Proposal: https://github.com/gpuweb/gpuweb/blob/main/proposals/fragment-depth.md

Motivation

The ability for fragment shaders to explicitly write to the depth buffer via the @builtin(frag_depth) attribute is critical for many modern rendering techniques, including custom depth biasing for shadows, precise depth fog, and complex depth-based effects.

In the current WGSL specification, the mere act of writing to @builtin(frag_depth) often incurs a significant performance penalty because driver heuristics cannot guarantee that the fragment shader output will adhere to the depth written by the rasterizer's interpolated depth. Consequently, writing to frag_depth typically forces the GPU to disable crucial early-Z optimizations for the entire draw call.

The introduction of a new depth_mode built-in parameter for the @builtin(frag_depth) with modes less, and greater directly addresses this performance limitation by letting the developer express their intent to the hardware.

  • The less and greater modes are used when a fragment shader declares it will only write depth values that are guaranteed to be less than (or greater than) the existing depth. The driver can be assured that any fragment whose interpolated depth already fails the declared comparison cannot possibly satisfy the conditional write, allowing it to be rejected early.

HLSL

  • SV_Depth: Depth buffer data. Can be written by pixel shader
  • SV_DepthGreaterEqual: In a pixel shader, allows outputting depth, as long as it is greater than or equal to the value determined by the rasterizer. Enables adjusting depth without disabling early Z.
  • SV_DepthLessEqual: In a pixel shader, allows outputting depth, as long as it is less than or equal to the value determined by the rasterizer. Enables adjusting depth without disabling early Z.

Source: https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics

Metal

If a fragment function writes a depth value, the depth_argument needs to be specified with one of the following values: any, greater, less.

Source: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf

OpenGL

The built-in fragment shader variable gl_FragDepth may be redeclared using one of the following layout qualifiers: depth_any, depth_greater, depth_less, depth_unchanged.

Source: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_conservative_depth.txt

Vulkan

In cases where you know that the fragment shader will move the depth value in only one direction (toward or away from the viewer), you can apply the SPIR-V DepthGreater or DepthLess execution modes to the fragment shader’s entry point with the OpExecutionMode instruction.

When DepthGreater is applied, then Vulkan knows that no matter what your shader does, the resulting depth values produced by the fragment shader will only be greater than the values produced by interpolation. Therefore, if the depth test is VK_COMPARE_OP_GREATER or VK_COMPARE_OP_GREATER_OR_EQUAL, then the fragment shader cannot negate the result of a depth test that’s already passed.

Likewise, when DepthLess is applied, then Vulkan knows that the fragment shader will only make the resulting depth value less than it would have been and therefore cannot negate the result of a passing VK_COMPARE_OP_LESS or VK_COMPARE_OP_LESS_OR_EQUAL test.

Source: https://usermanual.wiki/Document/Vulkan2BProgramming2BGuide2BThe2BOfficial2BGuide2Bto2BLearning2BVulkan.1140066211.pdf

WGSL

Language extension

WGSL language extension Description
fragment_depth Adds built-in depth_mode parameter support for fragment's depth

Built-in `depth_mode` parameter to `frag_depth`

An optional depth_mode parameter declared after the built-in frag_depth name is allowed and defaults to any.

It can be either @builtin(frag_depth), @builtin(frag_depth, greater), or @builtin(frag_depth, less)

This applies depth_mode on the frag_depth built-in output value in a fragment shader.

Example usage

wgsl
requires fragment_depth;

@fragment
fn main() -> @builtin(frag_depth, greater) f32 {
  return 1.0f;
}

Built-in Value Mappings

Built-in SPIR-V MSL HLSL GLSL
frag_depth SpvBuiltInFragDepth depth(any) SV_Depth depth_any
frag_depth, greater SpvBuiltInFragDepth + OpExecutionMode DepthGreater depth(greater) SV_DepthGreaterEqual depth_greater
frag_depth, less SpvBuiltInFragDepth + OpExecutionMode DepthLess depth(less) SV_DepthLessEqual depth_less

Open sub-issues

Resources


Tint Extensions Multisampled Array Textures (`docs/tint/extensions/multisampled_array_textures.md`)

Multisampled Array Textures

The multisampled_array_textures language feature adds the
texture_multisampled_2d_array<T> WGSL type. The texel type T must be f32, i32, or u32.

Status

This is an experimental language feature based on the
multisampled array textures proposal.

Syntax

The requires directive documents the module's dependency on the language feature. It does not
enable the feature; the implementation must support it.

wgsl
requires multisampled_array_textures;

@group(0) @binding(0)
var texture : texture_multisampled_2d_array<f32>;

The language feature adds overloads for:

  • textureDimensions(texture)
  • textureNumLayers(texture)
  • textureNumSamples(texture)
  • textureLoad(texture, coordinates, array_index, sample_index)

For example:

wgsl
requires multisampled_array_textures;

@group(0) @binding(0)
var texture : texture_multisampled_2d_array<f32>;

fn loadSample(coordinates : vec2<i32>, layer : i32, sample : i32) -> vec4<f32> {
    return textureLoad(texture, coordinates, layer, sample);
}

Tint Extensions Subgroup Size Control (`docs/tint/extensions/subgroup_size_control.md`)

Subgroup Size Control

The subgroup_size_control is an extension that allows the use of the
subgroup_size attribute in compute shaders in WGSL. When the attribute subgroup_size is declared, the compute shader will only be executed with the specified subgroup size, and the built-in value subgroup_size will be the the value of the attribute subgroup_size. Otherwise, the compute shader may be executed with a subgroup size chosen by the GPU driver, which is unknown before the execution of the compute shader.

Status

This extension has been accepted as an extension in the WebGPU specification.

Availability

The usage is restricted to compute shaders.

Backend Implementation Requirements
D3D12 [WaveSize(<numLanes>)] in HLSL Shader Model 6.6 and Wave Operations support
Vulkan requiredSubgroupSize in VkPipelineShaderStageRequiredSubgroupSizeCreateInfo on the API side subgroupSizeControl and computeFullSubgroups in VK_EXT_subgroup_size_control or Vulkan 1.3
Metal Enabled when mSubgroupMinSize == mSubgroupMaxSize (only available on Apple GPUs) mSubgroupMinSize == mSubgroupMaxSize
  • On D3D12, SubgroupSizeControl is enabled when wave operations are supported and the highest supported shader model is at least 6.6 (which provides the [WaveSize] attribute in HLSL).
  • On Vulkan, SubgroupSizeControl is enabled when VkPhysicalDeviceSubgroupSizeControlFeatures.subgroupSizeControl and VkPhysicalDeviceSubgroupSizeControlFeatures.computeFullSubgroups are both VK_TRUE.
  • On Metal, SubgroupSizeControl is enabled when SubgroupMinSize == SubgroupMaxSize. Although Metal doesn't support selecting a subgroup size, currently all the Apple GPUs have a fixed subgroup size of 32, so controlling the subgroup size is trivially satisfied. Non-Apple Metal GPUs (e.g. Intel/AMD Macs) have different min/max sizes and do not support this feature.

Specification

This extension adds a new subgroup_size attribute for compute shaders.

Enabling this extension on the API side will implicitly enable the subgroups extension.

text
subgroup_size_attr :
 '@' 'subgroup_size' '(' expression ',' ? ')'

Below are the validation rules against the extension subgroup_size_control and the attribute subgroup_size:

  • Enabling subgroup_size_control must also enable subgroups.
  • subgroup_size cannot be used when subgroup_size_control is not enabled.
  • subgroup_size can only be used in the compute stage.
  • subgroup_size must be a const-expression or an override-expression that resolves to an i32 or u32.
  • subgroup_size must be a power-of-two.
  • subgroup_size must be greater than or equal to SubgroupMinSize on the current adapter.
  • subgroup_size must be less than or equal to SubgroupMaxSize on the current adapter.
  • The total compute invocations per workgroup must be a multiple of the declared subgroup_size.

Example usage

wgsl
enable subgroups;
enable subgroup_size_control;

@subgroup_size(32)
@compute @workgroup_size(64, 1, 1)
fn main(@builtin(subgroup_invocation_id) sg_id : u32,
        @builtin(subgroup_size) sg_size : u32) {

}

Notes

  1. maxComputeWorkgroupSubgroups on Vulkan

maxComputeWorkgroupSubgroups is a limitation defined in the Vulkan extension VK_EXT_subgroup_size_control and defined in VkPhysicalDeviceSubgroupSizeControlProperties.maxComputeWorkgroupSubgroups. Note that there is no such limitation on D3D12.

Limit name Type Limit class Default Compatibility Mode Default
maxComputeWorkgroupSubgroups GPUSize32 maximum - Not Supported
  • On Vulkan the workgroup_size attribute, subgroup_size attribute and maxComputeWorkgroupSubgroups limit must follow below requirement:
text
workgroup_size.x * workgroup_size.y * workgroup_size.z <= subgroup_size * maxComputeWorkgroupSubgroups

We decide not to expose maxComputeWorkgroupSubgroups in Dawn because it is not a limit on D3D12 and it is not a limit on Metal. Instead, we will validate the subgroup_size attribute against the workgroup_size attribute and the maxComputeWorkgroupSubgroups limit on Vulkan.

  1. SubgroupMinSize and SubgroupMaxSize (in wgpu::AdapterInfo)

The subgroup_size attribute is validated against SubgroupMinSize and SubgroupMaxSize directly. The separate minExplicitComputeSubgroupSize and maxExplicitComputeSubgroupSize limits are no longer needed.

  • On D3D12, SubgroupMinSize and SubgroupMaxSize are sourced from D3D12_FEATURE_DATA_D3D12_OPTIONS1::waveLaneCountMin and waveLaneCountMax. Microsoft has clarified that WaveLaneCountMax is exactly the maximum wave lane count that can be used with the HLSL [WaveSize] attribute.
    • On some Intel GPUs, pixel shaders may run with wave lane count 8 while waveLaneCountMin reports 16. This is treated as a driver issue.
  • On Vulkan, SubgroupMinSize and SubgroupMaxSize are sourced from VkPhysicalDeviceSubgroupSizeControlPropertiesEXT::minSubgroupSize and maxSubgroupSize.

References


Tint Extensions Texel Buffers (`docs/tint/extensions/texel_buffers.md`)

Chromium Experimental Texel Buffers

The texel_buffers language feature allows to store WebGPU plain
color format data with a GPUBuffer, supporting format conversion on load and store
operations. Texel buffers roughly translate to buffer textures in Vulkan
(Dim = Buffer), texture_buffer<format> in Metal Shading Language, and typed
UAV/SRV buffer views in HLSL/D3D12.

Status

The feature is actively under review for adoption in WGSL. Although a [proposal]
(https://github.com/gpuweb/gpuweb/blob/main/proposals/texel-buffers.md) has been
accepted, implementations must not expose the feature
without an explicit draft-enable flag. Discussion is ongoing in a
[Texel Buffers Issue] (https://github.com/gpuweb/gpuweb/issues/162).

Syntax

A new texel_buffer type, parameterized by the color format and the access mode:
texel_buffer<Format, Access>.

  • Format must be a WebGPU plain color format.
  • Access is either read or read_write.

Texel buffers overload textureDimensions, textureLoad and textureStore
built-in functions.

Example usage

wgsl
@group(0) @binding(0) var myBuffer : texel_buffer<rgba8unorm, read_write>;

@compute @workgroup_size(32)
fn computeMain(@builtin(global_invocation_id) threadId : vec3<u32>) {
    let length = textureDimensions(myBuffer);
    if (threadId.x >= length) {
        return;
    }

    var value = textureLoad(myBuffer, threadId.x);
    value = value * value;
    textureStore(myBuffer, threadId.x, value);
}

--- METRICS ---