WebGPU now available for testing in Safari Technology Preview

WebGPU is a new standards-compliant API that enables high-performance 3D graphics and general-purpose computations on the Web. WebGPU programs are written in JavaScript but expose GPU functionality, allowing GPU computing to be used in Web content for the first time. Starting in Safari Technology Preview 185, WebGPU can be enabled for early testing and development.

To enable WebGPU, turn on the “WebGPU”, “GPU Process: DOM Rendering” and “GPU Process: Canvas Rendering” feature flags in the Feature Flags tab in Safari Preferences. If you don’t see the Feature Flags tab, you need to first check “Show features for web developers” in the Advanced tab.

Once you have WebGPU enabled in Safari Technology Preview 185, try out this example of WebGPU. It utilizes many of the best features of WebGPU.

WebGPU JavaScript API

The WebGPU API is accessed through JavaScript, similar to WebGL.

Creating a GPUDevice

In order to use WebGPU, a device must be created. Resources and pipeline state are created from a GPUDevice instance. To create a device with default limits and features which are supported on all devices supporting WebGPU, we can pass zero parameters to the invocations of requestAdapter and requestDevice.

const adapter = await navigator.gpu.requestAdapter();
device = await adapter.requestDevice();

Configuring a GPUCanvasContext

The GPUCanvasContext is an interface that allows you to configure how your content will be displayed in the corresponding HTMLCanvas element on the page.

context = canvas.getContext('webgpu');
const canvasFormat = "bgra8unorm";

const contextConfiguration = {
    device: device,
    format: canvasFormat,
    alphaMode: 'opaque',
};
context.configure(contextConfiguration);

Creating a GPURenderPipeline

A GPURenderPipeline or a corresponding GPUComputePipeline are used to configure the pipeline state of the graphics driver. This pipeline state is then used in a GPURenderPassEncoder or GPUComputePassEncoder as later illustrated.

const shaderModule = device.createShaderModule({ code: wgslSource });
const vertexStageDescriptor = { module: shaderModule, entryPoint: "vsmain" };
const fragmentStageDescriptor = { module: shaderModule, entryPoint: "fsmain" };
const renderPipelineDescriptor = {
    layout: 'auto',
    vertex: vertexStageDescriptor,
    fragment: fragmentStageDescriptor,
    primitive: {topology: "triangle-list" },
};
const renderPipeline = device.createRenderPipeline(renderPipelineDescriptor);

Issuing draw calls

A GPURenderPassEncoder is created to send draw calls to the graphics driver. In the below example, we draw a simple triangle which contains three vertices. A GPURenderPassEncoder can also draw multiple instances of the same geometry or draw from an offset of a vertex buffer.

const colorAttachmentDescriptor = {
    view: renderAttachment,
    loadOp: "clear",
    storeOp: "store",
    clearColor: { r: 0.15, g: 0.15, b: 0.5, a: 1 }
};
const renderPassDescriptor = { colorAttachments: [colorAttachmentDescriptor] };
const commandEncoder = device.createCommandEncoder();
const renderPassEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
renderPassEncoder.setPipeline(renderPipeline);
const vertexBufferSlot = 0;
renderPassEncoder.setVertexBuffer(vertexBufferSlot, vertexBuffer, 0);
renderPassEncoder.draw(3, 1, 0, 0); // 3 vertices, 1 instance, 0th vertex, 0th instance.
renderPassEncoder.end();
const commandBuffer = commandEncoder.finish();
const queue = device.queue;
queue.submit([commandBuffer]);

WebGPU Shading Language

WebGPU introduces WGSL, a platform independent shading language for the web. Here is an example of a WGSL shader source that would be passed in place of wgslSource in the above API call:

const wgslSource = `
    struct Vertex {
        @builtin(position) Position: vec4<f32>,
        @location(0) color: vec4<f32>,
    }

    @vertex fn vsmain(@builtin(vertex_index) VertexIndex: u32) -> Vertex
    {
        var pos: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
            vec2<f32>( 0.0,  0.5),
            vec2<f32>(-0.5, -0.5),
            vec2<f32>( 0.5, -0.5));
        var vertex_out : Vertex;
        vertex_out.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
        vertex_out.color = vec4<f32>(pos[VertexIndex] + vec2<f32>(0.5, 0.5), 0.0, 1.0);
        return vertex_out;
    }

    @fragment fn fsmain(in: Vertex) -> @location(0) vec4<f32>
    {
        return in.color;
    }
`;

Try WebGPU and file bugs!

We’re very excited to have an early version of WebGPU and WGSL in the latest version of Safari Technology Preview. Please do try it out. Check out the public repository of WebGPU samples. And file bugs or issues you discover at bugs.webkit.org.