This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Shaders and Pipelines
Shader modules
Shaders are SPIR-V. Compile GLSL with glslangValidator -V or glslc, or
write them in Lisp with clsl, which
compiles them to SPIR-V itself.
(gfx:create-shader-module device #p"shaders/triangle.vert.spv") ; a file
(gfx:create-shader-module device "shaders/triangle.frag.spv") ; a namestring
(gfx:create-shader-module device words) ; (simple-array (unsigned-byte 32) (*))
(gfx:create-shader-module device bytes) ; (simple-array (unsigned-byte 8) (*))
The code must be a whole number of 32-bit words. A module may hold several
entry points; pipelines pick one by name, "main" by default.
create-shader-module reads what the module declares, and signals
unsupported-error if it needs a device feature the device lacks: the
SPIR-V capabilities Geometry, Tessellation, Float64, and
StorageImageMultisample and ImageMSArray, which need :geometry-shaders,
:tessellation-shaders, :float64 and :multisample-storage-textures; and
the execution modes RoundingModeRTE and SignedZeroInfNanPreserve of 32-
or 64-bit floats, which need :rounding-mode-rte-float32 and its kin.
Once a pipeline is made, you may destroy its modules with
destroy-shader-module; the pipeline is unaffected.
Writing shaders for cl-gfx
- Clip space is Vulkan's: y points down and depth runs from 0 to 1.
Write
gl_Positionas you would for Vulkan; the OpenGL backend will convert, not your shader. A tessellation evaluation shader'sccwandcware Vulkan's too, in a domain whose origin is the top left. - Bind groups are descriptor sets. Bind group n of a pipeline is
layout(set = n, ...), and its bindings arebinding = .... - Vertex attributes are
layout(location = n) in, matching thelocationin the vertex buffer layout. - Uniform buffers use
std140; storage buffersstd430.
Render pipelines
A render pipeline is everything about how to draw that is fixed: shaders, vertex layout, attachment formats, depth testing, culling and blending. It is immutable.
(gfx:create-render-pipeline
device
:vertex (gfx:create-shader-module device "mesh.vert.spv")
:fragment (gfx:create-shader-module device "mesh.frag.spv")
:vertex-buffers '((:stride 32 :attributes ((0 :float32x3 0) ; position
(1 :float32x3 12) ; normal
(2 :float32x2 24)))) ; uv
:color-formats (list (gfx:surface-format surface))
:depth-format :depth32-float
:cull-mode :back
:bind-group-layouts (list frame-layout material-layout))
Shader stages
| Key | Entry key | Meaning |
|---|---|---|
:vertex |
:vertex-entry |
Required. |
:tessellation-control |
:tessellation-control-entry |
Optional; with :tessellation-evaluation, both or neither. Needs :tessellation-shaders. |
:tessellation-evaluation |
:tessellation-evaluation-entry |
As above. |
:geometry |
:geometry-entry |
Optional. Needs :geometry-shaders. |
:fragment |
:fragment-entry |
Optional: leave it out for a pipeline that only writes depth, such as a shadow pass. |
Every entry defaults to "main".
Vertex input
:vertex-buffers lists one layout per vertex buffer slot, by slot number
from 0:
(:stride bytes :attributes ((location format offset) ...) :step :vertex)
:strideis the distance in bytes from one vertex to the next.- Each attribute is
(location format offset): the shader'slocation, a vertex format, and its byte offset in the vertex. :stepis:vertex(the default) or:instance, for per-instance data.
;; Slot 0: per-vertex positions. Slot 1: per-instance offsets and colors.
:vertex-buffers '((:stride 12 :attributes ((0 :float32x3 0)))
(:stride 16 :step :instance :attributes ((1 :float32x2 0)
(2 :unorm8x4 8))))
Vertex formats:
| Format | Shader type | Bytes |
|---|---|---|
:float32, :float32x2, :float32x3, :float32x4 |
float, vec2 … vec4 |
4, 8, 12, 16 |
:float64, :float64x2, :float64x3, :float64x4 |
double, dvec2 … dvec4; needs :float64 |
8, 16, 24, 32 |
:uint32 … :uint32x4 |
uint, uvec2 … uvec4 |
4 … 16 |
:sint32 … :sint32x4 |
int, ivec2 … ivec4 |
4 … 16 |
:unorm8x4 |
vec4, each byte mapped to 0 to 1 |
4 |
vertex-format-size returns the size in bytes. Not every GPU reads doubles
from vertex buffers even when it has :float64. A portable way is to pass
each double as a pair of :uint32s and combine them in the shader with
packDouble2x32, as clsl does with a vertex shader's double inputs.
The pipeline doesn't have a vertex buffer layout if its vertex shader makes
its own vertices, as a full-screen triangle does: leave :vertex-buffers
out.
Primitive assembly
| Key | Default | Values |
|---|---|---|
:topology |
:triangle-list |
:triangle-list, :triangle-strip, :line-list, :line-strip, :point-list, and with tessellation, :patch-list |
:patch-size |
nil |
With tessellation: the number of vertices per patch, 1 to 32, as the tessellation control shader takes them. Required with tessellation. |
:cull-mode |
:none |
:none, :front or :back |
:front-face |
:counter-clockwise |
:counter-clockwise or :clockwise, as the triangle's corners run on the screen |
(gfx:create-render-pipeline device :vertex vertex
:tessellation-control control
:tessellation-evaluation evaluation
:geometry geometry
:fragment fragment
:topology :patch-list :patch-size 3
:color-formats '(:rgba8-unorm))
Attachments
| Key | Default | Meaning |
|---|---|---|
:color-formats |
() |
The formats of the color attachments the pipeline draws into, in order. For a window, (list (gfx:surface-format surface)). |
:depth-format |
nil |
The format of the depth attachment, or nil for none. |
:depth-compare |
:less |
The depth test, a compare function. |
:depth-write |
t |
Whether passing fragments write their depth. |
:samples |
1 | The number of samples, the same as the attachments'. |
:blend |
:replace |
:replace, :alpha or :premultiplied-alpha (below). |
The formats and sample count must match the attachments of the passes the pipeline draws in.
Blending
:blend |
Color | Alpha | Use for |
|---|---|---|---|
:replace |
src |
src.a |
opaque geometry |
:alpha |
src × src.a + dst × (1 − src.a) |
src.a + dst.a × (1 − src.a) |
colors of straight alpha, as most PNGs store them |
:premultiplied-alpha |
src + dst × (1 − src.a) |
src.a + dst.a × (1 − src.a) |
colors already multiplied by their alpha |
Both blends keep the target's alpha a coverage, so a target drawn with
either can itself be blended over another with :premultiplied-alpha.
Prefer premultiplied alpha for textures that are filtered or mipmapped: a
fully transparent texel of straight alpha still has a color, which bleeds
into its neighbours as a dark or coloured fringe. A shader can premultiply
straight colors itself, color.rgb *= color.a, and use
:premultiplied-alpha.
A quarter-opaque red, (1 0 0 0.25), drawn over opaque blue gives
(64 0 191 255) with :alpha and (255 0 191 255) with
:premultiplied-alpha, in 8 bits.
Bind group layouts
:bind-group-layouts lists the layouts of the bind groups the shaders read,
by index from 0, up to +max-bind-groups+ (4). Group n is the shaders'
set = n. See Bind Groups.
Compute pipelines
(gfx:create-compute-pipeline device :module (gfx:create-shader-module device "blur.comp.spv")
:entry "main"
:bind-group-layouts (list layout))
A compute pipeline runs one compute shader. Its :bind-group-layouts work as
a render pipeline's do. See Compute.
Destroying
destroy-render-pipeline and destroy-compute-pipeline mark the pipeline
destroyed at once and release it once no frame in flight uses it.