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.
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.
The device lifecycle is a bit more complicated than other objects in Dawn for multiple reasons:
There is a state machine State
defined in Device.h that controls all of the above. The most common state is Alive
when there are potentially GPU commands executing.
Initialization of a device looks like the following:
DeviceBase::DeviceBase
is called and does mostly nothing except setting State
to BeingCreated
(and initial toggles).backend::Device::Initialize
creates things like the underlying device and other stuff that doesn't run GPU commands.DeviceBase::Initialize
that enables the DeviceBase
facilities and sets the State
to Alive
.backend::Device::Initialize
can now enqueue GPU commands for its initialization.While it is Alive
the device can notify it has been disconnected by the backend, in which case it jumps directly to the Disconnected
state. Internal errors, or a call to LoseForTesting
can also disconnect the device, but in the underlying API commands are still running, so the frontend will finish all commands (with WaitForIdleForDesctruction
) and prevent any new commands to be enqueued (by setting state to BeingDisconnected
). After this the device is set in the Disconnected
state. If an Alive
device is destroyed, then a similar flow to LoseForTesting happens
.
All this ensures that during destruction or forceful disconnect of the device, it properly gets to the Disconnected
state with no commands executing on the GPU. After disconnecting, frontend will call backend::Device::DestroyImpl
so that it can properly free driver objects.
Toggles 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).
Toogles are to be used for any optional code path in Dawn, including:
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 force enabling of a toggle or, at the contrary, force the disabling of a toogle. 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 forcibly disabled, and in the default configuration.
DAWN_INSTANTIATE_TEST(RenderPassTest, D3D12Backend(), D3D12Backend({}, {"use_d3d12_render_pass"})); // The {} is the list of force enabled toggles, {"..."} the force disabled ones.
The initialization order of toggles looks as follows:
DeviceBase::SetToggle
DeviceBase::ForceSetToggle
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 configuation and with the toggle disabled.
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.
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 informations about formats, like what they translate to in the backing API.
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.