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.
Stages
A shader stage is defined with define-shader:
(define-shader name stage-designator (parameter ... &uniform uniform ... &shared shared ...)
[docstring]
body ...)
Stages are functions from their inputs to their outputs. A stage's parameters are its inputs, and the values its body returns are its outputs. cl-gsl checks that each stage's outputs match the next stage's inputs when a program is compiled.
&uniform and &shared are cl-gsl's exported symbols. In a package that uses cl-gsl, as the examples here do, they need no prefix; elsewhere write them through a nickname, as gsl:&uniform. Uniforms are covered in Uniforms and Resources, and shared variables in Compute Shaders.
| Stage designator | Stage |
|---|---|
:vertex |
Vertex shader |
(:tessellation-control :patch-size n :vertices n) |
Tessellation control shader |
(:tessellation-evaluation :patch-size n [:domain d :spacing s :winding w :point-mode b]) |
Tessellation evaluation shader |
(:geometry :input primitive :output primitive :max-vertices n [:invocations n]) |
Geometry shader |
:fragment |
Fragment shader |
(:compute :workgroup-size (x [y [z]])) |
Compute shader |
A graphics pipeline runs: vertex → [tessellation control → tessellation evaluation] → [geometry] → fragment.
Vertex shaders
The parameters are vertex inputs, (name type &key location). Each is a scalar or vector of ints, uints, floats or doubles. Locations count from 0 in order, unless given with :location; an input without one follows the input before it. A :dvec3 or :dvec4 takes two locations, every other input one.
(define-shader example-vertex :vertex ((p :vec3 :location 7) (q :vec2) (d :dvec4))
(values (vec4 p 1) q))
(mapcar (lambda (input) (list (vertex-input-name input) (vertex-input-location input)
(vertex-input-type input)))
(program-vertex-inputs (compile-program :vertex 'example-vertex)))
;; => ((P 7 :VEC3) (Q 8 :VEC2) (D 9 :DVEC4))
The vertex shader returns the clip-space position, a :vec4, and then any values for the next stage, with values.
(define-shader sprite-vertex :vertex ((position :vec2) (uv :vec2)
&uniform (view :mat4 :group :frame))
(values (* view (vec4 position 0 1)) uv))
Clip space is Vulkan's, as in cl-gfx: y points down, so the top of the target is at y −1, and depth runs from 0 to 1.
(program-vertex-inputs program) gives the inputs as vertex-inputs, with a vertex-input-name, vertex-input-location and vertex-input-type, for building a vertex buffer layout.
Built-ins: (vertex-index) and (instance-index), both :ints.
Fragment shaders
The parameters receive the previous stage's values, in the same order and with the same types: (name type &key interpolation).
| Interpolation | Meaning |
|---|---|
:smooth |
Perspective-correct interpolation. The default for floats. |
:noperspective |
Linear interpolation in screen space. |
:flat |
The value of one vertex of the primitive. Integer and double inputs are always flat. |
The fragment shader returns one colour per render target, with values if there are several. Colours are scalars or vectors of floats, ints or uints; not doubles. (discard) drops the fragment instead.
(define-shader sprite-fragment :fragment ((uv :vec2)
&uniform (tint :vec4)
(image :sampler2d :group :draw))
(* (sample image uv) tint))
Built-ins:
| Form | Type | What it is |
|---|---|---|
(fragment-coord) |
:vec4 |
Window position: X and Y in pixels from the top left at the pixel centre, Z the depth, W 1/w. |
(front-facing-p) |
:bool |
Whether the primitive faces the front. |
(point-coord) |
:vec2 |
Where the fragment is in its point, 0 to 1. |
(sample-index) |
:int |
The sample being shaded. Using it shades every sample apart. |
(sample-position) |
:vec2 |
Where that sample is in its pixel. Using it shades every sample apart. |
(helper-invocation-p) |
:bool |
True if this fragment only runs to compute its neighbours' derivatives. |
(primitive-id) |
:int |
The primitive's index in the draw. Needs the device's geometry shader feature, or a geometry shader before it. |
A fragment shader may also take derivatives (dfdx, dfdy, fwidth and their -fine and -coarse kinds) and interpolate an input elsewhere in the pixel (interpolate-at-centroid, interpolate-at-sample, interpolate-at-offset). See Operator Reference.
Tessellation shaders
Tessellation takes both stages: a control shader and an evaluation shader. A program with one but not the other is an error.
A tessellation or geometry stage receives the values of every vertex of its patch or primitive: each of its parameters is declared with the type of one vertex's value, and in the body it is an array with one element per vertex. (input-position i) gives the position the previous stage gave vertex i.
Tessellation control
(define-shader patch-control (:tessellation-control :patch-size 3 :vertices 3)
((color :vec3) &uniform (level :float))
(set-tessellation-levels (vec4 level level level 1) (vec2 level 0))
(values (input-position (invocation-id)) (aref color (invocation-id))))
| Option | Meaning |
|---|---|
:patch-size |
How many vertices each incoming patch has, 1 to 32. The pipeline draws patch lists of this size; program-patch-size returns it. |
:vertices |
How many vertices each outgoing patch has, 1 to 32. |
The shader runs once per outgoing vertex; (invocation-id) says which. It returns that vertex's position, a :vec4, and its values, as a vertex shader does.
(set-tessellation-levels outer inner) sets how finely the patch is divided: outer, a :vec4, along each edge, and inner, a :vec2, inside. A triangle uses the first three of outer and the first of inner; a quad uses all of them; isolines use the first two of outer, the number of lines and then the segments of each. Any invocation may set them; set them the same in every one.
Tessellation evaluation
(define-shader patch-evaluation (:tessellation-evaluation :patch-size 3 :spacing :fractional-odd)
((color :vec3))
(let ((weights (tessellation-coord)))
(values (+ (* (x weights) (input-position 0))
(* (y weights) (input-position 1))
(* (z weights) (input-position 2)))
(+ (* (x weights) (aref color 0))
(* (y weights) (aref color 1))
(* (z weights) (aref color 2))))))
| Option | Values | Default |
|---|---|---|
:patch-size |
The control shader's :vertices, 1 to 32. cl-gsl checks that they agree. |
required |
:domain |
:triangles, :quads, :isolines |
:triangles |
:spacing |
:equal, :fractional-even, :fractional-odd |
:equal |
:winding |
:counter-clockwise, :clockwise |
:counter-clockwise |
:point-mode |
true to make points instead of triangles or lines | nil |
The shader runs once per vertex the tessellator makes. (tessellation-coord), a :vec3, says where in the patch: barycentric coordinates in the :triangles domain, and X and Y from 0 to 1 in the :quads and :isolines domains. It returns the vertex's position and values.
Built-ins in both tessellation stages: (input-position i), (primitive-id); in the control stage, (invocation-id).
Geometry shaders
A geometry shader receives one primitive's vertices and emits any number of vertices, which make strips of its output primitive.
(define-shader explode (:geometry :input :triangles :output :triangle-strip :max-vertices 3)
((color :vec3) &uniform (offset :float))
(dotimes (corner 3)
(emit-vertex (+ (input-position corner) (vec4 0 0 offset 0))
(aref color corner)))
(end-primitive))
| Option | Values |
|---|---|
:input |
:points (1 vertex), :lines (2), :lines-adjacency (4), :triangles (3), :triangles-adjacency (6). Each parameter is an array of this many values. |
:output |
:points, :line-strip, :triangle-strip |
:max-vertices |
The most vertices one invocation emits. |
:invocations |
How many times the shader runs per primitive, 1 to 32. Defaults to 1. (invocation-id) says which run this is. |
(emit-vertex position value ...)emits a vertex atposition, a clip-space:vec4, passing the values on to the fragment shader. Everyemit-vertexin a shader passes values of the same types, and a geometry shader must emit at least one vertex.(end-primitive)ends the current strip, so the nextemit-vertexstarts another.- The geometry shader returns nothing; its outputs are what it emits.
If a tessellation evaluation shader comes before it, its :input must match what tessellation makes: :points with :point-mode, :lines for isolines, :triangles otherwise. cl-gsl checks this.
Built-ins: (input-position i), (invocation-id), (primitive-id).
Compute shaders
(define-shader double-all (:compute :workgroup-size (64))
(&uniform (data (:array :float) :storage :read-write))
(let ((i (x (global-invocation-id))))
(when (< i (uint (length data)))
(setf (aref data i) (* 2 (aref data i))))))
A compute shader has no inputs and returns nothing. It runs in workgroups of the size its designator gives, with missing counts defaulting to 1. It reads and writes storage buffers and images, and may share &shared variables across a workgroup. A compute program has the compute stage alone. See Compute Shaders.
Which built-ins each stage may use
A built-in that belongs to some stages only, such as (fragment-coord), emit-vertex or barrier, may still be used in a shader function. cl-gsl checks it when a program is compiled, against the stages that call the function, and the error names both:
(define-function fc () (fragment-coord))
(define-shader uses-fc :vertex () (fc))
(compile-program :vertex 'uses-fc)
;; error: In FC: FRAGMENT-COORD is available only in fragment shaders,
;; and this is used by the vertex shader USES-FC.