henrybetts / swift-webgpu

Swift bindings for WebGPU
MIT License
78 stars 3 forks source link
dawn gpu graphics swift webgpu

swift-webgpu

Swift bindings for WebGPU; a new graphics and compute API.

Despite being designed for the web, WebGPU can also be used natively, enabling developers with a modern, cross-platform API, without some of the complexities of lower-level graphics libraries.

Efforts are being made to define a standard native version of the API via a shared header. Note, however, that the specification is still work-in-progress.

Currently, swift-webgpu is based on the Dawn implementation, and generated from dawn.json.

Requirements

Dawn

To use swift-webgpu, you'll first need to build Dawn. See Dawn's documentation to get started.

swift-webgpu depends on the bundled libwebgpu_dawn library, which can be built and installed like so;

mkdir -p out/Release
cd out/Release
cmake -DCMAKE_BUILD_TYPE=Release -DDAWN_ENABLE_INSTALL=1 -DDAWN_BUILD_SAMPLES=0 -GNinja ../..
ninja
sudo ninja install

This should install the library and headers to /usr/local/ - this is probably the simplest way to allow Swift to find the library currently.

pkg-config

You may need to manually create a pkg-config file, depending on which tools you are using. For example, running swift build directly from the command line seems to search /usr/local/ automatically, whereas building with Xcode does not. If you run into this issue, create a file at /usr/local/lib/pkgconfig/webgpu.pc with the following contents;

prefix=/usr/local
includedir=${prefix}/include
libdir=${prefix}/lib

Cflags: -I${includedir}
Libs: -L${libdir} -lwebgpu_dawn

dawn.json

This file contains a description of the WebGPU native API. By default, swift-webgpu will look for it in /usr/local/share/dawn/, so you will need to copy it there manually;

sudo install -d /usr/local/share/dawn
sudo install ../../src/dawn/dawn.json /usr/local/share/dawn/

Alternatively, you can specify a DAWN_JSON environment variable when building swift-webgpu.

Running the Demos

First, clone this project;

git clone https://github.com/henrybetts/swift-webgpu.git
cd swift-webgpu

Build the package (assuming that Dawn is installed and Swift can find it automatically);

swift build -c release

Otherwise, you may need to specify the search paths manually;

DAWN_JSON=/path/to/dawn/src/dawn/dawn.json \
swift build -c release \
-Xcc -I/path/to/dawn/include \
-Xcc -I/path/to/dawn/out/Release/gen/include \
-Xlinker -L/path/to/dawn/out/Release/src/dawn/native \
-Xlinker -rpath -Xlinker /path/to/dawn/out/Release/src/dawn/native

Finally, run the demos;

cd .build/release
./DemoInfo
./DemoClearColor
./DemoTriangle
./DemoCube
./DemoBoids

Installation

To use swift-webgpu with Swift Package Manager, add it to your Package.swift file's dependencies;

.package(url: "https://github.com/henrybetts/swift-webgpu.git", branch: "main")

Then add WebGPU as a dependency of your target;

.executableTarget(
    name: "MyApp",
    dependencies: [.product(name: "WebGPU", package: "swift-webgpu")],
),

Basic Usage

Import the WebGPU module where needed;

import WebGPU

A typical application will start by creating an Instance, requesting an Adapter, and then requesting a Device. For example;

// create an instance
let instance = createInstance()

// find an adapter
let adapter = try await instance.requestAdapter()
print("Using adapter: \(adapter.properties.name)")

// create a device
let device = try await adapter.requestDevice()

You'll usually want to set an error handler, to log any errors produced by the device;

device.setUncapturedErrorCallback { (errorType, errorMessage) in
  print("Error (\(errorType)): \(errorMessage)")
}

With the device obtained, you can create most of the other types of WebGPU objects. A shader module, for example;

let shaderModule = device.createShaderModule(
  descriptor: .init(
    nextInChain: ShaderModuleWgslDescriptor(
      code: """
        @vertex
        fn vertexMain() -> @builtin(position) vec4f {
          return vec4f(0, 0, 0, 1);
        }

        @fragment
        fn fragmentMain() -> @location(0) vec4f {
          return vec4f(1, 0, 0, 1);
        }
      """
    )
  )
)

Or a render pipeline;

let renderPipeline = device.createRenderPipeline(
  descriptor: .init(
    vertex: VertexState(
      module: shaderModule,
      entryPoint: "vertexMain"
    ),
    fragment: FragmentState(
      module: shaderModule,
      entryPoint: "fragmentMain",
      targets: [ColorTargetState(format: .bgra8Unorm)]
    )
  )
)

Most objects are created with a descriptor. In some cases, WebGPU uses a chainable struct pattern to support future extensions or platform specific features. This is indicated by the nextInChain property. (There is scope to improve the ergonomics of this in Swift.)

See the demos for further usage examples.