Instances and Validation
An instance is the connection between your program and Vulkan. It holds the instance extensions and layers you enabled, and everything else is made from it.
Creating an instance
(vk:with-instance (instance :application-name "my game"
:extensions '("VK_KHR_surface" "VK_KHR_wayland_surface"))
...)
with-instance calls create-instance with its arguments, binds the
instance, and destroys it when the body is left, normally or not. When
the instance must outlive a dynamic extent, call create-instance and
destroy-instance yourself.
create-instance takes these keyword arguments:
| Argument | Default | Meaning |
|---|---|---|
:api-version |
'(1 4) |
The Vulkan version to use, a list (MAJOR MINOR). |
:extensions |
'() |
Instance extensions to enable, by name. |
:layers |
'() |
Layers to enable, by name. |
:validation |
nil |
When true, enable the Khronos validation layer and queue its messages. |
:validation-checks |
'() |
Extra validation checks, :synchronization and :best-practices. Turns validation on. |
:application-name |
"" |
Passed to drivers, which may use it to apply per-application fixes. |
:application-version |
0 |
Likewise, an integer. |
:engine-name |
"cl-vk" |
Likewise. |
:engine-version |
0 |
Likewise, an integer. |
Before creating anything, create-instance checks the request against
what is installed. If the loader does not support :api-version, or a
layer or extension is not available, it signals an unsupported-error
that names what is missing and says what to do:
(handler-case (vk:create-instance :extensions '("VK_KHR_no_such_thing"))
(vk:unsupported-error (e)
(list (princ-to-string e) (vk:unsupported-error-missing e))))
;; => ("The instance extension VK_KHR_no_such_thing is not available.
;; AVAILABLE-INSTANCE-EXTENSIONS lists the supported ones."
;; ("VK_KHR_no_such_thing"))
Which extensions?
To present to a window, enable "VK_KHR_surface" and the surface
extension of the window system:
| Window system | Extension |
|---|---|
| Wayland | "VK_KHR_wayland_surface" |
| Windows | "VK_KHR_win32_surface" |
| X11 (xcb) | "VK_KHR_xcb_surface" |
| X11 (Xlib) | "VK_KHR_xlib_surface" |
| none, for tests | "VK_EXT_headless_surface" |
Usually the library that owns the window says which extensions it needs.
available-instance-extensions lists what the loader and drivers
provide; given a layer's name, it lists what that layer provides.
Asking about the instance
| Function | Returns |
|---|---|
instance-api-version |
The version asked for, as (MAJOR MINOR PATCH). |
instance-extensions |
The extension names enabled, those validation added included. |
instance-layers |
The layer names enabled, likewise. |
Validation
The Khronos validation layer checks every call against the Vulkan
specification and reports what is wrong: a missing barrier, a wrong
image layout, a struct member out of range. Use it during development.
It needs the validation layers installed (vulkan-validationlayers on
Debian and Ubuntu); without them, :validation t signals an
unsupported-error that says so.
(vk:with-instance (instance :validation t)
;; ... create devices, record and submit work ...
(dolist (message (vk:take-debug-messages instance))
(when (eq (vk:debug-message-severity message) :error)
(format *error-output* "~&~A: ~A~%"
(vk:debug-message-id-name message)
(vk:debug-message-text message)))))
With :validation t, cl-vk enables the layer and the extension
VK_EXT_debug_utils, and installs a debug messenger. The messenger
receives messages on whatever thread the driver calls it from, queues
them, and returns at once; it never signals into the driver.
take-debug-messages returns the messages queued since the last call,
oldest first, and forgets them. Messages from creating the instance
itself are included.
A debug-message has:
| Reader | Value |
|---|---|
debug-message-severity |
:verbose, :info, :warning or :error |
debug-message-types |
a list of :general, :validation and :performance |
debug-message-text |
the message, a string |
debug-message-id-name |
the name of the check, such as "SYNC-HAZARD-WRITE-AFTER-READ", or nil |
All severities are queued, :verbose and :info included, so filter by
severity. Call take-debug-messages regularly, once a frame for
example: the queue is not bounded.
A test can make validation part of the check, as cl-vk's own tests do:
(defun validation-errors (instance)
(mapcar #'vk:debug-message-text
(remove :error (vk:take-debug-messages instance)
:key #'vk:debug-message-severity :test-not #'eq)))
Extra validation checks
Some checks are off in the layer by default because they are slow.
:validation-checks turns them on, and turns validation on with them:
:synchronizationchecks the barriers and semaphores between commands that use the same memory, shader accesses through descriptors included. It finds the hazards that cause flickering or corruption on one GPU and not another.:best-practicesreports uses that are valid but slow or fragile.
(vk:with-instance (instance :extensions '("VK_KHR_surface" "VK_EXT_headless_surface")
:validation-checks '(:synchronization))
...)
These need VK_EXT_layer_settings, which recent validation layers
provide; cl-vk enables it.
Destroying an instance
destroy-instance destroys the instance and its debug messenger. Every
device and surface made from it must be destroyed first; otherwise it
signals live-children-error and destroys nothing. Using an instance
after it is destroyed signals destroyed-object-error.
Thread rules
An instance may be used from several threads at once, but creating and
destroying the objects made from it must happen on one thread at a time,
and it must not be destroyed while another thread uses it.
take-debug-messages may be called from any thread.