hal/vulkan/gpu.lisp
Run body with the floating-point environment expected by native drivers.
Return a durable teardown which restores the Vulkan driver environment.
Queue maintenance already establishes this environment, but process-global finalizer retirement can replay a failed closure from an arbitrary thread.
Transfer leaked native ownership before signaling the discipline warning.
Return optional extensions and flags useful to luv's Vulkan instance.
Return Vulkan instance extensions and flags for provider.
Return device extensions enabled by provider.
Vulkan is luv's portable default. On Darwin, leave the choice open until the native Metal module has loaded so Apple hosts do not accidentally run through MoltenVK merely because the Vulkan backend appears first in ASDF.
Index of the newest queue submission using this object. Zero means it has never been submitted.
Return a thunk performing object's native teardown, or
NIL when object owns nothing to tear down.
The closure captures only extracted native handles and the device wrapper,
never object itself, so it can outlive object as its leak finalizer.
Capturing the device wrapper also orders finalization: a device stays
reachable until every child's pending finalizer has run.
Run native teardown body with DEVICE-VAR bound to device-object's
native handle, skipping it entirely once the device is destroyed.
The queue's recursive lock serializes teardown — including teardown on the finalizer thread — against submission and device destruction.
Return the live device queue which may durably own object's finalizer.
Explicit destroy cancels this finalizer. If the object is instead
reclaimed by the collector, the leak is a warned discipline failure
and the native resources are freed as a safety net. Anything the
queue still retains through a live submission record is reachable,
so a collected wrapper is always past the completion frontier.
Submission records not yet passed by the frontier, oldest first.
Guards the counter, live records, deferred destroys, and scheduled texture layouts across the canvas and REPL threads.
One queue submission awaiting completion, retaining what the GPU may use.
Textures sharing one external timeline semaphore in a submission.
Generation-safe shared high-water state for one retained native timeline.
Every GPU object wrapper the recorded commands depend on, including the tracked textures.
One-element list holding the tagged native resources, shared with the encoder's leak finalizer so mid-recording growth stays visible to it.
Serialize admitted device-native work against device destruction.
Return the first graphics-and-compute queue exposed by physical-device.
Arrange to warn about and reclaim device if it is collected undestroyed.
The finalizer captures the same progress-tracked native teardown as explicit
destroy, but not the device or queue wrappers. A failed explicit teardown can
therefore be abandoned without replaying native calls which already returned.
Create GPU wrappers for an already selected Vulkan device and queue.
Wrap an externally owned Vulkan image as a GPU texture.
Reject WebGPU requirements the initial Vulkan backend cannot honor yet.
Wrap one plane view of an AVVkFrame image without taking image ownership.
Create an owned Vulkan instance, logical device, and graphics queue.
A provider handed an explicit callback insists on the extension; the default messenger only asks for one when the instance already has it, so an ordinary run installs it and simply never hears anything. Nothing speaks through it but a validation layer someone loaded.
Create one persistently mapped, host-coherent uploadable buffer.
Copy a one-dimensional numeric array into mapped coherent memory.
data holds single-floats or unsigned 8-, 16-, 32-, or 64-bit integers; offset is
aligned to the element size. A storage buffer of packed sites arrives here
as its own element type rather than as reinterpreted floats.
One memcpy from the pinned storage vector: the element types
buffer-data-foreign-type admits are all stored unboxed, so the
bytes in the Lisp vector are the bytes the GPU wants.
One memcpy into the pinned result vector, mirroring write-buffer:
a byte-at-a-time loop over a whole frame's readback is hundreds of
milliseconds; this is the difference between a film and a slideshow.
Create one owned, single-mip Vulkan 2D texture and bind its memory.
This forced line separates pure Lisp/SPIR-V preparation from the first driver call if the latter takes the process or device down.
Give each of entries the stages that may read it on device.
A descriptor must name every stage that reads it, and a mesh pipeline reads from stages that did not exist when :VERTEX and :FRAGMENT were the whole graphics vocabulary. Naming the task and mesh stages is only legal once VK_EXT_mesh_shader is enabled, so ask the device first.
Return the cached render pass for gpu-formats and optional depth-format.
gpu-formats is a list in fragment-output location order. A single keyword
is accepted for callers predating multiple render targets.
Link task, mesh, and fragment modules into a VK_EXT_mesh_shader pipeline.
The result is an ordinary vulkan-gpu-render-pipeline with no vertex buffers:
what distinguishes it is that its draw is a workgroup dispatch, so binding and
render-pass compatibility need no separate vocabulary.
Allocate and begin one Vulkan primary command buffer.
Arrange to warn about and reclaim encoder if it is collected while it
still owns its command pool. finish and destroy transfer or release that
ownership and cancel this finalizer.
Record resource as a dependency of encoder's future submission.
Vulkan permits image copies between size-compatible color
formats. Every format in this initial vocabulary is one
four-byte color texel, including rgba storage -> BGRA
swapchain copies on Cocoa.
Lower one queue texture write through a private Vulkan command encoder.
Leave the texture where a reader can use it. An upload
that stops at :TRANSFER-DST-OPTIMAL makes whoever samples
the texture next responsible for the transition, and the
place that notices is set-bind-group -- inside a render
pass, where a layout transition is not allowed at all.
The upload is a whole operation, so it ends in the layout
an uploaded texture is for; every other consumer can
transition out of this one legally, outside a pass.
Issue one WebGPU queue write using Vulkan's portable staging path.
The host data is copied into coherent staging memory before returning. The staging buffer is retained by the private command buffer until submission has completed. Vulkan 1.4 host image copies may provide a more direct optional lowering later without changing this queue-level operation.
Vulkan render-pass compatibility is governed by attachment formats and sample counts, not load/store operations. Our textures are all single-sampled, so compare the two formats directly instead of the cached native render-pass handle (whose key also carries STORE-OP).
Dispatch task or mesh workgroups instead of drawing vertices.
Publish encoder's ended native ownership as one command-buffer wrapper.
The encoder and its finalizer retain ownership in :ENDED until a fully initialized command-buffer wrapper takes over.
Validate encoded layout assumptions and return the post-batch layouts.
Share one high-water state for the retained native semaphore generation.
Release one adopted texture reference and forget an exhausted generation.
Group submitted external textures by semaphore and retain the newest wait.
Return an attempt-all, retry-only-failures external callback obligation.
Build the durable HAL-state and external-owner publication obligation.
Every HAL wrapper advances before any owner callback runs. A failing plane therefore cannot leave its siblings on stale values.
Attempt submission's durable post-commit publication once.
Complete the live FIFO prefix of post-commit owner publications.
Return the index of the newest submission the GPU has fully completed.
Block until queue's completion frontier reaches index.
Whether queue owns no submitted, publishing, or retirement work.
Unroot queue only after backend quiescence was proved under its lock.
Retire live submissions the completion frontier has passed.
Retiring a submission drops the queue's references to everything it retained. The independent retirement ledger then attempts every native teardown which that frontier makes safe; failures remain queue-owned.
External-owner publication is part of the live record. It must finish before a completed record can release those owners or retirement can advance beyond it.
Service queue from the process custodian registry without caller access.
Never query a frontier through a retiring or retired VkDevice. Device teardown has already enforced the ledger/submission barrier; a stale empty root may now be removed without FFI.
Transfer one native owner after revalidating its queue under the lock.
Device teardown uses this same lock. Revalidate and choose the queue or direct path without a race window between them.
Transfer resource's native ownership, then logically invalidate it.
A live queue owns the retirement before invalidate marks the wrapper and
cancels its finalizer. Queue maintenance immediately attempts anything
already safe and retains failures. Without a live queue, native teardown
must succeed before invalidate is called.
Submit one WebGPU-style batch and track it on the queue's frontier.
wait-semaphores and signal-semaphores are LVK semaphore submit entries of
the form (SEMAPHORE STAGES &optional VALUE). Every submission additionally
signals the queue's timeline semaphore with a fresh submission index, which
is returned. The submission record retains the command buffers and every
resource they captured until the frontier passes the index, so callers may
destroy any of them immediately after this returns.
The first check can race while waiting for this lock. Device teardown closes admission under the same lock, so this is the authoritative check before any queue or device FFI.
A failed owner publication from an earlier native commit is a FIFO admission barrier. Retry it before scheduling dependent work.
Allocate the list cell before native commit. Once Vulkan accepts the batch, publication below performs no external callback before this durable queue record exists.
Native commit has happened. Publish every counter, wrapper state, dependency, and owner callback obligation before any fallible user callback can regain control.
Success clears the obligation slot. Failure propagates only after the rooted live record has retained its retry progress.
Schedule one WebGPU-style batch and return its submission index.
Flatten one tagged command resource into one-native-call retry steps.
Build one persistent, progress-tracked command ownership teardown.
Define destroy and the queueable native teardown closure for class.
destroy first transfers native-teardown to the queue, then marks the wrapper
destroyed and cancels its leak finalizer. Without a live queue, it marks and
cancels only after native teardown succeeds.
bindings extract every native handle native-teardown needs, so the
teardown closure captures raw handles and the device wrapper rather than
variable itself and can therefore serve as the wrapper's leak finalizer.
native-teardown runs only while the device is alive, under the queue
teardown lock, with DEVICE anaphorically bound to the native handle.
This owner is not a VkDevice child. Keep it outside the guarded device steps so a finalizer after vkDestroyDevice still releases it.
Return a persistent idle-and-ledger barrier for device destruction.
A ledger failure leaves admission open. Preserve a completed wait across retry, but renew it if another submission arrived.
The caller holds queue's lock, so the waited generation cannot
change between this barrier and closing admission.
Return a lazy, retryable drain of render-pass-table.
The snapshot happens only when teardown begins, after explicit destruction has closed device admission. Native destruction and hash bookkeeping have separate progress flags, so neither a late-created pass nor a Lisp-side error can make a successful native call repeat.
Return a native teardown which does not retain device or queue.
Finalizers must stop issuing device-level calls immediately, even
if a later instance-level owner fails and overall destroy retries.
Return device's shared explicit and leak-finalizer teardown closures.
Admission is closed after the first successful barrier. Recheck on every partial-teardown retry so an invariant violation can never be silently skipped.
Logical publication is deliberately outside the shared
native sequence so its leak finalizer never captures the
device or QUEUE wrappers through their back reference.
This Lisp lock outlives every native queue/device phase and stays usable across failures in the persistent teardown sequence.
(&body body)Logical conjunction of tests and raw truth values.
Logical negation of one test or raw truth value.
Logical disjunction of tests and raw truth values.
(teardown)Return a durable TEARDOWN which restores the Vulkan driver environment. Queue maintenance already establishes this environment, but process-global finalizer retirement can replay a failed closure from an arbitrary thread.
(resource-class label device owner teardown)(device owner teardown)Durably route a finalizer's OWNER through DEVICE or the fallback ledger. The fallback ledger takes ownership before any attempt. Its retryable routing step records when a backend queue has accepted OWNER, so a warning promoted to an error cannot cause a later retry to transfer the same native owner twice.
(resource-class label)Base condition for errors exposed by the luv GPU API.
VK_KHR_portability_enumeration
VK_EXT_debug_utils
Instances of GPU-PROVIDER subclasses are platform-specific factories for requesting GPU-DEVICE instances.
(provider)Return Vulkan instance extensions and flags for PROVIDER.
If you're lucky, someone has bound this to a working GPU-PROVIDER.
(object)Return a thunk performing OBJECT's native teardown, or NIL when OBJECT owns nothing to tear down. The closure captures only extracted native handles and the device wrapper, never OBJECT itself, so it can outlive OBJECT as its leak finalizer. Capturing the device wrapper also orders finalization: a device stays…
((device-object device-var) &body body)Run native teardown BODY with DEVICE-VAR bound to DEVICE-OBJECT's native handle, skipping it entirely once the device is destroyed. The queue's recursive lock serializes teardown — including teardown on the finalizer thread — against submission and device destruction.
(arguments)(object)Return the live device queue which may durably own OBJECT's finalizer.
((agent application-agent) &key)(handle)Logically invalidate HANDLE immediately. Native teardown may be deferred until submitted work which captured HANDLE has completed.
One queue submission awaiting completion, retaining what the GPU may use.
Textures sharing one external timeline semaphore in a submission.
Generation-safe shared high-water state for one retained native timeline.
How the children of a list are arranged.
Finished one-shot work accepted by a GPU queue's SUBMIT operation.
(object operation)(device operation thunk)((device operation) &body body)(physical-device)(physical-device)(physical-device)Optional extensions which let FFmpeg decode on luv's VkDevice.
VK_EXT_present_timing
VK_KHR_present_id2
(physical-device)(physical-device)(device)Arrange to warn about and reclaim DEVICE if it is collected undestroyed. The finalizer captures the same progress-tracked native teardown as explicit DESTROY, but not the device or queue wrappers. A failed explicit teardown can therefore be abandoned without replaying native calls which already returned.
(device queue)(instance physical-device queue-family descriptor
&key debug-messenger instance-extension-names enabled-extension-names
video-queue-family)(physical-device family-index &key enabled-extension-names
additional-family-indices)(device queue-family-index &optional (queue-index 0))(device &key (initial-value 0))(device semaphore)(device)(device image size format vk-format &key (usage '(:copy-dst)) owner aspect
(layout :undefined) semaphore
(semaphore-value 0) submitted)(&optional (stream *standard-output*))(queue semaphore initial-value)(queue state)(descriptor)(descriptor reason &optional details)(format descriptor)(descriptor)(format)(texture)(device native-object owner descriptor)Wrap a platform texture and its retained OWNER in the HAL.
(usages format)(usages)(memory-type-bits index)(device memory-requirements)(physical-device)(device memory-requirements &optional (operation :write-texture))Test whether two compatible scalars are equal.
(provider &optional descriptor)(&key
(application-name "luv")
((:application-version application-version-value)
(make-version 0 0 1))
(engine-name "luv")
((:engine-version engine-version-value) (make-version 0 0 1))
;; Luv assumes modern Vulkan. Timeline semaphores and
;; synchronization2 are mandatory core features at this version.
((:api-version api-version-value) (make-version 1 4 0))
flags
enabled-extension-names)Whether to install luv's own debug messenger on a new Vulkan instance. The messenger only ever hears from a layer that is loaded, so leaving this true costs nothing in a run without VK_LOADER_LAYERS_ENABLE. Bind it to NIL to keep a provider quiet even when the layer is present.
(message)Receive one message from the Vulkan layer stack. This runs inside the call that produced the message, so it says its piece to the log immediately -- a run that then wedges in the driver still leaves the complaint behind -- and retains it for the enclosing extent to signal.
(instance callback
&key
(severities '(:warning :error))
(types '(:general :validation :performance)))Install CALLBACK for INSTANCE and return an owned DEBUG-MESSENGER. CALLBACK receives one DEBUG-MESSAGE. VK_EXT_debug_utils must have been enabled when INSTANCE was created. Keep the returned messenger alive and destroy it before destroying INSTANCE.
(physical-device)VK_EXT_mesh_shader
(messenger)(instance)(device descriptor)Asks the DEVICE for a handle to newly created instance of some object fulfilling the DESCRIPTOR.
(device size usage)(device buffer)(device size memory-type-index)(device buffer memory &optional (offset 0))(device memory size &optional (offset 0))(device memory)(device buffer)(device memory)(data)Return the CFFI element type and byte size for a one-dimensional DATA array of single-floats or unsigned 8-, 16-, 32-, or 64-bit integers.
The non-negative remainder of integer division.
Test whether one compatible scalar is at most another.
Addition over compatible quantities.
Multiplication and scalar scaling.
Subtraction or unary negation.
(buffer &key offset size)Wait for BUFFER's device queue and copy mapped bytes back to the host.
(queue)Block until all work submitted to QUEUE so far has completed on the GPU.
(buffer &key offset size)Copy mapped BUFFER bytes only when its own newest submission is complete. Return the byte vector and true when ready, or NIL and NIL without waiting. Unlike READ-BUFFER this does not wait for unrelated newer queue work.
(queue)Retire live submissions the completion frontier has passed. Retiring a submission drops the queue's references to everything it retained. The independent retirement ledger then attempts every native teardown which that frontier makes safe; failures remain queue-owned.
(device &key type format width height (depth 1) usage
(mip-levels 1) (array-layers 1) (samples :1)
(tiling :optimal) (sharing-mode :exclusive)
(initial-layout :undefined))(device image)(device image memory &optional (offset 0))(device image)(object actual-device expected-device
operation)(device image format &key (view-type :2d) (aspect :color))(device &key (mag-filter :linear) (min-filter :linear)
(mipmap-mode :nearest)
(address-mode-u :clamp-to-edge)
(address-mode-v :clamp-to-edge)
(address-mode-w :clamp-to-edge)
compare)((name &key (tracy-value nil tracy-value-supplied-p)) &body body)Measure BODY as nested zone NAME for whichever measurement is watching. TRACY-VALUE, when supplied, is attached to the Tracy zone at exit. It does not affect the bounded CPU trace, whose zones retain time and runtime costs. Two independent things may be: a Tracy viewer attached to this image, and an opt-in…
(specification)(category control &rest arguments)Write one timestamped line about CATEGORY, formatted from CONTROL. CATEGORY is a keyword naming the subsystem -- :canvas, :watchdog, :vulkan -- so a reader can tell at a glance which machine is talking.
(device words)(descriptor)(descriptor)(descriptor)(device entries)Give each of ENTRIES the stages that may read it on DEVICE. A descriptor must name every stage that reads it, and a mesh pipeline reads from stages that did not exist when :VERTEX and :FRAGMENT were the whole graphics vocabulary. Naming the task and mesh stages is only legal once VK_EXT_mesh_shader is enabled, so…
(device &key (binding 0))(device &key (binding 0) (stages '(:vertex)))(device entries)(device set-layouts)(device shader-module layout &key (entry-point "main"))(device pipeline)(device layout)(device gpu-formats descriptor &optional depth-format
(depth-store-op :discard))Return the cached render pass for GPU-FORMATS and optional DEPTH-FORMAT. GPU-FORMATS is a list in fragment-output location order. A single keyword is accepted for callers predating multiple render targets.
(device formats &key depth-format (depth-store-op :discard))(device depth-format &key (depth-store-op :store))(descriptor buffers)(format)Return the scalar lane count of a portable vertex attribute FORMAT. The vocabulary is deliberately small and float-only: it names what the mesh and instance products this project actually writes contain, and every backend is expected to accept all of it.
(device vertex-module fragment-module layout render-pass
&key (vertex-entry-point "main") (fragment-entry-point "main")
(topology :triangle-strip) vertex-buffers
depth-compare depth-write-enabled blends blend)(device mesh-module fragment-module layout render-pass
&key task-module (task-entry-point "main") (mesh-entry-point "main")
(fragment-entry-point "main")
depth-compare depth-write-enabled blends blend)Link a task, mesh, and fragment stage into one VK_EXT_mesh_shader pipeline. A mesh pipeline draws no vertices, so it carries neither a vertex input nor an input assembly state; the mesh stage's own OpExecutionMode names its output topology.
(descriptor layout)(descriptor layout)(descriptor layout)(device descriptor layout)(device &key (max-sets 1))(device pool layout)(device descriptor-set buffer buffer-size &key (binding 0))(device pool)(device &key (max-sets 1))(device descriptor-set image-view &key (binding 0))Keyword/value pairs stay together; in body position each pair is a row with keys loosely aligned. A SETF with several pairs is a place/value table: each pair a subgrid row of its two-column box.
(device entries &key (max-sets 1))(device descriptor-set entries)(device queue-family-index &key flags)(device command-pool &key (level :primary))(command-buffer &key flags)(encoder)Arrange to warn about and reclaim ENCODER if it is collected while it still owns its command pool. FINISH and DESTROY transfer or release that ownership and cancel this finalizer.
(device command-pool)(device command-pool resources)(encoder operation)(encoder operation)(encoder resource)(encoder texture command required-usage)(encoder texture)(encoder texture new-layout)(command-buffer image old-layout new-layout
src-access dst-access src-stage dst-stage &key (aspect :color))(command)(command-buffer image layout color)(command source destination)(command-buffer source source-layout destination destination-layout
width height &optional (depth 1))(command-buffer image layout buffer width height &optional (depth 1))(destination reason &optional details)(value expected-length destination reason)(device command)Test whether one compatible scalar is at least another.
(data pointer width height offset bytes-per-row bytes-per-texel)(encoder command)(command-buffer buffer image layout width height
&key (buffer-offset 0) (buffer-row-length 0)
(buffer-image-height 0) (x 0) (y 0) (depth 1))Division of two represented quantities.
(pass-encoder index bind-group)(queue work)Schedule some command buffers on the QUEUE. Submission is asynchronous: returning does not mean the GPU has finished the work, only that the implementation retains everything the work depends on until it completes. Use SUBMITTED-WORK-DONE to wait.
(descriptor color)(descriptor depth)(encoder descriptor)(device render-pass image-views width height &key depth-view)(command-buffer render-pass framebuffer width height clear-colors
&key depth-clear-value)(command-buffer render-pass framebuffer width height depth-clear-value)(command-buffer width height)(device framebuffer)(pass operation)(command-buffer pipeline)(command-buffer pipeline-layout descriptor-set)Test whether one compatible scalar is less than another.
(command-buffer binding buffer &optional (offset 0))(command-buffer x y width height)(command-buffer vertex-count &optional (instance-count 1)
(first-vertex 0) (first-instance 0))(format)(command-buffer buffer offset index-type)(command-buffer index-count &optional (instance-count 1)
(first-index 0) (vertex-offset 0)
(first-instance 0))(device command-buffer x y z)(pass-encoder)(command-buffer)(encoder &optional descriptor)(pass operation)(command-buffer pipeline)(command-buffer pipeline-layout descriptor-set)(command-buffer x y &optional (z 1))(table)A simple Org table without formulas.
(table)(encoder device command-buffer command-pool native-resources)(command-buffer)(queue command-buffer)(command-buffers)Layouts: binding grids, clauses, loop rows. The selectors are the roles dexp.lisp assigns.
(first second)(queue semaphore)The maximum of compatible quantities.
(texture-layouts)Raise a dimensionless value to a dimensionless power.
(callbacks)(texture-layouts groups)(&rest steps)Return a retryable closure over ordered, individually idempotent STEPS. Each zero-argument step is removed only after it returns normally. A retry therefore resumes at the failing native call without repeating any earlier destructive call which already succeeded.
(submission)(device semaphore)(queue index)(device semaphore value &key (timeout #xffffffffffffffff))(ledger custodian)(ledger completed-frontier &key (operation :maintain-queue))Attempt the eligible FIFO prefix in LEDGER at COMPLETED-FRONTIER. Only a successful eligible prefix leaves the ledger. The first ineligible or failed entry is a FIFO barrier: it and the entire unattempted suffix remain ahead of ownership transferred recursively by teardown callbacks.
(custodian)Perform one safe eventual-retirement service pass for CUSTODIAN. Backend methods revalidate native liveness while holding their queue lock and return true when the pass made progress. They must never acquire a queue lock while the process-wide custodian registry lock is held.
(queue device)(resource device ready-after teardown invalidate operation)(resource teardown invalidate &key (operation :destroy))Perform TEARDOWN without a live queue, then invalidate RESOURCE. An error leaves the wrapper and its leak finalizer live and is re-signaled as a structured GPU-NATIVE-RETIREMENT-ERROR retaining the original cause.
(ledger resource ready-after teardown invalidate &optional custodian)Durably enqueue native ownership, then logically invalidate RESOURCE. INVALIDATE is called only after LEDGER owns the complete teardown. Queue implementations call this while holding the lock which also guards LEDGER.
(device owner teardown invalidate)Transfer OWNER's native teardown to DEVICE, then invalidate OWNER. Backend methods may transfer ownership to a durable device queue before INVALIDATE and attempt it immediately. The default has no such queue: native TEARDOWN must return successfully before INVALIDATE is called.
(resource device invalidate)Transfer RESOURCE's native ownership, then logically invalidate it. A live queue owns the retirement before INVALIDATE marks the wrapper and cancels its finalizer. Queue maintenance immediately attempts anything already safe and retains failures. Without a live queue, native teardown must succeed before INVALIDATE…
(queue command-buffers &key (wait-semaphores #())
(signal-semaphores #())
wait-for-completion)Submit one WebGPU-style batch and track it on the queue's frontier. WAIT-SEMAPHORES and SIGNAL-SEMAPHORES are LVK semaphore submit entries of the form (SEMAPHORE STAGES &optional VALUE). Every submission additionally signals the queue's timeline semaphore with a fresh submission index, which is returned. The…
(queue buffers &key (wait-semaphores #()) (signal-semaphores #()))Submit BUFFERS through vkQueueSubmit2. WAIT-SEMAPHORES and SIGNAL-SEMAPHORES are sequences of semaphore submit entries as understood by FILL-SEMAPHORE-SUBMIT-INFOS.
(ledger custodian)(device function)(device resource)((class variable) device-form bindings &body native-teardown)Define DESTROY and the queueable native teardown closure for CLASS. DESTROY first transfers NATIVE-TEARDOWN to the queue, then marks the wrapper destroyed and cancels its leak finalizer. Without a live queue, it marks and cancels only after native teardown succeeds. BINDINGS extract every native handle…
(device sampler)(device layout)(device shader-module)(device view)(device queue)(device)(ledger &key (operation :destroy-device))(native-device render-pass-table)Return a lazy, retryable drain of RENDER-PASS-TABLE. The snapshot happens only when teardown begins, after explicit destruction has closed device admission. Native destruction and hash bookkeeping have separate progress flags, so neither a late-created pass nor a Lisp-side error can make a successful native call…
(device render-pass)(device queue)
Custody is already durable even if a handler promotes this warning or leaves it by THROW, ERROR, or an interactive debugger restart.