Development and Testing
Running the tests
The suite is clsl/test, written with
Parachute. From a checkout of clsl:
stift test
In the Pyssel workspace, the suite runs with the other libraries' checkouts,
as the workspace's stiftwork names them; stift --no-workspace test runs it
from clsl's own pins, as CI does. asdf:test-system signals an error if any
test fails, so the command exits unsuccessfully.
What the suite needs
The suite holds clsl to SPIRV-Tools and to a GPU:
| Needs | For | On Debian or Ubuntu |
|---|---|---|
spirv-val, spirv-as and spirv-dis on the PATH |
validating every module the tests build, and checking the builder, the reader and the disassembler against the assembler and disassembler | spirv-tools |
| a Vulkan 1.4 device, such as Mesa's lavapipe, which needs no GPU | running shaders through cl-gfx | libvulkan1, mesa-vulkan-drivers |
| the Khronos validation layer, which must report nothing | checking every use of Vulkan the tests make | vulkan-validationlayers |
apt install spirv-tools libvulkan1 mesa-vulkan-drivers vulkan-validationlayers
On Windows, the Vulkan SDK gives the tools and the layer. A missing tool, driver or layer fails the suite with a message saying what to install: the tests never skip.
What the suite holds clsl to
- SPIRV-Tools.
spirv-valvalidates the modules the tests build, andspirv-asandspirv-discheck the builder, the reader and the disassembler: raw disassembly assembles back into the same words, and the friendly disassembly reads asspirv-diswrites it. - The IR. Every operation of the IR, in every type the verifier lets it take, validates, and computes on the GPU what the tests' evaluator, which runs the IR in Lisp, computes for the same IR.
- The language. Every built-in, in every signature it takes, compiles into
a shader that validates. On the GPU every built-in computes what Lisp
computes: Lisp's function of the same name, or the GPU's vocabulary, such as
mixandrefract, by its definition computed in doubles; integers exactly, and floats within the precision Vulkan grants each. Control flow,everyand its kin, loops, several values, structs and arrays, local functions, places and assignments compute what the same forms compute in Lisp; the bit operations compute what Lisp's do,ashby 32 bits or more and bytes past bit 31 included; and the guards give their defined results. - Data. Uniforms packed by
pack-uniformsreach the shader; structs and arrays packed by std140 and std430 come back from storage buffers aspack-valuelays them out. - Textures and images. Every kind of texture is read every way, at places, levels, layers and samples, with offsets, through filters and comparing samplers, as the evaluator reads it, in compute shaders and, at the level of detail derivatives pick, in fragment shaders; storage images of every kind and format are read, written and read back; textures and images are read and written in and out of range.
- Compute. Atomics count right across workgroups, and shared memory, which starts at zero, sums a workgroup's share between barriers.
- Drawing. Derivatives, fine and coarse, are what the evaluator computes
across each quad; a triangle, interpolation, the interpolation functions,
several render targets,
discardand Pyssel's quad shaders draw as expected. A program of all five graphics stages draws a triangle as a vertex and a fragment shader do, tessellated or not, and draws nothing of a patch whose levels its shader leaves at 0; a tessellation evaluation shader's:windingdecides which of its triangles culling drops; a geometry shader emits nothing past its:max-vertices; and a struct, a matrix and doubles pass through every stage. - Numbers. Doubles compute on the evaluator exactly what Lisp computes, and on the GPU within the precision Vulkan grants them; float pairs, within 2^-45 of what Lisp computes exactly of the reals they hold, and keep a position 10,000 km out to well under a micrometre; every complex function is held to Lisp's, computed in doubles.
- cl-gmath. Each function of cl-gmath, through
clsl/gmath, computes on the evaluator what it computes in Lisp, and each function cl-gmath exports has a shader form or a refusal that says why it has none. - Refusals. Every rule of the uniformity analysis is held to shader code it accepts and shader code it refuses, with the message; every form the language refuses is refused with its message.
- Generated code. The committed
spirv/grammar.lispis what the generator writes. cl-slug's pixel shader, rewritten in clsl, compiles and validates.
The shaders run on a Vulkan 1.4 device through cl-gfx, under the Khronos validation layer, which must report nothing.
Continuous integration
CI runs the suite on every push to main and every pull request, with SBCL
2.6.9 in a Debian forky container, on lavapipe, installing spirv-tools,
libvulkan1, mesa-vulkan-drivers and vulkan-validationlayers.
Working on clsl
The source, by the way a program goes:
| Where | What |
|---|---|
src/language/checker.lisp and the files after it |
the checker, which types shader code into a tree; each form and built-in has its file, such as loops.lisp, bits.lisp and textures.lisp |
src/language/lowerer.lisp, lowering.lisp |
lowering, which makes IR of the tree |
src/language/pairs.lisp, complex.lisp |
the shader functions of float pairs and complex numbers, written in clsl's own language |
src/language/programs.lisp, packing.lisp, documentation.lisp |
programs, packing and shader-documentation |
src/ir/ |
the IR: its types, values, operations, builder, external form, verifier (verifier.lisp) and uniformity analysis (uniformity.lisp) |
spirv/ |
SPIR-V: the tables, the encoding, the module builder, the reader and the disassembler |
spirv/back-end/translation.lisp |
the back end, which writes SPIR-V of the IR |
vulkan/target.lisp |
the :vulkan target |
gmath/gmath.lisp |
the cl-gmath glue |
generator/ |
the grammar generator; grammar/ holds the grammar files |
src/sys.lisp |
clsl/sys, the one place for SBCL's own code: the bits of floats and float arithmetic with its traps masked |
tests/evaluator.lisp |
the evaluator, which runs the IR in Lisp for the tests to compare with the GPU |
doc/language.org |
the language's reference, which describes every form and built-in |
The README describes the API, the IR and SPIR-V, doc/language.org the
language, and this wiki is the manual: a change to what a page describes, such
as the public API, behaviour, errors, thread rules or requirements, updates
the page with it.
SPIR-V assembly writes ids as %name and %1, and the IR's external form
labels values %1, %2 and so on: they are data, not Lisp names.