Skip to content

Renderer.kt

src/jvmMain/kotlin/afield/graphics/vulkan/Renderer.kt · 2175 lines · cited by GPU State — keeping a simulation on the card, Profiling

package and imports — 49 lines, from line 1
package afield.graphics.vulkan

import afield.app.diagnostics.LatencySpans
import afield.graphics.scene.*
import afield.graphics.vulkan.device.vkLayout
import afield.app.diagnostics.Named
import afield.app.protocol.Rect
import afield.runtime.lifecycle.Runloop
import afield.runtime.lifecycle.Runloop.Companion.isMainThread
import afield.graphics.vulkan.buffers.ParallelGeometryPool
import afield.graphics.vulkan.buffers.cleanupAllocatorForRenderer
import afield.graphics.vulkan.device.*
import afield.graphics.vulkan.device.Queue
import afield.graphics.vulkan.device.Queue.GraphicsQueue
import afield.graphics.vulkan.device.Queue.PresentQueue
import afield.graphics.texture.image_layout_transition
import afield.graphics.render.ComputeModulePointer
import afield.graphics.render.MeshModulePointer
import afield.graphics.render.ModulePointer
import afield.graphics.render.RayTracingModulePointer
import afield.graphics.vulkan.targets.ComputeTarget
import afield.graphics.vulkan.targets.RenderTarget
import afield.graphics.vulkan.targets.releaseOwnedModulesOf
import afield.graphics.vulkan.util.AutoDisposer
import afield.graphics.vulkan.util.MapOfSequences
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.Window
import afield.graphics.vulkan.util.drainDeferredCleanup
import afield.media.image.util.PngEncoder
import afield.app.diagnostics.logTime
import afield.app.diagnostics.logTime_
import afield.app.diagnostics.logging.SaveReloadLog
import afield.app.diagnostics.logging.lprintln
import kotlinx.coroutines.*
import afield.math.linalg.Rectanglef
import org.lwjgl.system.MemoryStack
import org.lwjgl.vulkan.*
import org.lwjgl.vulkan.VK13.*
import java.lang.Runnable
import java.nio.ByteBuffer
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import kotlin.coroutines.coroutineContext

The Vulkan renderer: one Renderer owns the instance, device, queues, command pools and the swapchain of each of its windows, and records, submits and presents a frame per tick.

A box beneath the renderer box reaches it as renderer and adds modules with renderer += module, which puts them in the main window's Renderer.renderList; compute modules go to Renderer.computeList. Each window has its own RenderTarget, found with Renderer.renderTargetFor. Renderer.captureWindowToFile writes a window's next frame to a PNG, and Renderer.cleanup shuts the renderer down, releasing its device, windows and targets.

The file also holds Target, the callback shape for drawing into a command buffer, and CPUSpan, the per-span CPU timing statistics the frame loop records.

fun interface Target { fun render(command: CommandBuffer, index: Int) }

Something that records drawing into CommandBuffer for swapchain image index.

fun target(a: (CommandBuffer, Int) -> Unit) = object : Target {

fun target(a: (CommandBuffer, Int) -> Unit) = object : Target {
    override fun render(command: CommandBuffer, index: Int) = a(command, index)
}

private const val PERF_LOG_INTERVAL_NS = 5_000_000_000L

object CPUSpan

Lightweight CPU span stats.

object CPUSpan {
    private const val WINDOW = 500
    private var lastSnapshotLogNs = 0L

data class Stat(val samples: ArrayDeque<Long> = ArrayDeque())

fun add(dtNs: Long)

        fun add(dtNs: Long) {
            samples.addLast(dtNs)
            if (samples.size > WINDOW) samples.removeFirst()
        }
    }

data class Snapshot

    data class Snapshot(
        val label: String,
        val n: Int,
        val avgMs: Double,
        val minMs: Double,
        val maxMs: Double,
        val p1Ms: Double,
        val p10Ms: Double,
        val p50Ms: Double,
        val p90Ms: Double,
        val p99Ms: Double
    )

val stats = linkedMapOf<String, Stat>()

val lock = Any()

val version = AtomicLong(0)

val listeners = mutableListOf<(Long) -> Unit>()

inline fun time(label: String, block: () -> Unit) {

    inline fun time(label: String, block: () -> Unit) {
        val t0 = System.nanoTime()
        block()
        val dt = System.nanoTime() - t0
        val v = synchronized(lock) {
            stats.getOrPut(label) { Stat() }.add(dt)
            version.incrementAndGet()
        }
        notifyListeners(v)
    }

fun notifyListeners(v: Long)

    fun notifyListeners(v: Long) {
        val targets = synchronized(lock) { listeners.toList() }
        targets.forEach { l -> runCatching { l(v) } }
    }

fun addListener(listener: (Long) -> Unit) {

    fun addListener(listener: (Long) -> Unit) {
        synchronized(lock) { listeners.add(listener) }
    }

fun snapshot(): List<Snapshot>

    fun snapshot(): List<Snapshot> {
        val copy: Map<String, List<Long>> = synchronized(lock) {
            stats.mapValues { it.value.samples.toList() }
        }
        return copy.map { (label, samples) -> buildSnapshot(label, samples) }
    }

    private fun buildSnapshot(label: String, samples: List<Long>): Snapshot {
        if (samples.isEmpty()) return Snapshot(label, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
        val sorted = samples.sorted()
        fun perc(frac: Double): Double {
            val idx = ((sorted.size - 1) * frac).toInt().coerceIn(0, sorted.size - 1)
            return sorted[idx] / 1e6
        }
        val avgMs = sorted.average() / 1e6
        return Snapshot(
            label = label,
            n = sorted.size,
            avgMs = avgMs,
            minMs = sorted.first() / 1e6,
            maxMs = sorted.last() / 1e6,
            p1Ms = perc(0.01),
            p10Ms = perc(0.10),
            p50Ms = perc(0.50),
            p90Ms = perc(0.90),
            p99Ms = perc(0.99)
        )
    }

fun snapshotAndMaybePrint(everyNFrames: Int, frame: Long, header: String = "CPU")

    fun snapshotAndMaybePrint(everyNFrames: Int, frame: Long, header: String = "CPU") {
        val now = System.nanoTime()
        if (lastSnapshotLogNs != 0L && now - lastSnapshotLogNs < PERF_LOG_INTERVAL_NS) return
        lastSnapshotLogNs = now
        val snaps = snapshot()
        if (snaps.isEmpty()) return
        val line = buildString {
            append("$header: ")
            append(snaps.joinToString("  ") { s ->
                "${s.label}=avg ${"%.3f".format(s.avgMs)}ms p50 ${"%.3f".format(s.p50Ms)}ms p90 ${"%.3f".format(s.p90Ms)}ms"
            })
        }
        lprintln("info,perf", line)
    }

fun version(): Long = version.get()

class Renderer

class Renderer(
    private val mainWindow: Window,
    additionalWindows: List<Window> = emptyList()
) {

First, so it precedes every other initializer: run measures construction to first frame from it.

    private val constructedAtNanos = System.nanoTime()

data class VertexUploadFrameStats

    data class VertexUploadFrameStats(
        val frameId: Long,
        val recordedBytes: Long,
        val immediateBytes: Long
    ) {

val totalBytes: Long get() = recordedBytes + immediateBytes

data class WindowContext

    data class WindowContext(
        val window: Window,
        val surface: Surface,
        var swapChain: SwapChain,
        var depthAttachments: MutableList<Attachment>,
        var commandBuffers: Array<CommandBuffer>,
        var computeCommandBuffers: Array<CommandBuffer>,
        var fences: Array<Fence>,
        var computeFences: Array<Fence>,
        var computeFinishedSemaphores: Array<Semaphore>,
        var numImages: Int,
        var currentImage: Int = 0,
        var renderTarget: RenderTarget,
        var profilerState: ProfilerState? = null,
        var computeSecondary: MutableMap<String, Array<CommandBuffer>> = mutableMapOf(),
        var graphicsSecondary: MutableMap<String, Array<CommandBuffer>> = mutableMapOf(),
        var computePoolsPerThread: MutableMap<Long, CommandPool> = mutableMapOf(),
        var graphicsPoolsPerThread: MutableMap<Long, CommandPool> = mutableMapOf()
    )

    private data class RecordedWindowCapture(
        val request: WindowCaptureRequest,
        val stagingBuffer: afield.graphics.vulkan.buffers.VulkanBuffer,
        val width: Int,
        val height: Int,
        val format: Int
    )

    private data class WindowCapturePixels(
        val request: WindowCaptureRequest,
        val rgba: ByteArray,
        val width: Int,
        val height: Int
    )

data class ProfilerState

    data class ProfilerState(
        val profiler: GpuProfiler,
        var markers: Array<FrameMarkers>,
        var perImageModuleMarks: Array<MutableList<ModuleMarks>>,
        val pending: ArrayDeque<Pending> = ArrayDeque(),
        val lock: Any = Any()
    )

val windows: List<Window> = listOf(mainWindow) + additionalWindows

val window: Window get() = windows.first()

    private val uploadStatsLock = Any()
    private var currentUploadRecordedBytes = 0L
    private var currentUploadImmediateBytes = 0L

var lastVertexUploadFrameStats: VertexUploadFrameStats

    @Volatile
    var lastVertexUploadFrameStats: VertexUploadFrameStats = VertexUploadFrameStats(
        frameId = -1L,
        recordedBytes = 0L,
        immediateBytes = 0L
    )

val commandPool: CommandPool

Core Vulkan objects

val computeCommandPool: CommandPool

val device: Device

val graphQueue: GraphicsQueue

val instance: Instance

val physicalDevice: PhysicalDevice

val pipelineCache: PipelineCache

val presentQueue: PresentQueue

val surface: Surface get() = defaultContext.surface

val swapChain: SwapChain get() = defaultContext.swapChain

var computeList: ComputeTarget = ComputeTarget(this)

var currentImage: Int

    var currentImage: Int
        get() = defaultContext.currentImage
        private set(value) { defaultContext.currentImage = value }

val numImages: Int

    val numImages: Int
        get() = defaultContext.numImages

val depthAttachments: MutableList<Attachment> get() = defaultContext.depthAttachments

var renderList: RenderTarget

    var renderList: RenderTarget
        get() = defaultContext.renderTarget
        set(value) { defaultContext.renderTarget = value }

Dedicated single-thread for all vkQueue* and xrBeginFrame/xrEndFrame calls.

    private val queueThreadRef = java.util.concurrent.atomic.AtomicReference<Thread?>()
    private val queueExecutor = Executors.newSingleThreadExecutor { r ->
        Thread(r, "vk-queue-thread").apply {
            isDaemon = true
            queueThreadRef.set(this)
        }
    }
    private val queueDispatcher = queueExecutor.asCoroutineDispatcher()

Dedicated workers for renderer-controlled parallel recording phases only.

    private val workerExecutor = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors().coerceIn(2, 8)
    ) { r ->
        Thread(r, "renderer-worker").apply { isDaemon = true }
    }
    private val workerDispatcher = workerExecutor.asCoroutineDispatcher()
    private val workerParallelDepth = AtomicInteger(0)

    private val windowContexts = mutableListOf<WindowContext>()
    private val defaultContext get() = windowContexts.first()

fun windowContext(index: Int = 0): WindowContext = windowContexts[index]

fun renderTargetFor(windowIndex: Int = 0): RenderTarget

    fun renderTargetFor(windowIndex: Int = 0): RenderTarget = windowContext(windowIndex).renderTarget

fun windowIndex(window: Window): Int

    fun windowIndex(window: Window): Int {
        val index = windows.indexOf(window)
        require(index >= 0) { "Window is not owned by this renderer" }
        return index
    }

fun renderTargetFor(window: Window): RenderTarget = renderTargetFor(windowIndex(window))

    private val windowCaptures = WindowCaptureQueue()
    private val graphicsCompletionLock = Any()
    private val graphicsRecordingContext = ThreadLocal<Pair<Int, Int>?>()
    private val graphicsCompletionCallbacks = mutableMapOf<Pair<Int, Int>, MutableList<() -> Unit>>()
    private val computeSubmissionLock = Any()
    private val computeRecordingContext = ThreadLocal<Pair<Int, Int>?>()
    private val computeSubmissionCallbacks = mutableMapOf<Pair<Int, Int>, MutableList<(Fence) -> Future<*>?>>()
    private val computePostSubmitTasks = mutableMapOf<Pair<Int, Int>, MutableList<Future<*>>>()

var cleanup: MutableList<Runnable> = java.util.Collections.synchronizedList(mutableListOf())

Releases to run once the frames now in flight have retired (see render). Synchronized: box threads add to it (VulkanBuffer.realloc, staged uploads, renderer.cleanup += …) while the render thread swaps it out.

var cleanupStack = mutableListOf<Pair<Long, MutableList<Runnable>>>()

    @Volatile
    private var cleanedUp = false

val isClosed: Boolean get() = cleanedUp

True after cleanup begins; between-frame producers must stop before using this device again. Volatile because the flag is read by afield.graphics.vulkan.buffers.VulkanBuffer.map on whichever thread writes a buffer, to refuse a write into memory this cleanup has freed.

internal val beforeDeviceCleanup = java.util.concurrent.CopyOnWriteArrayList<Runnable>()

Stop external GPU consumers before queue/device retirement. Callbacks must join their owner.

internal val isRecordingGraphicsFrame: Boolean

    internal val isRecordingGraphicsFrame: Boolean
        get() = graphicsRecordingContext.get() != null

internal fun afterCurrentGraphicsFrameComplete(callback: () -> Unit) {

    internal fun afterCurrentGraphicsFrameComplete(callback: () -> Unit) {
        val key = graphicsRecordingContext.get()
            ?: throw IllegalStateException("afterCurrentGraphicsFrameComplete must be called while recording a graphics frame")
        synchronized(graphicsCompletionLock) {
            graphicsCompletionCallbacks.getOrPut(key) { mutableListOf() }.add(callback)
        }
    }

fun whenCurrentGraphicsFrameComplete(callback: () -> Unit) = afterCurrentGraphicsFrameComplete(callback)

Finish a GPU-side capture without waiting in the render callback. Call while recording a graphics frame; callback runs after that submission's fence signals. It must stay short (complete a future or queue IO), because frame retirement also runs other cleanup work.

    fun whenCurrentGraphicsFrameComplete(callback: () -> Unit) = afterCurrentGraphicsFrameComplete(callback)

data class ExternalSemaphorePoint(val semaphore: Long, val value: Long)

    private data class ExternalFrameSync(
        val waits: MutableList<ExternalSemaphorePoint> = mutableListOf(),
        val signals: MutableList<ExternalSemaphorePoint> = mutableListOf()
    )
    private val externalFrameSync = java.util.concurrent.ConcurrentHashMap<Long, ExternalFrameSync>()

internal fun externalGraphicsSync(command: CommandBuffer, semaphore: Long, value: Long, wait: Boolean)

Add GPU interop ordering to this command buffer's submission, during graphics recording.

    internal fun externalGraphicsSync(command: CommandBuffer, semaphore: Long, value: Long, wait: Boolean) {
        check(graphicsRecordingContext.get() != null && currentCommandBuffer === command)
        val sync = externalFrameSync.computeIfAbsent(command.vkCommandBuffer.address()) { ExternalFrameSync() }
        val points = if (wait) sync.waits else sync.signals
        check(points.none { it.semaphore == semaphore }) { "Duplicate external semaphore in one submission" }
        points += ExternalSemaphorePoint(semaphore, value)
    }

internal fun afterCurrentComputeFrameSubmitted(callback: (Fence) -> Future<*>?) {

body — 56 lines, from line 357
    internal fun afterCurrentComputeFrameSubmitted(callback: (Fence) -> Future<*>?) {
        val key = computeRecordingContext.get()
            ?: throw IllegalStateException("afterCurrentComputeFrameSubmitted must be called while recording a compute frame")
        synchronized(computeSubmissionLock) {
            computeSubmissionCallbacks.getOrPut(key) { mutableListOf() }.add(callback)
        }
    }

    private fun drainComputeSubmissionCallbacks(ctxIndex: Int, imageIndex: Int, fence: Fence) {
        val key = ctxIndex to imageIndex
        val callbacks = synchronized(computeSubmissionLock) {
            computeSubmissionCallbacks.remove(key)?.toList() ?: emptyList()
        }
        val tasks = mutableListOf<Future<*>>()
        callbacks.forEach { callback ->
            runCatching { callback(fence) }.onSuccess { task ->
                if (task != null) tasks += task
            }.onFailure {
                lprintln("warning,graphics", "compute submission callback failed: ${it.message ?: it::class.simpleName}")
            }
        }
        if (tasks.isNotEmpty()) {
            synchronized(computeSubmissionLock) {
                computePostSubmitTasks.getOrPut(key) { mutableListOf() }.addAll(tasks)
            }
        }
    }

    private fun awaitComputePostSubmitTasks(ctxIndex: Int, imageIndex: Int) {
        val tasks = synchronized(computeSubmissionLock) {
            computePostSubmitTasks.remove(ctxIndex to imageIndex)?.toList() ?: emptyList()
        }
        tasks.forEach { it.get() }
    }

    private fun drainCompletedGraphicsCallbacks(
        ctx: WindowContext,
        ctxIndex: Int,
        imageIndex: Int,
        requireSignaled: Boolean = false
    ) {
        if (requireSignaled) {
            ctx.fences[imageIndex].fenceWait()
        } else if (!ctx.fences[imageIndex].isSignaled()) {
            return
        }
        val key = ctxIndex to imageIndex
        val callbacks = synchronized(graphicsCompletionLock) {
            graphicsCompletionCallbacks.remove(key)?.toList() ?: emptyList()
        }
        callbacks.forEach { callback ->
            runCatching { callback() }.onFailure {
                lprintln("warning,graphics", "graphics completion callback failed: ${it.message ?: it::class.simpleName}")
            }
        }
    }

Vulkan queues require external synchronization. Use this lock to serialize all queue operations (submit/present) across any threads that might enter the renderer. Use the same lock as Queue to serialize all vkQueue* calls across the app.

    private val queueLock = Queue.globalQueueLock

fun <T> runOnQueueThread(block: () -> T): T {

Run a block on the dedicated queue thread; if already on it, run inline.

    fun <T> runOnQueueThread(block: () -> T): T {
        val qt = queueThreadRef.get()
        return if (Thread.currentThread() === qt) {
            block()
        } else {
            try { queueExecutor.submit<T> { block() }.get() }
            catch (e: java.util.concurrent.ExecutionException) { throw e.cause ?: e }
        }
    }

suspend fun <T> onQueueThread(block: () -> T): T {

Suspend-friendly variant to hop to the queue thread.

    suspend fun <T> onQueueThread(block: () -> T): T {
        val qt = queueThreadRef.get()
        return if (Thread.currentThread() === qt) block() else kotlinx.coroutines.withContext(queueDispatcher) { block() }
    }

var renderStart = MapOfSequences<String>()

var renderEnd = MapOfSequences<String>()

var currentCommandBuffer: CommandBuffer? = null

    private val workerParallelismActive: Boolean
        get() = workerParallelDepth.get() > 0

    private inline fun <T> withWorkerParallelism(block: () -> T): T {
        workerParallelDepth.incrementAndGet()
        try {
            return block()
        } finally {
            workerParallelDepth.decrementAndGet()
        }
    }

    private fun <T> CoroutineScope.asyncWorker(block: suspend CoroutineScope.() -> T): Deferred<T> {
        check(workerParallelismActive) {
            "Renderer worker parallelism is not active outside render() parallel sections"
        }
        return async(workerDispatcher, block = block)
    }

data class GeometryFrameToken

    data class GeometryFrameToken(
        val frameId: Long,
        val imageIndex: Int,
        val windowIndex: Int
    )

inner class GeometryPhaseScope(val token: GeometryFrameToken, private val parallelAllowed: Boolean)

    inner class GeometryPhaseScope(val token: GeometryFrameToken, private val parallelAllowed: Boolean) {
        private val scope = if (parallelAllowed) CoroutineScope(SupervisorJob() + workerDispatcher) else null
        private val jobs = Collections.synchronizedList(mutableListOf<Job>())

fun submit(block: () -> Unit): Job {

        fun submit(block: () -> Unit): Job {
            val job: Job = if (parallelAllowed) {
                scope!!.launch { block() }
            } else {
                val completed = CompletableDeferred<Unit>()
                runCatching { block() }
                    .onSuccess { completed.complete(Unit) }
                    .onFailure { completed.completeExceptionally(it) }
                completed
            }
            jobs += job
            return job
        }

fun <T> submitValue(block: () -> T): Deferred<T> {

        fun <T> submitValue(block: () -> T): Deferred<T> {
            val deferred: Deferred<T> = if (parallelAllowed) {
                scope!!.async { block() }
            } else {
                val completed = CompletableDeferred<T>()
                runCatching { block() }
                    .onSuccess { completed.complete(it) }
                    .onFailure { completed.completeExceptionally(it) }
                completed
            }
            jobs += deferred
            return deferred
        }

internal fun awaitCompletion()

        internal fun awaitCompletion() {
            runBlocking {
                jobs.toList().joinAll()
            }
        }

internal fun close()

        internal fun close() {
            scope?.cancel()
        }
    }

val geometryProducers = linkedMapOf<String, (GeometryPhaseScope) -> Unit>()

    private val geometryPoolsLock = Any()
    private val geometryPools = LinkedHashSet<ParallelGeometryPool>()
    private val geometryPhaseStateLock = Any()

    private data class ExternalGeometryPhase(val token: GeometryFrameToken)
    private data class PreAcquiredImage(val frameId: Long, val imageIndex: Int)

    @Volatile
    private var openGeometryPhase: ExternalGeometryPhase? = null

    @Volatile
    private var preAcquiredPrimaryImage: PreAcquiredImage? = null

    @Volatile
    private var runloopGeometryHookKey: String? = null

internal fun registerParallelGeometryPool(pool: ParallelGeometryPool)

    internal fun registerParallelGeometryPool(pool: ParallelGeometryPool) {
        synchronized(geometryPoolsLock) {
            geometryPools += pool
        }
    }

internal fun unregisterParallelGeometryPool(pool: ParallelGeometryPool)

body — 79 lines, from line 531
    internal fun unregisterParallelGeometryPool(pool: ParallelGeometryPool) {
        synchronized(geometryPoolsLock) {
            geometryPools.remove(pool)
        }
    }

    private fun geometryPoolsSnapshot(): List<ParallelGeometryPool> {
        return synchronized(geometryPoolsLock) { geometryPools.toList() }
    }

    private fun beginGeometryPools(token: GeometryFrameToken) {
        val pools = geometryPoolsSnapshot()
        pools.forEach { it.beginFrame(token) }
    }

    private fun sealGeometryPools(token: GeometryFrameToken) {
        val pools = geometryPoolsSnapshot()
        pools.forEach { it.sealFrame(token) }
    }

    private fun runGeometryProducers(token: GeometryFrameToken, parallelAllowed: Boolean) {
        val scope = GeometryPhaseScope(token, parallelAllowed)
        try {
            geometryProducers.forEach { (_, producer) ->
                producer(scope)
            }
            scope.awaitCompletion()
        } finally {
            scope.close()
        }
    }

    private fun runGeometryPhase(token: GeometryFrameToken) {
        beginGeometryPools(token)
        try {
            withWorkerParallelism {
                runGeometryProducers(token, parallelAllowed = true)
            }
        } finally {
            sealGeometryPools(token)
        }
    }

    private fun acquireFrameImage(windowIndex: Int): Int {
        val ctx = windowContexts[windowIndex]
        val w = ctx.window

        // Keeps swapchain pipeline moving; left as-is from the main render path.
        if (w.bestEffortPresentation && !ctx.fences[ctx.swapChain.currentFrame].isSignaled()) {
            throw ImageNotAvailable("Optional window is still presenting", retryable = true)
        }
        ctx.fences[ctx.swapChain.currentFrame].fenceWait()

        var imageIndex = 0
        CPUSpan.time("acquire[$windowIndex]") {
            imageIndex = runOnQueueThread {
                var idx = ctx.swapChain.acquireNextImage()
                if (w.isResized || idx < 0) {
                    w.resetResized()
                    resize(ctx)
                    idx = ctx.swapChain.acquireNextImage()
                }
                idx
            }
        }

        // Geometry/uniform writes are indexed by the acquired swapchain image. Make sure that
        // image slot is no longer in-flight on the GPU before any CPU-side writes touch its
        // per-image buffers.
        ctx.fences[imageIndex].fenceWait()
        drainCompletedGraphicsCallbacks(ctx, windowIndex, imageIndex, requireSignaled = true)

        unavailableWindows.remove(windowIndex)?.let { since ->
            if(System.nanoTime()-since>250_000_000L)presentationRevision++
        }
        ctx.currentImage = imageIndex
        if (windowIndex == 0) currentImage = imageIndex
        return imageIndex
    }

fun beginGeometryWrites(windowIndex: Int = 0): GeometryFrameToken

Opens a geometry-write window for the current frame and returns the token used by pools.

Use this to run geometry generation from anywhere in the Runloop before render, then either: - call endGeometryWrites, or - let render auto-close it at frame start.

    fun beginGeometryWrites(windowIndex: Int = 0): GeometryFrameToken {
        require(windowIndex == 0) {
            "beginGeometryWrites currently supports windowIndex=0 only"
        }

        synchronized(geometryPhaseStateLock) {
            val frameId = frameCounter
            val existing = openGeometryPhase
            if (existing != null) {
                if (existing.token.frameId == frameId && existing.token.windowIndex == windowIndex) {
                    return existing.token
                }
                throw IllegalStateException(
                    "Geometry writes already open for frame ${existing.token.frameId}, window ${existing.token.windowIndex}"
                )
            }
            if (preAcquiredPrimaryImage?.frameId == frameId) {
                throw IllegalStateException("Geometry writes already prepared for frame $frameId")
            }

            val imageIndex = acquireFrameImage(windowIndex)
            val token = GeometryFrameToken(
                frameId = frameId,
                imageIndex = imageIndex,
                windowIndex = windowIndex
            )

            beginGeometryPools(token)
            openGeometryPhase = ExternalGeometryPhase(token)
            if (windowIndex == 0) {
                preAcquiredPrimaryImage = PreAcquiredImage(frameId = frameId, imageIndex = imageIndex)
            }
            return token
        }
    }

fun endGeometryWrites(windowIndex: Int = 0)

Explicitly closes a previously opened geometry-write window.

If not called, render closes any still-open window for the current frame automatically.

    fun endGeometryWrites(windowIndex: Int = 0) {
        val token = synchronized(geometryPhaseStateLock) {
            val phase = openGeometryPhase ?: return
            require(phase.token.windowIndex == windowIndex) {
                "Geometry writes open for window ${phase.token.windowIndex}, not window $windowIndex"
            }
            openGeometryPhase = null
            phase.token
        }
        try {
            runGeometryProducers(token, parallelAllowed = false)
        } finally {
            sealGeometryPools(token)
        }
    }

    private fun takeOpenGeometryPhaseTokenForFrame(frameId: Long): GeometryFrameToken? {
        synchronized(geometryPhaseStateLock) {
            val phase = openGeometryPhase ?: return null
            if (phase.token.frameId != frameId) return null
            openGeometryPhase = null
            return phase.token
        }
    }

    private fun peekPreAcquiredPrimaryImageForFrame(frameId: Long): Int? {
        return synchronized(geometryPhaseStateLock) {
            preAcquiredPrimaryImage?.takeIf { it.frameId == frameId }?.imageIndex
        }
    }

    private fun clearPreAcquiredPrimaryImageForFrame(frameId: Long) {
        synchronized(geometryPhaseStateLock) {
            val pending = preAcquiredPrimaryImage ?: return
            if (pending.frameId == frameId) {
                preAcquiredPrimaryImage = null
            }
        }
    }

fun installRunloopGeometryBeginHook

Installs a Runloop pre-frame hook that opens geometry writes every runloop tick.

This is useful when geometry workers are launched from arbitrary runloop code before render.

    fun installRunloopGeometryBeginHook(
        runloop: Runloop = Runloop.runLoop,
        key: String = "renderer:${System.identityHashCode(this)}:beginGeometryWrites"
    ) {
        runloop.preRun.setUnowned(key, sequence {
            while (true) {
                try {
                    beginGeometryWrites()
                } catch (_: ImageNotAvailable) {
                    // Skip this runloop tick; render() handles this case too.
                } catch (ise: IllegalStateException) {
                    // If a frame is already pre-acquired and sealed for this frame, skip quietly.
                    if (ise.message?.contains("already prepared for frame") != true) {
                        lprintln("warning,graphics", "runloop geometry preRun failed: ${ise.message}")
                    }
                } catch (t: Throwable) {
                    lprintln("warning,graphics", "runloop geometry preRun failed: ${t.message}")
                }
                yield(null)
            }
        })
        runloopGeometryHookKey = key
    }

fun uninstallRunloopGeometryBeginHook(runloop: Runloop = Runloop.runLoop)

    fun uninstallRunloopGeometryBeginHook(runloop: Runloop = Runloop.runLoop) {
        runloopGeometryHookKey?.let { runloop.preRun.remove(it) }
        runloopGeometryHookKey = null
    }

var openXRHmdTarget: afield.graphics.vulkan.targets.OpenXRHmdTarget? = null

Optional OpenXR HMD target rendered once per frame (after window presents).

data class FrameMarkers

    data class FrameMarkers(
        var tsFrameBegin: Int = -1,
        var tsAfterCompute: Int = -1,
        var tsAfterGraphics: Int = -1,
        var tsFrameEnd: Int = -1,
        var pipeStatsCompute: Int = -1,
        var pipeStatsGraphics: Int = -1,
        var perfFrameIndex: Int = -1
    )

data class Pending(val imageIndex: Int)

    private companion object { const val READBACK_LAG = 3 }

data class ModuleMarks

Per-module instrumentation

    data class ModuleMarks(
        val name: String,
        var tsBegin: Int = -1,
        var tsEnd: Int = -1,
        var pipeStatsIndex: Int = -1,
        var perfIndex: Int = -1
    )

fun registerModuleMarks(imageIndex: Int, marks: ModuleMarks)

    fun registerModuleMarks(imageIndex: Int, marks: ModuleMarks) {
        registerModuleMarks(windowIndex = 0, imageIndex = imageIndex, marks = marks)
    }

fun registerModuleMarks(windowIndex: Int, imageIndex: Int, marks: ModuleMarks)

    fun registerModuleMarks(windowIndex: Int, imageIndex: Int, marks: ModuleMarks) {
        val ctx = windowContexts.getOrNull(windowIndex) ?: return
        val ps = ctx.profilerState ?: return
        synchronized(ps.lock) {
            ps.perImageModuleMarks.getOrNull(imageIndex)?.add(marks)
        }
    }

init

body — 49 lines, from line 768
    init {
        if (Options.enableVR) {
            // Prepare OpenXR instance/system and fetch graphics requirements before VkDevice creation.
            Instance.prepareOpenXRBootstrap()
        }
        // A missing loader, no Vulkan driver, or a driver lacking a feature Field needs all fail here,
        // and on a fresh machine this is the first place the user meets Vulkan. The welcome page's
        // Vulkan panel says which of those it is and how to install what is missing.
        try {
            instance = Instance(Options.validate)
            physicalDevice = PhysicalDevice.createPhysicalDevice(instance, Options.preferredDevice)
            device = Device(physicalDevice, instance)
        } catch (t: Throwable) {
            throw IllegalStateException(
                "Could not start Vulkan: ${t.message ?: t.javaClass.simpleName}. " +
                    "See https://localhost:${afield.app.server.globalServerPort}/welcome#vulkan for what this machine has and how to install Vulkan.",
                t
            )
        }
        Options.chooseDefaultDepthFormat(physicalDevice.vkPhysicalDevice)
        if (Options.dumpToolingInfo) {
            instance.dumpToolingInfo(instance.vkInstance, physicalDevice.vkPhysicalDevice)
        }
        graphQueue = GraphicsQueue(device, 0)
        val primarySurface = Surface(physicalDevice, window.windowHandle)
        // Prefer a different queue index for present to avoid contending with the graphics queue (which XR/runtime may also touch).
        val gfxFamily = graphQueue.queueFamilyIndex
        val gfxQueueCount = physicalDevice.vkQueueFamilyProps[gfxFamily].queueCount()
        val presentQueueIndex = if (gfxQueueCount > 1) 1 else 0
        presentQueue = PresentQueue(device, primarySurface, presentQueueIndex)
        commandPool = CommandPool(device, graphQueue.queueFamilyIndex)
        computeCommandPool = CommandPool(device, graphQueue.queueFamilyIndex)
        pipelineCache = PipelineCache(device)
        windows.forEachIndexed { idx, w ->
            w.associatedRenderer = this
            val surfaceOverride = if (idx == 0) primarySurface else null
            windowContexts += createWindowContext(w, idx, surfaceOverride)
        }

        // ---- Name core objects for tooling ----
        device.nameObject(instance.vkInstance.address(), VK10.VK_OBJECT_TYPE_INSTANCE, "Instance")
        device.nameObject(physicalDevice.vkPhysicalDevice.address(), VK10.VK_OBJECT_TYPE_PHYSICAL_DEVICE, "PhysicalDevice")
        device.nameObject(device.vkDevice.address(), VK10.VK_OBJECT_TYPE_DEVICE, "Device")
        device.nameObject(graphQueue.vkQueue.address(), VK10.VK_OBJECT_TYPE_QUEUE, "GraphicsQueue")
        device.nameObject(presentQueue.vkQueue.address(), VK10.VK_OBJECT_TYPE_QUEUE, "PresentQueue")
        device.nameObject(commandPool.vkCommandPool, VK10.VK_OBJECT_TYPE_COMMAND_POOL, "GraphicsCmdPool")
        device.nameObject(computeCommandPool.vkCommandPool, VK10.VK_OBJECT_TYPE_COMMAND_POOL, "ComputeCmdPool")
        device.nameObject(pipelineCache.vkPipelineCache, VK10.VK_OBJECT_TYPE_PIPELINE_CACHE, "PipelineCache")
    }

fun cleanup()

body — 73 lines, from line 818
    fun cleanup() {
        if (cleanedUp) return
        cleanedUp = true
        running = false
        // The modules boxes put on this renderer's targets die with its device; end their ledger
        // ownerships now, rather than when each box retires, so the modules and the snippet
        // classloaders that built them are not kept by the box's run scope.
        releaseOwnedModulesOf(this)
        // Before the waitIdle below: a Queue.submit from another thread either finished before this
        // (and the waits cover it) or is refused. Renderer's own frame submits use vkQueueSubmit2
        // on this thread and render() already refuses once cleanedUp is set.
        device.closeSubmissions()
        beforeDeviceCleanup.toList().forEach { it.run() }
        beforeDeviceCleanup.clear()
        uninstallRunloopGeometryBeginHook()

        presentQueue.waitIdle()
        graphQueue.waitIdle()
        device.waitIdle()
        // A final capture must complete even when no subsequent frame polls its fence.
        // Run before deferred buffer releases and before the device/fences disappear.
        windowContexts.forEachIndexed { contextIndex, ctx ->
            ctx.fences.indices.forEach { imageIndex ->
                drainCompletedGraphicsCallbacks(ctx, contextIndex, imageIndex)
            }
        }
        // Pending releases normally wait for later frames. Shutdown has no later frame;
        // release them now, before their fences, command pools and device disappear.
        var deferredFailure = drainDeferredCleanup(cleanup, cleanupStack)
        if (Options.enableVR) {
            Instance.cleanupOpenXRBootstrap()
        }

        openXRHmdTarget?.cleanup()
        openXRHmdTarget = null

        windowContexts.forEach { ctx ->
            ctx.profilerState?.profiler?.destroy()
            ctx.commandBuffers.forEach { it.cleanup() }
            ctx.computeCommandBuffers.forEach { it.cleanup() }
            ctx.fences.forEach { it.cleanup() }
            ctx.computeFences.forEach { it.cleanup() }
            ctx.computeFinishedSemaphores.forEach { it.cleanup() }
            cleanupSecondaryMap(ctx.computeSecondary)
            cleanupSecondaryMap(ctx.graphicsSecondary)
            cleanupPoolMap(ctx.computePoolsPerThread)
            cleanupPoolMap(ctx.graphicsPoolsPerThread)
            ctx.depthAttachments.forEach { it.cleanup() }
            ctx.swapChain.cleanup()
            ctx.surface.cleanup()
        }
        pipelineCache.cleanup()
        commandPool.cleanup()
        computeCommandPool.cleanup()
        workerDispatcher.close()
        workerExecutor.shutdownNow()
        queueDispatcher.close()
        queueExecutor.shutdownNow()

        // A document can still hold its resources when its renderer closes. GC alone
        // cannot establish the lifetime of native objects owned by this device.
        AutoDisposer.releaseOwner(device) { cleanup.add(it) }
        drainDeferredCleanup(cleanup, cleanupStack)?.let { failure ->
            if (deferredFailure == null) deferredFailure = failure
            else deferredFailure.addSuppressed(failure)
        }
        cleanupAllocatorForRenderer(this)
        device.cleanup()
        physicalDevice.cleanup()
        instance.cleanup()
        windows.forEach { it.associatedRenderer = null }
        deferredFailure?.let { throw it }
    }

var frameCounter = 0L

Monotonic submitted-frame id; acquisition-only retries do not advance GPU retirement.

    private var lastVertexUploadStatsLogNs = 0L
    private var lastRunFpsLogNs = 0L
    private var lastRunFpsFrame = 0

    private fun beginFrameUploadStats() {
        synchronized(uploadStatsLock) {
            currentUploadRecordedBytes = 0L
            currentUploadImmediateBytes = 0L
        }
    }

fun recordVertexBufferUpload(bytes: Long, immediate: Boolean)

    fun recordVertexBufferUpload(bytes: Long, immediate: Boolean) {
        if (bytes <= 0L) return
        synchronized(uploadStatsLock) {
            if (immediate) {
                currentUploadImmediateBytes += bytes
            } else {
                currentUploadRecordedBytes += bytes
            }
        }
    }

    private fun endFrameUploadStats(frameId: Long): VertexUploadFrameStats {
        return synchronized(uploadStatsLock) {
            VertexUploadFrameStats(
                frameId = frameId,
                recordedBytes = currentUploadRecordedBytes,
                immediateBytes = currentUploadImmediateBytes
            ).also {
                lastVertexUploadFrameStats = it
            }
        }
    }

    private fun maybeLogVertexUploadStats(frame: Long, stats: VertexUploadFrameStats, everyNFrames: Int = 120) {
        val now = System.nanoTime()
        if (lastVertexUploadStatsLogNs != 0L && now - lastVertexUploadStatsLogNs < PERF_LOG_INTERVAL_NS) return
        lastVertexUploadStatsLogNs = now
        fun mib(bytes: Long): Double = bytes.toDouble() / (1024.0 * 1024.0)
        lprintln(
            "info,perf,upload",
            "vertex_upload frame=${stats.frameId} total=${"%.3f".format(mib(stats.totalBytes))}MiB recorded=${"%.3f".format(mib(stats.recordedBytes))}MiB immediate=${"%.3f".format(mib(stats.immediateBytes))}MiB"
        )
    }

fun supportsDirectWindowCapture(window: Window = this.window): Boolean

    fun supportsDirectWindowCapture(window: Window = this.window): Boolean {
        val windowIndex = windows.indexOf(window)
        if (windowIndex < 0) return false
        return windowContexts.getOrNull(windowIndex)?.swapChain?.supportsTransferSrc == true
    }

suspend fun captureWindowToFile

body — 289 lines, from line 945
    suspend fun captureWindowToFile(
        window: Window = this.window,
        outputPath: String,
        timeoutMs: Long = 15_000L
    ): String {
        val startedAt = System.nanoTime()
        val windowIndex = windows.indexOf(window)
        require(windowIndex >= 0) { "Window is not owned by this renderer" }
        require(supportsDirectWindowCapture(window)) { "Swapchain does not support transfer-source capture" }

        val request = windowCaptures.enqueue(windowIndex, outputPath, startedAt)
        val queueSize = windowCaptures.pendingCount(windowIndex)
        SaveReloadLog.log(
            "window-capture-requested",
            "path=$outputPath windowIndex=$windowIndex queueSize=$queueSize size=${window.width}x${window.height} timeoutMs=$timeoutMs"
        )
        try {
            val result = windowCaptures.await(request, timeoutMs)
            val totalMs = SaveReloadLog.logTiming(
                "window-capture-total",
                startedAt,
                "path=$outputPath windowIndex=$windowIndex queueSizeAtRequest=$queueSize size=${window.width}x${window.height}"
            )
            if (totalMs >= 1000L) {
                lprintln(
                    "warning,graphics",
                    "slow direct window capture path=$outputPath ms=$totalMs size=${window.width}x${window.height}"
                )
            }
            return result
        } catch (t: Throwable) {
            SaveReloadLog.log(
                "window-capture-total",
                "ms=${SaveReloadLog.elapsedMillis(startedAt)} path=$outputPath windowIndex=$windowIndex ok=false error=${t.message ?: t::class.simpleName}"
            )
            throw t
        }
    }

    private fun dequeueWindowCaptureRequests(windowIndex: Int): List<WindowCaptureRequest> {
        return windowCaptures.takeForFrame(windowIndex)
    }

    private fun recordPendingWindowCaptures(
        ctx: WindowContext,
        ctxIndex: Int,
        imageIndex: Int,
        cmd: CommandBuffer
    ): List<RecordedWindowCapture> {
        val startedAt = System.nanoTime()
        val requests = dequeueWindowCaptureRequests(ctxIndex)
        if (requests.isEmpty()) return emptyList()

        val width = ctx.swapChain.swapChainExtent.width()
        val height = ctx.swapChain.swapChainExtent.height()
        if (width <= 0 || height <= 0) {
            val error = IllegalStateException("Window has invalid capture size ${width}x$height")
            requests.forEach { request ->
                request.completion.completeExceptionally(error)
            }
            return emptyList()
        }

        val format = ctx.swapChain.surfaceFormat.imageFormat
        val bytesPerPixel = try {
            GpuImageCapture.bytesPerPixel(format, "window")
        } catch (t: Throwable) {
            // Unknown swapchain format. Fail the requests the way the invalid-size guard above
            // does — throwing out of here would take the frame with it.
            requests.forEach { request ->
                request.completion.completeExceptionally(t)
            }
            return emptyList()
        }
        val byteCount = width.toLong() * height.toLong() * bytesPerPixel.toLong()
        val captures = requests.map { request ->
            RecordedWindowCapture(
                request = request,
                stagingBuffer = afield.graphics.vulkan.buffers.VulkanBuffer(
                    renderer = this,
                    requestedSize = byteCount,
                    usage = VK10.VK_BUFFER_USAGE_TRANSFER_DST_BIT,
                    reqMask = VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT or VK10.VK_MEMORY_PROPERTY_HOST_CACHED_BIT
                ),
                width = width,
                height = height,
                format = format
            )
        }

        MemoryStack.stackPush().use { stack ->
            val colorRange = VkImageSubresourceRange.calloc(stack)
                .aspectMask(VK10.VK_IMAGE_ASPECT_COLOR_BIT)
                .baseMipLevel(0)
                .levelCount(1)
                .baseArrayLayer(0)
                .layerCount(1)

            image_layout_transition(
                cmd.vkCommandBuffer,
                ctx.swapChain.imageViews[imageIndex].image,
                VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
                VK_PIPELINE_STAGE_2_COPY_BIT,
                VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
                VK_ACCESS_2_TRANSFER_READ_BIT,
                KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
                VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                colorRange
            )

            val copyRegion = VkBufferImageCopy.calloc(1, stack)
            copyRegion[0]
                .bufferOffset(0L)
                .bufferRowLength(0)
                .bufferImageHeight(0)
                .imageSubresource(
                    VkImageSubresourceLayers.calloc(stack)
                        .aspectMask(VK10.VK_IMAGE_ASPECT_COLOR_BIT)
                        .mipLevel(0)
                        .baseArrayLayer(0)
                        .layerCount(1)
                )
                .imageOffset(VkOffset3D.calloc(stack).x(0).y(0).z(0))
                .imageExtent(VkExtent3D.calloc(stack).width(width).height(height).depth(1))

            captures.forEach { capture ->
                VK10.vkCmdCopyImageToBuffer(
                    cmd.vkCommandBuffer,
                    ctx.swapChain.imageViews[imageIndex].image,
                    vkLayout(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL),
                    capture.stagingBuffer.buffer,
                    copyRegion
                )
            }

            image_layout_transition(
                cmd.vkCommandBuffer,
                ctx.swapChain.imageViews[imageIndex].image,
                VK_PIPELINE_STAGE_2_COPY_BIT,
                VK_PIPELINE_STAGE_2_NONE,
                VK_ACCESS_2_TRANSFER_READ_BIT,
                0,
                VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
                colorRange
            )
        }

        SaveReloadLog.logTiming(
            "window-capture-recorded",
            startedAt,
            "windowIndex=$ctxIndex imageIndex=$imageIndex requests=${requests.size} size=${width}x${height} format=$format bytesPerPixel=$bytesPerPixel"
        )
        return captures
    }

    private fun completeWindowCapture(capture: RecordedWindowCapture) {
        val readStartedAt = System.nanoTime()
        val pixels = readWindowCapturePixels(capture)
        if (pixels == null) {
            SaveReloadLog.log(
                "window-capture-readback",
                "ms=${SaveReloadLog.elapsedMillis(readStartedAt)} path=${capture.request.outputPath} ok=false size=${capture.width}x${capture.height} queuedMs=${SaveReloadLog.elapsedMillis(capture.request.requestedAtNs)}"
            )
            return
        }
        val readMs = SaveReloadLog.elapsedMillis(readStartedAt)
        SaveReloadLog.log(
            "window-capture-readback",
            "ms=$readMs path=${capture.request.outputPath} size=${capture.width}x${capture.height} queuedMs=${SaveReloadLog.elapsedMillis(capture.request.requestedAtNs)}"
        )
        if (readMs >= 500L) {
            lprintln(
                "warning,graphics",
                "slow window capture readback path=${capture.request.outputPath} ms=$readMs size=${capture.width}x${capture.height}"
            )
        }
        @OptIn(DelicateCoroutinesApi::class)
        GlobalScope.launch(Runloop.miscPool.asCoroutineDispatcher()) {
            encodeWindowCapture(pixels)
        }
    }

    private fun readWindowCapturePixels(capture: RecordedWindowCapture): WindowCapturePixels? {
        try {
            if (!capture.request.completion.isActive) return null
            var rgba: ByteArray? = null
            capture.stagingBuffer.mapAndRead_bytes {
                rgba = GpuImageCapture.readRgba(this, capture.format, capture.width, capture.height, "window")
            }
            return WindowCapturePixels(capture.request, requireNotNull(rgba), capture.width, capture.height)
        } catch (t: Throwable) {
            capture.request.completion.completeExceptionally(t)
            lprintln("warning,graphics", "direct window capture readback failed: ${t.message}")
            return null
        } finally {
            capture.stagingBuffer.cleanup()
        }
    }

    private fun encodeWindowCapture(capture: WindowCapturePixels) {
        if (!capture.request.completion.isActive) return
        val startedAt = System.nanoTime()
        var extension = ""
        try {
            extension = Path.of(capture.request.outputPath).fileName.toString()
                .substringAfterLast('.', "")
                .lowercase(Locale.getDefault())
            if (extension == "jpg" || extension == "jpeg") {
                writeWindowCaptureJpeg(capture)
            } else {
                writeWindowCapturePng(capture)
            }
            capture.request.completion.complete(capture.request.outputPath)
            val path = Path.of(capture.request.outputPath)
            val byteSize = runCatching { Files.size(path) }.getOrDefault(-1L)
            val totalMs = SaveReloadLog.logTiming(
                "window-capture-encode",
                startedAt,
                "path=${capture.request.outputPath} extension=$extension size=${capture.width}x${capture.height} bytes=$byteSize queuedMs=${SaveReloadLog.elapsedMillis(capture.request.requestedAtNs)}"
            )
            if (totalMs >= 1000L) {
                lprintln(
                    "warning,graphics",
                    "slow window capture encode path=${capture.request.outputPath} ms=$totalMs extension=$extension size=${capture.width}x${capture.height}"
                )
            }
        } catch (t: Throwable) {
            capture.request.completion.completeExceptionally(t)
            SaveReloadLog.log(
                "window-capture-encode",
                "ms=${SaveReloadLog.elapsedMillis(startedAt)} path=${capture.request.outputPath} extension=$extension ok=false error=${t.message ?: t::class.simpleName}"
            )
            lprintln("warning,graphics", "direct window capture encode failed: ${t.message}")
        }
    }

    private fun writeWindowCapturePng(capture: WindowCapturePixels) {
        val startedAt = System.nanoTime()
        val pngBytes = PngEncoder.encodeFromBuffer(
            ByteBuffer.wrap(capture.rgba),
            capture.width,
            capture.height,
            // vkCmdCopyImageToBuffer preserves the presented swapchain's top-to-bottom row order.
            flipY = false,
            // A presented swapchain image is opaque; its alpha is leftover fragment coverage. Keeping
            // it made three docs figures render up to 3.28x brighter on white than on the dark page.
            opaque = true
        )
        val encodeMs = SaveReloadLog.elapsedMillis(startedAt)
        val writeStartedAt = System.nanoTime()
        Files.write(
            Path.of(capture.request.outputPath),
            pngBytes,
            StandardOpenOption.CREATE,
            StandardOpenOption.TRUNCATE_EXISTING,
            StandardOpenOption.WRITE
        )
        SaveReloadLog.log(
            "window-capture-png",
            "ms=${SaveReloadLog.elapsedMillis(startedAt)} path=${capture.request.outputPath} size=${capture.width}x${capture.height} encodedBytes=${pngBytes.size} encodeMs=$encodeMs writeMs=${SaveReloadLog.elapsedMillis(writeStartedAt)}"
        )
    }

    private fun writeWindowCaptureJpeg(capture: WindowCapturePixels) {
        val startedAt = System.nanoTime()
        val rgb = GpuImageCapture.packRgbaToRgbTopDown(capture.rgba, capture.width, capture.height)
        val packMs = SaveReloadLog.elapsedMillis(startedAt)
        val writeStartedAt = System.nanoTime()
        val quality = windowCaptureJpegQuality()
        GpuImageCapture.writeJpegRgb(
            capture.request.outputPath,
            rgb,
            capture.width,
            capture.height,
            quality
        )
        val writeMs = SaveReloadLog.elapsedMillis(writeStartedAt)
        SaveReloadLog.log(
            "window-capture-jpeg",
            "ms=${SaveReloadLog.elapsedMillis(startedAt)} path=${capture.request.outputPath} size=${capture.width}x${capture.height} quality=$quality packMs=$packMs writeMs=$writeMs"
        )
    }

    private fun windowCaptureJpegQuality(): Int {
        val configured = System.getProperty("afield.history.screenshotJpegQuality")
            ?: System.getenv("AFIELD_HISTORY_SCREENSHOT_JPEG_QUALITY")
        return configured?.toIntOrNull()?.coerceIn(1, 100) ?: 82
    }

var presentationRevision=0L; private set

Changes after a presentation gap; temporal adapters must reject old history.

    private val unavailableWindows=mutableMapOf<Int,Long>()

fun render()

body — 429 lines, from line 1239
    fun render() {
        check(!cleanedUp) { "Renderer has been cleaned up" }
        val maxFramesInFlight = windowContexts.maxOfOrNull { it.numImages } ?: return
        val frameId = frameCounter
        beginFrameUploadStats()

        // If geometry writes were opened earlier in this runloop frame, close them now.
        takeOpenGeometryPhaseTokenForFrame(frameId)?.let { token ->
            try {
                withWorkerParallelism {
                    runGeometryProducers(token, parallelAllowed = true)
                }
            } finally {
                sealGeometryPools(token)
            }
        }

        // Options.profiling can change on a running renderer: follow it at the frame boundary. A
        // retired profiler's query pools go through the deferred cleanup, after in-flight frames.
        windowContexts.forEach { ctx ->
            val current = ctx.profilerState
            if (Options.profiling && current == null) {
                ctx.profilerState = createProfilerState(ctx.fences.size)
            } else if (!Options.profiling && current != null) {
                ctx.profilerState = null
                cleanup += Runnable { current.profiler.destroy() }
            }
        }

        // Drain ready frames (no stalls) per window.
        windowContexts.forEach { ctx ->
            val ps = ctx.profilerState ?: return@forEach
            while (ps.pending.isNotEmpty()) {
                val head = ps.pending.first()
                val enoughLag = ps.pending.size > READBACK_LAG
                val signaled = ctx.fences[head.imageIndex].isSignaled()
                if (!enoughLag && !signaled) break
                if (signaled) {
                    resolveProfilingForImage(ctx, head.imageIndex)
                    ps.pending.removeFirst()
                } else break
            }
        }
        var computeSubmittedThisFrame = false
        windowContexts.forEachIndexed { ctxIndex, ctx ->
            for (imageIndex in 0 until ctx.numImages) {
                drainCompletedGraphicsCallbacks(ctx, ctxIndex, imageIndex)
            }
        }

        // OpenXR must locate the current predicted views BEFORE stereo preTargets render.
        // The mirror and compute work keep their existing threads; only the frame boundary moves.
        val renderScene = {
            windowContexts.forEachIndexed { ctxIndex, ctx ->
                val w = ctx.window
                if (!w.presentationEnabled || w.width <= 0 || w.height <= 0) return@forEachIndexed

                var imageIndex = 0
                val preAcquiredImage = if (ctxIndex == 0) peekPreAcquiredPrimaryImageForFrame(frameId) else null

                try {
                    imageIndex = preAcquiredImage ?: acquireFrameImage(ctxIndex)

                    ctx.currentImage = imageIndex
                    if (ctxIndex == 0) {
                        currentImage = imageIndex
                        if (preAcquiredImage == null) {
                            runGeometryPhase(
                                GeometryFrameToken(
                                    frameId = frameId,
                                    imageIndex = imageIndex,
                                    windowIndex = ctxIndex
                                )
                            )
                        }
                    }
                    ctx.profilerState?.let { ps ->
                        synchronized(ps.lock) {
                            ps.profiler.beginFrame(imageIndex)
                            ps.perImageModuleMarks.getOrNull(imageIndex)?.clear()
                        }
                    }

                    withWorkerParallelism {
                        runBlocking {
                            val runComputeForWindow = !Options.computeOncePerFrame || !computeSubmittedThisFrame
                            val computeRecording = if (runComputeForWindow) {
                                asyncWorker {
                                    recordComputePass(ctx, ctxIndex, imageIndex, frameId)
                                }
                            } else null
                            val graphicsRecording = asyncWorker {
                                recordGraphicsPass(ctx, ctxIndex, imageIndex, frameId)
                            }

                            computeRecording?.await()
                            if (runComputeForWindow) {
                                CPUSpan.time("submit_cs[$ctxIndex]") {
                                    submitCompute(
                                        ctx,
                                        ctx.computeCommandBuffers[imageIndex],
                                        ctx.computeFences[imageIndex],
                                        ctx.computeFinishedSemaphores[imageIndex],
                                        graphQueue
                                    )
                                }
                                drainComputeSubmissionCallbacks(ctxIndex, imageIndex, ctx.computeFences[imageIndex])
                                computeSubmittedThisFrame = true
                            }

                            val recordedWindowCaptures = graphicsRecording.await()
                            CPUSpan.time("submit_gfx[$ctxIndex]") {
                                submit(
                                    ctx,
                                    ctx.commandBuffers[imageIndex],
                                    ctx.fences[imageIndex],
                                    graphQueue,
                                    ctx.computeFinishedSemaphores[imageIndex].takeIf { runComputeForWindow }
                                )
                            }
                            if (recordedWindowCaptures.isNotEmpty()) {
                                ctx.fences[imageIndex].fenceWait()
                                recordedWindowCaptures.forEach { capture ->
                                    completeWindowCapture(capture)
                                }
                            }
                        }
                    }

                    CPUSpan.time("present[$ctxIndex]") {
                        runOnQueueThread {
                            if (ctx.swapChain.presentImage(presentQueue, imageIndex, frameId, ctxIndex).resize) w.isResized = true
                        }
                    }

                    ctx.profilerState?.pending?.addLast(Pending(imageIndex))
                } catch (e: ImageNotAvailable) {
                    unavailableWindows.putIfAbsent(ctxIndex,System.nanoTime())
                    if (!e.retryable) lprintln("warning,graphics", e.message ?: "ImageNotAvailable")
                }
            }
        }
        val xr = openXRHmdTarget
        if (xr == null) renderScene() else xr.renderFrame(renderScene)
        clearPreAcquiredPrimaryImageForFrame(frameId)

        // Deferred cleanup queue (use max frames-in-flight across all contexts)
        if (cleanup.isNotEmpty()) cleanupStack.add(frameCounter to cleanup)
        cleanup = java.util.Collections.synchronizedList(mutableListOf())
        val thresholdFrame = frameCounter - maxFramesInFlight * 8
        while (cleanupStack.isNotEmpty() && cleanupStack.first().first <= thresholdFrame) {
            val (_, toRun) = cleanupStack.removeAt(0)
            if (toRun.isNotEmpty()) {
                val copy = ArrayList(toRun)
                toRun.clear()
                copy.forEach { it.run() }
            }
        }

        // Drain GC-driven disposals onto the render-thread cleanup queue
        AutoDisposer.drain(device) { cleanup.add(it) }

        renderEnd.update()
        CPUSpan.snapshotAndMaybePrint(everyNFrames = 120, frame = frameCounter)
        maybeLogVertexUploadStats(frameCounter, endFrameUploadStats(frameId))

        if (windowContexts.isNotEmpty()) {
            currentImage = (defaultContext.currentImage + 1) % defaultContext.numImages
        }

        // Failed acquire attempts must not age the GPU retirement queue while all windows
        // are hidden. A later successful submission resumes its normal frame accounting.
        if (computeSubmittedThisFrame || openXRHmdTarget != null) frameCounter++
        currentCommandBuffer = null
    }

    private fun recordComputePass(
        ctx: WindowContext,
        ctxIndex: Int,
        imageIndex: Int,
        frameId: Long
    ) {
        CPUSpan.time("record_cs[$ctxIndex]") {
            ctx.computeFences[imageIndex].fenceWait()
            awaitComputePostSubmitTasks(ctxIndex, imageIndex)
            ctx.computeFences[imageIndex].reset()

            val computeElements = mutableListOf<Pair<String, (CommandBuffer) -> Unit>>()
            computeList.modules.forEach { (name, ptr) ->
                computeElements += name to { cb -> ptr.render(cb, Rectanglef()) }
            }
            computeList.others.forEach { (name, target) ->
                computeElements += name to { cb -> target.render(cb, imageIndex) }
            }

            val secondaryBuffers = arrayOfNulls<CommandBuffer>(computeElements.size)
            runBlocking {
                val jobs = computeElements.mapIndexed { idx, (name, action) ->
                    asyncWorker {
                        val cb = getSecondaryCommandBuffer(
                            ctx.computeSecondary,
                            "compute:$name",
                            computeCommandPool,
                            ctx.computePoolsPerThread,
                            ctx.numImages,
                            imageIndex
                        )
                        cb.reset()
                        cb.beginRecording(CommandBuffer.SecondaryInheritance())
                        computeRecordingContext.set(ctxIndex to imageIndex)
                        try {
                            action(cb)
                        } finally {
                            computeRecordingContext.remove()
                        }
                        cb.endRecording()
                        secondaryBuffers[idx] = cb
                    }
                }
                jobs.awaitAll()
            }

            val ccmd = ctx.computeCommandBuffers[imageIndex]
            ccmd.reset(); ccmd.beginRecording()

            // --- Label start (compute pass) ---
            MemoryStack.stackPush().use { s ->
                val c = s.floats(0.25f, 0.65f, 0.85f, 1f) // cyan-ish
                ccmd.beginLabel("Compute Pass [ctx=$ctxIndex]", c)
            }

            val profilerState = ctx.profilerState
            var csScope: GpuProfiler.PipeStatScope? = null
            var pcScope: GpuProfiler.PerfScope? = null
            profilerState?.let { p ->
                synchronized(p.lock) {
                    p.markers[imageIndex].tsFrameBegin = p.profiler.writeTimestamp2(
                        ccmd.vkCommandBuffer, imageIndex, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT
                    )
                    val cs = p.profiler.beginPipelineStats(ccmd.vkCommandBuffer, imageIndex)
                    csScope = cs
                    csScope?.let { p.markers[imageIndex].pipeStatsCompute = it.index }
                    pcScope = p.profiler.beginPerfCounters(ccmd.vkCommandBuffer, imageIndex)
                }
            }

            if (secondaryBuffers.isNotEmpty()) {
                MemoryStack.stackPush().use { stack ->
                    val pb = stack.mallocPointer(secondaryBuffers.size)
                    secondaryBuffers.forEach { cb ->
                        pb.put(cb!!.vkCommandBuffer.address())
                    }
                    pb.flip()
                    VK10.vkCmdExecuteCommands(ccmd.vkCommandBuffer, pb)
                }
            } else {
                computeRecordingContext.set(ctxIndex to imageIndex)
                try {
                    computeList.render(ccmd, imageIndex)
                } finally {
                    computeRecordingContext.remove()
                }
            }

            profilerState?.let { p ->
                synchronized(p.lock) {
                    p.profiler.endPerfCounters(ccmd.vkCommandBuffer, imageIndex, pcScope)
                    p.profiler.endPipelineStats(ccmd.vkCommandBuffer, csScope)
                    p.markers[imageIndex].tsAfterCompute = p.profiler.writeTimestamp2(
                        ccmd.vkCommandBuffer, imageIndex, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT
                    )
                }
            }

            // --- Label end (compute pass) ---
            ccmd.endLabel()

            ccmd.endRecording()
        }
    }

    private fun recordGraphicsPass(
        ctx: WindowContext,
        ctxIndex: Int,
        imageIndex: Int,
        frameId: Long
    ): List<RecordedWindowCapture> {
        var recordedWindowCaptures: List<RecordedWindowCapture> = emptyList()
        CPUSpan.time("record_gfx[$ctxIndex]") {
            // Ensure the per-image graphics fence from any previous use is complete before re-recording
            ctx.fences[imageIndex].fenceWait()
            ctx.fences[imageIndex].reset()

            val cmd = ctx.commandBuffers[imageIndex]
            cmd.reset(); cmd.beginRecording()

            // Upload dirty mesh buffers once on the primary command buffer before any
            // secondary command buffers are recorded with render-pass inheritance.
            prepareTargetMeshesForDraw(ctx.renderTarget, cmd)

            val swapChainExtent = ctx.swapChain.swapChainExtent
            val width = swapChainExtent.width()
            val height = swapChainExtent.height()

            val dynRendering = CommandBuffer.DynamicRenderingInheritance(
                colorFormats = intArrayOf(ctx.swapChain.surfaceFormat.imageFormat),
                depthFormat = ctx.depthAttachments.getOrNull(imageIndex)?.image?.format ?: Options.defaultDepthFormat
            )

            val moduleElements = mutableListOf<Pair<String, (CommandBuffer) -> Unit>>()
            ctx.renderTarget.modules.forEach { (name, mp) ->
                moduleElements += "mod:$name" to { cb ->
                    MemoryStack.stackPush().use { stack ->
                        val viewport = mp.current.parameters.viewport ?: Rect(
                            0.0, 0.0, width.toDouble(), height.toDouble()
                        )
                        setViewportAndScissor(cb.vkCommandBuffer, viewport, stack)
                    }
                    mp.render(cb, Rectanglef(0f, 0f, width.toFloat(), height.toFloat()))
                }
            }
            ctx.renderTarget.meshModules.forEach { (name, mp) ->
                moduleElements += "mesh:$name" to { cb ->
                    MemoryStack.stackPush().use { stack ->
                        val viewport = mp.current.parameters.viewport ?: Rect(
                            0.0, 0.0, width.toDouble(), height.toDouble()
                        )
                        setViewportAndScissor(cb.vkCommandBuffer, viewport, stack)
                    }
                    mp.render(cb, Rectanglef(0f, 0f, width.toFloat(), height.toFloat()))
                }
            }

            val enableSecondaryModules =
                Options.secondaryGraphicsCommandBuffers && device.supportsMaintenance7
            val moduleBuffers: List<CommandBuffer> =
                if (enableSecondaryModules && moduleElements.isNotEmpty()) {
                    val collected = arrayOfNulls<CommandBuffer>(moduleElements.size)
                    runBlocking {
                        val jobs = moduleElements.mapIndexed { idx, (name, action) ->
                            asyncWorker {
                        val cb = getSecondaryCommandBuffer(
                            ctx.graphicsSecondary,
                            "gfx:$name",
                            commandPool,
                            ctx.graphicsPoolsPerThread,
                            ctx.numImages,
                            imageIndex
                        )
                                cb.reset()
                                cb.beginRecording(
                                    CommandBuffer.SecondaryInheritance(
                                        dynamicRendering = dynRendering
                                    )
                                )
                                action(cb)
                                cb.endRecording()
                                collected[idx] = cb
                            }
                        }
                        jobs.awaitAll()
                    }
                    collected.filterNotNull()
                } else emptyList()

            // --- Label start (graphics frame) ---
            MemoryStack.stackPush().use { s ->
                val c = s.floats(0.85f, 0.45f, 0.15f, 1f) // orange-ish
                cmd.beginLabel("Graphics Frame [ctx=$ctxIndex]", c)
            }

            currentCommandBuffer = cmd
            externalFrameSync.remove(cmd.vkCommandBuffer.address())
            graphicsRecordingContext.set(ctxIndex to imageIndex)
            try {
                renderStart.update()

                val profilerState = ctx.profilerState
                var gsScope: GpuProfiler.PipeStatScope? = null
                var perfFrameScope: GpuProfiler.PerfScope? = null

                profilerState?.let { p ->
                    synchronized(p.lock) {
                        gsScope = p.profiler.beginPipelineStats(cmd.vkCommandBuffer, imageIndex)
                        gsScope?.let { p.markers[imageIndex].pipeStatsGraphics = it.index }
                        perfFrameScope = p.profiler.beginPerfCounters(cmd.vkCommandBuffer, imageIndex)
                    }
                }

                // Bracket the scene render itself
                MemoryStack.stackPush().use { s ->
                    val c = s.floats(0.45f, 0.85f, 0.25f, 1f) // green-ish
                    cmd.beginLabel("RenderTargets", c)
                }

                if (moduleBuffers.isNotEmpty()) {
                    ctx.renderTarget.preRecordedInsideRendering = moduleBuffers
                }

                ctx.renderTarget.render(cmd, imageIndex)
                recordedWindowCaptures = recordPendingWindowCaptures(ctx, ctxIndex, imageIndex, cmd)

                cmd.endLabel() // end "RenderTargets"

                profilerState?.let { p ->
                    synchronized(p.lock) {
                        p.profiler.endPerfCounters(cmd.vkCommandBuffer, imageIndex, perfFrameScope)
                        perfFrameScope?.let { p.markers[imageIndex].perfFrameIndex = it.index }
                        p.profiler.endPipelineStats(cmd.vkCommandBuffer, gsScope)
                        p.markers[imageIndex].tsAfterGraphics = p.profiler.writeTimestamp2(
                            cmd.vkCommandBuffer, imageIndex, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT
                        )
                        p.markers[imageIndex].tsFrameEnd = p.profiler.writeTimestamp2(
                            cmd.vkCommandBuffer, imageIndex, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT
                        )
                    }
                }

                // --- Label end (graphics frame) ---
                cmd.endLabel()

                cmd.endRecording()
            } finally {
                graphicsRecordingContext.remove()
                currentCommandBuffer = null
            }
        }
        return recordedWindowCaptures
    }

class ImageNotAvailable(s: String, val retryable: Boolean = false) : RuntimeException(s)

fun submit

body — 67 lines, from line 1671
    fun submit(
        context: WindowContext,
        commandBuffer: CommandBuffer,
        currentFence: Fence,
        queue: Queue,
        computeSemaphore: Semaphore?
    ) {
        runOnQueueThread {
            MemoryStack.stackPush().use { stack ->
                val acquireSync = context.swapChain.syncSemaphoresList[context.swapChain.currentFrame]
                val presentSync = context.swapChain.syncSemaphoresList[context.currentImage]
                val external = externalFrameSync.remove(commandBuffer.vkCommandBuffer.address()) ?: ExternalFrameSync()

                // Command buffer info
                val cbInfo = VkCommandBufferSubmitInfo.calloc(1, stack)
                    .sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO)
                cbInfo[0]
                    .commandBuffer(commandBuffer.vkCommandBuffer)
                    .deviceMask(0)

                // Always wait for image acquisition; wait for compute only when this window ran it.
                val ordinaryWaitCount = if (computeSemaphore == null) 1 else 2
                val waits = VkSemaphoreSubmitInfo.calloc(ordinaryWaitCount + external.waits.size, stack)
                waits[0].sType(VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO)
                    .semaphore(acquireSync.imgAcquisitionSemaphore.vkSemaphore)
                    .value(acquireSync.imgAcquisitionSemaphore.currentValue())
                    .stageMask(VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT)
                    .deviceIndex(0)
                if (computeSemaphore != null) {
                    waits[1].sType(VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO)
                        .semaphore(computeSemaphore.vkSemaphore)
                        .value(computeSemaphore.currentValue())
                        // conservative: ensure graphics won't start any stage before compute finished
                        .stageMask(VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT)
                        .deviceIndex(0)
                }

                // Signal semaphore: render complete
                external.waits.forEachIndexed { i, point ->
                    waits[ordinaryWaitCount + i].sType(VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO)
                        .semaphore(point.semaphore).value(point.value).stageMask(VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT)
                }
                val signals = VkSemaphoreSubmitInfo.calloc(1 + external.signals.size, stack)
                signals[0].sType(VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO)
                    .semaphore(presentSync.renderCompleteSemaphore.vkSemaphore)
                    .value(presentSync.renderCompleteSemaphore.nextSignalValue())
                    .stageMask(VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT)
                    .deviceIndex(0)

                external.signals.forEachIndexed { i, point ->
                    signals[1 + i].sType(VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO)
                        .semaphore(point.semaphore).value(point.value).stageMask(VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT)
                }

                val submit2 = VkSubmitInfo2.calloc(1, stack)
                    .sType(VK_STRUCTURE_TYPE_SUBMIT_INFO_2)
                submit2[0]
                    .pWaitSemaphoreInfos(waits)
                    .pSignalSemaphoreInfos(signals)
                    .pCommandBufferInfos(cbInfo)

                synchronized(queueLock) {
                    vkQueueSubmit2(queue.vkQueue, submit2, currentFence.vkFence)
                }
            }
        }
    }

fun submitCompute

body — 183 lines, from line 1740
    fun submitCompute(
        context: WindowContext,
        commandBuffer: CommandBuffer,
        currentComputeFence: Fence,
        signalSemaphore: Semaphore,
        queue: Queue
    ) {
        runOnQueueThread {
            MemoryStack.stackPush().use { stack ->
                val cbInfo = VkCommandBufferSubmitInfo.calloc(1, stack)
                    .sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO)
                cbInfo[0]
                    .commandBuffer(commandBuffer.vkCommandBuffer)
                    .deviceMask(0)

                // No waits; just signal on completion of the compute work
                val signals = VkSemaphoreSubmitInfo.calloc(1, stack)
                signals[0].sType(VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO)
                    .semaphore(signalSemaphore.vkSemaphore)
                    .value(signalSemaphore.nextSignalValue())
                    .stageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT)
                    .deviceIndex(0)

                val submit2 = VkSubmitInfo2.calloc(1, stack)
                    .sType(VK_STRUCTURE_TYPE_SUBMIT_INFO_2)
                submit2[0]
                    .pCommandBufferInfos(cbInfo)
                    .pSignalSemaphoreInfos(signals)

                synchronized(queueLock) {
                    vkQueueSubmit2(queue.vkQueue, submit2, currentComputeFence.vkFence)
                }
            }
        }
    }

    private fun createDepthImages(swapChain: SwapChain): MutableList<Attachment> {
        val count = swapChain.numImages
        val extent = swapChain.swapChainExtent
        return MutableList(count) {
            Attachment(
                device,
                extent.width(),
                extent.height(),
                Options.defaultDepthFormat,
                VK10.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
            )
        }
    }

    private fun createProfilerState(numImages: Int): ProfilerState {
        val opts = ProfilingOptions(
            enabled = true,
            timestamps = true,
            pipelineStats = Options.isWindows && device.pipelineStatisticsQueryEnabled,
            perfCounters = Options.isWindows && device.performanceQueryEnabled
        )
        val profiler = GpuProfiler(
            device = device.vkDevice,
            physicalDevice = device.physicalDevice.vkPhysicalDevice,
            queueFamilyIndex = graphQueue.queueFamilyIndex,
            framesInFlight = numImages,
            opts = opts
        )
        val markers = Array(numImages) { FrameMarkers() }
        val perImageModuleMarks = Array(numImages) { mutableListOf<ModuleMarks>() }
        return ProfilerState(profiler, markers, perImageModuleMarks)
    }

    private fun createWindowContext(window: Window, index: Int, surfaceOverride: Surface? = null): WindowContext {
        lateinit var ctxRef: WindowContext
        val ctxProvider = { ctxRef }
        val surface = surfaceOverride ?: Surface(physicalDevice, window.windowHandle)
        val swapChain = SwapChain(
            device,
            surface,
            window,
            Options.requestedImages,
            Options.vsync && !window.bestEffortPresentation,
            presentQueue,
            arrayOf(graphQueue)
        )
        val numImages = swapChain.imageViews.size
        // One-time submit: each frame resets and re-records these, then submits them once
        // (recordGraphicsPass / recordComputePass). KosmicKrisp encodes a one-time buffer's Metal
        // commands as it is recorded rather than at vkQueueSubmit, which LunarG recommend for
        // performance (XDC 2026). Resubmitting one without re-recording is a validation error.
        val commandBuffers = Array(numImages) { CommandBuffer(commandPool, primary = true, oneTimeSubmit = Options.oneTimeSubmitFrames) }
        val computeCommandBuffers = Array(numImages) { CommandBuffer(computeCommandPool, primary = true, oneTimeSubmit = Options.oneTimeSubmitFrames) }
        val fences = Array(numImages) { Fence(device, signaled = true) }
        val computeFences = Array(numImages) { Fence(device, signaled = true) }
        val computeFinishedSemaphores = Array(numImages) { Semaphore(device) }
        val depthAttachments = createDepthImages(swapChain)
        val profilerState = if (Options.profiling) createProfilerState(numImages) else null
        val renderTarget = RenderTarget(this, ctxProvider, index)

        ctxRef = WindowContext(
            window = window,
            surface = surface,
            swapChain = swapChain,
            depthAttachments = depthAttachments,
            commandBuffers = commandBuffers,
            computeCommandBuffers = computeCommandBuffers,
            fences = fences,
            computeFences = computeFences,
            computeFinishedSemaphores = computeFinishedSemaphores,
            numImages = numImages,
            currentImage = 0,
            renderTarget = renderTarget,
            profilerState = profilerState,
            computeSecondary = mutableMapOf(),
            graphicsSecondary = mutableMapOf(),
            computePoolsPerThread = mutableMapOf(),
            graphicsPoolsPerThread = mutableMapOf()
        )
        nameContextObjects(ctxRef, index)
        return ctxRef
    }

    private fun nameContextObjects(ctx: WindowContext, index: Int) {
        device.nameObject(ctx.surface.vkSurface, KHRSurface.VK_OBJECT_TYPE_SURFACE_KHR, "Surface[$index]")
        ctx.commandBuffers.forEachIndexed { i, cb ->
            device.nameObject(cb.vkCommandBuffer.address(), VK10.VK_OBJECT_TYPE_COMMAND_BUFFER, "GFX CB[$index,$i]")
        }
        ctx.computeCommandBuffers.forEachIndexed { i, cb ->
            device.nameObject(cb.vkCommandBuffer.address(), VK10.VK_OBJECT_TYPE_COMMAND_BUFFER, "COMPUTE CB[$index,$i]")
        }
        ctx.fences.forEachIndexed { i, f ->
            device.nameObject(f.vkFence, VK10.VK_OBJECT_TYPE_FENCE, "GFX Fence[$index,$i]")
        }
        ctx.computeFences.forEachIndexed { i, f ->
            device.nameObject(f.vkFence, VK10.VK_OBJECT_TYPE_FENCE, "Compute Fence[$index,$i]")
        }
        ctx.computeFinishedSemaphores.forEachIndexed { i, s ->
            device.nameObject(s.vkSemaphore, VK10.VK_OBJECT_TYPE_SEMAPHORE, "Compute->Graphics Semaphore[$index,$i]")
        }
        ctx.depthAttachments.forEachIndexed { i, a ->
            device.nameObject(a.image.vkImage, VK10.VK_OBJECT_TYPE_IMAGE, "DepthImage[$index,$i]")
            device.nameObject(a.imageView.vkImageView, VK10.VK_OBJECT_TYPE_IMAGE_VIEW, "DepthView[$index,$i]")
        }
    }

    private fun resize(context: WindowContext) {
        device.waitIdle()
        graphQueue.waitIdle()

        context.swapChain.cleanup()
        context.swapChain = SwapChain(
            device, context.surface, context.window, Options.requestedImages,
            Options.vsync && !context.window.bestEffortPresentation, presentQueue, arrayOf(graphQueue)
        )

        context.depthAttachments.forEach { it.cleanup() }
        context.depthAttachments = createDepthImages(context.swapChain)

        context.commandBuffers.forEach { it.cleanup() }
        context.computeCommandBuffers.forEach { it.cleanup() }
        context.fences.forEach { it.cleanup() }
        context.computeFences.forEach { it.cleanup() }
        context.computeFinishedSemaphores.forEach { it.cleanup() }
        cleanupSecondaryMap(context.computeSecondary)
        cleanupSecondaryMap(context.graphicsSecondary)

        val newImages = context.swapChain.imageViews.size
        context.commandBuffers = Array(newImages) { CommandBuffer(commandPool, primary = true, oneTimeSubmit = Options.oneTimeSubmitFrames) }
        context.computeCommandBuffers = Array(newImages) { CommandBuffer(computeCommandPool, primary = true, oneTimeSubmit = Options.oneTimeSubmitFrames) }
        context.fences = Array(newImages) { Fence(device, signaled = true) }
        context.computeFences = Array(newImages) { Fence(device, signaled = true) }
        context.computeFinishedSemaphores = Array(newImages) { Semaphore(device) }
        context.computeSecondary = mutableMapOf()
        context.graphicsSecondary = mutableMapOf()
        cleanupPoolMap(context.computePoolsPerThread)
        cleanupPoolMap(context.graphicsPoolsPerThread)
        context.computePoolsPerThread = mutableMapOf()
        context.graphicsPoolsPerThread = mutableMapOf()
        context.numImages = newImages
        context.currentImage = 0

        context.profilerState?.profiler?.destroy()
        context.profilerState = if (Options.profiling) createProfilerState(newImages) else null

        nameContextObjects(context, windowContexts.indexOf(context))
    }

var running

    @Volatile
    var running = false

Identifies the newest call to run or runForever. Live coding commonly re-launches the renderer box without stopping it first. Each invocation used to enter the render loop with its own local frame counter, so two coroutines could alternate submissions against the same per-frame fences and command buffers. The newest invocation now takes ownership at the next coroutine yield / loop boundary; an older invocation simply retires.

source — 60 lines, from line 1934
    private val runInvocation = AtomicLong(0)

    private fun cleanupSecondaryMap(map: MutableMap<String, Array<CommandBuffer>>) {
        map.values.forEach { arr -> arr.forEach { it.cleanup() } }
        map.clear()
    }

    private fun cleanupPoolMap(map: MutableMap<Long, CommandPool>) {
        map.values.forEach { it.cleanup() }
        map.clear()
    }

    private fun getSecondaryCommandBuffer(
        store: MutableMap<String, Array<CommandBuffer>>,
        key: String,
        basePool: CommandPool,
        poolMap: MutableMap<Long, CommandPool>,
        numImages: Int,
        imageIndex: Int
    ): CommandBuffer {
        val threadId = Thread.currentThread().threadId()
        val threadKey = "$key@$threadId"
        val pool = synchronized(poolMap) {
            poolMap.getOrPut(threadId) { CommandPool(basePool.device, basePool.queueFamilyIndex) }
        }
        val existing = store[threadKey]
        val buffers = if (existing == null || existing.size != numImages) {
            existing?.forEach { it.cleanup() }
            Array(numImages) { CommandBuffer(pool, primary = false, oneTimeSubmit = true) }
        } else existing
        store[threadKey] = buffers
        return buffers[imageIndex]
    }

    private fun prepareTargetMeshesForDraw(target: RenderTarget, command: CommandBuffer) {
        target.modules.forEach { (_, mp) ->
            mp.current.meshes.values.forEach { mesh ->
                mesh.prepareForDraw(command)
            }
        }
    }

    private fun setViewportAndScissor(cmd: VkCommandBuffer, rect: Rect, stack: MemoryStack) {
        val viewport = VkViewport.calloc(1, stack)
            .x(rect.x.toFloat())
            .y((rect.y + rect.h).toFloat()) // flip Y
            .height((-rect.h).toFloat())
            .width(rect.w.toFloat())
            .minDepth(0f).maxDepth(1f)
        VK10.vkCmdSetViewport(cmd, 0, viewport)

        val scissor = VkRect2D.calloc(1, stack)
            .extent(
                VkExtent2D.calloc(stack)
                    .width(rect.w.toInt())
                    .height(rect.h.toInt())
            )
            .offset(VkOffset2D.calloc(stack).x(rect.x.toInt()).y(rect.y.toInt()))
        VK10.vkCmdSetScissor(cmd, 0, scissor)
    }

fun runForever(update: () -> Unit) {

    fun runForever(update: () -> Unit) {
        val invocation = runInvocation.incrementAndGet()
        running = true
        var initialTime = System.nanoTime()
        var frame = 0
        while (running && runInvocation.get() == invocation && windows.any { !it.shouldClose() }) {
            afield.app.diagnostics.logging.PinnedConsole.beat("render")
            windows.forEach { it.pollEvents() }
            val renderStart = System.nanoTime()
            update()
            render()
            frame++
            Options.maxFrameRate.takeIf { it > 0.0 }?.let { cap ->
                val remaining = (1_000_000_000.0 / cap).toLong() - (System.nanoTime() - renderStart)
                if (remaining > 0) Thread.sleep(remaining / 1_000_000)
            }
            val now = System.nanoTime()
            if (now - initialTime >= PERF_LOG_INTERVAL_NS) {
                lprintln("info,perf,fps", "fps=${1_000_000_000.0 * frame / (now - initialTime)}")
                initialTime = now
                frame = 0
            }
        }
        // A newer live-code launch owns the same renderer now; do not tear its device down.
        if (runInvocation.get() == invocation) cleanup()
    }

suspend fun run()

    suspend fun run() {
        val invocation = runInvocation.incrementAndGet()
        running = true
        lastRunFpsLogNs = 0L
        lastRunFpsFrame = 0
        var frame = 0
        while (running && runInvocation.get() == invocation && windows.any { !it.shouldClose() }) {
            logTime("pollEvents") { windows.forEach { it.pollEvents() } }
            val renderStart = System.nanoTime()
            logTime("render") { render() }
            frame++
            Options.maxFrameRate.takeIf { it > 0.0 }?.let { cap ->
                val remaining = (1_000_000_000.0 / cap).toLong() - (System.nanoTime() - renderStart)
                if (remaining > 0) kotlinx.coroutines.delay(remaining / 1_000_000)
            }
            if (frame == 1) {
                LatencySpans.record("renderer.firstRender", renderStart, detail = "invocation=$invocation")
                if (invocation == 1L) LatencySpans.record("renderer.toFirstFrame", constructedAtNanos)
            }
            afield.app.diagnostics.logging.PinnedConsole.beat("render")
            val now = System.nanoTime()
            if (lastRunFpsLogNs == 0L) {
                lastRunFpsLogNs = now
                lastRunFpsFrame = frame
            } else if (now - lastRunFpsLogNs >= PERF_LOG_INTERVAL_NS) {
                val framesSinceLastLog = frame - lastRunFpsFrame
                lprintln("info,perf,fps", "fps=${1_000_000_000.0 * framesSinceLastLog / (now - lastRunFpsLogNs)} Thread = ${Thread.currentThread().name} = ${System.identityHashCode(Thread.currentThread().name)} ${isMainThread}")
                lastRunFpsLogNs = now
                lastRunFpsFrame = frame
            }
            logTime_("compute") { yield() }
            if (coroutineContext[Job]!!.isCancelled || runInvocation.get() != invocation) break
        }
    }

operator fun plusAssign(a: Pair<String, ModulePointer>)

    @JvmName("plusAssign_modulePointer")
    operator fun plusAssign(a: Pair<String, ModulePointer>) { renderList += a }

operator fun plusAssign(a: ModulePointer)

    @JvmName("plusAssign_modulePointer_defaultName")
    operator fun plusAssign(a: ModulePointer) { renderList += a }

operator fun plusAssign(a: Pair<String, MeshModulePointer>)

    @JvmName("plusAssign_meshModulePointer")
    operator fun plusAssign(a: Pair<String, MeshModulePointer>) { renderList += a }

operator fun plusAssign(a: MeshModulePointer)

    @JvmName("plusAssign_modulePointer_defaultName")
    operator fun plusAssign(a: MeshModulePointer) { renderList += a }

operator fun plusAssign(a: Named<ModulePointer>) { this += a.name to a.value }

operator fun plusAssign(a: Pair<String, ComputeModulePointer>)

    @JvmName("plusAssign_computeModulePointer")
    operator fun plusAssign(a: Pair<String, ComputeModulePointer>) { computeList += a }

operator fun plusAssign(a: Pair<String, RayTracingModulePointer>)

    @JvmName("plusAssign_rayTracingModulePointer")
    operator fun plusAssign(a: Pair<String, RayTracingModulePointer>) { computeList += a }

operator fun plusAssign(a: Named<ComputeModulePointer>)

    @JvmName("plusAssign_computeModulePointerNamed")
    operator fun plusAssign(a: Named<ComputeModulePointer>) { computeList += a }

operator fun plusAssign(a: Named<RayTracingModulePointer>)

    @JvmName("plusAssign_rayTracingModulePointerNamed")
    operator fun plusAssign(a: Named<RayTracingModulePointer>) { computeList += a.name to a.value }

fun getProfiler(): GpuProfiler? = defaultContext.profilerState?.profiler

source — 89 lines, from line 2086
    private fun resolveProfilingForImage(context: WindowContext, imageIndex: Int) {
        val ps = context.profilerState ?: return
        synchronized(ps.lock) {
            val p = ps.profiler
            val m = ps.markers.getOrNull(imageIndex) ?: return

            if (m.perfFrameIndex >= 0 && p.opts.perfCounters) {
                val meta = p.perfMeta
                val vals = p.fetchPerfCounters(imageIndex, m.perfFrameIndex, meta.size)
                val n = kotlin.math.min(vals.size, meta.size)
                if (n > 0) {
                    val byScope = meta.withIndex().groupBy { it.value.scope }
                    for ((scope, items) in byScope) {
                        lprintln("info,perf", "scope=$scope")
                        for (e in items) {
                            val i = e.index
                            lprintln("info,perf", "  ${e.value.name}: ${p.formatPerfValue(vals[i], e.value.unit)}")
                        }
                    }
                }
            }

            if (m.tsFrameBegin >= 0 && m.tsFrameEnd >= 0) {
                val ts = p.fetchTimestampNsRange(imageIndex, m.tsFrameBegin, m.tsFrameEnd)
                fun at(marker: Int): Long? {
                    val offset = marker - m.tsFrameBegin
                    return ts.getOrNull(offset)
                }
                val frameBegin = at(m.tsFrameBegin)
                val afterCompute = at(m.tsAfterCompute)
                val afterGraphics = at(m.tsAfterGraphics)
                val frameEnd = at(m.tsFrameEnd)
                if (frameBegin != null && afterCompute != null && afterGraphics != null && frameEnd != null) {
                    val frameNs = frameEnd - frameBegin
                    val computeNs = afterCompute - frameBegin
                    val graphicsNs = afterGraphics - afterCompute
                    val postNs = frameEnd - afterGraphics
                    lprintln(
                        "info,perf",
                        "GPU frame=${frameNs/1e6} ms (compute=${computeNs/1e6} ms, graphics=${graphicsNs/1e6} ms, post=${postNs/1e6} ms)"
                    )
                }
            }

            if (m.pipeStatsCompute >= 0 && p.opts.pipelineStats) {
                val ps = p.fetchPipelineStats(imageIndex, m.pipeStatsCompute)
                lprintln("info,perf", "CS invocations: ${ps.getOrNull(10) ?: -1}")
            }
            if (m.pipeStatsGraphics >= 0 && p.opts.pipelineStats) {
                val ps = p.fetchPipelineStats(imageIndex, m.pipeStatsGraphics)
                val iaVerts = ps.getOrNull(0) ?: -1
                val iaPrims = ps.getOrNull(1) ?: -1
                val vsInv   = ps.getOrNull(2) ?: -1
                val fsInv   = ps.getOrNull(7) ?: -1
                lprintln("info,perf", "GFX IA verts=$iaVerts, IA prims=$iaPrims, VS invoc=$vsInv, FS invoc=$fsInv")
            }

            val pm = ps.perImageModuleMarks.getOrNull(imageIndex) ?: mutableListOf()
            if (pm.isNotEmpty()) {
                for (mm in pm) {
                    if (mm.tsBegin >= 0 && mm.tsEnd >= 0) {
                        val ts = p.fetchTimestampNsRange(imageIndex, mm.tsBegin, mm.tsEnd)
                        if (ts.size >= 2) {
                            val dtMs = (ts.last() - ts.first()) / 1e6
                            lprintln("info,perf", "MOD '${mm.name}': ${"%.3f".format(dtMs)} ms")
                        }
                    }
                    if (mm.pipeStatsIndex >= 0 && p.opts.pipelineStats) {
                        val ps = p.fetchPipelineStats(imageIndex, mm.pipeStatsIndex)
                        val fsInv = ps.getOrNull(7) ?: -1
                        lprintln("info,perf", "MOD '${mm.name}': FS invocations=$fsInv")
                    }
                    if (mm.perfIndex >= 0 && p.opts.perfCounters) {
                        val meta = p.perfMeta
                        val vals = p.fetchPerfCounters(imageIndex, mm.perfIndex, meta.size)
                        val n = kotlin.math.min(vals.size, meta.size)
                        if (n > 0) {
                            lprintln("info,perf", "MOD '${mm.name}' PERF:")
                            for (i in 0 until n) {
                                val mi = meta[i]
                                lprintln("info,perf", "  ${mi.name}: ${p.formatPerfValue(vals[i], mi.unit)}")
                            }
                        }
                    }
                }
            }
        }
    }
}