Getting Started
Requirements
- SBCL (the main target) or CCL.
- glslang's development files. cl-gsl compiles GLSL to SPIR-V in-process with glslang, through its C interface. On Debian and Ubuntu, install
glslang-dev. - A C++ compiler,
c++, or whatever$CXXnames. - Lisp dependencies:
cffi, cl-threads, and for the optionalcl-gsl/gmathsystem, cl-gmath. The repository'sqlfilepins them for qlot.
How glslang gets linked
Distributions ship glslang as static archives only (libglslang.a, libSPIRV.a, libSPIRV-Tools.a and so on). When ASDF builds cl-gsl/ffi, it links those archives into a shared library of its own, libglslang-cl, keeps it with the compiled files, and rebuilds it when glslang is upgraded.
| Environment variable | Effect |
|---|---|
CXX |
The C++ compiler to link with. Defaults to c++. |
GLSLANG_LIBRARY_DIR |
The directory holding glslang's .a archives, if the compiler does not look there by default. |
Limitation. This is a stopgap. It works where the archives are position-independent code, as Debian's are. It does not work on Windows yet.
Loading
With qlot, from the repository's directory:
qlot install
qlot exec sbcl
(asdf:load-system :cl-gsl)
;; and, to use cl-gmath's names in shader code:
(asdf:load-system :cl-gsl/gmath)
A first program
Use the cl-gsl package. It exports the shader language's own operators, such as vec3, dot and sample, as well as the Lisp API. The Common Lisp operators that shader code uses (let, +, floor and so on) are cl's own symbols.
(defpackage #:example
(:use #:cl #:cl-gsl))
(in-package #:example)
(define-shader triangle-vertex :vertex ((position :vec3) (color :vec3))
"Place each vertex where its POSITION says, and pass its COLOR on."
(values (vec4 position 1) color))
(define-function brightness ((color :vec3))
"Return the perceived brightness of the linear COLOR."
(dot color (vec3 0.2126 0.7152 0.0722)))
(define-shader triangle-fragment :fragment ((color :vec3))
"Draw COLOR, dimmed where it is bright."
(vec4 (mix color (* color 0.5) (brightness color)) 1))
(defvar *program*
(compile-program :vertex 'triangle-vertex :fragment 'triangle-fragment))
What happened:
define-shaderdefined two stages. The vertex shader takes two vertex inputs and returns the clip-space position and then a value for the fragment shader. The fragment shader takes that value and returns a colour.define-functiondefined a shader function, which any stage or function may call.- Each definition was checked when it was evaluated. Had
brightnessreturned the wrong type, or had a typo, the error would have named the definition and the form, and the previous definition would have stayed. compile-programchecked that the stages fit together, generated GLSL for each, and compiled it to SPIR-V.
Now ask the program what you need:
(program-stages *program*) ; => (:VERTEX :FRAGMENT)
(program-spirv *program* :vertex) ; => #(119734787 67072 ...), (unsigned-byte 32) words
(program-vertex-inputs *program*)
;; => (#S(VERTEX-INPUT :NAME POSITION :LOCATION 0 :TYPE :VEC3)
;; #S(VERTEX-INPUT :NAME COLOR :LOCATION 1 :TYPE :VEC3))
(write-string (program-glsl *program* :fragment))
The GLSL is there for reading:
#version 450
layout(location = 0) in vec3 color;
layout(location = 0) out vec4 fragment_output_0;
float brightness(vec3 color_1) {
return dot(color_1, vec3(0.2126, 0.7152, 0.0722));
}
void main() {
fragment_output_0 = vec4(mix(color, (color * 0.5), brightness(color)), 1.0);
}
Drawing it with cl-gfx
cl-gfx takes the SPIR-V as it is:
(gfx:create-render-pipeline
device
:vertex (gfx:create-shader-module device (program-spirv *program* :vertex))
:fragment (gfx:create-shader-module device (program-spirv *program* :fragment))
:vertex-buffers '((:stride 24 :attributes ((0 :float32x3 0) (1 :float32x3 12))))
:color-formats (list (gfx:surface-format surface)))
The attribute locations, 0 and 1, are the ones program-vertex-inputs gives. Clip space is WebGPU's, as in cl-gfx: y points up, and depth runs from 0 to 1.
Finding your way around
(shader-documentation 'mix)describes any operator of the shader language, and any function, stage, struct or shader variable you define.(shader-documentation 'shader-type)lists every type.(documentation 'compile-program 'function)and so on cover the Lisp API, and so does API Reference.
Running the tests
qlot install
qlot exec sbcl --non-interactive --eval '(asdf:test-system :cl-gsl)'
The suite covers type checking, GLSL generation and SPIR-V compilation for every stage, uniforms and layouts, textures, compute, doubles, complex numbers and ratios, shader variables and packing, and the cl-gmath glue. CI runs it on SBCL and CCL.
Next: Shader Code.