luv

Workshop wiki

canvas.lisp

hal/metal/canvas.lisp

system luv · 23 definitions · on GitHub

Metal 4 presentation as a relationship between an SDL canvas and device.

in-package#:luv
defmethodsdl-presentation-api-for
declare
ignoreprovider
:metal
defclassmetal-canvas-context
canvas:initarg:canvas:readercontext-canvas
provider:initarg:provider:readermetal-canvas-provider
view:initarg:view:accessormetal-canvas-view
layer:initarg:layer:readermetal-canvas-layer
device:initformnil:accessorcanvas-device
configuration:initformnil:accessorcanvas-context-configuration
extent:initformnil:accessorcanvas-extent
format:initformnil:accessorcanvas-format
current-texture:initformnil:accessormetal-canvas-current-texture
state:initform:unconfigured:accessorcanvas-context-state
defunensure-metal-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
defunmetal-pixel-format
format
caseformat
otherwise
error'canvas-error:operation:configure:reason:unsupported-format:detailsformat
defungpu-metal-pixel-format
format
caseformat
otherwise
error'canvas-error:operation:configure:reason:unsupported-native-format:detailsformat
defunsynchronize-metal-canvas-drawable-size

Make the layer follow SDL's physical-pixel extent, including Retina scale.

multiple-value-bind
multiple-value-bind
layer-widthlayer-height
unless
and
=width
roundlayer-width
=height
roundlayer-height
error'canvas-error:canvas:operation:drawable-size:reason:native-size-mismatch:details
list:sdl:metal
listlayer-widthlayer-height
defunconfigure-metal-canvas-context
contextconfiguration
unless
typepconfiguration'canvas-configuration
error'canvas-error:canvas:operation:configure:reason:invalid-configuration:detailsconfiguration
let
device
canvas-configuration-deviceconfiguration
unless
typepdevice'metal-gpu-device
error'canvas-error:canvas:operation:configure:reason:metal-device-required:detailsdevice
ensure-live-metal-objectdevice:configure-canvas
when
eq:configured
canvas-context-statecontext
ensure-metal-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
let*
layer
metal-canvas-layercontext
native-format
metal-pixel-format
canvas-configuration-formatconfiguration
luv.metal:set-layer-devicelayer
metal-native-objectdevice

A render-only drawable can remain framebuffer-only. Copies need the broader CAMetalLayer texture contract used by captures and luvcraft.

luv.metal:set-layer-framebuffer-onlylayer
if
equal'
:render-attachment
canvas-configuration-usageconfiguration
10
let
setf
canvas-devicecontext
device
format
canvas-context-configurationcontext
make-canvas-configuration:devicedevice:formatformat:usage
canvas-configuration-usageconfiguration
canvas-context-statecontext
:configured
context
defununconfigure-metal-canvas-context
when
eq:in-frame
canvas-context-statecontext
ensure-metal-canvas-statecontext:unconfigure:configured
unless
member
canvas-context-statecontext
'
:unconfigured:destroyed
setf
metal-canvas-current-texturecontext
nil
canvas-context-configurationcontext
nil
canvas-devicecontext
nil
nilnil
canvas-context-statecontext
:unconfigured
values
defundestroy-metal-canvas-context
unless
eq:destroyed
canvas-context-statecontext
when
metal-canvas-viewcontext
sdl3:metal-destroy-view
metal-canvas-viewcontext
setf
metal-canvas-viewcontext
nil
setf
canvas-context-statecontext
:destroyed
values
defmethodmake-canvas-context
&optionalconfiguration
unless
eq:metal
sdl-canvas-presentation-apicanvas
error'canvas-error:canvascanvas:operation:make-context:reason:presentation-api-mismatch:details
list:canvas
sdl-canvas-presentation-apicanvas
:provider:metal
when
error'canvas-error:canvascanvas:operation:make-context:reason:context-already-exists
call-on-sdl-canvas-threadcanvas
lambda
let
view
sdl3:metal-create-view
sdl-canvas-windowcanvas
when
cffi:null-pointer-pview
error'canvas-error:canvascanvas:operation:make-context:reason:metal-view-failed:details
sdl3:get-error
let
handler-case
let
layer
sdl3:metal-get-layerview
when
cffi:null-pointer-player
error'canvas-error:canvascanvas:operation:make-context:reason:metal-layer-failed:details
sdl3:get-error
setfcontext
make-instance'metal-canvas-context:canvascanvas:providerprovider:viewview:layer
luv.objective-c:wrap-objective-c-objectlayer:ownership:borrowed:protocol-name"CAMetalLayer"
context
whenconfiguration
context
error
condition
ifcontext
sdl3:metal-destroy-viewview
errorcondition
defmethodget-current-texture
or
metal-canvas-current-texturecontext
error'canvas-state-error:canvas:operation:get-current-texture:reason:outside-frame:state
canvas-context-statecontext
:expected-state:in-frame
defmethodcanvas-frame-resource-key
declare
ignorecontext

CAMetalLayer cycles a bounded native drawable pool, but each NEXTDRAWABLE call is represented by a fresh borrowed Lisp texture wrapper. Metal 4's resource identity survives those wrappers and becomes reusable only when the layer makes that drawable available again.

getf
luv.metal:metal-texture-resource-id
metal-native-objectsurface-texture
'luv.metal::value
zdefun
luv.objective-c:with-autorelease-pool

All selectors and resource relationships below have already crossed the Lisp validation boundary. Keep the inspectable exception bridge for setup and diagnosis, but do established per-frame traffic as direct objc_msgSend calls on the native thread that actually encodes.

luv.objective-c:with-unchecked-objective-c-messages
let*
queue
native-queue
metal-native-objectqueue
drawable
with-cpu-trace-zone
:canvas/acquire-drawable
unlessdrawable
error'canvas-error:canvas:operation:frame:reason:no-metal-drawable
let
encodernil
command-buffernil
texturenil
with-cpu-trace-zone
:metal/allocate-frame-resources
setfencoder
createdevice
make-command-encoder-descriptor:label"Metal canvas frame"
unwind-protect
let
native-texture
setftexture
make-instance'metal-gpu-texture:devicedevice:native-objectnative-texture:owned-pnil:size:dimensions:2d:format:usage
canvas-configuration-usage
canvas-context-configurationcontext
setf
metal-canvas-current-texturecontext
texture
canvas-context-statecontext
:in-frame
with-cpu-trace-zone
:gpu/encode
let
call-with-canvas-timepresentation-time
lambda
funcallfunctiontextureencoderpresentation-time
when
metal-encoder-active-passencoder
error'canvas-error:canvas:operation:frame:reason:metal-pass-left-open
with-cpu-trace-zone
:gpu/finish-encoding
setfcommand-buffer
finishencoder
with-cpu-trace-zone
:metal/wait-for-drawable
luv.metal:wait-for-drawablenative-queuedrawable
with-cpu-trace-zone
:gpu/submit
submit-metal-command-buffersqueue
vectorcommand-buffer
:after-commit
lambda
luv.metal:signal-drawablenative-queuedrawable
texture
setf
metal-canvas-current-texturecontext
nil
canvas-context-statecontext
:configured
whencommand-buffer
destroycommand-buffer
whenencoder
destroyencoder
whentexture
destroytexture
defparameter+metal-clear-submission-sequence+'
"beginCommandBufferWithAllocator:""setClearColor:""renderCommandEncoderWithDescriptor:""endEncoding""endCommandBuffer""waitForDrawable:""commit:count:""signalDrawable:""present""signalEvent:value:"
defunvalidate-metal-clear-submission
selectors

Require the Metal 4 drawable operations in their semantic order.

let
tailselectors
dolist
setftail
memberrequiredtail:test#'string=
unlesstail
error'canvas-error:operation:frame:reason:incomplete-metal-4-submission:details
list:missingrequired:selectorsselectors
setftail
resttail
selectors
defuncollect-metal-clear-evidence
canvasdevicecontextcolor
multiple-value-bind
logical-widthlogical-height
multiple-value-bind
pixel-widthpixel-height
multiple-value-bind
layer-widthlayer-height
let
trace

Objective-C sends happen on the Cocoa thread. Establish the dynamic tracing runtime there rather than on this caller thread.

request-canvas-framecanvas
lambda
timestamp
declare
ignoretimestamp
luv.objective-c:with-objective-c-trace
active-trace
setftraceactive-trace
apply#'render-canvas-colorcontext
coercecolor'list
let
list:device:logical-size
listlogical-widthlogical-height
:pixel-size
listpixel-widthpixel-height
:drawable-size
list
roundlayer-width
roundlayer-height
:pixel-format:clear-color
coercecolor'list
:submission-selectorsselectors:presentedt
defunprobe-sdl-metal-clear
&key
visible-pnil
color
0.80d00.10d00.70d01.0d0

Present one Metal 4 clear and return bounded SDL/layer/submission evidence.

let
canvas
make-sdl-canvas:title"luv Metal 4 clear":widthwidth:heightheight:visible-pvisible-p:presentation-api:metal
provider
make-instance'metal-gpu-provider
devicenil
evidencenil
unwind-protect
progn
setfdevicecontext
make-canvas-contextcanvasprovider
make-canvas-configuration:devicedevice
evidence
when
member'
:opening:open
whendevice
destroydevice
setf
getfevidence:canvas-state
getfevidence:context-state
canvas-context-statecontext
getfevidence:view-destroyed
null
metal-canvas-viewcontext
getfevidence:window-destroyed
null
sdl-canvas-windowcanvas
getfevidence:device-destroyed
metal-object-destroyed-pdevice
evidence