3 Getting Started
dxtr edited this page 2026-10-14 13:46:25 +02:00

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 $CXX names.
  • Lisp dependencies: cffi, cl-threads, and for the optional cl-gsl/gmath system, cl-gmath. The repository's qlfile pins 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:

  1. define-shader defined 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.
  2. define-function defined a shader function, which any stage or function may call.
  3. Each definition was checked when it was evaluated. Had brightness returned the wrong type, or had a typo, the error would have named the definition and the form, and the previous definition would have stayed.
  4. compile-program checked 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.