Targets
A target is one API's rules for the code a program becomes. compile-program
takes one by its name, :target :vulkan, lowers each stage of the program to
the IR, and hands the IR to the target, which makes its code of it.
clsl/vulkan defines :vulkan, the one target so far; clsl/glsl, which
prints GLSL, and clsl/opengl, for OpenGL 4.5 and later, come later.
Finding a target
The targets and the generic functions they answer are in clsl/ir:
| Function | Does |
|---|---|
(find-target name) |
returns the target name, a keyword such as :vulkan, or signals a no-target-error |
(target-name target) |
the target's name |
(target-compile target module) |
returns the target's code of an IR module, one stage of a program: for :vulkan, the words of a SPIR-V module |
(target-features target module code) |
returns the optional device features the code needs, named as the GPU layer the target is for names them |
(target-disassemble target code stream) |
prints the code for people to read, such as SPIR-V assembly |
(clsl/ir:find-target :vulkan)
;; => #<CLSL/VULKAN:VULKAN-TARGET :VULKAN>
A target never changes, and may be shared between threads. A target no system
loaded has defined signals a no-target-error, whose
no-target-error-target is its name and whose message names the system that
defines it, or the targets clsl has:
The target :VULKAN is not loaded: load the system clsl/vulkan, which defines it.
clsl has no target :OPENGL. Its targets are :vulkan, in the system clsl/vulkan.
:vulkan
clsl/vulkan defines :vulkan: SPIR-V 1.6 and the memory model Logical
GLSL450, as glslang makes them for Vulkan 1.4. Fragment shaders are
OriginUpperLeft; a bind group is the descriptor set of its number; and
discard is OpDemoteToHelperInvocation, which cl-gfx enables, so that a
discarded fragment stays a helper of its quad's derivatives. Its capabilities
are those Vulkan 1.4 allows a module to declare, from its specification's
table of them, less those only an extension provides. Its features are
cl-gfx's: :geometry-shaders, :tessellation-shaders, :float64 and
:multisample-storage-textures, from the capabilities a module declares;
:vertex-stores, for a stage before the fragment shader that may write a
storage buffer or image; and the float controls' properties (see
Programs).
clsl/vulkan's package exports only vulkan-target, its class, for back ends
and tests: programs name the target :vulkan and need nothing of the package.
The SPIR-V back end
clsl/spirv-back-end, in the system clsl/spirv, translates the IR into
SPIR-V for a target that makes SPIR-V, a spirv-target. translate-module
is its entry point, and the target-compile of every spirv-target:
(clsl/spirv-back-end:translate-module module (clsl/ir:find-target :vulkan))
verifies the module and translates it into the words of a SPIR-V module,
entered at "main", as cl-gfx's create-shader-module takes them. It
translates rather than compiles:
- each instruction is chosen by its operands' types:
:addisOpFAddorOpIAdd,:lessisOpFOrdLessThan,OpSLessThanorOpULessThan, and:minisGLSL.std.450'sFMin,SMinorUMin; - an
:ifbecomes a selection with its merge block, a:loopa loop header, body, continue target and merge block, and the value either gives anOpPhiat its merge; - the structs and arrays of uniform blocks and storage buffers are declared
laid out, with their offsets and strides, and those of values plain, as
Vulkan requires; values convert between the two with
OpCopyLogical; - the capabilities a module declares are worked out from what it uses, by the
grammar's tables, those of
GLSL.std.450included, and refused with anunsupported-errorwhere the target does not allow them; - a fragment shader's built-in input of integers,
SampleId, is decoratedFlat, as its other integer inputs are; and from SPIR-V 1.6, where a fragment may be demoted,HelperInvocationisVolatile, so that it reads what it is after the demotion; - the entry point lists every global its call tree uses, and names become
OpNameandOpMemberName.
What SPIR-V's version decides, the back end decides by it: storage buffers
are of the StorageBuffer storage class from SPIR-V 1.3, and an entry point
lists every global it uses from 1.4. The same module always gives the same
words. A module the back end cannot translate validly is a bug in clsl,
signalled as an internal-error with the IR's external form.
What a SPIR-V target decides
A spirv-target has a SPIR-V version, and the capabilities and extensions its
API allows, which its readers give, and a subclass answers the four generic
functions after them:
| Function | :vulkan answers |
|---|---|
(target-spirv-version target) |
(1 6) |
(target-capabilities target) |
the 85 capabilities Vulkan 1.4 allows, from :matrix, :shader and :input-attachment on |
(target-extensions target) |
nil |
(target-execution-modes target stage) |
((:origin-upper-left)) for :fragment, and nothing for the others |
(target-built-in target built-in) |
the SPIR-V BuiltIn of an IR built-in: :vertex-index for :vertex-index, :frag-coord for :fragment-coord, :num-workgroups for :workgroup-count |
(target-descriptor-binding target group binding) |
the descriptor set and binding of a bind group's binding: the same two numbers |
(target-discard-instruction target) |
:demote-to-helper-invocation |
Defining a target
A system that defines a target defines a subclass of clsl/ir:target, or of
spirv-target for one that makes SPIR-V, whose instances take :name, and
registers it with define-target. A SPIR-V target takes :spirv-version,
:capabilities and :extensions, and answers the generic functions above. A
target that allows less than Vulkan does is the shortest:
(defpackage #:target-example
(:use #:cl)
(:local-nicknames (#:ir #:clsl/ir)))
(in-package #:target-example)
(defclass shader-only-target (clsl/vulkan:vulkan-target)
()
(:default-initargs :capabilities '(:shader :matrix))
(:documentation "Vulkan's rules, but no capability beside Shader and Matrix."))
(ir:define-target :shader-only shader-only-target)
(ir:find-target :shader-only)
;; => #<SHADER-ONLY-TARGET :SHADER-ONLY>
(clsl:define-shader halved :fragment ((x :double :interpolation :flat))
(clsl:vec4 (float (/ x 2) 1.0) 0 0 1))
(handler-case (clsl:compile-program '(:fragment halved) :target :shader-only)
(clsl:unsupported-error (error)
(list (clsl:unsupported-error-feature error) (princ-to-string error))))
;; => ("the capability Float64"
;; "The target :SHADER-ONLY cannot compile this module: 64-bit floats needs the capability Float64, which :SHADER-ONLY does not allow.")
unsupported-error-feature names what the target lacks, and
unsupported-error-form the form that needs it, where one form does, as for
an IR module's global:
(handler-case
(ir:target-compile
(ir:find-target :shader-only)
(ir:read-module-form '(:module :compute
(:global d :private :double)
(:function main () :void
(:store d (:double 1d0))
(:return))
(:entry-point main :workgroup-size (1 1 1)))))
(clsl:unsupported-error (error)
(clsl:unsupported-error-form error)))
;; => (:GLOBAL D :PRIVATE :DOUBLE)
The same program compiles for :vulkan, and needs the device's :float64
feature.