luv

Workshop wiki

canvas.lisp

hal/canvas.lisp

system luv · 103 definitions · on GitHub

Portable canvas and presentation protocols.

A canvas is a native place with a lifetime, size, event source, and frame clock. A canvas-context is a configured relationship between that place and a GPU implementation. Platform hosts implement the former protocol; presentation backends implement the latter.

in-package#:luv
define-conditioncanvas-error
error
canvas:initarg:canvas:initformnil:readercanvas-error-canvas
operation:initarg:operation:initformnil:readercanvas-error-operation
reason:initarg:reason:readercanvas-error-reason
details:initarg:details:initformnil:readercanvas-error-details
:report
lambda
conditionstream
formatstream"Canvas operation ~S failed: ~S~@[ (~A)~]."
canvas-error-operationcondition
canvas-error-reasoncondition
canvas-error-detailscondition
defparameter*canvas-dispatch-timeout*5.0

How long a cross-thread canvas call waits before deciding it is stuck.

Work handed to the canvas thread is serviced once a frame, so it completes in milliseconds whenever that thread is running at all. Anything approaching this many seconds does not mean slow, it means the thread is not coming back -- and a caller that waited forever would take a REPL, a screenshot, or an editor session down with it, silently. Bind it larger only for an operation known to be genuinely long.

define-conditioncanvas-dispatch-timeout
seconds:initarg:seconds:initformnil:readercanvas-dispatch-timeout-seconds
:report
lambda
conditionstream
formatstream

The canvas thread did not service ~S on ~S within ~,1F seconds.~@ It is not servicing requests at all: it is blocked, and every later call will ~ queue behind the same block. The usual cause is the render loop parked in ~ presentation -- vkQueuePresentKHR waits forever when the compositor will not ~ take frames, which is what a headless, locked, or unmapped session looks ~ like. Get a backtrace of the canvas thread before restarting anything.

canvas-error-operationcondition
canvas-error-canvascondition
or
canvas-dispatch-timeout-secondscondition
0.0
:documentation

A cross-thread canvas call gave up rather than hang.

This is deliberately an error and not a longer wait. A silent unbounded wait on the canvas thread is indistinguishable from a crash and destroys the one thing an interactive image is for.

define-conditioncanvas-state-error
state:initarg:state:readercanvas-state-error-state
expected-state:initarg:expected-state:readercanvas-state-error-expected-state
:report
lambda
conditionstream
formatstream"Cannot perform ~S on ~S in canvas state ~S; expected ~S."
canvas-error-operationcondition
canvas-error-canvascondition
canvas-state-error-statecondition
canvas-state-error-expected-statecondition
defunmonotonic-seconds
/
get-internal-real-time
coerceinternal-time-units-per-second'double-float
defclasslazy-clock
source:initarg:source:initform#'monotonic-seconds:readerlazy-clock-source
timestamp:initformnil:accessorlazy-clock-timestamp
:documentation

A clock which samples its source once, then retains that time until cleared.

An event-loop turn therefore has one coherent NOW. Rendering may temporarily override it with a predicted presentation time without changing the source.

defunmake-lazy-clock
&key
make-instance'lazy-clock:sourcesource
defunlazy-clock-now
clock
or
lazy-clock-timestampclock
setf
lazy-clock-timestampclock
funcall
lazy-clock-sourceclock
defunlazy-clock-now-unadjusted
clock

Sample clock's source without consulting or changing its remembered time.

funcall
lazy-clock-sourceclock
defunclear-lazy-clock
clock
setf
lazy-clock-timestampclock
nil
clock
defuncall-with-lazy-clock-time
clocktimestampfunction

Call function while clock consistently denotes timestamp.

let
saved
lazy-clock-timestampclock
unwind-protect
progn
setf
lazy-clock-timestampclock
timestamp
funcallfunction
setf
lazy-clock-timestampclock
saved
defclasscanvas-clock
:documentation

A policy object deciding when a canvas should run frames.

defclassdemand-clock
:documentation

A clock whose frames happen only when explicitly requested.

defclassanimated-clock
frame-function:initarg:frame-function:readerclock-frame-function
:documentation

A clock which owns a continuously animated frame function.

defclasscadence-clock
frames-per-second:initarg:frames-per-second:initform60:readerclock-frames-per-second
next-frame-time:initformnil:accessorcadence-clock-next-frame-time
:documentation

A clock which calls a frame function at a regular cadence.

defclasspresentation-clock
:documentation

A clock paced by presentation availability inside its frame function.

It requests exactly one frame per event-loop turn. A direct-display backend blocks acquiring that frame until FIFO scanout releases an image, so no independent host timer competes with the display's cadence.

defmethodinitialize-instance:after
unless
functionp
clock-frame-functionclock
error"FRAME-FUNCTION must be a function."
defmethodinitialize-instance:after
&key
unless
and
realp
clock-frames-per-secondclock
plusp
clock-frames-per-secondclock
error"FRAMES-PER-SECOND must be a positive real number."
defunmake-demand-clock

Construct a clock for explicitly requested frames.

make-instance'demand-clock
defunmake-cadence-clock
frame-function&key
frames-per-second60

Construct a clock which calls frame-function with canvas and timestamp.

make-instance'cadence-clock:frame-functionframe-function:frames-per-secondframes-per-second
defunmake-presentation-clock
frame-function

Construct a clock whose frame acquisition supplies its cadence.

make-instance'presentation-clock:frame-functionframe-function
defgenericclock-wait-timeout
clocktimestamp
:documentation

Return milliseconds until clock is due, or NIL to wait indefinitely.

defgenericservice-canvas-clock
clockcanvastimestamp
:documentation

Run any frame clock has made due at timestamp; true if one ran.

defmethodclock-wait-timeout
timestamp
declare
ignoreclocktimestamp
nil
defmethodservice-canvas-clock
canvastimestamp
declare
ignoreclockcanvastimestamp
nil
defmethodclock-wait-timeout
timestamp
let
next
cadence-clock-next-frame-timeclock
if
or
nullnext
<=nexttimestamp
0

SDL's event timeout has millisecond resolution. Rounding upward makes a 60 Hz deadline (16.667 ms) late by construction; on a 120 Hz display that can miss the intended presentation refresh. Wake on the last whole millisecond before the deadline and let the event loop poll through the sub-millisecond remainder.

floor
*1000
-nexttimestamp
defmethodservice-canvas-clock
canvastimestamp
let
next
cadence-clock-next-frame-timeclock
when
or
nullnext
<=nexttimestamp

Deliberately do not accumulate missed frames. A cadence is a pacing policy, not a demand to replay time spent in a debugger.

setf
cadence-clock-next-frame-timeclock
let
interval
/1.0d0
clock-frames-per-secondclock
ifnext

Preserve the established phase after an ordinary late wakeup or a long pause, while skipping every missed frame.

+next
*interval
1+
floor
/
-timestampnext
interval
+timestampinterval
funcall
clock-frame-functionclock
canvastimestamp
t
defmethodclock-wait-timeout
timestamp
declare
ignoreclocktimestamp
0
defmethodservice-canvas-clock
funcall
clock-frame-functionclock
canvastimestamp
t
defclasscanvas
clock:initarg:clock:initform:accessorcanvas-clock
time:initarg:time:initform:readercanvas-time-source
event-handler:initarg:event-handler:initformnil:accessorcanvas-event-handler
:documentation

A native destination with a lifetime and frame clock.

defuncanvas-time

Return the one stable time shared by canvas's current event-loop turn.

lazy-clock-now
canvas-time-sourcecanvas
defuncanvas-time-unadjusted

Sample canvas's underlying monotonic time without changing logical NOW.

defgenericcanvas-presentation-time
:documentation

Predict when a frame acquired now will become visible on canvas.

defmethod:before
declare
ignorecanvas
unless
typepclock'canvas-clock
error'type-error:datumclock:expected-type'canvas-clock
defclasscanvas-context
:documentation

A GPU presentation relationship configured for a canvas.

defgenericcanvas-frame-resource-key
contextsurface-texture
:documentation

Return the stable presentation slot key for surface-texture in context.

Applications use this to retain per-frame resources without assuming that a backend returns the same Lisp wrapper every time it revisits a native drawable.

defmethodcanvas-frame-resource-key
declare
ignorecontext
surface-texture

Portable input vocabulary. Native backends translate into these objects; consumers never need to know the SDL event ABI.

defclasscanvas-event-handler
:documentation

Protocol class for objects receiving canvas events.

defclasscanvas-event
timestamp:initarg:timestamp:readercanvas-event-timestamp
defclasscanvas-pointer-event
x:initarg:x:readercanvas-pointer-event-x
y:initarg:y:readercanvas-pointer-event-y
defclasscanvas-pointer-motion-event
delta-x:initarg:delta-x:initform0.0:readercanvas-pointer-event-delta-x
delta-y:initarg:delta-y:initform0.0:readercanvas-pointer-event-delta-y
defclasscanvas-pointer-button-event
button:initarg:button:readercanvas-pointer-event-button
clicks:initarg:clicks:initform1:readercanvas-pointer-event-clicks
defclasscanvas-pointer-wheel-event
scroll-x:initarg:scroll-x:initform0.0:readercanvas-pointer-event-scroll-x
scroll-y:initarg:scroll-y:initform0.0:readercanvas-pointer-event-scroll-y
:documentation

A scroll, carrying where the pointer was and how far the wheel turned.

The amounts are in wheel notches rather than pixels, positive up and right, already corrected for a natural-scrolling platform -- what the window system says the user asked for, not what the hardware reported.

defclasscanvas-key-event
key-name:initarg:key-name:readercanvas-key-event-key-name
modifiers:initarg:modifiers:initformnil:readercanvas-key-event-modifiers
character:initarg:character:initformnil:readercanvas-key-event-character
unshifted-character:initarg:unshifted-character:initformnil:readercanvas-key-event-unshifted-character
repeat-p:initarg:repeat-p:initformnil:readercanvas-key-event-repeat-p
:documentation

A portable physical-key event with logical modifiers and layout text.

defclasscanvas-window-size-event
width:initarg:width:readercanvas-window-event-width
height:initarg:height:readercanvas-window-event-height
defgenerichandle-canvas-event
handlercanvasevent
:documentation

Deliver portable event from canvas to handler.

defmethodhandle-canvas-event
handlernull
event
declare
ignorehandlercanvasevent
nil
defmethodhandle-canvas-event
funcallhandlercanvasevent
defvar*canvas-events-held-p*nil

True on a canvas loop while its frames are held or parked by a failure: the window is still pumped, but no event reaches the application.

defstructcanvas-configuration

The small portable portion of a canvas presentation configuration.

deviceformat
usage'
:copy-dst

Native-place protocol.

defgenericopen-canvas
:documentation

Realize canvas in its native window system.

defgenericclose-canvas
:documentation

Close canvas and all presentation contexts attached to it.

defgenericcanvas-thread-p
:documentation

Return true when the caller is canvas's native event/frame thread.

defmethodcanvas-thread-p
declare
ignorecanvas
nil
defgenericcanvas-title
:documentation

Return canvas's native title.

defgenericcanvas-size
:documentation

Return canvas's drawable width and height as two values.

defgenericcanvas-logical-size
:documentation

Return canvas's logical width and height as two values.

defgenericcanvas-position
:documentation

Return canvas's native x and y position as two values.

defgenericcanvas-visible-p
:documentation

Return whether canvas is intended to be visible.

defgenericcanvas-health
:documentation

Return a plist describing whether canvas's native loop is still alive.

The keys are :STATE, :PHASE, :PHASE-SECONDS, :TICKS, and :STALLED-P. A caller reads this to answer the question a beachballing window raises -- is anything servicing that window at all -- without attaching a debugger.

defgenericcanvas-stalled-seconds
:documentation

Return how long canvas's loop has been in one phase past its deadline.

NIL means the loop is healthy: either it is cycling, or it is parked in a bounded wait that the window system is pumping for it.

defgenericcanvas-fullscreen-p
:documentation

Return whether canvas occupies its display.

defgenericcanvas-clipboard-text
:documentation

The text on the system clipboard, or NIL when there is none or the window system has no clipboard to ask.

:method
declare
ignorecanvas
nil
defgenericset-canvas-fullscreen
canvasenabled
:documentation

Give canvas its whole display, or hand it back to the window manager.

defgenericset-canvas-relative-pointer-mode
canvasenabled
:documentation

Capture or release relative pointer motion for canvas.

defgenericshow-canvas
:documentation

Make an open canvas visible.

defgenerichide-canvas
:documentation

Hide canvas without destroying its native resources.

defgenericmove-canvas
:documentation

Move canvas to native position X, Y.

defgenericresize-canvas
:documentation

Resize canvas to width by height logical units.

defgenericraise-canvas
:documentation

Request that the native host raise canvas.

defgenericminimize-canvas
:documentation

Request that the native host minimize canvas.

defgenericrestore-canvas
:documentation

Restore a minimized or maximized canvas.

defgenericcanvas-state
:documentation

Return the native lifecycle state of canvas.

defgenericcanvas-context
:documentation

Return canvas's presentation context, or NIL.

defgenericrequest-canvas-frame
:documentation

Run function with a timestamp on canvas's native frame/event thread.

The initial native implementation is synchronous: the caller waits for the function's values. The protocol leaves room for a real frame scheduler.

Presentation-relationship protocol.

defgenericmake-canvas-context
canvasgpu-provider&optionalconfiguration
:documentation

Create a GPU presentation relationship between canvas and gpu-provider. When configuration is omitted, return the context unconfigured.

defgenericcontext-canvas
:documentation

Return the native canvas presented by context.

defgenericcontext-device
:documentation

Return the GPU device used by context, or NIL before first configuration.

defgenericcanvas-extent
:documentation

Return context's configured drawable extent as (width height), or NIL.

defgenericcanvas-format
:documentation

Return context's configured presentation format, or NIL.

defgenericconfigure-canvas-context
contextconfiguration
:documentation

Configure or reconfigure context for presentation.

defgenericunconfigure-canvas-context
:documentation

Release context's current presentation configuration.

defgenericdestroy-canvas-context
:documentation

Destroy context and its backend relationship.

defgenericget-current-texture
:documentation

Return the borrowed GPU texture current during a canvas frame.

defgenericcall-with-canvas-frame
:documentation

Acquire a frame texture, call function with texture, encoder, and predicted presentation time, then complete presentation. function runs on the canvas's native frame thread, with canvas-time overridden to that same prediction.

defunpresent-canvas-frame

Schedule and present one frame through context.

defunrender-canvas-color
contextredgreenblue&optional
alpha1.0

Clear and present one frame through context.

present-canvas-framecontext
lambda
textureencoderpresentation-time
declare
ignorepresentation-time
encodeencoder
make-gpu-clear-texture-command:texturetexture:color
vectorredgreenbluealpha