simvx.graphics.gpu.retire¶
Deferred GPU-resource frees, clocked on completed frames.
One hazard, stated narrowly: a resource unbound mid-frame may still be read by a command buffer that has been submitted and has not completed, so destroying it is a use-after-free. The answer is to delay the destroy until every frame that could name the resource has retired.
That is all this module does. Repointing a descriptor at a different resource is
a different hazard with a different answer (an UPDATE_AFTER_BIND binding, a
ring slot no pending frame uses, or a wait), and deferring a free solves none of
it. Keeping the two apart is deliberate: see
design/render_parallelism_and_multi_gpu.md and
packages/graphics/tests/test_update_after_bind_contract.py.
Module Contents¶
Classes¶
Defers a GPU-resource free until every frame that could reference it has retired. |
Data¶
API¶
- simvx.graphics.gpu.retire.log¶
‘getLogger(…)’
- simvx.graphics.gpu.retire.__all__¶
[‘RetireQueue’]
- class simvx.graphics.gpu.retire.RetireQueue(depth: int = FRAMES_IN_FLIGHT)¶
Defers a GPU-resource free until every frame that could reference it has retired.
Hand a destructor here instead of calling it: the queue holds the callable for as long as it takes the frames in flight at retirement time to complete, then runs it.
The clock is COMPLETED FRAMES, not calls. :meth:
frame_completedis called from exactly one place, immediately after a frame fence has actually signalled, so a run of frames that submit nothing (a pending resize, a lost device) does not advance it.A completed-frame count proves that the frames which referenced a resource have retired. It proves nothing about an out-of-band
vkQueueSubmitissued between two frames: that needs a fence on that submission, which this queue does not take and deliberately does not offer.This queue solves lifetime only. It has no idea what a descriptor is, and deferring a free does not make a descriptor rewrite safe.
Initialization
- property depth: int¶
Completed frames a queued free waits for.
- property pending: int¶
Number of queued frees. Read by tests and by the leak check.
- property closed: bool¶
True once teardown has run. A retire on a closed queue is dropped.
- retire(free: collections.abc.Callable[[], None]) None¶
Queue
freeto run once :attr:depthfurther frames have completed.On a closed queue the callable is dropped with a warning: the device it would act on is being destroyed and every handle with it.
- retire_handles(device: Any, **handles: Any) None¶
Queue the common handle shapes for destruction on
device.Accepts
view,image,memory,buffer,sampleranddescriptor_pool; falsy handles are skipped. Destruction order is fixed (view, image, memory, buffer, sampler, pool) so a caller cannot get it wrong.
- frame_completed() None¶
One frame’s fence has signalled. Run whatever is now safe.
- drain(*, close: bool = True) None¶
Run every pending free NOW, then optionally close.
The caller guarantees the device is idle. Anything a running callable retires is run in the same drain.
close=Falseleaves the queue re-openable, which is what a secondary device wants: its every submission ends invkQueueWaitIdle, so it drains rather than counting frames.
- abandon() None¶
Drop every pending free WITHOUT running it, then close.
Device loss only: every handle from a lost device is invalid and every
vkDestroy*against it is undefined.