Surfaces and Swapchains
A surface is where Vulkan presents: a window, or nowhere for tests. A swapchain is the set of images presented to a surface in turn: each frame you acquire one, render to it, and present it.
Surfaces
cl-vk knows no window system. The library that owns the window creates
the VkSurfaceKHR and gives it to cl-vk with adopt-surface. From then
on the surface belongs to cl-vk: destroy it with destroy-surface.
With cl-windowing, its
optional system cl-windowing/vulkan does this for you:
(vk:with-instance (instance :extensions (cl-windowing/vulkan:vulkan-instance-extensions
window-system))
(let ((surface (cl-windowing/vulkan:create-vulkan-surface window instance)))
...
(vk:destroy-surface surface)))
To adopt a surface yourself, create it through the raw layer and pass the
handle. For a Wayland window, given its wl_display and wl_surface
pointers, with the instance made with "VK_KHR_surface" and
"VK_KHR_wayland_surface":
(defun create-wayland-surface (instance display wl-surface)
(vk:with-structs ((info vks:wayland-surface-create-info-khr
:display display :surface wl-surface))
(cl-ffi:with-foreign-object (handle :uint64)
(vk:check (vk-ffi:vk-create-wayland-surface-khr (vk:instance-commands instance)
(vk:raw-handle instance)
info (cl-ffi:null-pointer) handle)
"vkCreateWaylandSurfaceKHR")
(vk:adopt-surface instance (cl-ffi:mem-ref handle :uint64)))))
For tests and off-screen work, create-headless-surface makes a surface
that presents nowhere. The instance needs the extensions
"VK_KHR_surface" and "VK_EXT_headless_surface"; without them it
signals an unsupported-error that says which to add.
Asking what a surface supports
What a surface supports depends on the physical device:
(vk:surface-formats gpu surface)
;; => ((:R8G8B8A8-UNORM :SRGB-NONLINEAR) (:B8G8R8A8-UNORM :SRGB-NONLINEAR)
;; (:R8G8B8A8-SRGB :SRGB-NONLINEAR) (:B8G8R8A8-SRGB :SRGB-NONLINEAR))
(vk:surface-present-modes gpu surface)
;; => (:MAILBOX :FIFO)
(vk:surface-supported-p gpu 0 surface) ; can queue family 0 present to it?
;; => T
surface-capabilities returns a surface-capabilities object:
| Reader | Value |
|---|---|
surface-capabilities-min-image-count |
The fewest swapchain images. |
surface-capabilities-max-image-count |
The most, or nil for no limit. |
surface-capabilities-current-extent |
The surface's size (WIDTH HEIGHT), or nil when the swapchain decides it, as on Wayland and for headless surfaces. |
surface-capabilities-min-extent, surface-capabilities-max-extent |
The bounds of the size. |
surface-capabilities-max-array-layers |
The most image layers. |
surface-capabilities-supported-transforms, surface-capabilities-current-transform |
Rotations and mirrors, as keywords. |
surface-capabilities-supported-composite-alpha |
How alpha blends with the desktop, as keywords. |
surface-capabilities-supported-usage |
Image usages a swapchain may have, such as :color-attachment and :transfer-dst. |
Creating a swapchain
(vk:create-swapchain device surface :extent '(800 600))
;; => #<SWAPCHAIN 800x600 b8g8r8a8-srgb fifo>
The device needs the extension "VK_KHR_swapchain", and one of its
queues must be able to present to the surface. create-swapchain takes:
| Argument | Default | Meaning |
|---|---|---|
:extent |
none | The size (WIDTH HEIGHT), for a surface that leaves it to the swapchain. Ignored when the surface has a size of its own. Clamped to what the surface allows. |
:format |
an sRGB format | The image format, a vks:vk-format keyword. By default :b8g8r8a8-srgb, else :r8g8b8a8-srgb, else the first supported. |
:color-space |
:srgb-nonlinear |
The colour space, a vks:color-space-khr keyword, used with :format. |
:present-mode |
:fifo |
A vks:present-mode-khr keyword: :fifo waits for vertical blank and is always supported; :mailbox replaces the queued image; :immediate tears; :fifo-relaxed tears when late. |
:image-count |
one more than the minimum | The number of images. |
:usage |
'(:color-attachment) |
Image usage flags. Add :transfer-dst to clear or copy into the images. |
:old-swapchain |
nil |
The swapchain this one replaces; see below. |
The other settings are fixed: one array layer, exclusive sharing, the surface's current transform, opaque composite alpha when supported, and clipping on.
Anything the surface does not support signals an unsupported-error
before anything is created, naming what it does support:
The surface does not support the present mode :IMMEDIATE. It supports :MAILBOX, :FIFO.
When the surface leaves the size to the swapchain and no :extent is
given, create-swapchain signals a vk-error that asks for one.
The swapchain's images
| Reader | Value |
|---|---|
swapchain-images |
The raw VkImage handles, in index order: an escape hatch to the raw layer. |
swapchain-format, swapchain-color-space |
Keywords, a vks:vk-format and a vks:color-space-khr. |
swapchain-extent |
(WIDTH HEIGHT). |
swapchain-present-mode |
A vks:present-mode-khr keyword. |
swapchain-usage |
A list of keywords. |
swapchain-device, swapchain-surface |
What it was made from. |
The images belong to the swapchain: do not destroy them. cl-vk does not
wrap them, so swapchain-images is an escape hatch, as raw-handle is:
image views for them are made through the raw layer, and are yours to
destroy before the swapchain. Once the swapchain is destroyed,
swapchain-images signals destroyed-object-error.
Each frame
(multiple-value-bind (index status)
(vk:acquire-next-image swapchain :semaphore image-available)
(case status
((:success :suboptimal)
;; Record and submit work that renders to (nth index (vk:swapchain-images swapchain)),
;; waiting on IMAGE-AVAILABLE and signalling RENDER-FINISHED.
(case (vk:present queue swapchain index :wait-semaphore render-finished)
((:suboptimal :out-of-date) (recreate))))
(:out-of-date (recreate))))
acquire-next-image asks for the next image to render to. It signals
the given :semaphore, :fence, or both, when the image is ready; at
least one is needed. It waits at most :timeout nanoseconds, forever by
default, and returns two values, the image's index and a status:
| Status | Index | Meaning |
|---|---|---|
:success |
an integer | Render to it. |
:suboptimal |
an integer | Usable, but the swapchain no longer matches the surface exactly: recreate it soon. |
:out-of-date |
nil |
The swapchain no longer matches the surface: recreate it, then acquire again. |
:timeout |
nil |
No image within :timeout. |
:not-ready |
nil |
No image yet, with :timeout 0. |
present queues the image for presentation on a queue, after
:wait-semaphore is signaled, and returns :success, :suboptimal
(presented, but recreate the swapchain soon) or :out-of-date (recreate
the swapchain).
Neither function signals for these statuses, since they are part of
normal operation; other failures, such as a lost device or surface,
signal a result-error. Neither allocates.
Recipes has a complete frame loop, with the command buffer, the barriers and the submission, that runs clean under synchronization validation.
Recreating a swapchain
After a resize, the surface and swapchain no longer match. Wait for the
device to finish with the old images, then call recreate-swapchain:
(vk:device-wait-idle device)
(setf swapchain (vk:recreate-swapchain swapchain))
;; or, for a surface that leaves its size to the swapchain:
(setf swapchain (vk:recreate-swapchain swapchain :extent (list new-width new-height)))
It creates a swapchain with the same settings for the surface's current
size, or :extent, passing the old one as :old-swapchain so the driver
can reuse its resources, and destroys the old one. Without :extent and
without a size from the surface, the old extent is kept. Image views and
framebuffers of the old images must be recreated too.
Destroying
destroy-swapchain destroys a swapchain; its images must no longer be in
use. destroy-surface destroys a surface; the swapchains made for it must
be destroyed first, or it signals live-children-error.
Thread rules
A surface may be queried from several threads at once; creating a swapchain for it or destroying it must not overlap any other use. Acquiring, presenting and destroying a swapchain must happen on one thread at a time: in practice the render thread owns it.