Surfaces are used to continuously present color texture data to users in an OS Window, HTML <canvas>
, or other similar outputs. The webgpu.h
concept of WGPUSurface is similar to WebGPU's GPUCanvasContext
but includes additional options to control behavior or query options that are specific to the environment the application runs in. In other GPU APIs, similar concepts are called "default framebuffer", "swapchain" or "presentable".
To use a surface, there is a one-time setup, then additional per-frame operations. The one time setup is: environment-specific creation (to wrap a <canvas>
, HWND
, Window
, etc.), querying capabilities of the surface, and configuring the surface. Per-frame operations are: getting a current WGPUSurfaceTexture to render content to, rendering content, presenting the surface, and when appropriate reconfiguring the surface (typically when the window is resized).
Sections below give more details about these operations, including the specification of their behavior.
A WGPUSurface is child object of a WGPUInstance and created using wgpuInstanceCreateSurface
. The description of a WGPUSurface is a WGPUSurfaceDescriptor with a sub-descriptor chained containing the environment-specific objects used to identify the surface.
Surfaces that can be presented to using webgpu.h
(but not necessarily by all implementations) are:
ANativeWindow
on Android with WGPUSurfaceSourceAndroidNativeWindowCAMetalLayer
on various Apple OSes like macOS and iOS with WGPUSurfaceSourceMetalLayer<canvas>
HTML elements in Emscripten (targeting WebAssembly) with WGPUSurfaceSourceCanvasHTMLSelector_Emscripten
HWND
on Windows with WGPUSurfaceSourceWindowsHWNDWindow
using Xlib with WGPUSurfaceSourceXlibWindowwl_surface
on Wayland systems with WGPUSurfaceSourceWaylandSurfacexcb_window_t
using XCB windows with WGPUSurfaceSourceXCBWindowNote, if the same environment-specific object is used as the output of two different things simultaneously (two different WGPUSurface
s, or one WGPUSurface
and something else outside webgpu.h
), the behavior is undefined.
For example, creating an WGPUSurface from an HWND
is done like so:
In addition, a WGPUSurface has a bunch of internal fields that could be represented like this (in C/Rust-like pseudocode):
The behavior of wgpuInstanceCreateSurface
(instance, descriptor)
is:
descriptor
are known to this implementation.descriptor
contains information about exactly one OS surface.HWND
doesn't exist and isn't valid).instance
member initialized with the instance
parameter and other values defaulted.Depending on the OS, GPU used, backing API for WebGPU and other factors, different capabilities are available to render and present the WGPUSurface. For this reason, negotiation is done between the WebGPU implementation and the application to choose how to use the WGPUSurface. This first step of the negotiation is querying what capabilities are available using wgpuSurfaceGetCapabilities
that fills an WGPUSurfaceCapabilities structure with the following information:
The call to wgpuSurfaceGetCapabilities
may allocate memory for pointers filled in the WGPUSurfaceCapabilities structure so wgpuSurfaceCapabilitiesFreeMembers
must be called to avoid leaking memory once the capabilities are no longer needed.
This is an example of how to query the capabilities or a WGPUSurface:
The behavior of wgpuSurfaceGetCapabilities
(surface, adapter, caps)
is:
caps
are known to this implementation.surface
and adapter
are created from the same WGPUInstance.caps
with adapter
's capabilities to render to surface
.Before it can use it for rendering, the application must configure the surface. The configuration is the second step of the negotiation, done after analyzing the results of wgpuSurfaceGetCapabilities
. It contains the following kinds of parameters:
wgpuSurfaceGetCurrentTexture
.This is an example of how to configure a WGPUSurface:
The parameters for the texture are used to create the texture each frame (see Presenting to Surface) with the equivalent WGPUTextureDescriptor computed like this:
When a surface is successfully configured, the new configuration overrides any previous configuration and destroys the previous current texture (if any) so it can no longer be used.
The behavior of wgpuSurfaceConfigure
(surface, config)
is:
surface
is not an error.adapter
be the adapter used to create device
.caps
be the WGPUSurfaceCapabilities filled with wgpuSurfaceGetCapabilities
(surface, adapter, &caps)
.caps
are known to this implementation.device
is alive.config->presentMode
is in caps->presentModes
.config->alphaMode
is either WGPUCompositeAlphaMode_Auto
or in caps->alphaModes
.config->format
if in caps->formats
.config->usage
is a subset of caps->usages
.textureDesc
be GetSurfaceEquivalentTextureDescriptor(config)
.wgpuDeviceCreateTexture(device, &textureDesc)
would succeed.surface.config
to a deep copy of config
.surface.currentFrame
is not None
:wgpuTextureDestroy
(surface.currentFrame)
was called.surface.currentFrame
to None
.It can also be useful to remove the configuration of a WGPUSurface without replacing it with a valid one. Without removing the configuration, the WGPUSurface will keep referencing the WGPUDevice that cannot be totally reclaimed.
The behavior of wgpuSurfaceUnconfigure
()
is:
surface.config
to None
.surface.currentFrame
is not None
:wgpuTextureDestroy
(surface.currentFrame)
was called.surface.currentFrame
to None
.Each frame, the application retrieves the WGPUTexture for the frame with wgpuSurfaceGetCurrentTexture
, renders to it and then presents it on the screen with wgpuSurfacePresent
.
Issues can happen when trying to retrieve the frame's WGPUTexture, so the application must check WGPUSurfaceTexture .status
to see if the surface or the device was lost, or some other windowing system issue caused a timeout. The environment can also change the surface without breaking it, but making the current configuration suboptimal. In this case, WGPUSurfaceTexture .status
will be WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal and the application should (but isn't required to) handle it. Surfaces often become suboptimal when the window is resized (so presenting requires resizing a texture, which is both performance overhead, and a visual quality degradation).
This is an example of how to render to a WGPUSurface each frame:
The behavior of wgpuSurfaceGetCurrentTexture
(surface, surfaceTexture)
is:
surfaceTexture->texture
to NULL
.surfaceTexture->status
to WGPUSurfaceGetCurrentTextureStatus_Error
and return (TODO send error to device?).surface
is not an error.surface.config
is not None
.surface.currentFrame
is None
.textureDesc
be GetSurfaceEquivalentTextureDescriptor(surface.config)
.If surface.config.device
is alive:
surfaceTexture->status
to an appropriate status (something other than SuccessOptimal
, SuccessSuboptimal
, or Error
) and return.t
, as if calling wgpuDeviceCreateTexture(surface.config.device, &textureDesc)
, but wrapping the appropriate backing resource.surfaceTexture->status
to WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal
. Otherwise, set it to WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal
.Otherwise:
t
, as if calling wgpuDeviceCreateTexture(surface.config.device, &texturedesc)
.surfaceTexture->status
to WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal
.surface.currentFrame
to t
.t
.surfaceTexture->texture
to a new reference to t
.The behavior of wgpuSurfacePresent
(surface)
is:
surface
is not an error.surface.currentFrame
is not None
.wgpuTextureDestroy
(surface.currentFrame)
was called.surface.currentFrame
to the surface
.surface.currentFrame
to None
.