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.hversion that Dawn implements. - A C++ wrapper for the
webgpu.h.
- The
- 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:
- Dawn bug tracker if you find issues with Dawn. Create a new issue here.
- Tint bug tracker if you find issues with Tint. Create a new issue here.
- Dawn's mailing list for other discussions related to Dawn.
- Dawn's source code
- Dawn's Matrix chatroom for live discussion around contributing or using Dawn.
- WebGPU's Matrix chatroom
Documentation table of content
Developer documentation:
- Dawn overview
- Building
- Contributing
- Code of Conduct
- Testing Dawn
- Testing Tint
- Running the WebGPU CTS with the standalone runner
- Debugging Dawn
- Dawn's infrastructure
- Dawn errors
- Tint experimental extensions
- Quickstart with CMake
- Becoming a committer
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
generatedawn_wirefiles, 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 codecommon: helper code that is allowed to be used by Dawn's core
libraries,dawn_nativeanddawn_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
exampled3d12,metalorvulkan.
- ``: code for the implementation of the backend on a specific graphics API, for
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 theWGPUSurfaceobject.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 ofdawn_nativeordawn_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 fordawn_nativeanddawn_wire.platform: definition of interfaces for dependency injection indawn_nativeordawn_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 intodawn_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 theprocTable.deviceCreateBuffer function pointer. This is useful in two ways:
- It allows deciding at runtime whether to use
dawn_nativeanddawn_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 usingwgpuGetProcAddressrepeatedly.
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 istint::spirv::reader::.
Overall flow
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).
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.
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.Post-process the AST to make it valid for WGSL.
Example:
- Rewrite strided arrays and matrices (remove
@strideattribute) - Rewrite atomic functions
- Remove unreachable statements, to satisfy WGSL's behaviour analysis.
- Rewrite strided arrays and matrices (remove
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_Positiongl_PointSizegl_ClipDistancegl_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:
%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_2However, 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:
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 asSpvOpImageSampleImplicitLod
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 invokingOpSampledImage.
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:
#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):
; 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
OpFunctionEndWhat the SPIR-V Reader currently generates:
@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 tobreak,continue, andreturn,
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'sif,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:
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 orsampler_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:
#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:
@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:
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 explicitread_writeaccess 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_toolsin your path- A build generator, one of:
GN, provided bydepot_toolsCMake3.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-configcommand: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
- CPU:
Mac
- CPU:
- Apple Silicon
- Intel x86-64
- Xcode 12.2+.
- The macOS 13.0 SDK. Run
xcode-selectto check whether you have it.shls `xcode-select -p`/SDKs
- CPU:
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
- CPU:
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.
# 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 syncThe standalone build dependencies includes Microsoft Direct 3D WARP with the following terms:
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 thetools/fetch_dawn_dependencies.py to clone the dependencies' repositories:
# 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.pyUse 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 andDAWN_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).
libfuse2libxrandr-devlibxinerama-devlibxcursor-dev
(libxkbcommon-devlibxi-devmesa-common-devlibx11-xcb-devpkg-confignodejsnpm
sudo apt-get install libfuse2 libxrandr-dev libxinerama-dev libxcursor-dev libxkbcommon-dev libxi-dev mesa-common-dev libx11-xcb-dev pkg-config nodejs npmNote, nodejs and npm are only needed if building dawn.node.
Build Dawn
Compiling using CMake + Ninja
mkdir -p out/Debug
cd out/Debug
cmake -GNinja ../..
ninja # or autoninjaCompiling using CMake + make
mkdir -p out/Debug
cd out/Debug
cmake ../..
make # -j N for N-way parallel buildCompiling using gn + ninja
mkdir -p out/Debug
gn gen out/Debug
autoninja -C out/DebugThe most common GN build option is is_debug=true/false; otherwisegn 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 andninja-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.
Add your target OS to the
target_osarray in.gclient. Or, usestandalone-maximal.gclientand follow the instructions in it.
This will enable downloading toolchains and any target-specific Dawn DEPS.- Windows: ~16GB.
- Mac: ~4GB.
- Android: ~2.5GB.
gclient sync.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 hoststarget_os = "win"target_cpu = "x64"on Linux and Mac hosts- On Mac hosts, many Windows files can't build using Siso remote builds.
Useautoninja --offline(orautoninja -k=0 ; autoninja --offline
to first compile everything possible with Siso, then compile the rest
locally). See https://crbug.com/446124900.
- On Mac hosts, many Windows files can't build using Siso remote builds.
target_os = "androidtarget_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.
# 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 -sUsing 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:
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:
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:
ld: file not found:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib/darwin/libclang_rt.fuzzer_osx.aor 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
sudo xcode-select -s /Library/Developer/CommandLineToolsThis 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.
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.batuse the appropriate script from third_party\depot_tools\win_toolchain, i.e.
C:\src\dawn\third_party\depot_tools\win_toolchain\vs_files\68a20d6dee\Windows Kits\10\bin\SetEnv.cmdClang Tidy (`docs/clang-tidy.md`)
Running Clang-Tidy
Add
"checkout_clang_tidy": Trueto.gclientfile in thecustom_vars
(or usestandalone-maximal.gclientwhich includes this).py"custom_vars": { "checkout_clang_tidy": True, }gclient syncSet 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 --helpfor instructions. Running the
tool with--defaulttakes 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.jsonwith the full findings,
andout/*/clang-tidy-*-summary.txtwith 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):
Runtools/apply-clang-tidy-fixes.py --helpfor 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 theFastCheckFilteroption mentioned there.In VSCode you can
lessthe 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:
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:
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:
DanglingPointerDetector: A pointer was dangling!
Documentation: https://source.chromium.org/chromium/chromium/src/+/main:third_party/dawn/docs/dangling-pointer-detector.mdTo 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
replacingSetDanglingRawPtrReleasedFnbySetDanglingRawPtrDetectedFnin
src/dawn/tests/PartitionAllocSupport.cpp`.
Other resources
- MiraclePtr in Dawn design doc
- MiraclePtr in chrome
- Use-after-freedom: MiraclePtr
- MiraclePtr: protecting users from use-after-free vulnerabilities on more platforms
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.gclientinstead ofstandalone.gclient. - Set the
dawn_maximal = trueanduse_libfuzzer = trueGN 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 = trueis optional but still useful for cross-compile builds.
- Also note you can run
gn gen --checkto 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 usestandalone-maximal.gclient). - Point your IDE to the binary at
third_party/llvm-build/Release+Asserts/bin/clangd.- In VSCode:
clangd.path
- In VSCode:
- Configure clangd: Paste the relevant text below into
.clangdand then update
theout/paths in that file according to your needs.- Note the
PathMatchpaths 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
- In VSCode:
- For Chromium builds, take a look at
chrome-remote-index.
- Note the
Other documentation that may be useful:
`.clangd`
For Mac/Linux hosts:
# 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-winFor Linux hosts, also add this:
---
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-androidQuickstart 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
# 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
$ 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/ReleaseDAWN_FETCH_DEPENDENCIES=ONuses a Python scriptfetch_dawn_dependencies.pyas a less tedious alternative to setting up depot_tools and fetching dependencies viagclient.DAWN_ENABLE_INSTALL=ONconfigures 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:
$ cmake --build out/Release
...
[100%] Linking CXX static library libdawn_utils.a
[100%] Built target dawn_utilsOnce you have built the Dawn library, install it with cmake
$ cmake --install out/Release --prefix install/ReleaseThis installs Dawn from the out/Release build tree into install/Release.
Linking your code to the Dawn installation
Create test code
$ mkdir TestDawn
$ cd TestDawnNow, create a hello_webgpu.cpp C++ file within the TestDawn directory.
/* 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_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.
$ 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/ReleaseNow build our test application:
$ 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_webgpuNow run your binary:
$ ./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.2Note: 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-cligcloud auth logingcloud 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, usingscripts/standalone-with-v8.gclient in the Get the code step so gclient
fetches both the Node bindings (dawn_node) and V8 (checkout_v8):
# 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 syncIf you are configuring an existing checkout, ensure .gclient sets both"checkout_v8": True and "dawn_node": True in custom_vars before runninggclient 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):
gn gen out/Release --args="is_debug=false dawn_use_swiftshader=false"
autoninja -C out/Release dawn_standalone_cts_runneris_debug=falseis strongly recommended: the default debug build compiles a
debug V8, which runs the CTS roughly 17x slower than a release build.dawn_use_swiftshader=falseskips 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 yourPATH first:
# 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/:
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 outsidethird_party/webgpu-cts):
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 singlekey=value and may be repeated:
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 tod3d12on Windows,vulkanon Linux/Android, andmetalon
macOS; can also be set via theDAWNNODE_BACKENDenvironment variable)adapter=<name>— substring match against the adapter device name. Pass an
invalid name (for exampleadapter=help) to list all available adapters in
the error outputvalidate=<1|true>— enable full backend validation layersverbose=1— print the selected GPU adapter nameenable-dawn-features=<toggles>— comma-separated list of
Dawn togglesdisable-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:
printf '\ntarget_os = ["android"]\n' >> .gclient
gclient syncBuild
Generate an arm64 Android release build on Linux and compile the runner:
gn gen out/Android --args='target_os="android" target_cpu="arm64" is_debug=false'
autoninja -C out/Android dawn_standalone_cts_runnerStaging onto the device
Only the executable and the compiled CTS JavaScript are needed on the device.
[!NOTE]
dawn.nodeis deliberately not pushed. The runner's module loader returns
its statically-linked binding forrequire('dawn.node'), so the shared
library is never loaded.
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.tgzRunning
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 teachrun-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)
- There is no point in checking a null pointer if the very next line it will trigger a null page
- 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 useDAWN_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`)
- Buffer mapping dawn wire memory transfer interface design
- TODO: make a md doc targeted at code walkthrough for contributors
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 inout/<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.hC header - the Dawn and Emscripten
webgpu_cpp.hC++ wrapper over the C header - libraries that implements
webgpu.hby calling in a static orthread_localproc 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 value0x0002_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 thefooCountfoospattern 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 tonullptr(unless"no_default"is set totrue)."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 calledUndefined) is passed in, Dawn applies the default
value specified here. SeeWithTrivialFrontendDefaults()inapi_structs_defaults.hfor how
this works.
- Dawn implements "trivial defaulting" for enums, similarly to the upstream WebGPU spec's WebIDL:
"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 inlibrary_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. hasnextInChain). "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. hasnextInChainandsType) 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/.hthe most important piece: the meat of the serialization / deserialization code
for WebGPU structures and commandsServerHandlers/Doers.cppthat does the complete handling of all regular WebGPU methods in the
serverApiProcs.cppthat 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 makesWireCmdskip it on its on-wire format.
- Each record member can have an extra
"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.cppthat implements all of the WebGPU function by converting arguments and forwarding
to the correct method/function indawn::native.dawn_platform_autogen.handwgpu_structs_autogen.cppthat define thedawn::native
equivalents of types inwebgpu.h(for example with objects being actual pointers to objects
and not just opaque handles) as well as conversion functions.api_absl_format.cppto 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 andwebgpu.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 indawn_native.json:addins: maps fully qualified instance names to attribute addins. Supported addins are:"omitted": trueon 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 adawn.jsontype 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 aslibdawn_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 There are various debug-related Toggles that can help diagnose issues. Useful debug
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
DawnTogglesDescriptorchained onDeviceDescriptor.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-featuresand/or--disable-dawn-featuresto
force enable/disable toggles. Toggles should be comma-delimited.For example:
--enable-dawn-features=dump_shaders,disable_symbol_renamingCommand-line for dawn_end2end_tests/dawn_unittests
Run Dawn test binaries with command line flags
--enable-togglesand/or--disable-togglesto
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_ERRORErrors 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_ERRORto 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=1must 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:
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::DeviceBaseis called and does mostly nothing except settingStatetoBeingCreated(and initial toggles).backend::Device::Initializecreates things like the underlying device and other stuff that
doesn't run GPU commands.- It then calls
DeviceBase::Initializethat enables theDeviceBasefacilities and sets theStatetoAlive. - Optionally,
backend::Device::Initializecan 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 theDisconnected 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:
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.
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::SetupBackendDeviceTogglesusingTogglesState::ForceSet. - The backend device default toggles are applied (unless already set) in
Adapter::SetupBackendDeviceTogglesusingTogglesState::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 theDAWN_UNLIKELY() macro to indicate that the expression is not expected to evaluate to true. For
example:
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:
// 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...orThe [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 thanDAWN_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:
<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:
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.
- None -- no error generated
- Validation -- An API validation error has occurred
- 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. - 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. - 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. - 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 usingUnknownError 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
Validationerror and aUnrecoverableErrorcan be placed in aMaybeUnknownorResultOrUnknownerror. - A
Validationerror can be placed into aMaybeErrororResultOrErrorbut it will convert to
Unrecoverable and no longer be a validation error. - A
Unrecoverableerror cannot be placed into aValidationerror. 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 theResult, 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 ValidationValidationerror.DAWN_PIPELINE_UNCATEGORIZED_ERROR-- Makes a PipelineUncategorizedUnrecoverableerror.DAWN_BACKEND_DEVICE_LOST_ERROR-- Makes a BackendDeviceLostUnrecoverableerror.DAWN_UNRECOVERABLE_ERROR-- Makes an UnrecoverableUnrecoverableerror.DAWN_OUT_OF_MEMORY_ERROR-- Makes an OutOfMemoryUnrecoverableerror.DAWN_INVALID_IF-- Creates a validation error if the given condition is falseDAWN_PIPELINE_UNCATEGORIZED_IF-- Creates the pipeline error if the condition is falseDAWN_UNRECOVERABLE_ERROR_IF-- Creates an unrecoverable error if the condition is falseDAWN_TRY-- Will execute the given command. If the command returns aResultset to an error
it will use theErrorAdapterto return the correct type for the parent method.DAWN_TRY_ASSIGN-- Similar to the above, but will move the success value out of theResult
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 aConsumeErrorImpl method which takes an UnknownError and emits it in some fashion.
Dawn External Resources (`docs/dawn/external_resources.md`)
Dawn's external resources links
Design Docs
- Buffer mapping dawn wire memory transfer interface design
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 thedawn.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:
- Standalone Dawn checkouts, which directly check out Dawn and its dependencies. This is used to
provide test coverage of Dawn itself. - 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:
- Check out the repo
- Compile the project (both Dawn & Tint)
- Depending on the specific environment this may include multiple configurations, i.e. with
dawn.nodeor fuzzing enabled in addition to the default build options
- Depending on the specific environment this may include multiple configurations, i.e. with
- Run various tests
- Including unit tests, end-to-end tests, CTS, and others as appropriate for the specific build
configuration
- Including unit tests, end-to-end tests, CTS, and others as appropriate for the specific build
- 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
- This is controlled by running a special set of tests and only runs on one CI builder that is
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 aslinux-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 thelibwebgpu_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-bazelcan create
symlinks without elevation. Without it the build fails withos error 1314. LongPathsEnabledset to1underHKLM\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:
solutions = [
{
"name": ".",
"url": "https://dawn.googlesource.com/dawn",
"deps_file": "DEPS",
"managed": False,
"custom_vars": {
"checkout_litert_lm": True,
},
},
]After editing .gclient, sync dependencies:
gclient syncThis 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:
./tools/setup-build gn releaseThis 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:
autoninja -C out/active litert_lmHow the Build Works
- 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
- Linux:
- 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_dawnand platform prebuilts. - Builds the Bazel target
//runtime/engine:litert_lm_advanced_main. - Copies the resulting
litert_lm_advanced_mainbinary and required shared libraries intoout/active/.
- Configures Bazelisk with the hermetic Clang/LLVM toolchain from Dawn's
3. Run
You can run the benchmark binary directly from the root of your Dawn checkout.
Linux
LD_LIBRARY_PATH=out/active ./out/active/litert_lm_advanced_main \
--benchmark \
--backend=gpu \
--model_path=third_party/litert-lm/data/model.litertlmmacOS
DYLD_LIBRARY_PATH=out/active ./out/active/litert_lm_advanced_main \
--benchmark \
--backend=gpu \
--model_path=third_party/litert-lm/data/model.litertlmWindows
.\out\active\litert_lm_advanced_main.exe `
--benchmark `
--backend=gpu `
--model_path=third_party\litert-lm\data\model.litertlmTroubleshooting
LiteRT-LM dependencies not found
Ensure "checkout_litert_lm": True is present under custom_vars in your .gclient file, then rungclient sync.
GPU accelerator could not be loaded and registered
The prebuilt libLiteRtWebGpuAccelerator cannot find libwebgpu_dawn. Ensure thatLD_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:
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 rundawn_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_testsmetrics are reported as time per iteration. - Step: A group of Iterations run together. The number of
iterationsPerStepis provided to the
constructor ofDawnPerfTestBase. - Trial: A group of Steps run consecutively.
kNumTrialsare run for each test. A Step in a Trial
is run repetitively for approximatelykCalibrationRunTimeSeconds. Metrics are accumulated
per-trial and reported as the total time divided bynumSteps * iterationsPerStep.maxStepsInFlightis passed to theDawnPerfTestsBaseconstructor 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:
scripts/perf_test_runner.py DrawCallPerf.Run/Vulkan__e_skip_validationTests
BufferUploadPerf
Tests repetitively uploading data to the GPU using either WriteBuffer or CreateBuffer withmappedAtCreation = 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
With a Chromium build manually
Go to https://gpuweb.github.io/cts/standalone/
or clone the CTS repo and serve it locally
git clone https://github.com/gpuweb/cts.git
cd cts
npm ci
npm startThen 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:
./tools/run cts roll --max-attempts 0 --repo <repo-url> --revision <commit-hash>Example:
./tools/run cts roll --max-attempts 0 --repo https://github.com/greggman/cts.git --revision 1b3173b36a1084917e0ff29e5f40c6290b6f8adcThe 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 withgit cl upload --force --bypass-hooks and start a dry run on gerrit.
Tint Arch (`docs/tint/arch.md`)
Tint Architecture
/* Detailed source-code truncated for AI context efficiency. */Reader
Readers are responsible for parsing a shader program and populating aProgramBuilder 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 toresolver::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 newProgramBuilder.
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 soast::Nodes are not shared.
The AST does not perform any verification of its content. For example, theast::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 correspondingast::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 thesem::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:
type MyI32 = i32;
const a : i32 = 1;
const b : MyI32 = 2;The semantic types for the variables a and b will both be the samesem::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 thePrograms 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 theProgramBuilder along with semantic information generated by theResolver.
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- selecttint_benchmarkbot- select the waterfall bot from which to view datatest- 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:
if (condition_a) {
statement_a;
} else if (condition_b) {
statement_b;
} else {
statement_c;
}Semantic tree:
sem::IfStatement {
condition_a
sem::BlockStatement {
statement_a
}
sem::IfStatement {
condition_b
sem::BlockStatement {
statement_b
}
sem::BlockStatement {
statement_c
}
}
}for loop
WGSL:
for (initializer; condition; continuing) {
statement;
}Semantic tree:
sem::ForLoopStatement {
sem::Statement initializer
sem::Expression condition
sem::Statement continuing
sem::LoopBlockStatement {
sem::Statement statement
}
}while
WGSL:
while (condition) {
statement;
}Semantic tree:
sem::WhileStatement {
sem::Expression condition
sem::LoopBlockStatement {
sem::Statement statement
}
}loop
WGSL:
loop (condition) {
statement_a;
continuing {
statement_b;
}
}Semantic tree:
sem::LoopStatement {
sem::Expression condition
sem::LoopBlockStatement {
sem::Statement statement_a
sem::LoopContinuingBlockStatement {
sem::Statement statement_b
}
}
}switch statement
WGSL:
switch (condition) {
case literal_a, literal_b: {
statement_a;
}
default {
statement_b;
}
}Semantic tree:
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 bydepot_tools) - Clang toolchain
Building Tint with coverage generation enabled
To generate coverage, you must build with Clang source-based coverage
instrumentation enabled.
Using GN (Recommended)
Add the following flags to your args.gn:
use_clang_coverage = true
is_component_build = false
is_debug = false
dcheck_always_on = trueIf you are building a fuzzer target, also include:
optimize_for_fuzzing = false
use_clang_modules = false
use_libfuzzer = trueUsing 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:
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:
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 atcoverage_report/linux/coverage.lcov.
2. Generate an HTML Report
To generate a browsable HTML report with line-by-line coverage:
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:
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-equivalenceflags 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:
/* 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:
shader.wgsl:7:1 error: Cannot take the address of expression.Do:
shader.wgsl:7:1 error: cannot take the address of expressionJustification:
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:
shader.wgsl:5:7 error: cannot multiply 'expr_a * expr_b' with types i32 and f32
var res : f32 = expr_a * expr_b
^^^^^^^^^^^^^^^Do:
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.
Use `note` diagnostics for providing additional links to relevant code
Don't:
shader.wgsl:5:11 error: type cannot be used in address space 'storage' as it is non-host-shareable
cond : bool;
^^^^Do:
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:
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:
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):
./tools/run testsYou 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:
Choose a name for the feature, to be used in an
enabledirective.
An experimental extension should use prefix ofgoogle_experimental_
Example:enable google_experimental_f16;
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.
File a tracking bug for adding the feature.
Note: Should the Tint repo have a label for experimental features?File a tracking bug for removing the feature or converting it to
non-experimental.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.
- Who is the point of contact for this feature? The point of contact
- Ideally, this plan is committed to this repository, especially the
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. Seetint.gni for the core fuzzer target
rules.
Tint's fuzzers are implemented as functions registered with the
macros:
TINT_WGSL_PROGRAM_FUZZER()
registers a fuzzer function that is handed atint::Program.TINT_IR_MODULE_FUZZER()
registers a fuzzer function that is handed atint::core::ir::Module.
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
tocmake) - GN: Define
use_libfuzzer = trueinargs.gn.
Building tint_ir_fuzzer requires additional flags to be set:
- CMake: Define
TINT_BUILD_IR_BINARY=1(pass-DTINT_BUILD_IR_BINARY=1tocmake) - GN: Define
tint_build_ir_binary = trueinargs.gn.
Running fuzzers
Local fuzzing
The tint_wgsl_fuzzer andtint_ir_fuzzer executables accept the standardlibFuzzer 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 ontest/tint, and using the dictionary insrc/tint/cmd/fuzz/wgsl/dictionary.txtrun:tools/run fuzz runTo check that all the test files,
test/tintby
default, pass the fuzzers without crashing and then exit, run:tools/run fuzz checkNote: 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:
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 fromtest/tint no additional flags are needed
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.
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 oftint_wgsl_fuzzer.
An example of generating and then using a custom WGSL corpus:
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_corpustint_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 targetfuzzer_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:
tools/run fuzz generate -ir -out out/Fuzzer/wgsl_corpusGenerating a corpus based on custom WGSL files:
tools/run fuzz generate -ir -input my_super_cool_tests/ -out out/Fuzzer/wgsl_corpusAn example of generating and using a custom IR corpus:
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_corpusMinimizing 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
Create a new source file with a
_fuzz.ccsuffix.#include "src/tint/cmd/fuzz/wgsl/fuzz.h"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
contextparameter holds information about the
Programand the environment used to run the fuzzers.Note: Any number of additional fuzzer-populated parameters can be
appended to the function signature.Implement your fuzzer function, using
TINT_ICE()to catch invalid
state. Return early if the fuzzer cannot handle the input.At the bottom of the file, in the global namespace, register the
fuzzer with:TINT_WGSL_PROGRAM_FUZZER(MyFuzzer);Use
tools/run gen buildto generate the build files for this new
fuzzer.
Example:
#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
Create a new source file with a
_fuzz.ccsuffix.#include "src/tint/cmd/fuzz/ir/fuzz.h"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.Implement your fuzzer function, using
TINT_ICE()to catch invalid
state. Return early if the fuzzer cannot handle the input.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 ]
- Use
tools/run gen buildto generate the build files for this new
fuzzer.
Example:
#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
constreference, 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 atsrc/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
Programto an IR module and run the
function for each function registered withTINT_IR_MODULE_FUZZER(). Note: TheProgramis 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 useslibprotobuf-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 atsrc/tint/cmd/fuzz/ir/main_fuzz.cc.
Extended command line arguments
This fuzzer accepts the same set of flags as tint_wgsl_fuzzer (bothlibFuzzer 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
Programfor 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:
out/Fuzzer/protoc --decode tint.cmd.fuzz.ir.pb.Root ./src/tint/utils/protos/ir_fuzz/ir_fuzz.proto < ./input.tirbThe 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:
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:
autoninja -C out/libfuzz tint_wgsl_fuzzer
out/libfuzz/tint_wgsl_fuzzer -filter=tint::msl::writer::IRFuzzerIt 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:
...
#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:
out/libfuzz/tint_wgsl_fuzzer ./crash-21563a85afd5322d9e17c1c43fd3d4029778d6e7Profiling
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:
sudo apt get valgrind
# Optionally for visualization
sudo apt get kcachegrindNote: 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
# 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> = falseExample 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.
valgrind \
--tool=callgrind \
--trace-children=yes \
--dump-instr=yes \
--collect-jumps=yes \
out/gn-callgrind/tint_wgsl_fuzzer triage/really_slow.wgsl--trace-children=yesis required for valgrind to follow flow
across forks. Without this you will just get a trace of the libfuzzer
framework--dump-instr=yesand--collect-jumps=yesare 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.eout/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.
sed -i 's#\./\.\./\.\./#./#g' callgrind.out.<pid> > callgrind.out.fixed.<pid>And then to visualize and start inspecting the trace
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:
use_libfuzzer = true
tint_build_wgsl_reader = true
use_clang_coverage = true
optimize_for_fuzzing = false # Cannot be used with coverage enabledAdditional flags must be enabled depending on the fuzzers selected:
- IR Fuzzers (
tint_ir_fuzzer,tint_ir_mesa_fuzzer): Requiretint_build_ir_binary = trueandtint_has_protobuf = true. - Mesa Fuzzers (
tint_wgsl_mesa_fuzzer,tint_ir_mesa_fuzzer):
Requiretint_build_mesa = trueandtint_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:
- An
experiment.jsonconfiguration file. - A
corpora/directory holding the seed corpora for the various
fuzzing modes.
Directory Structure
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:
{
"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}
]
}nameis just a user visible title for this experiment which will
appear in reports/logging, but has no semantic meaninghashis 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)fuzzersare the specific fuzzer binaries to testburnin_enabled,normalization_duration, andnormalization_iterationsare 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_corpusare the corpora to use when establishing
baselines for normalizing performance numbers between
machines/environments*_corporaare the various starting corpora to experiment usingdefault_iterationsspecifies how many times to run each experiment
if not explicitly overriddendurationsspecifies 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:
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:
- 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 (hashfield) and runsgclient syncautomatically. (The state of repo should be returned
to the original state) - Binary Preparation: It builds the selected fuzzer binaries and
copies them, alongside their dynamic library dependencies and
required LLVM tools (llvm-profdata,llvm-cov), intomy_experiment/bin/. - 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. - Normalization: It executes multiple normalization profiling
runs for each fuzzer against its designated normalization corpus
(controlled bynormalization_durationandnormalization_iterations). The resulting average execution rate
(runs/sec) and standard error (SEM) are saved tonormalization_scores.jsonand are used to compute "Normalized CPU
Seconds" for each fuzzer. All individual profiling runs are
archived innormalization_iterations.csv. - 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 undermy_experiment/results/. Each task directory stores astate.json
file, captured logs, mutated corpus files, and.profrawcoverage
profiles.
Processing and Analyzing Results
Once execution has completed, you can aggregate and process results
using the analyze subcommand:
tools/run fuzz analyze my_experiment/Analysis Pipeline
- Coverage Generation: The analyzer locates the
.profrawfile
for each completed iteration, merges them usingllvm-profdata,
and executescoverage.pyto produce standard.lcovcoverage
logs. - Component Mapping: It parses the
.lcovprofiles and
aggregates line coverage metrics separately for Tint
(src/tint/andsrc/utils/), Mesa (third_party/mesa/), and
DXC (third_party/directx-headers/). - 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)
- 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 forexperiment.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 orruns.
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:
cmdtargets are executables.libtargets are libraries used in production code, and can also be used as
a dependency by any other target kind .testtargets are libraries used by Tint unittests. Must not be used by
production code.test_cmdare test executables.benchtargets are libraries used by Tint benchmarks. Must not be used by
production code.bench_cmdare benchmark executables.fuzztargets are libraries used by Tint fuzzers. Must not be used by
production code.fuzz_cmdare 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
testor with a_testsuffix are classed astestlibrary targets.
Example:parser_test.cc. - Source files named
benchor with a_benchsuffix are classed asbenchlibrary targets.
Example:writer_bench.cc. - Source files named
fuzzor with a_fuzzsuffix are classed asfuzzlibrary targets.
Example:writer_fuzz.cc. - Source files with the name
mainare classed as executable targets.
These typically exist undersrc/tint/cmd.
Example:cmd/tint/main.cc. - Source files with the name
main_testare classed as test executable targets.
These typically exist undersrc/tint/cmd/test.
Example:cmd/test/main_test.cc. - Source files with the name
main_benchare classed as benchmark executable targets.
These typically exist undersrc/tint/cmd/bench.
Example:cmd/benchmark/main_bench.cc. - Source files with the name
main_fuzzare classed as fuzzer executable targets.
These typically exist undersrc/tint/cmd/fuzz.
Example:cmd/benchmark/main_bench.cc. - All other files are considered
libtargets.
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:
{
"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:
{
/* 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:
{
/* 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:
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:
@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 f16Types 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:
@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.
- Builtin Functions have a
fnprefix - Value constructors have a
ctorprefix - Value conversions have a
convprefix - Operators have a
opprefix
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:
// A function overload that has no explicit or implicit template parameters
fn F(i32) -> i32// 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// 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// 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) -> TMatching 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:
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:
- The overload resolver begins by attempting to match the parameter types from left to right.
The first argument type is compared against the parameter typeT.
As the implicit template typeThas not been defined yet,Tis defined as the type of the first argument.
There's no verification that theTtype is a scalar at this stage. - The second argument is then compared against the second parameter.
As the template typeTis now defined as the first argument type, the second argument type is
compared against the type ofT. Depending on the comparison of the argument type to the
template type, either the actions of (a), (b) or (c) from above will occur. - 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 astruct 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 haskModuleConstantCapabilities). 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 aState 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 theOpGroupNonUniformSMin 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:
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> whereNewType is the name of the new type. At a minimum the class needs to override:
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:
/// @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 aMatchNewType 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:
@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:
- Transforms rebuild the AST and SEM which causes slowness
- 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:
- Easy to integrate into current system by replacing AST transforms
piecemeal - 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 andconst_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, andinstructions.
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 andloop-continuing. Those blocks are optional. The terminators may holdvalues which are returned out of the block being terminated.
Control flow is handled through ControlInstructions. These are theif, 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
aliasnodes - No
const_assertnodes
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 offunctions, 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 aProgram 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 aBreakIf terminator.
The loop initializer is always terminated with a NextIteration.
A while loop is decomposed as listed in the WGSL spec:
while (a < b) {
c += 1;
}becomes:
loop {
if (!(a < b)) {
break;
}
c += 1;
}A for loop is decomposed as listed in the WGSL spec:
for (var i = 0; i < 10; i++) {
c += 1;
}becomes:
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.
var a = b + c - (4 * k);becomes:
%0:i32 = add %b, %c
%1:i32 = mul 4u, %k
%2:i32 = sub %0, %1
%v:ptr<function, i32, read_write> = var %2This 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.
let c = a() && b()becomes
let c = a();
if (c) {
c = b();
}Values
There are several types of values used in the SSA form.
ConstantValueInstructionResultValueFunctionValueFunctionParamValueBlockParamValue
Constant Value
All values in IR are concrete, there are no abstract values as
materialization has already happened. Each constant holds a lower levelconstant::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 aMultiInBlock (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.
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 andsrc/tint/util.
/* 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
sigmember of the return type offrexp()has been renamed tofract. tint:1766 - Calling a function with multiple pointer arguments that alias each other is now a error. tint:1675
typedeprecation has been removed.aliasmust be used now. tint:1812static_assertdeprecation has been removed.const_assertmust now be used. tint:1807
Changes for M111
New features
- The
workgroupUniformLoadbuiltin function is now supported. tint:1780 - The
diagnosticdirective and@diagnosticattribute 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 aroundb<cord>e. tint:1810. - Uniformity analysis failures are now an error tint:880
- The
derivative_uniformitydiagnostic filter can be used to modify the severity if needed.
- The
Deprecated Features
- The keyword to alias a type has been renamed from
typetoalias. tint:1812 static_asserthas been renamed toconst_assert. tint:1807
Changes for M110
Breaking changes
- The
textureSampleLevel()overload fortexture_externalhas been removed. UsetextureSampleBaseClampToEdge(). tint:1671
Deprecated Features
- The
sigmember of the return type offrexp()has been renamed tofract. 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()andtextureNumLevels()now return unsigned integers / vectors. tint:1526- The
@stageattribute has been removed. The short forms should be used
instead (@vertex,@fragment, or@compute). tint:1503 - Module-scope
letis now an error. Use module-scopeconstinstead. tint:1580 - Reserved words are now an error instead of a deprecation. tint:1463
- You may no longer use pointer parameters in
workgroupaddress 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_textureoverload oftextureSampleLevel()has been deprecated. UsetextureSampleBaseClampToEdge()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
saturate()has been implemented. tint:1591
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:1628static_assertstatement 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
varcan now infer the storage type, like function-scopevar. tint:1584 - The
acosh,asinh, andatanhbuiltin functions are now supported tint:1465
Breaking changes
- The
smoothStep()builtin has been removed (usesmoothstepinstead). tint:1483 - Module-scope
lethas been replaced with module-scopeconst. tint:1580- Note: Module-scope
constdoes not support structure types. Usevarif you need a module-scope structure type.
- Note: Module-scope
- 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
@stageattribute 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
utint:1504 - Parsing of
@compute,@fragmentand@vertexadded.
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
atomicCompareExchangeWeakreturns 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
@blockattribute has been removed. tint:1324 - The
@strideattribute has been removed. tint:1381 - Attributes using
[[attribute]]syntax are no longer supported. tint:1382 - The
elseifkeyword is no longer supported. tint:1289
Deprecated Features
- The
smoothStep()builtin has been renamed tosmoothstep(). tint:1483
Changes for M101
New Features
- Tint now supports unicode identifiers. tint:1437
Breaking changes
- The
isNan(),isInf(),isFinite(), andisNormal()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;. breakstatements incontinuingblocks are now correctly validated.
New Features
- Module-scope declarations can now be declared in any order. tint:1266
- The
overridekeyword and@id()attribute for pipeline-overridable constants are now supported, replacing the@overrideattribute. 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
@decorationsyntax instead of the[[decoration]]syntax. tint:1382 elseifhas been replaced withelse 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()andradians()for converting between degrees and radians. tint:1329 letarrays 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()andisNormal()builtins has been deprecated and will be removed in M101. tint:1312
New Features
- New texture gather builtins:
textureGather()andtextureGatherCompare(). 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
returnif 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
letin aworkgroup_sizemay result in a compilation error. tint:1320
Changes for M97
Breaking Changes
- Deprecated
modf()andfrexp()builtin overloads that take a pointer second parameter have been removed. - Deprecated texture builtin functions that accepted a
readaccess controlled storage texture have been removed. - Storage textures must now only use the
writeaccess control.
Deprecated Features
- The
ignore()builtin has been replaced with phony-assignment. gpuweb#2127
New Features
any()andall()now support aboolparameter. 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
vec3types instorageanduniformbuffers 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_workgroupsbuiltin 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/)
sudo ~/magic_trace/magic-trace run -full-execution -multi-thread ./out/active/tint -- ./test/tint/benchmark/shadow-fragment.wgsl --format spirvThe 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.
- Insert a bitcast if the WGSL builtin variable has different signedness
- 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.
- Insert a bitcast if the WGSL builtin variable has different signedness
- Return the return structure.
- Copy formal parameter values into pseudo-in variables.
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
#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:
@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:
// '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 oftextOpKill.
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
Inputvariable.- 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
Outputvariable 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 anOpReturn(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 right-hand side evaluates to a non-reference type (atomic-free
- 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.
- When these only have one use, then these often fold away.
- 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.
- This could map to either pointer or reference, and adjustments
- 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.
- Gpuweb issue 1530
is filed to allow those operations to work on pointers.
- Gpuweb issue 1530
- Map to reference, inserting a dereference operator if needed.
- WGSL array-access and subfield access only works on references.
- 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.
- This could have been mapped either way, but it's easiest to
- 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-formatwith the provided .clang-format
file. Thetools/formatscript runs the formatter.Code should not have linting errors.
Thetools/lintscript runs the linter. So doesgit cl upload.Do not use C++ exceptions
Do not use C++ RTTI.
Instead, usetint::Castable::As<T>()from
src/castable.hGenerally, avoid
assert. Instead, issue a diagnostic
and fail gracefully, possibly by returning an error sentinel value.
Code that should not be reachable should callTINT_UNREACHABLEmacro
and other internal error conditions should call theTINT_ICEmacro.
See src/debug.hUse
typeas 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, usekindinstead.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-declarestruct Xin namespaceAandstruct Y
in namespaceA::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 ...
- Use forward declarations where possible, instead of using
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.ccTo run them, build the
tint_unitteststarget to produce thetint_unittests
executable, and then run the executable.The
tint_unittestsexecutable 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
OptionsEnd-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 thewebgpu:shaderhierarchy.
See https://github.com/gpuweb/cts
All test can be run in Chrome, and many tests can be run viadawn.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 withNonWritable. 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
#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:
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 anif, others don't), the derivatives become undefined, leading to visual artifacts. RequiringtextureSample()to be called in uniform control flow helps graphics developers write functionally correct shaders.Safety: Synchronization primitives like
workgroupBarrier()require all threads in the workgroup to reach the barrier together. If the barrier is inside a non-uniformifblock, 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
@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.
@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.
@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:
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:
- Download and install Graphviz: https://graphviz.org/download/
- Enable the dump flag in src/tint/lang/wgsl/resolver/uniformity.cc, by changing
#define TINT_DUMP_UNIFORMITY_GRAPH 0to1. - Rebuild Tint.
- 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 - 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:
memcpyis intrinsically unsafe but necessary for
performance-critical byte copying. By usingtint::Copy, we
localize the suppressions to a single file and provide a safer API
that acceptsstd::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 bystd::span(ptr, size).
- Rationale: This is often unavoidable when interfacing with older
APIs (likestd::string_view::data()) or when reinterpreting byte
buffers (e.g.,std::span<std::byte>tostd::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.hsrc/tint/utils/text/unicode.cc
3. Tight Loops and Performance-Critical Indexing
In some performance-critical code (like UTF-8 decoding), we useUNSAFE_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.ccsrc/tint/utils/containers/vector.h
4. External API Interfacing
Code that interfaces with C-style external APIs (likestd::from_chars or terminal IO) requires raw pointers.
- Rationale:
std::from_charsrequires 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(forline_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.hsrc/tint/utils/containers/hashmap_base.hsrc/tint/utils/memory/block_allocator.hsrc/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::compatibleSurfaceis ignored.RequestAdapterOptions::featureLevelall 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::powerPreferenceadapters 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::backendTypefilters adapters such that only those on a particular backend are discovered. IfWGPURequestAdapterType_Undefinedis passed, all backends may be discovered.RequestAdapterOptions::forceFallbackAdapterall 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.
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.
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.
struct AdapterPropertiesVk {
uint32_t driverVersion; // The Vulkan driver version
};Likewise dawn::native::vulkan::AdapterPropertiesVulkanUUIDs may be chained on wgpu::AdapterInfo to query the following:
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.
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::BufferMapExtendedUsagesfeature allows creating a buffer withwgpu::BufferUsage::MapReadand/orwgpu::BufferUsage::MapWriteand any otherwgpu::BufferUsage. wgpu::Feature::BufferMapExtendedUsagesis a superset ofBufferMapWriteExtendedUsages. EnablingBufferMapExtendedUsageson a device implicitly enablesBufferMapWriteExtendedUsagesas well.
Example Usage:
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 withwgpu::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 permissiveBufferMapExtendedUsages feature, which additionally allowswgpu::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, enablingBufferMapExtendedUsages 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
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 combineswgpu::BufferUsage::MapWritewith non-wgpu::BufferUsage::CopySrcusages. wgpu::BufferUsage::MapReadcannot 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
memcpyout 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 andwgpu::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.
- This will be either Load or Clear depending on what the driver sees as the best performing
wgpu::StoreOp::Undefined- This will be either Store or Discard depending on what the driver sees as the best performing
option.
- This will be either Store or Discard depending on what the driver sees as the best performing
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. Ifwgpu::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:
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:
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::ExpandResolveTextureenum value to specify that the MSAA attachment will load the pixels from its corresponding resolve texture. This is cheaper thanwgpu::LoadOp::Loadwhich will load the existing pixels of the MSAA attachment itself. - Adds
wgpu::ColorTargetStateExpandResolveTextureDawnas chained struct forwgpu::RenderPipelineDescriptor::FragmentState::ColorTargetState. It hasenabledflag to indicate that the render pipeline is going to be used in a render pass withExpandResolveTextureload op in the respective color attachment.
Example Usage:
/* Detailed source-code truncated for AI context efficiency. */Notes:
- If a resolve texture is used in a
wgpu::LoadOp::ExpandResolveTextureoperation, it must havewgpu::TextureUsage::TextureBindingusage. - The
wgpu::ColorTargetStateExpandResolveTextureDawnchained struct controls the compatibility between a render pipeline and a render pass: - If the chained struct is not included in any
wgpu::RenderPipelineDescriptor::FragmentState::ColorTargetStatethen the render pipeline can only be used on any render pass not using anyExpandResolveTextureload 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
enabledboolean 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
ihaswgpu::ColorTargetStateExpandResolveTextureDawn.enabled= true, then the compatible render pass must haveExpandResolveTextureload op on attachmenti. - If render pipeline's color target
ihaswgpu::ColorTargetStateExpandResolveTextureDawn.enabled= false, then the compatible render pass's attachmentimust have a resolve target and its load op must not beExpandResolveTexture. - If render pipeline's color target
ihas nowgpu::ColorTargetStateExpandResolveTextureDawnincluded, then the compatible render pass's attachmentimust not have any resolve target.
- If render pipeline's color target
- Currently the
ExpandResolveTextureLoadOp 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
ExpandResolveTextureload 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::DawnInstanceDescriptormay be chained onwgpu::InstanceDescriptoron instance creation to pass in the platform and additional search paths, set backend validation level, and receive instance level errors.wgpu::DawnWGSLBlocklistmay be chained onwgpu::InstanceDescriptoron instance creation to block some WGSL features.wgpu::DawnCacheDeviceDescriptormay be chained onwgpu::DeviceDescriptoron 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::DeviceDescriptordoes 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::RenderPassDescriptorResolveRectas chained struct forwgpu::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:
- Expand the resolve texture's region {resolveOffsetX, resolveOffsetY, width, height} into the MSAA texture's region {colorOffsetX, colorOffsetY, width, height}.
- Optional user draws to the MSAA texture.
- Resolve the MSAA texture's region {colorOffsetX, colorOffsetY, width, height} into the resolve texture's region {resolveOffsetX, resolveOffsetY, width, height}.
/* Detailed source-code truncated for AI context efficiency. */Example Usage 2, Clear and RenderPassDescriptorResolveRect:
Similar to the above example, but using wgpu::LoadOp::Clear:
- Clear the entire MSAA texture using the clearValue.
- Optional user draws to the MSAA texture.
- Resolve the MSAA texture's region {colorOffsetX, colorOffsetY, width, height} into the resolve texture's region {resolveOffsetX, resolveOffsetY, width, height}.
// 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 = ▭Example Usage 3, Load and RenderPassDescriptorResolveRect:
Similar to the first example, but using wgpu::LoadOp::Load:
- Load the MSAA texture.
- Optional user draws to the MSAA texture.
- Resolve the MSAA texture's region {colorOffsetX, colorOffsetY, width, height} into the resolve texture's region {resolveOffsetX, resolveOffsetY, width, height}.
// 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 = ▭Notes:
- In case that the target size of a render pass is very large, the cost of using
wgpu::LoadOp::ExpandResolveTexturecan 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 ofwgpu::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::APISetScissorRectis used to set the scissor rect towgpu::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::DawnTexelCopyBufferRowAlignmentLimitsas chained struct forwgpu::SupportedLimits. It has a memberminTexelCopyBufferRowAlignmentto 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 thechromium_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 : Tfragment 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,f16orvec{2,3,4}{i,u,f,h}. - Requires
Nto be unique percolor - The
Nvalue 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,bindingvalue provided for eachcolorNvalue from HostAdd the
OpCapability InputAttachmentFor each
color(N)entryCreate an
OpTypeImage %type SubpassData 0 0 0 2 Unknownwhere%typeisf32,i32oru32. Forf16, the%typewill bef32and theOpImageReadwill be decorated withRelaxedPrecision.Create a
OpTypePointer UniformConstant %imageCreate a
OpVariable %ptr_uniform_image UniformConstantDecorate the var with
OpDecorate %inputVar InputAttachmentIndex NWe will then replace the value of the input variable with the value read from the attachment
OpImageRead %v4float %img %zerowhere%zeroisOpConstantComposite %v2int %int_0 %int_0- For
f16, the result of theRelaxedPrecision-decoratedOpImageReadis converted tof16.
For Dawn, this would probably map to creating the image in ShaderIO and then calling the
image_readintrinsic 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::HostMappedPointermust 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
MapViewOfFileat a non-zero offset.
Example Usage
// 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::DawnRenderPassSampleCountas chained struct forwgpu::RenderPassDescriptorto specify number of samples to be rendered for the respective single-sampled attachment.
Example Usage:
// 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::DawnRenderPassSampleCountchained struct. TheresolveTargetfield of the the color attachment must benullptr. - Only colorAttachment zero may be used.
- If a multisampled depth/stencil attachment is specified, it must have
TextureUsage::TransientAttachment, its sample count must match thesampleCountspecified 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.
// 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::Indirectusage and be large enough to storemaxDrawCountdraw calls. - If
wgpu::FeatureName::IndirectFirstInstanceis not enabled, thefirstInstancefield of every draw call encoded in the indirect buffer must be0, otherwise the draw call with a non-zerofirstInstancewill 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
R8Unormplane 0 (luminance). - a 1/4 res
RG8Unormplane 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
R16Unormplane 0 (luminance) with only the first 10 bits used in the decoding of the videos. - a 1/4 res
RG16Unormplane 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
R8Unormplane 0 (luminance). - a 1/4 res
RG8Unormplane 1 (chrominance). - a full res
R8Unormplane 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
R8Unormplane 0 (luminance). - a 1/2 res
RG8Unormplane 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
R8Unormplane 0 (luminance). - a full res
RG8Unormplane 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
R16Unormplane 0 (luminance) with only the first 10 bits used in the decoding of the videos. - a 1/2 res
RG16Unormplane 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
R16Unormplane 0 (luminance) with only the first 10 bits used in the decoding of the videos. - a full res
RG16Unormplane 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::R16Unormwgpu::TextureFormat::RG16Unormwgpu::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.
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:
// 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::OpaqueYCbCrAndroidbut does not enable any use of them except noted below. Texture::CreateViewis allowed onOpaqueYCbCrAndroid(without aYCbCrVkDescriptor).- These views can be used in
ExternalTextureDescriptor::plane0as the single plane specified (views with aYCbCrVkDescriptorare rejected). - These views cannot be used in
BindGroupseven 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_localstorage class in WGSL that can be used withvar pls : u32/MyStructwhich declares the pixel local data in the shader.
It requires a WGSLenable, seechromium_experimental_framebuffer_fetch.Pixel local storage descriptors for both
wgpu::PipelineLayoutDescritorandwgpu::RenderPassDescriptorthat 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::StorageAttachmentthat allows a texture to be used as a storage attachment in a render pass.The
wgpu::RenderPassEncoder::PixelLocalStorageBarriermethod 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
// 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,R32SintandR32Floattexture formats can specifywgpu::TextureUsage::StorageAttachment.StorageAttachmenttextures must be single-sampled.In a
PixelLocalStoragedescriptor:- 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_localstorage class that's compatible with its layout's PLS descriptor.- The totalPLSSize must match the size of the
pixel_localblock. - 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
u32WGSL type.
- The totalPLSSize must match the size of the
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::RenderPassRenderAreaRectas chained struct forwgpu::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:
wgpu::RenderPassRenderAreaRect rect;
rect.x = 10;
rect.y = 10;
rect.width = 100;
rect.height = 100;
wgpu::RenderPassDescriptor renderPassDesc = ...;
renderPassDesc.nextInChain = ▭
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 inwgpu::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::SharedBufferMemoryD3D12Resourcewgpu::FeatureName::SharedBufferMemoryFromWindowsHandlewgpu::FeatureName::SharedBufferMemoryHostPointer
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.
wgpu::SharedBufferMemoryProperties properties;
memory.GetProperties(&properties);
switch (properties.usage) {
// ... handle supported usage
}Then, a buffer can be created from it. For example:
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::MapReadandwgpu::BufferUsage::MapWrite, which means the buffer will only contain GPU-accessible buffer usages.
// 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:
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::SharedFenceVkSemaphoreOpaqueFDwgpu::FeatureName::SharedFenceSyncFDwgpu::FeatureName::SharedFenceVkSemaphoreZirconHandlewgpu::FeatureName::SharedFenceDXGISharedHandlewgpu::FeatureName::SharedFenceMTLSharedEventwgpu::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::SharedTextureMemoryAHardwareBufferwgpu::FeatureName::SharedTextureMemoryDmaBufwgpu::FeatureName::SharedTextureMemoryOpaqueFDwgpu::FeatureName::SharedTextureMemoryZirconHandlewgpu::FeatureName::SharedTextureMemoryDXGISharedHandlewgpu::FeatureName::SharedTextureMemoryD3D11Texture2Dwgpu::FeatureName::SharedTextureMemoryIOSurface
Each memory type uses an extension struct chained on wgpu::SharedTextureMemoryDescriptor to create the memory. For example:
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.
wgpu::SharedTextureMemoryProperties properties;
memory.GetProperties(&properties);
switch (properties.usage) {
// ... handle supported usage
}Then, a texture can be created from it. For example:
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:
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.)
- Backgrounder: https://docs.nvidia.com/deeplearning/performance/dl-performance-matrix-multiplication/index.html
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:
Accum = A * Bor
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:Mequalsconfig.MKequalsconfig.KTmatchesconfig.componentType
- For
subgroup_matrix_right:Kequalsconfig.KNequalsconfig.NTmatchesconfig.componentType
- For
subgroup_matrix_result:Mequalsconfig.MNequalsconfig.NTmatchesconfig.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.
- For row-major:
- 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_uniformitydiagnostic if uniformity analysis cannot prove the call is in subgroup uniform control flow.
Value constructors
Overloads:
@must_use fn T(value : S) -> T
@must_use fn T() -> TPreconditions:
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:
@must_use fn
subgroupMatrixLoad<T>(p : ptr<AS, SA, AM>,
offset : u32,
col_major : bool,
stride : u32) -> TPreconditions:
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:
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:Mequalsconfig.MKequalsconfig.KTmatchesconfig.componentType
- For
R:Kequalsconfig.KNequalsconfig.NTmatchesconfig.componentType
- For
RT:Mequalsconfig.MNequalsconfig.NTRmatchesconfig.resultType
Overload:
@must_use fn
subgroupMatrixMultiply<RT>(left : L, right : R) -> RTPreconditions:
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:
@must_use fn
subgroupMatrixMultiplyAccumulate<RT>(left : L,
right : R,
acc : RT) -> RTPreconditions:
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:
@must_use fn
subgroupMatrixScalarAdd(matrix : M, value : S) -> MPreconditions:
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:
@must_use fn
subgroupMatrixScalarSubtract(matrix : M, value : S) -> MPreconditions:
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:
@must_use fn
subgroupMatrixScalarMultiply(matrix : M, value : S) -> MPreconditions:
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:
@must_use fn
subgroupMatrixScalarDivide(matrix : M, value : S) -> MPreconditions: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)::cooperativeMatrixisVK_TRUE[vkPhysicalDeviceCooperativeMatrixPropertiesKHR](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#VkPhysicalDeviceCooperativeMatrixPropertiesKHR)::cooperativeMatrixSupportedStagesincludesVK_SHADER_STAGE_COMPUTE_BIT- The supported configurations list is non-empty
[vkPhysicalDeviceVulkanMemoryModelFeatures](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkPhysicalDeviceVulkanMemoryModelFeatures)::vulkanMemoryModelisVK_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_BITandVK_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.
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
subgroupMatrixMultiplyandsubgroupMatrixMultipleAccumulateare part of a singleGPUSubgroupMatrixConfig - The x-dimension of
workgroup_sizeis a multiple ofGPUSupportedLimits::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> |
- All OpTypeCooperativeMatrixKHR use subgroup scope.
- Component type enum maps directly to SPIR-V type (e.g. i8 to OpTypeInt 8 1).
- 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 |
- 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 |
- Filter out f16 from the device properties if
shader-f16is 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:
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:
querySetmust be a valid query set.querySetmust have been created withwgpu::QueryType::Timestamp.queryIndexmust be less thanquerySet's size.- During the
wgpu::Queue::Submitfor these commands,querySetmust 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:
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/storein 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/RG16Unormformats even if the formats are not otherwise allowed on the device. - Validation of
CreateExternalTextureconsidersR/RG16Unormto be filterable float, even if they are unfilterable-float (even when enabled withwgpu::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 optionalsampledTextureBindingto 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 iffVK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BITis
present in the AHB format features and FilterMode::Nearest otherwiseforceExplicitReconstruction: will be set to true iffVK_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
sampledTextureBindingannotation 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
sampledTextureBindingannotation, 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
sampledTextureBindingannotation - The
sampledTextureBindingannotation 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 thebarycentric_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
@fragment fn fs_main(@builtin(barycentric_coord) bary_coord: vec3f) -> @builtin(color) vec4f {
return vec4f(bary_coord, 1.0);
}References
- DirectXShaderCompiler SV_Barycentrics
- Metal p.119
- MTLDevice supportsShaderBarycentricCoordinates
- Vulkan VK_KHR_fragment_shader_barycentric
- GLSL_EXT_fragment_shader_barycentric
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
@colorattribute. - The use of
@color(N)with a multi-sampled color attachment will cause the fragment shader to run once per sample.
Example usage
@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
@colorinputs?
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`
@must_use fn hasResource<T>(index: I) -> boolIis ani32oru32Tis 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`
@must_use fn getResource<T>(index: I) -> TIis ani32oru32Tis 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
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
@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
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
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"
- WGSL:
- Filter Mode:
- WGSL:
nearest=0,linear=1 - WebGPU equivalent:
"nearest","linear"
- WGSL:
- Mipmap Filter Mode:
- WGSL:
mipmap_nearest=0,mipmap_linear=1 - WebGPU equivalent:
"nearest","linear"
- WGSL:
- 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"
- WGSL:
Example usage
@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 thehandleaddress space, whereTisu32,i32orf32. It's only allowed on module-scoped variable declarations.- This will be mapped to
OpTypeImagewith Dim=SubpassDatain SPIRV.
- This will be mapped to
- A new
input_attachment_index(n)attribute wherenis au32ori32with a positive value. Unlikebindingwhich specifies the binding point in a BindGroup, this attribute specifies the index of the input attachment in the render pass descriptor. This is required forinput_attachment<T>type.- This is equivalent to
InputAttachmentIndexdecoration in SPIRV.
- This is equivalent to
- A new
inputAttachmentLoadbuiltin function with signature:inputAttachmentLoad(input_attachment<T>) -> vec4<T>- This will emit
OpImageReadfunction call in SPIRV.
- This will emit
- All of the above are only available in fragment state.
Example usage
@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
lessandgreatermodes 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 shaderSV_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.
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
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
- https://github.com/gfx-rs/wgpu/pull/7676
- https://github.com/gpuweb/gpuweb/issues/4891#issuecomment-3341897141
- https://therealmjp.github.io/posts/to-earlyz-or-not-to-earlyz/
Tint Extensions Multisampled Array Textures (`docs/tint/extensions/multisampled_array_textures.md`)
Multisampled Array Textures
The multisampled_array_textures language feature adds thetexture_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.
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:
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 thesubgroup_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,
SubgroupSizeControlis 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,
SubgroupSizeControlis enabled whenVkPhysicalDeviceSubgroupSizeControlFeatures.subgroupSizeControlandVkPhysicalDeviceSubgroupSizeControlFeatures.computeFullSubgroupsare bothVK_TRUE. - On Metal,
SubgroupSizeControlis enabled whenSubgroupMinSize == 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.
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_controlmust also enablesubgroups. subgroup_sizecannot be used whensubgroup_size_controlis not enabled.subgroup_sizecan only be used in the compute stage.subgroup_sizemust be a const-expression or an override-expression that resolves to ani32oru32.subgroup_sizemust be a power-of-two.subgroup_sizemust be greater than or equal toSubgroupMinSizeon the currentadapter.subgroup_sizemust be less than or equal toSubgroupMaxSizeon the currentadapter.- The total compute invocations per workgroup must be a multiple of the declared
subgroup_size.
Example usage
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
maxComputeWorkgroupSubgroupson 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_sizeattribute,subgroup_sizeattribute andmaxComputeWorkgroupSubgroupslimit must follow below requirement:
workgroup_size.x * workgroup_size.y * workgroup_size.z <= subgroup_size * maxComputeWorkgroupSubgroupsWe 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.
SubgroupMinSizeandSubgroupMaxSize(inwgpu::AdapterInfo)
The subgroup_size attribute is validated against SubgroupMinSize and SubgroupMaxSize directly. The separate minExplicitComputeSubgroupSize and maxExplicitComputeSubgroupSize limits are no longer needed.
- On D3D12,
SubgroupMinSizeandSubgroupMaxSizeare sourced fromD3D12_FEATURE_DATA_D3D12_OPTIONS1::waveLaneCountMinandwaveLaneCountMax. Microsoft has clarified thatWaveLaneCountMaxis 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
waveLaneCountMinreports 16. This is treated as a driver issue.
- On some Intel GPUs, pixel shaders may run with wave lane count 8 while
- On Vulkan,
SubgroupMinSizeandSubgroupMaxSizeare sourced fromVkPhysicalDeviceSubgroupSizeControlPropertiesEXT::minSubgroupSizeandmaxSubgroupSize.
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>.
Formatmust be a WebGPU plain color format.Accessis either read or read_write.
Texel buffers overload textureDimensions, textureLoad and textureStore
built-in functions.
Example usage
@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 ---
- Files Extracted: 94
- Estimated Token Budget: ~107136 tokens
- Canonical Reference: https://codewiki.google/github.com/google/dawn