Work in progress. This project is under development; expect missing features and breaking changes.
Common Lisp FFI bindings for WebGPU via wgpu-native.
From the takeiteasy Quicklisp dist, which is served over HTTPS so Quicklisp needs ql-https:
(ql-dist:install-dist "https://takeiteasy.github.io/ql-dist/dist/takeiteasy.txt")
(ql:quickload :cl-webgpu)Or clone into Quicklisp's local-projects:
git clone https://github.com/takeiteasy/cl-webgpu ~/quicklisp/local-projects/cl-webgpu- SBCL (or another CFFI-capable Lisp)
- Quicklisp
- Rust / Cargo — to build wgpu-native from source (or use a pre-built binary)
- GLFW 3 — for the GLFW windowed examples (install via Homebrew, apt, etc.)
- SDL 3 — for the SDL3 windowed examples (
cl-webgpu/sdl3); see docs/sdl3.md - C compiler (
cc/clang/gcc) - cl-nuklear — required for
cl-webgpu/nuklearonly; symlink../cl-nuklearinto~/quicklisp/local-projects/ - cl-dear-imgui — required for
cl-webgpu/imguionly; symlink../cl-dear-imguiinto~/quicklisp/local-projects/and build its native lib (git submodule update --init && makein that repo)
The vendored wgpu-native source in deps/wgpu-native/ includes local patches from
patches/ (registered in dependencies.rb). These are already applied to the vendored
source tree; you do not need to apply them manually.
cd deps/wgpu-native
cargo build --releaseOr download a pre-built binary from the wgpu-native releases page and place libwgpu_native.{dylib,so,dll} in deps/wgpu-native/target/release/.
make # builds libwebgpu_shim + libglfw3webgpu + libSDL3_webgpu in shim/The shim wraps functions that pass structs by value (e.g. WGPUStringView), which CFFI handles poorly. On macOS the shim uses -undefined dynamic_lookup, so it resolves wgpu-native symbols at runtime.
(ql:quickload :cl-webgpu)
(cl-webgpu:load-wgpu-libraries
:wgpu-path "deps/wgpu-native/target/release/"
:shim-path "shim/");; Minimal triangle (raw bindings)
(load "examples/triangle.lisp")
;; Triangle with wrapper layer + shader DSL
(load "examples/triangle-wrapper.lisp")
;; Headless triangle — no window, renders to a PNG (for SSH/CI)
(load "examples/headless-triangle.lisp")
;; Nuklear GUI — GLFW window / SDL3 window
(load "examples/nuklear-static.lisp")
(load "examples/nuklear-static-sdl3.lisp")
;; Dear ImGui GUI — GLFW window / SDL3 window
(load "examples/imgui-demo.lisp")
(load "examples/imgui-demo-sdl3.lisp")| System | Purpose |
|---|---|
cl-webgpu |
Auto-generated FFI bindings (types, enums, functions) |
cl-webgpu/glfw |
GLFW surface creation helper (requires cl-glfw3) |
cl-webgpu/sdl3 |
SDL3 surface creation helper (requires sdl3) — see docs/sdl3.md |
cl-webgpu/wrapper |
CLOS wrapper layer — easy-to-use API |
cl-webgpu/headless |
Offscreen render target + PNG readback, no window/display server (requires zpng) |
cl-webgpu/shader |
WGSL shader DSL |
cl-webgpu/nuklear |
Nuklear immediate-mode GUI backend (requires cl-nuklear) |
cl-webgpu/nuklear-input-common |
Shared Nuklear input plumbing for the per-backend glue systems |
cl-webgpu/nuklear-glfw-glue |
GLFW mouse/keyboard/scroll input wiring for cl-webgpu/nuklear |
cl-webgpu/nuklear-sdl3-glue |
SDL3 mouse/keyboard/scroll input wiring for cl-webgpu/nuklear |
cl-webgpu/imgui |
Dear ImGui immediate-mode GUI backend (requires cl-dear-imgui) — see docs/imgui.md |
cl-webgpu/imgui-input-common |
Shared Dear ImGui input plumbing for the per-backend glue systems |
cl-webgpu/imgui-glfw-glue |
GLFW mouse/keyboard/scroll/text input wiring for cl-webgpu/imgui |
cl-webgpu/imgui-sdl3-glue |
SDL3 mouse/keyboard/scroll/text input wiring for cl-webgpu/imgui |
cl-webgpu/codegen |
Re-generates cl-webgpu bindings from C headers |
The wrapper eliminates the foreign-alloc / foreign-slot-value boilerplate by providing:
CLOS handle classes — each wraps a raw wgpu pointer and dispatches release to the correct wgpu*Release function. Every object a wrapper function returns is one of these — there's no raw-pointer escape hatch to remember to release by hand:
gpu-instance gpu-adapter gpu-device gpu-surface
gpu-shader-module gpu-render-pipeline gpu-command-encoder
gpu-render-pass gpu-texture-view gpu-queue gpu-buffer gpu-texture
gpu-sampler gpu-bind-group gpu-bind-group-layout
with-gpu* — flat RAII scoping over any number of handles, without nesting a nine-deep nest of one-per-resource macros by hand. Bind (var form) for a single value or ((var1 var2) form) for a form that returns multiple values (e.g. make-texture-2d); every bound variable is released in reverse order when the body exits, normally or via an error:
(with-gpu* ((inst (make-gpu-instance))
(adapter (request-gpu-adapter inst))
(device (request-gpu-device inst adapter))
(queue (get-device-queue device))
((tex view) (make-texture-2d device 256 256)))
...) ; view, tex, queue, device, adapter, inst all released on exitThe original one-resource-per-macro forms (with-gpu-instance, with-gpu-adapter, with-gpu-device, with-gpu-shader-module, with-gpu-render-pipeline, with-gpu-command-encoder, with-render-pass) still exist for the single-resource case:
(with-gpu-instance (inst)
...) ; inst released on exit, even on errorwith-wgpu-struct — scoped zeroed foreign struct (no manual foreign-alloc/foreign-free):
(with-wgpu-struct (desc '(:struct wgpu-render-pipeline-descriptor))
(setf (foreign-slot-value desc ...) ...)
...)Creation helpers hide descriptor boilerplate entirely:
(make-gpu-instance)
(request-gpu-adapter instance &key power-preference backend)
(request-gpu-device instance adapter &key label)
(get-device-queue device) ; → gpu-queue
(make-shader-module device wgsl-source &key label)
(get-surface-format surface adapter) ; → keyword e.g. :bgra8-unorm
(make-render-pipeline device &key vertex-module fragment-module
entry-point surface-format label)
(configure-surface surface device format width height)
(make-command-encoder device &key label)
(begin-render-pass encoder texture-view &key clear-r clear-g clear-b clear-a)
(end-and-submit encoder pass queue surface)
;; buffers -- WRITE-BUFFER and MAKE-BUFFER-WITH-DATA take a Lisp vector
;; directly (single-float, (unsigned-byte 32), or (unsigned-byte 8)) and
;; do the foreign-alloc/copy/free internally; a foreign pointer + explicit
;; SIZE still works for callers that already have one.
(make-buffer device &key size usage mapped-at-creation label) ; → gpu-buffer
(make-buffer-with-data device queue data usage &key label) ; create + upload in one call
(write-buffer queue buffer offset data &optional size)
;; textures, samplers, bind groups
(make-texture-2d device width height &key format usage label) ; → (values gpu-texture gpu-texture-view)
(write-texture queue texture data data-size &key width height bytes-per-row) ; DATA: pointer or (unsigned-byte 8) vector
(make-sampler device &key mag-filter min-filter address-mode) ; → gpu-sampler
(get-pipeline-bind-group-layout pipeline group-index) ; → gpu-bind-group-layout
(make-bind-group device layout entries) ; → gpu-bind-groupSee examples/triangle-wrapper.lisp for a complete example.
This is a fork of 3bgl-shaders for WGSL shaders.
Write WGSL shaders in Common Lisp. The DSL compiles Lisp forms, infers types, and emits WGSL.
(ql:quickload :cl-webgpu/shader)
;; Define a fragment colour output
(cl-webgpu/shader/internal:shader-output frag-color :vec4
:stage :fragment :location 0)
;; Define fragment entry point
(cl-webgpu/shader/internal:shader-defun main ()
(setf frag-color (vec4 1.0 0.5 0.0 1.0)))
;; Generate combined vertex+fragment WGSL string
(cl-webgpu/shader:generate-shader :vertex 'vs-main :fragment 'main)| Macro | Purpose |
|---|---|
shader-defun name args &body |
Define a shader function |
shader-output name type &key stage location |
Declare a stage output |
shader-input name type &key stage location |
Declare a stage input |
shader-uniform name type &key stage group binding |
Declare a uniform |
shader-interface name (&key in out uniform) &body |
Declare an interface block |
Vertex: vertex-index, instance-index
Fragment: frag-position, front-facing, frag-depth, sample-index
Compute: global-invocation-id, local-invocation-id, workgroup-id, num-workgroups
generate-shader uses an "old-style" struct I/O convention. The vertex entry point
receives an auto-built VertexInput struct (from :vertex stage inputs) and returns
VertexOutput (always includes @builtin(position)). Vertex outputs flow through to
the fragment stage as FragmentInput.
(defparameter *shader-src*
(cl-webgpu/shader:generate-shader :vertex 'vs-main :fragment 'fs-main))
;; Then feed to the wrapper:
(make-shader-module device *shader-src* :label "My Shader");; Compile a single form into the shader IR
(cl-webgpu/shader:compile-form
'(cl:defun vs-main () (setf position (vec4 0.0 0.0 0.0 1.0))))
;; Generate WGSL for one stage (also returns uniforms, attributes)
(cl-webgpu/shader:generate-stage :vertex 'vs-main)The files under src/ (except library.lisp) are auto-generated from the C headers
in deps/webgpu/. After updating headers:
(ql:quickload :cl-webgpu/codegen)
(cl-webgpu/codegen:generate)- 3bgl-shaders forked + modified (MIT)
- wgpu-native MIT/Apache 2.0
- webgpu-headers BSD-3-Clause
- glfw3webgpu MIT
- glfw3 ZLIB
- sdl3webgpu MIT (vendored in
deps/sdl3webgpu/, forcl-webgpu/sdl3) - SDL3 ZLIB
- Nuklear MIT (via cl-nuklear, for
cl-webgpu/nuklear) - Dear ImGui MIT (via cl-dear-imgui, for
cl-webgpu/imgui) - Dependencies: cl-glfw3 (for
cl-webgpu/glfw), cl-sdl3 (forcl-webgpu/sdl3), cl-dear-imgui (forcl-webgpu/imgui), zpng (forcl-webgpu/headless), cffi, bordeaux-threads, alexandria, cl-ppcre, uiop
The MIT License (MIT)
Copyright (c) 2026 George Watson
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.