WebGPU Headers
The WebGPU C API
 
Loading...
Searching...
No Matches
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:

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:

.chain = { .sType = WGPUSType_SurfaceSourceWindowsHWND, },
.hinstance = GetModuleHandle(nullptr),
.hwnd = myHWND,
};
.nextInChain = &hwndDesc.chain,
.label = {.data = "Main window", .length = WGPU_STRLEN},
};
WGPUSurface surface = wgpuInstanceCreateSurface(myInstance, &desc);
struct WGPUSurfaceImpl * WGPUSurface
Definition webgpu.h:167
#define WGPU_STRLEN
Definition webgpu.h:129
WGPUSurface wgpuInstanceCreateSurface(WGPUInstance instance, WGPUSurfaceDescriptor const *descriptor)

In addition, a WGPUSurface has a bunch of internal fields that could be represented like this (in C/Rust-like pseudocode):

struct WGPUSurface {
// The parent object
WGPUInstance instance;
// The current configuration
Option<WGPUSurfaceConfiguration> config = None;
// A reference to the frame's texture, if any.
Option<WGPUTexture> currentFrame = None;
};

The behavior of wgpuInstanceCreateSurface(instance, descriptor) is:

  • If any of these validation steps fails, return an error WGPUSurface:
    • Validate that all the sub-descriptors in the chain for descriptor are known to this implementation.
    • Validate that descriptor contains 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 HWND doesn't exist and isn't valid).
  • Return a new WGPUSurface with its instance member initialized with the instance parameter 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:

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:

// Get the capabilities
if (!wgpuSurfaceGetCapabilities(mySurface, myAdapter, &caps)) {
// Either a validation error happened or the adapter doesn't support the surface.
// TODO: This should be a WGPUStatus.
return;
}
// Do things with capabilities
bool canSampleSurface = caps.usages & WGPUTextureUsage_TextureBinding;
WGPUTextureFormat preferredFormat = caps.format[0];
bool supportsMailbox = false;
for (size_t i = 0; i < caps.presentModeCount; i++) {
if (caps.presentModes[i] == WGPUPresentMode_Mailbox) supportsMailbox = true;
}
// Cleanup
WGPUTextureFormat
Definition webgpu.h:878
@ WGPUPresentMode_Mailbox
Definition webgpu.h:676
void wgpuSurfaceCapabilitiesFreeMembers(WGPUSurfaceCapabilities surfaceCapabilities)
WGPUStatus wgpuSurfaceGetCapabilities(WGPUSurface surface, WGPUAdapter adapter, WGPUSurfaceCapabilities *capabilities)
WGPUTextureUsage usages
Definition webgpu.h:1811

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 caps are known to this implementation.
    • Validate that surface and adapter are created from the same WGPUInstance.
  • Fill caps with adapter's capabilities to render to surface.
  • 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:

This is an example of how to configure a WGPUSurface:

nextInChain = nullptr,
device = myDevice,
format = preferredFormat,
width = 640, // Depending on the window size.
height = 480,
usage = WGPUTextureUsage_RenderAttachment,
presentMode = supportsMailbox ? WGPUPresentMode_Mailbox : WGPUPresentMode_Fifo,
};
wgpuSurfaceConfigure(mySurface, &config);
@ WGPUPresentMode_Fifo
Definition webgpu.h:657
@ WGPUCompositeAlphaMode_Auto
Definition webgpu.h:440
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:

WGPUTextureDescriptor GetSurfaceEquivalentTextureDescriptor(const WGPUSurfaceConfiguration* config) {
return {
// Parameters controlled by the surface configuration.
.size = {config->width, config->height, 1},
.usage = config->usage,
.format = config->format,
.viewFormatCount = config->viewFormatCount,
.viewFormats = config->viewFormats,
// Parameters that cannot be changed.
.nextInChain = nullptr,
.label = {.data = "", .length = WGPU_STRLEN},
.dimension = WGPUTextureDimension_2D,
.sampleCount = 1,
.mipLevelCount = 1,
};
}
WGPUTextureUsage usage
Definition webgpu.h:1848
WGPUTextureFormat format
Definition webgpu.h:1844

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:

  • If any of these validation steps fails, TODO: what should happen on failure?
    • Validate that surface is not an error.
    • Let adapter be the adapter used to create device.
    • Let caps be the WGPUSurfaceCapabilities filled with wgpuSurfaceGetCapabilities(surface, adapter, &caps).
    • Validate that all the sub-descriptors in the chain for caps are known to this implementation.
    • Validate that device is alive.
    • Validate that config->presentMode is in caps->presentModes.
    • Validate that config->alphaMode is either WGPUCompositeAlphaMode_Auto or in caps->alphaModes.
    • Validate that config->format if in caps->formats.
    • Validate that config->usage is a subset of caps->usages.
    • Let textureDesc be GetSurfaceEquivalentTextureDescriptor(config).
    • Validate that wgpuDeviceCreateTexture(device, &textureDesc) would succeed.
  • Set surface.config to a deep copy of config.
  • If surface.currentFrame is not None:
    • Do as if wgpuTextureDestroy(surface.currentFrame) was called.
    • Set 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:

  • Set surface.config to None.
  • If surface.currentFrame is not None:
    • Do as if wgpuTextureDestroy(surface.currentFrame) was called.
    • Set surface.currentFrame to None.

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:

// Get the texture and handle exceptional cases.
WGPUSurfaceTexture surfaceTexture;
wgpuSurfaceGetCurrentTexture(mySurface, &surfaceTexture);
if (surfaceTexture.texture == NULL) {
// Recover if possible.
return;
}
// Since the texture is not null, the status is some kind of success.
// We can optionally handle the "SuccessSuboptimal" case.
HandleResize();
return;
}
// The application renders to the texture.
RenderTo(surfaceTexture.texture);
// Present the texture, it is no longer accessible after that point.
wgpuSurfacePresent(mySurface);
// Release the reference we got to the now presented texture. (it can safely be done before present as well)
wgpuTextureRelease(surfaceTexture.texture);
@ WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal
Definition webgpu.h:825
WGPUStatus wgpuSurfacePresent(WGPUSurface surface)
void wgpuSurfaceGetCurrentTexture(WGPUSurface surface, WGPUSurfaceTexture *surfaceTexture)
WGPUTexture texture
Definition webgpu.h:1980
WGPUSurfaceGetCurrentTextureStatus status
Definition webgpu.h:1984

The behavior of wgpuSurfaceGetCurrentTexture(surface, surfaceTexture) is:

  1. Set surfaceTexture->texture to NULL.
  1. If any of these validation steps fails, set surfaceTexture->status to WGPUSurfaceGetCurrentTextureStatus_Error and return (TODO send error to device?).
    1. Validate that surface is not an error.
    1. Validate that surface.config is not None.
    1. Validate that surface.currentFrame is None.
  1. Let textureDesc be GetSurfaceEquivalentTextureDescriptor(surface.config).
  1. If surface.config.device is alive:

    1. If the implementation detects any other problem preventing use of the surface, set surfaceTexture->status to an appropriate status (something other than SuccessOptimal, SuccessSuboptimal, or Error) and return.
    1. Create a new WGPUTexture t, as if calling wgpuDeviceCreateTexture(surface.config.device, &textureDesc), but wrapping the appropriate backing resource.
    1. If the implementation detects a reason why the current configuration is suboptimal, set surfaceTexture->status to WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal. Otherwise, set it to WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal.

    Otherwise:

    1. Create a new invalid WGPUTexture t, as if calling wgpuDeviceCreateTexture(surface.config.device, &texturedesc).
    1. Set surfaceTexture->status to WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal.
  1. Set surface.currentFrame to t.
  1. Add a new reference to t.
  1. Set surfaceTexture->texture to a new reference to t.

The behavior of wgpuSurfacePresent(surface) is:

  • If any of these validation steps fails, TODO send error to device?
    • Validate that surface is not an error.
    • Validate that surface.currentFrame is not None.
  • Do as if wgpuTextureDestroy(surface.currentFrame) was called.
  • Present surface.currentFrame to the surface.
  • Set surface.currentFrame to None.