Physical Devices and Devices
A physical device is a GPU, or a software renderer, that an instance can use. A device is your connection to one: it has the queues you submit work to, and the extensions and features you enabled.
Physical devices
(vk:physical-devices instance)
;; => (#<PHYSICAL-DEVICE "llvmpipe (LLVM 21.1.8, 256 bits)" cpu Vulkan 1.4.354>)
physical-devices reads each GPU's properties once and returns them as
immutable physical-device objects:
| Reader | Value |
|---|---|
physical-device-name |
The name, such as "AMD Radeon RX 7800 XT (RADV NAVI32)". |
physical-device-type |
:discrete-gpu, :integrated-gpu, :virtual-gpu, :cpu or :other. |
physical-device-api-version |
The Vulkan version the driver supports, (MAJOR MINOR PATCH). |
physical-device-driver-version |
The driver's version, an integer in the vendor's own encoding. |
physical-device-vendor-id, physical-device-device-id |
PCI-style identifiers. |
physical-device-queue-families |
A list of queue-family. |
physical-device-extensions |
The names of the device extensions it supports. |
physical-device-instance |
The instance it belongs to. |
A physical device is valid while its instance is, and is never destroyed.
Queue families
A GPU's queues come in families. All queues of a family can do the same things:
(mapcar (lambda (family)
(list (vk:queue-family-index family)
(vk:queue-family-flags family)
(vk:queue-family-count family)))
(vk:physical-device-queue-families gpu))
;; => ((0 (:GRAPHICS :COMPUTE :TRANSFER :SPARSE-BINDING) 1))
A queue-family has an index, flags (:graphics, :compute,
:transfer, :sparse-binding, :protected, :video-decode and so on),
a count of queues, and queue-family-timestamp-valid-bits.
find-queue-family returns the first family that satisfies a predicate:
(vk:find-queue-family gpu (lambda (family)
(member :compute (vk:queue-family-flags family))))
Whether a family can present to a surface depends on the surface; ask
with surface-supported-p. See Surfaces and
Swapchains.
Features
Features are optional capabilities, each a VkBool32 field of a feature
struct. They are named by the struct, a symbol of cl-vk/structs, and
the field, a keyword:
| Struct | Holds |
|---|---|
vks:physical-device-features |
the Vulkan 1.0 features, such as :sampler-anisotropy |
vks:physical-device-vulkan11-features |
Vulkan 1.1 features |
vks:physical-device-vulkan12-features |
Vulkan 1.2 features, such as :timeline-semaphore |
vks:physical-device-vulkan13-features |
Vulkan 1.3 features, such as :dynamic-rendering, :synchronization2 |
vks:physical-device-vulkan14-features |
Vulkan 1.4 features |
| any extension's features struct | its features, such as vks:physical-device-mesh-shader-features-ext |
physical-device-feature-p asks about one:
(vk:physical-device-feature-p gpu 'vks:physical-device-vulkan13-features :dynamic-rendering)
;; => T
(vk:physical-device-feature-p gpu 'vks:physical-device-features :sampler-anisotropy)
;; => T
A field that is not a feature, or a struct that is not a feature struct, signals an error that says so, and an unknown field lists the struct's fields.
Creating a device
(vk:with-device (device gpu
:extensions '("VK_KHR_swapchain")
:features '((vks:physical-device-vulkan13-features
:dynamic-rendering :synchronization2)
(vks:physical-device-vulkan12-features
:timeline-semaphore)))
...)
with-device calls create-device and, when its body is left, waits
for the device to finish its work and destroys it. create-device takes
the physical device and:
| Argument | Default | Meaning |
|---|---|---|
:queues |
one queue of the first family with :graphics |
The queues to make, a list of (FAMILY COUNT), one entry per family. |
:extensions |
'() |
Device extensions to enable, by name. |
:features |
'() |
Features to enable, a list of (STRUCT FIELD ...). |
Before creating anything, it checks every request against the physical
device and signals an unsupported-error for a queue family that does
not exist or has too few queues, an extension it does not support, or a
feature it lacks:
#<PHYSICAL-DEVICE "llvmpipe (LLVM 21.1.8, 256 bits)" cpu Vulkan 1.4.354> does not
support these features: physical-device-ray-tracing-motion-blur-features-nv
:RAY-TRACING-MOTION-BLUR.
A malformed request, such as a queue request that is not (FAMILY COUNT)
or a feature request that is not a list, signals a type-error that shows
the expected form; a queue family given twice signals a vk-error: "The
queue family 0 is asked for twice; ask once, for all its queues."
To present, enable "VK_KHR_swapchain". To use a Vulkan 1.3 feature,
enable it here even though the device supports Vulkan 1.3: features are
off unless enabled.
Queues
device-queue returns a queue of the device:
(vk:device-queue device) ; the first queue the device was made with
(vk:device-queue device :family 2) ; queue 0 of family 2
(vk:device-queue device :family 2 :index 1) ; queue 1 of family 2
device-queues lists them all. A queue knows its queue-device, its
queue-family (a queue-family object) and its queue-index within the
family. Asking for a queue the device was not created with signals a
vk-error that lists the queues it has.
Several queues, for example a graphics queue and a separate transfer queue when the GPU has one:
(let* ((graphics (vk:find-queue-family gpu (lambda (f) (member :graphics (vk:queue-family-flags f)))))
(transfer (vk:find-queue-family gpu (lambda (f)
(and (member :transfer (vk:queue-family-flags f))
(not (member :graphics (vk:queue-family-flags f))))))))
(vk:create-device gpu :queues (if transfer
(list (list (vk:queue-family-index graphics) 1)
(list (vk:queue-family-index transfer) 1))
(list (list (vk:queue-family-index graphics) 1)))))
Asking about a device
| Function | Returns |
|---|---|
device-physical-device |
The physical device it runs on. |
device-queues |
Its queues. |
device-extensions |
The extensions it was created with. |
device-features |
The feature requests it was created with. |
device-wait-idle |
Waits until all work submitted to its queues has finished. |
Destroying a device
destroy-device needs the swapchains, fences and semaphores made from
the device destroyed first; otherwise it signals live-children-error.
Wait for the device's work to finish first, with device-wait-idle.
Objects you made through the raw layer, such as command pools, are not
tracked: destroy them yourself before the device.
Thread rules
A physical device is immutable and may be shared between threads. A device 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. Submitting to and presenting on a queue must happen on one thread at a time; different queues may be used from different threads.