1 Targets
dxtr edited this page 2026-10-18 19:16:53 +02:00

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: :add is OpFAdd or OpIAdd, :less is OpFOrdLessThan, OpSLessThan or OpULessThan, and :min is GLSL.std.450's FMin, SMin or UMin;
  • an :if becomes a selection with its merge block, a :loop a loop header, body, continue target and merge block, and the value either gives an OpPhi at 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.450 included, and refused with an unsupported-error where the target does not allow them;
  • a fragment shader's built-in input of integers, SampleId, is decorated Flat, as its other integer inputs are; and from SPIR-V 1.6, where a fragment may be demoted, HelperInvocation is Volatile, so that it reads what it is after the demotion;
  • the entry point lists every global its call tree uses, and names become OpName and OpMemberName.

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.