Mojo bindings for wgpu-native, providing a lightweight WebGPU wrapper for Mojo applications with RAII-friendly GPU objects and GLFW-based examples.
- Mojo
>= 0.26.3 - Pixi package manager
libwgpu_native.soavailable atffi/lib/libwgpu_native.so- GLFW installed and available through your Conda environment
- Platform GPU drivers and runtime support for your system
This repository provides the Mojo wrapper and Pixi tasks, but does not bundle GPU drivers or native runtime libraries.
Download the correct pre-built binary from the wgpu-native releases page and place it in ffi/lib/:
| Platform | Asset to download | File to copy |
|---|---|---|
| Linux x86-64 | wgpu-linux-x86_64-release.zip |
libwgpu_native.so → ffi/lib/ |
| macOS arm64 | wgpu-macos-aarch64-release.zip |
libwgpu_native.dylib → ffi/lib/ |
| macOS x86-64 | wgpu-macos-x86_64-release.zip |
libwgpu_native.dylib → ffi/lib/ |
| Windows x64 | wgpu-windows-x86_64-release.zip |
wgpu_native.dll + .lib → ffi/lib/ |
mkdir -p ffi/lib
# Linux example — replace the tag with the version matching wgpu-native-git-tag
TAG=$(cat ffi/wgpu-native-meta/wgpu-native-git-tag)
wget "https://github.com/gfx-rs/wgpu-native/releases/download/${TAG}/wgpu-linux-x86_64-release.zip"
unzip wgpu-linux-x86_64-release.zip -d /tmp/wgpu-native
cp /tmp/wgpu-native/libwgpu_native.so ffi/lib/Verify: ls -lh ffi/lib/libwgpu_native.so before building callbacks.
| Platform | What you need |
|---|---|
| Linux | Vulkan drivers: mesa-vulkan-drivers + libvulkan1 (Intel/AMD) or the NVIDIA proprietary stack |
| macOS | Metal is built into macOS — no extra drivers needed |
| Windows | D3D12 or Vulkan drivers — typically already installed with your GPU vendor's driver package |
GLFW is provided via Conda through Pixi on Linux. On macOS/Windows, install it via brew or vcpkg and ensure it is on your library path.
Note: The Pixi workspace is currently configured for
linux-64. On macOS or Windows you can still build and run the code manually withmojo run -I . hello.mojoafter placing the correct native library inffi/lib/.
- Install Mojo and Pixi.
- Build the native callback libraries:
pixi run build-callbacks
pixi run build-callback-probe- Run the hello triangle example:
pixi run helloIf the window appears, the core runtime path is working: wgpu-native → FFI bridge → Mojo wrappers → GLFW window.
hello.mojo renders an RGB vertex-coloured triangle in a GLFW window. This is the exact pattern the file uses:
from wgpu.gpu import GPU
from wgpu._ffi.types import OpaquePtr
from wgpu._ffi.structs import WGPUColor
from rendercanvas import RenderCanvas
comptime WGSL = """
struct VertexOut {
@builtin(position) pos: vec4<f32>,
@location(0) col: vec3<f32>,
}
@vertex
fn vs_main(@builtin(vertex_index) i: u32) -> VertexOut {
var pos = array<vec2<f32>, 3>(
vec2( 0.0, 0.5), vec2(-0.5, -0.5), vec2( 0.5, -0.5),
);
var col = array<vec3<f32>, 3>(
vec3(1.0, 0.0, 0.0), vec3(0.0, 1.0, 0.0), vec3(0.0, 0.0, 1.0),
);
var out: VertexOut;
out.pos = vec4<f32>(pos[i], 0.0, 1.0);
out.col = col[i];
return out;
}
@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
return vec4<f32>(in.col, 1.0);
}
"""
def main() raises:
var gpu = GPU()
var device = gpu.request_device()
var canvas = RenderCanvas(gpu, device, 800, 600, "wgpu-mojo: hello triangle")
var shader = device.create_shader_module_wgsl(WGSL, "hello")
var layout = device.create_pipeline_layout(List[OpaquePtr](), "layout")
var pipeline = device.create_render_pipeline(
shader, "vs_main", "fs_main",
canvas.surface_format(), layout,
primitive_topology=UInt32(4), # TriangleList
)
while canvas.is_open():
canvas.poll()
var frame = canvas.next_frame()
if not frame.is_renderable():
continue
var enc = device.create_command_encoder("frame")
var rpass = enc.begin_surface_clear_pass(
frame.texture,
WGPUColor(Float64(0), Float64(0), Float64(0), Float64(1)),
"pass",
)
rpass.set_pipeline(pipeline)
rpass.draw(UInt32(3), UInt32(1), UInt32(0), UInt32(0))
rpass^.end()
device.queue_submit(enc^.finish())
canvas.present()Run it with pixi run hello. See examples/triangle_window.mojo for an identical standalone version.
For headless work (ML, simulation, data processing), skip RenderCanvas entirely:
from wgpu import GPU, WGPUBufferUsage
def main() raises:
var gpu = GPU()
var device = gpu.request_device()
var buf = device.create_buffer(
UInt64(1024),
WGPUBufferUsage.STORAGE | WGPUBufferUsage.COPY_DST,
label="my_buffer",
)
# GPU objects release automatically when they go out of scope (RAII).See examples/compute_add.mojo for a full vector-addition pipeline with buffer readback.
pixi run build-callbacks— build the C callback bridgepixi run build-callback-probe— build the callback probe librarypixi run hello— runhello.mojopixi run example-triangle— runexamples/triangle_window.mojopixi run example-compute— runexamples/compute_add.mojopixi run example-enumerate— runexamples/enumerate_adapters.mojopixi run example-clear— runexamples/clear_screen.mojopixi run example-input— runexamples/input_demo.mojopixi run test— run non-GPU testspixi run test-glfw-input— run GLFW input integration test
hello.mojo— hello triangle quickstart (RGB vertices, GLFW window)examples/triangle_window.mojo— identical standalone triangle demoexamples/— GPU compute, adapter enumeration, clear-screen, and input demostests/— Mojo test files for wrapper behavior and API compatibilitywgpu/— high-level Mojo wrapper layer for WebGPU objectswgpu/_ffi/— raw FFI bindings and type definitionsffi/— native C callback bridge and headers
wgpu/gpu.mojo—GPU()entrypoint, adapter/device request, logging controlwgpu/instance.mojo— adapter enumeration and device discoverywgpu/device.mojo— device creation, queue submission, buffer/texture/pipeline helperswgpu/buffer.mojo— buffer creation, mapping, and data transfer helperswgpu/texture.mojo— texture and texture view handlingwgpu/sampler.mojo— sampler creationwgpu/shader.mojo— shader module creationwgpu/bind_group.mojo— bind group and layout helperswgpu/pipeline_layout.mojo— pipeline layout creationwgpu/pipeline.mojo— compute and render pipeline helperswgpu/command.mojo— command encoder managementwgpu/compute_pass.mojo— compute pass encoder APIswgpu/render_pass.mojo— render pass encoder APIswgpu/query_set.mojo— query set support
The wrappers are built around RAII, but Mojo lifetime rules still matter when you extract raw handles or pointers.
- Keep owning wrappers alive after extracting raw handles or passing
unsafe_ptr()references. - Prefer wrapper-first APIs instead of raw
WGPU*Handlevalues. - Call
finish(),end(), orabandon()onCommandEncoder,RenderPassEncoder, andComputePassEncoderwhen required. - When you need to pin an object, use
_ = value^until the consuming GPU call completes.
Minimal pin example:
var gpu = GPU()
var device = gpu.request_device()
var buf = device.create_buffer(UInt64(256), WGPUBufferUsage.STORAGE)
var raw_handle = buf.handle().raw
# ... use raw_handle in descriptors or FFI calls ...
_ = buf^
_ = device^
_ = gpu^This project uses a thin, dynamic FFI layer to load libwgpu_native.so at runtime. The Mojo wrapper layer exposes high-level WebGPU concepts while the ffi/ C bridge handles callback-based APIs such as device requests and buffer mapping.
pixi run testis intended for non-GPU tests and does not require a GPU device.- GPU examples and GPU-specific tests require
pixi run build-callbacksfirst. example-inputis the correct example task name for the input demo.
See LICENSE.