DeferredCleanup.kt
src/jvmMain/kotlin/afield/graphics/vulkan/util/DeferredCleanup.kt · 56 lines
package afield.graphics.vulkan.util
Releases a renderer's deferred GPU resources at shutdown, when no later frame will come to age them out.
While a renderer is running, a resource retired by one frame waits in a queue until the frames
that might still be reading it have finished. Shutdown removes that guarantee, so Renderer
drains the queues by hand: once the present queue, the graph queue and the device are idle, and
then again after releasing the resources a document still held, because releasing those enqueues
more work of its own.
Each batch is cleared before it is invoked, so a destructor may enqueue dependent work, and a destructor that throws does not strand the resources queued behind it. Failures are collected and handed back for the owner to report once the rest of its teardown is done, which is why a broken release shows up as one exception with the others suppressed on it rather than as a half-finished shutdown.
internal fun drainDeferredCleanup
Drain a renderer's retirement queues after its device has become idle. There will be no more frames to age the last batches. Clear each batch before invoking it so a destructor can enqueue dependent work; a failed destructor must not strand the remaining resources.
Call on the render thread, while command pools, fences and the device are still alive. Return any failures for the owner to report after the rest of its teardown.
internal fun drainDeferredCleanup(
pending: MutableList<Runnable>,
retired: MutableList<Pair<Long, MutableList<Runnable>>>,
): IllegalStateException? {
var failure: IllegalStateException? = null
while (pending.isNotEmpty() || retired.isNotEmpty()) {
val batch = ArrayList<Runnable>()
retired.forEach { (_, actions) ->
batch.addAll(actions)
actions.clear()
}
retired.clear()
batch.addAll(pending)
pending.clear()
batch.forEach { action ->
try {
action.run()
} catch (error: Throwable) {
val report = failure ?: IllegalStateException("Deferred GPU cleanup failed").also {
failure = it
}
report.addSuppressed(error)
}
}
}
return failure
}