WebGPU Headers: Surfaces
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.
Surface Creation
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:
ANativeWindowon Android with WGPUSurfaceSourceAndroidNativeWindowCAMetalLayeron various Apple OSes like macOS and iOS with WGPUSurfaceSourceMetalLayer<canvas>HTML elements in Emscripten (targeting WebAssembly) withWGPUSurfaceSourceCanvasHTMLSelector_EmscriptenHWNDon Windows with WGPUSurfaceSourceWindowsHWNDWindowusing Xlib with WGPUSurfaceSourceXlibWindowwl_surfaceon Wayland systems with WGPUSurfaceSourceWaylandSurfacexcb_window_tusing XCB windows with WGPUSurfaceSourceXCBWindow
Note, if the same environment-specific object is used as the output of two different things simultaneously (two different WGPUSurfaces, 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:
.hinstance = GetModuleHandle(nullptr),
.hwnd = myHWND,
};
.label = {.data = "Main window", .length = WGPU_STRLEN},
};
@ WGPUSType_SurfaceSourceWindowsHWND
struct WGPUSurfaceImpl * WGPUSurface
WGPUSurface wgpuInstanceCreateSurface(WGPUInstance instance, WGPUSurfaceDescriptor const *descriptor)
WGPUChainedStruct * nextInChain
In addition, a WGPUSurface has a bunch of internal fields that could be represented like this (in C/Rust-like pseudocode):
Option<WGPUSurfaceConfiguration> config = None;
Option<WGPUTexture> currentFrame = None;
};
struct WGPUInstanceImpl * WGPUInstance
The behavior of wgpuInstanceCreateSurface(instance, descriptor) is:
- If any of these validation steps fails, return an error WGPUSurface object:
- Validate that all the sub-descriptors in the chain for
descriptorare known to this implementation. - Validate that
descriptorcontains information about exactly one OS surface. - As best as possible, validate that the OS surface described in the descriptor is valid (for example a zero
HWNDdoesn't exist and isn't valid).
- Validate that all the sub-descriptors in the chain for
- Return a new WGPUSurface with its
instancemember initialized with theinstanceparameter and other values defaulted.
Querying Surface Capabilities
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:
- A bit set of supported WGPUTextureUsage that are guaranteed to contain WGPUTextureUsage_RenderAttachment.
- A list of supported WGPUTextureFormat values, in order of preference.
- A list of supported WGPUPresentMode values (guaranteed to contain WGPUPresentMode_Fifo).
- A list of supported WGPUCompositeAlphaMode values (WGPUCompositeAlphaMode_Auto is always supported but never listed in capabilities as it just lets the implementation decide what to use).
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 of a WGPUSurface:
return;
}
bool supportsMailbox = false;
}
static const WGPUTextureUsage WGPUTextureUsage_TextureBinding
@ WGPUPresentMode_Mailbox
void wgpuSurfaceCapabilitiesFreeMembers(WGPUSurfaceCapabilities surfaceCapabilities)
WGPUStatus wgpuSurfaceGetCapabilities(WGPUSurface surface, WGPUAdapter adapter, WGPUSurfaceCapabilities *capabilities)
WGPUPresentMode const * presentModes
The behavior of wgpuSurfaceGetCapabilities(surface, adapter, caps) is:
- If any of these validation steps fails, return false. (TODO return an error WGPUStatus):
- Validate that all the sub-descriptors in the chain for
capsare known to this implementation. - Validate that
surfaceandadapterare created from the same WGPUInstance.
- Validate that all the sub-descriptors in the chain for
- Fill
capswithadapter's capabilities to render tosurface. - Return true. (TODO return WGPUStatus_Success)
Surface Configuration
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:
- The WGPUDevice that will be used to render to the surface.
- Parameters for the textures returned by wgpuSurfaceGetCurrentTexture.
- WGPUPresentMode and WGPUCompositeAlphaMode parameters for how and when the surface will be presented to the user.
This is an example of how to configure a WGPUSurface:
nextInChain = nullptr,
device = myDevice,
format = preferredFormat,
width = 640,
height = 480,
};
static const WGPUTextureUsage WGPUTextureUsage_RenderAttachment
@ WGPUCompositeAlphaMode_Auto
void wgpuSurfaceConfigure(WGPUSurface surface, WGPUSurfaceConfiguration const *config)
The parameters for the texture are used to create the texture each frame (see Presenting to Surface) with the equivalent WGPUTextureDescriptor computed like this:
return {
.usage = config->usage,
.format = config->format,
.nextInChain = nullptr,
.label = {.data = "", .length = WGPU_STRLEN},
.sampleCount = 1,
.mipLevelCount = 1,
};
}
@ WGPUTextureDimension_2D
WGPUTextureFormat const * viewFormats
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:
- Unconfigure the surface.
- If
config->deviceis nullptr, produce Implementation-Defined Logging and return. - If any of these validation steps fails, report the error as a Device Error to
config->deviceand return. (If the device is lost, it won't report errors. There may be Implementation-Defined Logging.)- Validate that
configdoes not have any Struct-Chaining Error. - Validate that
surfaceis not an error. - Validate that
config->deviceis not lost. - Let
adapterbe the adapter used to createconfig->device. - Let
capsbe the WGPUSurfaceCapabilities filled withwgpuSurfaceGetCapabilities(surface, adapter, &caps). - Validate that all the sub-descriptors in the chain for
capsare known to this implementation. - Validate that
config->presentModeis incaps->presentModes. - Validate that
config->alphaModeis eitherWGPUCompositeAlphaMode_Autoor incaps->alphaModes. - Validate that
config->formatif incaps->formats. - Validate that
config->usageis a subset ofcaps->usages. - Let
textureDescbeGetSurfaceEquivalentTextureDescriptor(config). - Validate that
wgpuDeviceCreateTexture(config->device, &textureDesc)would succeed.
- Validate that
- Set
surface.configto a deep copy ofconfig. - If
surface.currentFrameis notNone:- Do as if
wgpuTextureDestroy(surface.currentFrame)was called. - Set
surface.currentFrametoNone.
- Do as if
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:
- Set
surface.configtoNone. - If
surface.currentFrameis notNone:- Do as if
wgpuTextureDestroy(surface.currentFrame)was called. - Set
surface.currentFrametoNone.
- Do as if
Presenting to Surface
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:
if (surfaceTexture.texture == NULL) {
return;
}
HandleResize();
return;
}
RenderTo(surfaceTexture.texture);
@ WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal
WGPUStatus wgpuSurfacePresent(WGPUSurface surface)
void wgpuSurfaceGetCurrentTexture(WGPUSurface surface, WGPUSurfaceTexture *surfaceTexture)
void wgpuTextureRelease(WGPUTexture texture)
WGPUSurfaceGetCurrentTextureStatus status
The behavior of wgpuSurfaceGetCurrentTexture(surface, surfaceTexture) is:
- Set
surfaceTexture->texturetoNULL.
- If any of these validation steps fails, set
surfaceTexture->statustoWGPUSurfaceGetCurrentTextureStatus_Errorand return (TODO send error to device?).- Validate that
surfaceis not an error.
- Validate that
surface.configis notNone.
- Validate that
surface.currentFrameisNone.
- Validate that
- Let
textureDescbeGetSurfaceEquivalentTextureDescriptor(surface.config).
If
surface.config.deviceis alive:- If the implementation detects any other problem preventing use of the surface, set
surfaceTexture->statusto an appropriate status (something other thanSuccessOptimal,SuccessSuboptimal, orError) and return.
- Create a new WGPUTexture
t, as if callingwgpuDeviceCreateTexture(surface.config.device, &textureDesc), but wrapping the appropriate backing resource.
- If the implementation detects a reason why the current configuration is suboptimal, set
surfaceTexture->statustoWGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal. Otherwise, set it toWGPUSurfaceGetCurrentTextureStatus_SuccessOptimal.
Otherwise:
- Create a new invalid WGPUTexture
t, as if callingwgpuDeviceCreateTexture(surface.config.device, &texturedesc).
- Set
surfaceTexture->statustoWGPUSurfaceGetCurrentTextureStatus_SuccessOptimal.
- If the implementation detects any other problem preventing use of the surface, set
- Set
surface.currentFrametot.
- Add a new reference to
t.
- Set
surfaceTexture->textureto a new reference tot.
The behavior of wgpuSurfacePresent(surface) is:
- If any of these validation steps fails, TODO send error to device?
- Validate that
surfaceis not an error. - Validate that
surface.currentFrameis notNone.
- Validate that
- Do as if
wgpuTextureDestroy(surface.currentFrame)was called. - Present
surface.currentFrameto thesurface. - Set
surface.currentFrametoNone.