luv

Workshop wiki

canvas.lisp

hal/vulkan/canvas.lisp

system luv · 59 definitions · on GitHub

Vulkan presentation contexts and their SDL bridge.

in-package#:luv
defmethodsdl-presentation-api-for
declare
ignoreprovider
:vulkan
defclassvulkan-canvas-context
canvas:initarg:canvas:readercontext-canvas
provider:initarg:provider:readervulkan-canvas-provider
instance:initarg:instance:initformnil:accessorvulkan-canvas-instance
physical-device:initarg:physical-device:initformnil:accessorvulkan-canvas-physical-device
device:initarg:device:initformnil:accessorcanvas-device
surface:initarg:surface:initformnil:accessorvulkan-canvas-surface
configuration:initformnil:accessorcanvas-context-configuration
swapchain:initformnil:accessorvulkan-canvas-swapchain
textures:initform
:accessorvulkan-canvas-textures
extent:initformnil:accessorcanvas-extent
window-size:initformnil:accessorvulkan-canvas-window-size:documentation

The window's pixel size when this swapchain was built.

Compared against the window rather than against EXTENT, which comes from the surface's own idea of its current extent: a platform where the two disagree would otherwise ask for a rebuild on every single frame.

format:initformnil:accessorcanvas-format
render-done:initform
:accessorvulkan-canvas-render-done
frame-slots:initform
:accessorvulkan-canvas-frame-slots
next-frame-slot:initform0:accessorvulkan-canvas-next-frame-slot
presentation-timeline:initformnil:accessorvulkan-canvas-presentation-timeline
current-texture:initformnil:accessorvulkan-canvas-current-texture
state:initform:unconfigured:accessorcanvas-context-state
defclassvulkan-presentation-timeline
status:initform:warming:accessorvulkan-presentation-timeline-status
reason:initformnil:accessorvulkan-presentation-timeline-reason
stage:initarg:stage:readervulkan-presentation-timeline-stage
absolute-time-p:initarg:absolute-time-p:initformnil:readervulkan-presentation-timeline-absolute-time-p
time-domain:initformnil:accessorvulkan-presentation-timeline-time-domain
time-domain-id:initform0:accessorvulkan-presentation-timeline-time-domain-id
refresh-duration:initform0:accessorvulkan-presentation-timeline-refresh-duration
refresh-interval:initform0:accessorvulkan-presentation-timeline-refresh-interval
next-present-id:initform1:accessorvulkan-presentation-timeline-next-present-id
latest-result-id:initform0:accessorvulkan-presentation-timeline-latest-result-id
latest-result-nanoseconds:initform0:accessorvulkan-presentation-timeline-latest-result-nanoseconds
count:initform0:accessorvulkan-presentation-timeline-count
dropped-count:initform0:accessorvulkan-presentation-timeline-dropped-count
present-ids:initform
make-array+vulkan-presentation-timing-capacity+:element-type'
unsigned-byte64
:initial-element0
:readervulkan-presentation-timeline-present-ids
predicted-seconds:initform
make-array+vulkan-presentation-timing-capacity+:element-type'double-float:initial-element0d0
:readervulkan-presentation-timeline-predicted-seconds
submitted-seconds:initform
make-array+vulkan-presentation-timing-capacity+:element-type'double-float:initial-element0d0
:readervulkan-presentation-timeline-submitted-seconds
target-nanoseconds:initform
make-array+vulkan-presentation-timing-capacity+:element-type'
unsigned-byte64
:initial-element0
:readervulkan-presentation-timeline-target-nanoseconds
actual-nanoseconds:initform
make-array+vulkan-presentation-timing-capacity+:element-type'
unsigned-byte64
:initial-element0
:readervulkan-presentation-timeline-actual-nanoseconds
actual-time-domains:initform
make-array+vulkan-presentation-timing-capacity+:initial-elementnil
:readervulkan-presentation-timeline-actual-time-domains
actual-time-domain-ids:initform
make-array+vulkan-presentation-timing-capacity+:element-type'
unsigned-byte64
:initial-element0
:readervulkan-presentation-timeline-actual-time-domain-ids
:documentation

One swapchain's bounded, presentation-ID-indexed display observations.

defstructpresentation-timing-observation
present-id0:type
unsigned-byte64
predicted-seconds0d0:typedouble-float
submitted-seconds0d0:typedouble-float
target-nanoseconds0:type
unsigned-byte64
actual-nanoseconds0:type
unsigned-byte64
actual-time-domain
actual-time-domain-id0:type
unsigned-byte64
defstructpresentation-timing-snapshotstatusreasonstagetime-domainabsolute-time-p
time-domain-id0:type
unsigned-byte64
refresh-duration0:type
unsigned-byte64
refresh-interval0:type
unsigned-byte64
dropped-count0:type
unsigned-byte64
observations
:typevector
defunnote-vulkan-presentation-submission
timelinepresent-idpredicted-secondssubmitted-seconds&optional
target-nanoseconds0
let
setf
aref
vulkan-presentation-timeline-present-idstimeline
index
present-id
aref
vulkan-presentation-timeline-predicted-secondstimeline
index
floatpredicted-seconds1d0
aref
vulkan-presentation-timeline-submitted-secondstimeline
index
floatsubmitted-seconds1d0
aref
vulkan-presentation-timeline-target-nanosecondstimeline
index
target-nanoseconds
aref
vulkan-presentation-timeline-actual-nanosecondstimeline
index
0
aref
vulkan-presentation-timeline-actual-time-domainstimeline
index
nil
aref
vulkan-presentation-timeline-actual-time-domain-idstimeline
index
0
vulkan-presentation-timeline-counttimeline
min+vulkan-presentation-timing-capacity+
1+
vulkan-presentation-timeline-counttimeline
vulkan-presentation-timeline-next-present-idtimeline
1+present-id
present-id
defunnote-vulkan-presentation-result
timelinepresent-idactual-nanosecondstime-domaintime-domain-id
let
when
=present-id
aref
vulkan-presentation-timeline-present-idstimeline
index
setf
aref
vulkan-presentation-timeline-actual-nanosecondstimeline
index
actual-nanoseconds
aref
vulkan-presentation-timeline-actual-time-domainstimeline
index
time-domain
aref
vulkan-presentation-timeline-actual-time-domain-idstimeline
index
time-domain-id
when
and
>present-id
vulkan-presentation-timeline-latest-result-idtimeline
eqtime-domain
vulkan-presentation-timeline-time-domaintimeline
=time-domain-id
vulkan-presentation-timeline-time-domain-idtimeline
setf
vulkan-presentation-timeline-latest-result-idtimeline
present-id
vulkan-presentation-timeline-latest-result-nanosecondstimeline
actual-nanoseconds
t
defunpredict-vulkan-presentation-target
timelinefallback-time

Return fallback-time and the next native display beat when available.

The selected Vulkan time domain may be opaque and unrelated to Lisp's monotonic clock. Keep animation prediction in the host domain and advance native targets only from observed or already-scheduled native timestamps.

let*
base-id
vulkan-presentation-timeline-latest-result-idtimeline
duration
vulkan-presentation-timeline-refresh-durationtimeline
previous-id
1-
vulkan-presentation-timeline-next-present-idtimeline
previous-index
and
pluspprevious-id
previous-target
andprevious-index
=previous-id
aref
vulkan-presentation-timeline-present-idstimeline
previous-index
aref
vulkan-presentation-timeline-target-nanosecondstimeline
previous-index
if
and
eq:recording
vulkan-presentation-timeline-statustimeline
vulkan-presentation-timeline-absolute-time-ptimeline
or
pluspbase-id
andprevious-target
pluspprevious-target

Feedback seeds the native clock. After that, requested targets own cadence: a late result is evidence of a missed beat, not a new phase from which to delay the following frame.

let
native-base
if
andprevious-target
pluspprevious-target
previous-target
vulkan-presentation-timeline-latest-result-nanosecondstimeline
valuesfallback-time
+native-baseduration
valuesfallback-timenil
defunsnapshot-vulkan-presentation-timeline
timeline
let*
count
vulkan-presentation-timeline-counttimeline
next
vulkan-presentation-timeline-next-present-idtimeline
first-id
-nextcount
observations
make-arraycount
dotimes
offsetcount
let*
present-id
+first-idoffset
setf
arefobservationsoffset
make-presentation-timing-observation:present-idpresent-id:predicted-seconds
aref
vulkan-presentation-timeline-predicted-secondstimeline
index
:submitted-seconds
aref
vulkan-presentation-timeline-submitted-secondstimeline
index
:target-nanoseconds
aref
vulkan-presentation-timeline-target-nanosecondstimeline
index
:actual-nanoseconds
aref
vulkan-presentation-timeline-actual-nanosecondstimeline
index
:actual-time-domain
aref
vulkan-presentation-timeline-actual-time-domainstimeline
index
:actual-time-domain-id
aref
vulkan-presentation-timeline-actual-time-domain-idstimeline
index
make-presentation-timing-snapshot:status
vulkan-presentation-timeline-statustimeline
:reason
vulkan-presentation-timeline-reasontimeline
:stage
vulkan-presentation-timeline-stagetimeline
:absolute-time-p
vulkan-presentation-timeline-absolute-time-ptimeline
:time-domain
vulkan-presentation-timeline-time-domaintimeline
:time-domain-id
vulkan-presentation-timeline-time-domain-idtimeline
:refresh-duration
vulkan-presentation-timeline-refresh-durationtimeline
:refresh-interval
vulkan-presentation-timeline-refresh-intervaltimeline
:dropped-count
vulkan-presentation-timeline-dropped-counttimeline
:observationsobservations
defunvulkan-canvas-presentation-timing-snapshot

Return a coherent copy of context's bounded presentation observations.

call-on-sdl-canvas-thread
lambda
let
timeline
vulkan-canvas-presentation-timelinecontext
defuncompleted-presentation-timing-observations
snapshot
remove-if
lambda
observation
zerop
presentation-timing-observation-actual-nanosecondsobservation
coerce
presentation-timing-snapshot-observationssnapshot
'list
defunpresentation-timing-interval-milliseconds
observations
loopforprevious=nilthenobservationforobservationinobservationswhen
andprevious
=
1+
presentation-timing-observation-present-idprevious
presentation-timing-observation-present-idobservation
eq
presentation-timing-observation-actual-time-domainprevious
presentation-timing-observation-actual-time-domainobservation
=
presentation-timing-observation-actual-time-domain-idprevious
presentation-timing-observation-actual-time-domain-idobservation
collect
/
-
presentation-timing-observation-actual-nanosecondsobservation
presentation-timing-observation-actual-nanosecondsprevious
1d6
defunpresentation-timing-phase-errors-milliseconds
observations
whenobservations
let
origin
firstobservations
loopforobservationinobservationswhen
and
eq
presentation-timing-observation-actual-time-domainorigin
presentation-timing-observation-actual-time-domainobservation
=
presentation-timing-observation-actual-time-domain-idorigin
presentation-timing-observation-actual-time-domain-idobservation
collect
*1d3
-
/
-
presentation-timing-observation-actual-nanosecondsobservation
presentation-timing-observation-actual-nanosecondsorigin
1d9
-
presentation-timing-observation-predicted-secondsobservation
presentation-timing-observation-predicted-secondsorigin
defunpresentation-timing-summary
values
whenvalues
let*
count
lengthvalues
sorted
sort
copy-seqvalues
#'<
mean
/
reduce#'+values
count
variance
/
reduce#'+values:key
lambda
value
expt
-valuemean
2
count
list:countcount:minimum
firstsorted
:meanmean:p50
eltsorted
floor
*0.50d0
1-count
:p95
eltsorted
floor
*0.95d0
1-count
:maximum
car
lastsorted
:standard-deviation
sqrtvariance
defunprint-vulkan-canvas-presentation-timing
context&optional
stream*standard-output*

Print display cadence and prediction drift observed for context.

let
unlesssnapshot
formatstream"No Vulkan presentation timeline exists.~%"
let*
interval-summary
phase-summary
duration
presentation-timing-snapshot-refresh-durationsnapshot
formatstream"Vulkan presentation timing: ~A~@[ (~A)~]~%"
presentation-timing-snapshot-statussnapshot
presentation-timing-snapshot-reasonsnapshot
formatstream" stage/domain: ~A / ~A [~D]~%"
presentation-timing-snapshot-stagesnapshot
presentation-timing-snapshot-time-domainsnapshot
presentation-timing-snapshot-time-domain-idsnapshot
formatstream" absolute display scheduling: ~:[unavailable~;enabled~]~%"
presentation-timing-snapshot-absolute-time-psnapshot
when
formatstream" display: ~,6F ms (~,3F Hz), interval granularity ~,6F ms~%"
/
presentation-timing-snapshot-refresh-intervalsnapshot
1d6
formatstream" frames: ~D submitted, ~D timed, ~D timing drops~%"
length
presentation-timing-snapshot-observationssnapshot
lengthobservations
presentation-timing-snapshot-dropped-countsnapshot
wheninterval-summary
formatstream" actual interval ms: mean ~,6F sd ~,6F min/p50/p95/max ~,6F / ~,6F / ~,6F / ~,6F~%"
getfinterval-summary:mean
getfinterval-summary:standard-deviation
getfinterval-summary:minimum
getfinterval-summary:p50
getfinterval-summary:p95
getfinterval-summary:maximum
whenphase-summary
formatstream" prediction drift ms: rms ~,6F min/max ~,6F / ~,6F~%"
sqrt
/
reduce#'+phase-errors:key
lambda
x
*xx
lengthphase-errors
getfphase-summary:minimum
getfphase-summary:maximum
snapshot
defunwrite-vulkan-canvas-presentation-timing-csv
contextpathname

Write context's raw presentation observations to pathname.

let*
observations
andsnapshot
coerce
presentation-timing-snapshot-observationssnapshot
'list
origin
firstcompleted
unlesssnapshot
error"No Vulkan presentation timeline exists for ~S."context
ensure-directories-existpathname
with-open-file
streampathname:direction:output:if-exists:supersede:if-does-not-exist:create
formatstream"present_id,predicted_seconds,submitted_seconds,target_nanoseconds,actual_nanoseconds,time_domain,time_domain_id,actual_interval_ms,prediction_drift_ms,target_error_ms~%"
loopwithprevious=nilforobservationinobservationsforactual=
presentation-timing-observation-actual-nanosecondsobservation
fortarget=
presentation-timing-observation-target-nanosecondsobservation
forcomparable-p=
andorigin
pluspactual
eq
presentation-timing-observation-actual-time-domainorigin
presentation-timing-observation-actual-time-domainobservation
=
presentation-timing-observation-actual-time-domain-idorigin
presentation-timing-observation-actual-time-domain-idobservation
forinterval=
andprevious
pluspactual
plusp
presentation-timing-observation-actual-nanosecondsprevious
=
1+
presentation-timing-observation-present-idprevious
presentation-timing-observation-present-idobservation
/
-actual
presentation-timing-observation-actual-nanosecondsprevious
1d6
fordrift=
andcomparable-p
*1d3
-
/
-actual
presentation-timing-observation-actual-nanosecondsorigin
1d9
-
presentation-timing-observation-predicted-secondsobservation
presentation-timing-observation-predicted-secondsorigin
fortarget-error=
and
plusptarget
pluspactual
eq
presentation-timing-observation-actual-time-domainobservation
presentation-timing-snapshot-time-domainsnapshot
=
presentation-timing-observation-actual-time-domain-idobservation
presentation-timing-snapshot-time-domain-idsnapshot
/
-actualtarget
1d6
do
formatstream"~D,~,9F,~,9F,~D,~D,~A,~D,~:[~;~,9F~],~:[~;~,9F~],~:[~;~,9F~]~%"
presentation-timing-observation-present-idobservation
presentation-timing-observation-predicted-secondsobservation
presentation-timing-observation-submitted-secondsobservation
targetactual
or
presentation-timing-observation-actual-time-domainobservation
""
presentation-timing-observation-actual-time-domain-idobservation
intervalintervaldriftdrifttarget-errortarget-error
setfpreviousobservation
pathname
defclassvulkan-canvas-frame-slot
image-ready:initarg:image-ready:readervulkan-frame-slot-image-ready
submission-index:initformnil:accessorvulkan-frame-slot-submission-index:documentation"Queue submission index of this slot's frame in flight."
commands:initformnil:accessorvulkan-frame-slot-commands
defunmake-vulkan-canvas-frame-slot
native-device
make-instance'vulkan-canvas-frame-slot:image-ready
defunmake-vulkan-canvas-frame-slots
native-devicecount
let
slots
make-arraycount:initial-elementnil
completed-pnil
unwind-protect
progn
dotimes
indexcount
setf
arefslotsindex
setfcompleted-pt
slots
unlesscompleted-p
loopforslotacrossslotswhenslotdo
lvk:destroy-semaphorenative-device
vulkan-frame-slot-image-readyslot
defunmake-vulkan-semaphores
native-devicecount
let
semaphores
make-arraycount:initial-elementnil
completed-pnil
unwind-protect
progn
dotimes
indexcount
setf
arefsemaphoresindex
setfcompleted-pt
semaphores
unlesscompleted-p
loopforsemaphoreacrosssemaphoreswhensemaphoredo
lvk:destroy-semaphorenative-devicesemaphore
defundestroy-vulkan-canvas-frame-slot
native-deviceslot
when
vulkan-frame-slot-commandsslot
destroy
vulkan-frame-slot-commandsslot
setf
vulkan-frame-slot-commandsslot
nil
lvk:destroy-semaphorenative-device
vulkan-frame-slot-image-readyslot
values
defunrecycle-vulkan-canvas-frame-slot
queueslot

Wait for slot's frame to pass the queue frontier, then release it.

when
vulkan-frame-slot-commandsslot
wait-for-vulkan-submissionqueue
vulkan-frame-slot-submission-indexslot
destroy
vulkan-frame-slot-commandsslot
setf
vulkan-frame-slot-commandsslot
nil
vulkan-frame-slot-submission-indexslot
nil
slot
defunsdl-vulkan-instance-extensions
cffi:with-foreign-object
count:uint32
let
names
sdl3:vulkan-get-instance-extensionscount
when
cffi:null-pointer-pnames
error"SDL could not report Vulkan instance extensions: ~A"
sdl3:get-error
loopforindexbelow
cffi:mem-refcount:uint32
collect
cffi:foreign-string-to-lisp
cffi:mem-arefnames:pointerindex
defunsdl-vulkan-presentation-ready-p
member:video
sdl3:was-init:video
defmethodvulkan-provider-instance-options:around
multiple-value-bind
extensionsflags
call-next-method
defmethodvulkan-gpu-device-extension-names:around
declare
ignoreprovider
let
extensions
call-next-method
if
remove-duplicates:test#'string=
extensions
defuncreate-sdl-vulkan-canvas-surface
canvasproviderinstance
declare
ignoreprovider
cffi:with-foreign-object
surface:pointer
unless
sdl3:vulkan-create-surface
sdl-canvas-windowcanvas
instance
cffi:null-pointer
surface
error"SDL could not create a Vulkan surface: ~A"
sdl3:get-error
cffi:mem-refsurface:pointer
defundestroy-sdl-vulkan-canvas-surface
canvasproviderinstancesurface
declare
ignorecanvasprovider
sdl3:vulkan-destroy-surfaceinstancesurface
cffi:null-pointer
values
defunchoose-vulkan-presentation-stage
stages
find-if
lambda
stage
memberstagestages
'
:image-first-pixel-visible:image-first-pixel-out:request-dequeued:queue-operations-end
defunmake-vulkan-canvas-presentation-timeline

Describe and, when possible, enable timing observation for context.

let*
device-extensions
vulkan-device-extension-namesdevice
instance-extensions
vulkan-device-instance-extension-namesdevice
timeline
make-instance'vulkan-presentation-timeline:stagenil
labels
unsupported
reason
setf
vulkan-presentation-timeline-statustimeline
:unsupported
vulkan-presentation-timeline-reasontimeline
reason
timeline
cond
unsupported:not-direct-display
not
memberlvk:+surface-capabilities-2-extension-name+instance-extensions:test#'string=
unsupported:missing-surface-capabilities-2
not
memberlvk:+present-timing-extension-name+device-extensions:test#'string=
unsupported:missing-present-timing-extension
not
memberlvk:+present-id-2-extension-name+device-extensions:test#'string=
unsupported:missing-present-id-2-extension
t
multiple-value-bind
timing-feature-pabsolute-prelative-pid-feature-p
lvk:physical-device-presentation-features
vulkan-canvas-physical-devicecontext
:present-timing-pt:present-id-2-pt
declare
ignorerelative-p
unlesstiming-feature-p
return-frommake-vulkan-canvas-presentation-timeline
unsupported:missing-present-timing-feature
unlessid-feature-p
return-frommake-vulkan-canvas-presentation-timeline
unsupported:missing-present-id-2-feature
let*
capabilities
lvk:get-presentation-timing-surface-capabilities
vulkan-canvas-physical-devicecontext
vulkan-canvas-surfacecontext
stage
choose-vulkan-presentation-stage
lvk:presentation-timing-capabilities-stagescapabilities
cond
not
lvk:presentation-timing-capabilities-supported-pcapabilities
unsupported:surface-does-not-support-present-timing
not
lvk:presentation-timing-capabilities-present-id-2-pcapabilities
unsupported:surface-does-not-support-present-id-2
notstage
unsupported:surface-offers-no-presentation-stage
t
make-instance'vulkan-presentation-timeline:stagestage:absolute-time-p
andabsolute-p
lvk:presentation-timing-capabilities-absolute-time-pcapabilities
defunrefresh-vulkan-presentation-timeline
timelinenative-deviceswapchain
when
eq:warming
vulkan-presentation-timeline-statustimeline
let
properties
domains
lvk:get-swapchain-time-domainsnative-deviceswapchain
when
andpropertiesdomains
let
domain
or
assoc:clock-monotonicdomains
assoc:swapchain-local-extdomains
assoc:present-stage-local-extdomains
firstdomains
setf
vulkan-presentation-timeline-refresh-durationtimeline
lvk:swapchain-timing-properties-refresh-durationproperties
vulkan-presentation-timeline-refresh-intervaltimeline
lvk:swapchain-timing-properties-refresh-intervalproperties
vulkan-presentation-timeline-time-domaintimeline
cardomain
vulkan-presentation-timeline-time-domain-idtimeline
cdrdomain
vulkan-presentation-timeline-statustimeline
:recording
vulkan-presentation-timeline-reasontimeline
nil
timeline
defundrain-vulkan-presentation-timeline
timelinenative-deviceswapchain
when
eq:recording
vulkan-presentation-timeline-statustimeline
lvk:map-past-presentation-timingsnative-deviceswapchain
lambda
present-idstagetimetime-domaintime-domain-id
when
eqstage
vulkan-presentation-timeline-stagetimeline
note-vulkan-presentation-resulttimelinepresent-idtimetime-domaintime-domain-id
timeline
defunbind-vulkan-canvas-device
contextdevice

Attach unconfigured context to device and create its native SDL surface.

unless
typepdevice'vulkan-gpu-device
error'canvas-error:canvas:operation:configure:reason:unsupported-device:detailsdevice
ensure-live-vulkan-objectdevice:configure-canvas
let
when
andbound-device
not
eqbound-devicedevice
error'canvas-error:canvas:operation:configure:reason:device-mismatch:detailsdevice
unless
let*
required-instance-extensions
missing-instance-extensions
set-differencerequired-instance-extensions
vulkan-device-instance-extension-namesdevice
:test#'string=
whenmissing-instance-extensions
error'canvas-error:canvas:operation:configure:reason:missing-instance-extensions:detailsmissing-instance-extensions
unless
memberlvk:+swapchain-extension-name+
vulkan-device-extension-namesdevice
:test#'string=
error'canvas-error:canvas:operation:configure:reason:missing-swapchain-extension:detailslvk:+swapchain-extension-name+
let*
instance
vulkan-device-instancedevice
physical-device
vulkan-device-physical-devicedevice
queue-family
vulkan-device-queue-familydevice
surfacenil
completed-pnil
unwind-protect
progn
setfsurface
unless
error'canvas-error:canvas:operation:configure:reason:device-cannot-present-to-surface:detailsdevice
setf
vulkan-canvas-instancecontext
instance
vulkan-canvas-physical-devicecontext
physical-device
canvas-devicecontext
device
vulkan-canvas-surfacecontext
surface
completed-pt
unlesscompleted-p
whensurface
context
defungpu-canvas-format-to-vulkan
format
or
cdr
assocformat'
:rgba8-unorm.:r8g8b8a8-unorm
:rgba8-unorm-srgb.:r8g8b8a8-srgb
:bgra8-unorm.:b8g8r8a8-unorm
:bgra8-unorm-srgb.:b8g8r8a8-srgb
error'canvas-error:operation:configure:reason:unsupported-format:detailsformat
defunvulkan-canvas-format-to-gpu
format
or
cdr
assocformat'
:r8g8b8a8-unorm.:rgba8-unorm
:r8g8b8a8-srgb.:rgba8-unorm-srgb
:b8g8r8a8-unorm.:bgra8-unorm
:b8g8r8a8-srgb.:bgra8-unorm-srgb
error'canvas-error:operation:configure:reason:unsupported-format:detailsformat
defunchoose-vulkan-canvas-format
contextrequested-format
let*
physical-device
vulkan-canvas-physical-devicecontext
surface
vulkan-canvas-surfacecontext
formats
lvk:get-surface-formatsphysical-devicesurface
requested-vulkan-format
andrequested-format
ifrequested-vulkan-format
or
find-if
lambda
format
eqrequested-vulkan-format
lvk:presentation-format-formatformat
formats
error'canvas-error:canvas:operation:configure:reason:unsupported-surface-format:detailsrequested-format
or
find-if
lambda
format
and
eq:b8g8r8a8-srgb
lvk:presentation-format-formatformat
eq:srgb-nonlinear-khr
lvk:presentation-format-color-spaceformat
formats
firstformats
error'canvas-error:canvas:operation:configure:reason:no-surface-formats
defunclamp-canvas-extent
valueminimummaximum
maxminimum
minvaluemaximum
defunchoose-vulkan-canvas-extent
contextcapabilities
let
current
lvk:presentation-capabilities-current-extentcapabilities
if
/=#xffffffff
firstcurrent
current
multiple-value-bind
let
minimum
lvk:presentation-capabilities-min-image-extentcapabilities
maximum
lvk:presentation-capabilities-max-image-extentcapabilities
list
clamp-canvas-extentwidth
firstminimum
firstmaximum
defunchoose-vulkan-canvas-image-count
capabilities
let
desired
1+
lvk:presentation-capabilities-min-image-countcapabilities
maximum
lvk:presentation-capabilities-max-image-countcapabilities
if
pluspmaximum
mindesiredmaximum
desired
defunensure-vulkan-canvas-state
contextoperationexpected-state
unless
member
canvas-context-statecontext
if
listpexpected-state
expected-state
listexpected-state
error'canvas-state-error:canvas:operationoperation:reason:invalid-context-state:state
canvas-context-statecontext
:expected-stateexpected-state
defunconfigure-vulkan-canvas-context
contextconfiguration
unless
typepconfiguration'canvas-configuration
error'canvas-error:canvas:operation:configure:reason:invalid-configuration:detailsconfiguration
let
configured-device
canvas-configuration-deviceconfiguration
unlessconfigured-device
error'canvas-error:canvas:operation:configure:reason:device-required
when
eq:configured
canvas-context-statecontext
ensure-vulkan-canvas-statecontext:configure:unconfigured
unless
and
canvas-configuration-usageconfiguration
every
lambda
memberusage'
:copy-src:copy-dst:render-attachment
canvas-configuration-usageconfiguration
error'canvas-error:canvas:operation:configure:reason:unsupported-usage:details
canvas-configuration-usageconfiguration
with-vulkan-gpu-driver-environment
let*
usage
canvas-configuration-usageconfiguration
native-usage
mapcar
lambda
value
ecasevalue
:copy-src:transfer-src
:copy-dst:transfer-dst
:render-attachment:color-attachment
usage
native-device
physical-device
vulkan-canvas-physical-devicecontext
surface
vulkan-canvas-surfacecontext
capabilities
lvk:get-surface-capabilitiesphysical-devicesurface
surface-format
choose-vulkan-canvas-formatcontext
canvas-configuration-formatconfiguration
vk-format
lvk:presentation-format-formatsurface-format
gpu-format
color-space
lvk:presentation-format-color-spacesurface-format
composite-alpha
or
find:opaque
lvk:presentation-capabilities-composite-alphacapabilities
first
lvk:presentation-capabilities-composite-alphacapabilities
present-timing-p
eq:warming
vulkan-presentation-timeline-statuspresentation-timeline
swapchainnil
textures
frame-slots
render-done
completed-pnil
unless
every
lambda
value
membervalue
lvk:presentation-capabilities-usagecapabilities
native-usage
error'canvas-error:canvas:operation:configure:reason:unsupported-surface-usage:detailsusage
unwind-protect
progn
setfswapchain
lvk:create-swapchainnative-devicesurfacevk-formatcolor-spaceextent:min-image-count:usagenative-usage:pre-transform
lvk:presentation-capabilities-current-transformcapabilities
:composite-alphacomposite-alpha:present-mode:fifo-khr:present-timing-ppresent-timing-p
textures
map'vector
lambda
image
make-borrowed-vulkan-textureimageextentgpu-formatvk-format:usageusage
lvk:get-swapchain-imagesnative-deviceswapchain
frame-slotsrender-done
make-vulkan-semaphoresnative-device
lengthtextures
whenpresent-timing-p
refresh-vulkan-presentation-timelinepresentation-timelinenative-deviceswapchain
setf
vulkan-canvas-swapchaincontext
swapchain
vulkan-canvas-texturescontext
textures
extent
vulkan-canvas-window-sizecontext
gpu-format
vulkan-canvas-render-donecontext
render-done
vulkan-canvas-frame-slotscontext
frame-slots
vulkan-canvas-next-frame-slotcontext
0
vulkan-canvas-presentation-timelinecontext
presentation-timeline
canvas-context-configurationcontext
make-canvas-configuration:device:formatgpu-format:usage
canvas-configuration-usageconfiguration
canvas-context-statecontext
:configured
completed-pt
context
unlesscompleted-p
loopforsemaphoreacrossrender-donewhensemaphoredo
lvk:destroy-semaphorenative-devicesemaphore
loopforslotacrossframe-slotswhenslotdo
loopfortextureacrosstexturesdo
destroytexture
whenswapchain
lvk:destroy-swapchainnative-deviceswapchain
defununconfigure-vulkan-canvas-context
when
eq:in-frame
canvas-context-statecontext
ensure-vulkan-canvas-statecontext:unconfigure:configured
unless
member
canvas-context-statecontext
'
:unconfigured:destroyed
with-vulkan-gpu-driver-environment
let
native-device
loopforslotacross
vulkan-canvas-frame-slotscontext
do
loopfortextureacross
vulkan-canvas-texturescontext
do
destroytexture
loopforsemaphoreacross
vulkan-canvas-render-donecontext
do
lvk:destroy-semaphorenative-devicesemaphore
when
vulkan-canvas-swapchaincontext
lvk:destroy-swapchainnative-device
vulkan-canvas-swapchaincontext
setf
vulkan-canvas-swapchaincontext
nil
vulkan-canvas-texturescontext
vulkan-canvas-window-sizecontext
nil
vulkan-canvas-render-donecontext
vulkan-canvas-frame-slotscontext
vulkan-canvas-next-frame-slotcontext
0
vulkan-canvas-presentation-timelinecontext
nil
vulkan-canvas-current-texturecontext
nil
canvas-context-configurationcontext
nil
nilnil
canvas-context-statecontext
:unconfigured
values
defmethoddestroy-canvas-context
unless
eq:destroyed
canvas-context-statecontext
when
vulkan-canvas-surfacecontext
destroy-sdl-vulkan-canvas-surface
vulkan-canvas-providercontext
vulkan-canvas-instancecontext
vulkan-canvas-surfacecontext
setf
vulkan-canvas-surfacecontext
nil
canvas-context-statecontext
:destroyed
values
defmethodmake-canvas-context
&optionalconfiguration
when
error'canvas-error:canvascanvas:operation:make-context:reason:context-already-exists
call-on-sdl-canvas-threadcanvas
lambda
let
context
make-instance'vulkan-canvas-context:canvascanvas:providerprovider
handler-case
progn
whenconfiguration
context
error
condition
errorcondition
defmethodget-current-texture
or
vulkan-canvas-current-texturecontext
error'canvas-state-error:canvas:operation:get-current-texture:reason:outside-frame:state
canvas-context-statecontext
:expected-state:in-frame

A swapchain is only ever right for one window size. Resizing the window leaves the images, their views, and the surface's own extent describing a window that no longer exists, and Vulkan says so by refusing to hand out another image. Rebuilding is the whole answer, and it is cheap enough to do from inside the frame that discovered the problem.

defunrebuild-vulkan-canvas-swapchain

Replace context's swapchain with one built for the window's present size.

The configuration is the one the application already asked for: only the extent, the images, and the per-image synchronization are made again. Returns NIL when the window has no pixels to present to at all, which is an ordinary state for a minimized window and not an error.

let
configuration
canvas-context-configurationcontext
unlessconfiguration
error'canvas-state-error:canvas:operation:rebuild-swapchain:reason:invalid-context-state:state
canvas-context-statecontext
:expected-state:configured
log-event:vulkan"rebuilt the swapchain for ~{~D~^x~}"
t
defunensure-vulkan-canvas-swapchain

Make the swapchain agree with the window before a frame asks for an image.

Returns NIL when this frame cannot be presented, which the caller skips.

let
size
cond
or
zerop
firstsize
zerop
secondsize
nil
equalsize
vulkan-canvas-window-sizecontext
t
defunacquire-vulkan-canvas-image

Acquire the next presentable image, rebuilding a stale swapchain once.

Returns the frame slot, its index, and the image index, or NIL when the surface still cannot supply an image and this frame should be skipped.

let
dotimes
attempt2
let*
slot-index
vulkan-canvas-next-frame-slotcontext
slot
aref
vulkan-canvas-frame-slotscontext
slot-index
with-cpu-trace-zone
:vulkan/recycle-frame-slot
multiple-value-bind
image-indexresult
with-cpu-trace-zone
:canvas/acquire-drawable
lvk:acquire-next-image
vulkan-handledevice
vulkan-canvas-swapchaincontext
vulkan-frame-slot-image-readyslot
if
eqresult:error-out-of-date-khr

The semaphore is left unsignalled by a refused acquisition, so the rebuilt swapchain's own slots start clean.

return-fromacquire-vulkan-canvas-image
valuesslotslot-indeximage-index
nil
defunvulkan-canvas-presentation-target

Choose the display beat represented by context's next frame.

let*
timeline
vulkan-canvas-presentation-timelinecontext
iftimeline
valuesfallbacknil
defunpresent-vulkan-canvas-image
contextqueueimage-indexrender-donepredicted-presentation-timetarget-nanoseconds

Present one image and opportunistically collect its display timestamp.

let*
native-device
vulkan-handledevice
swapchain
vulkan-canvas-swapchaincontext
timeline
vulkan-canvas-presentation-timelinecontext
labels
plain-present
lvk:present
vulkan-handlequeue
swapchainimage-index:wait-semaphores
vectorrender-done
timed-present
present-id
lvk:present
vulkan-handlequeue
swapchainimage-index:wait-semaphores
vectorrender-done
:present-idpresent-id:present-stage
vulkan-presentation-timeline-stagetimeline
:time-domain-id
vulkan-presentation-timeline-time-domain-idtimeline
:target-timetarget-nanoseconds:target-time-domain-present-stage
andtarget-nanoseconds
eq:present-stage-local-ext
vulkan-presentation-timeline-time-domaintimeline
vulkan-presentation-timeline-stagetimeline
cond
andtimeline
eq:recording
vulkan-presentation-timeline-statustimeline
let*
present-id
vulkan-presentation-timeline-next-present-idtimeline
submitted-seconds
result
timed-presentpresent-id
when
eqresult:error-present-timing-queue-full-ext
drain-vulkan-presentation-timelinetimelinenative-deviceswapchain
setfresult
timed-presentpresent-id
if
eqresult:error-present-timing-queue-full-ext
progn
incf
vulkan-presentation-timeline-dropped-counttimeline
plain-present
progn
unless
eqresult:error-out-of-date-khr
note-vulkan-presentation-submissiontimelinepresent-idpredicted-presentation-timesubmitted-seconds
ortarget-nanoseconds0
drain-vulkan-presentation-timelinetimelinenative-deviceswapchain
result
t
prog1
plain-present
when
andtimeline
eq:warming
vulkan-presentation-timeline-statustimeline
refresh-vulkan-presentation-timelinetimelinenative-deviceswapchain
zdefun
let*
queue
encodernil
commandsnil
predicted-presentation-timenil
target-nanosecondsnil
multiple-value-bind
slotslot-indeximage-index
unlessslot
let
texture
aref
vulkan-canvas-texturescontext
image-index
render-done
aref
vulkan-canvas-render-donecontext
image-index
unwind-protect
progn
setfencoder
createdevice
make-command-encoder-descriptor
vulkan-canvas-current-texturecontext
texture
canvas-context-statecontext
:in-frame
let
timeline
vulkan-canvas-presentation-timelinecontext
when
andtimeline
eq:recording
vulkan-presentation-timeline-statustimeline
drain-vulkan-presentation-timelinetimeline
vulkan-handledevice
vulkan-canvas-swapchaincontext
with-cpu-trace-zone
:gpu/encode
multiple-value-bind
presentation-timenative-target
setfpredicted-presentation-timepresentation-timetarget-nanosecondsnative-target
call-with-canvas-timepresentation-time
lambda
funcallfunctiontextureencoderpresentation-time
with-cpu-trace-zone
:gpu/finish-encoding
transition-vulkan-textureencodertexture:present-src-khr
setfcommands
finishencoder
let
submission-index
with-cpu-trace-zone
:gpu/submit
submit-vulkan-command-buffersqueue
vectorcommands
:wait-semaphores
vector
list
vulkan-frame-slot-image-readyslot
mapcar
lambda
ecaseusage
:copy-src:transfer
:copy-dst:transfer
:render-attachment:color-attachment-output
canvas-configuration-usage
canvas-context-configurationcontext
:signal-semaphores
vector
listrender-done'
:all-commands
:wait-for-completionnil
setf
vulkan-frame-slot-commandsslot
commands
vulkan-frame-slot-submission-indexslot
submission-index
commandsnil
when
eq:error-out-of-date-khr
with-cpu-trace-zone
:canvas/present
present-vulkan-canvas-imagecontextqueueimage-indexrender-donepredicted-presentation-timetarget-nanoseconds

The image was presented to a surface that has already moved on. Forgetting the size this swapchain was built for is what makes the next frame rebuild it, even when the window itself reports the same pixels as before.

setf
vulkan-canvas-window-sizecontext
nil
setf
vulkan-canvas-next-frame-slotcontext
mod
1+slot-index
length
vulkan-canvas-frame-slotscontext
texture
setf
vulkan-canvas-current-texturecontext
nil
canvas-context-statecontext
:configured
whencommands
destroycommands
whenencoder
destroyencoder
defuncall-with-vulkan-canvas-frame

Run one frame, then account for whatever the validation layer said.

The end of the frame is the safe point: every Vulkan call it made has returned, so a complaint can become a condition without unwinding through a driver mid-call. guarding-sdl-canvas catches it, retains it with the backtrace the callback took while the offending frames were still on the stack, and parks the canvas -- the same treatment as any other frame that failed, and visible from ./sly as a reported failure rather than a line somebody has to go looking for.