Skip to content

RendererCleanupSmoke.kt

src/jvmMain/kotlin/afield/examples/four/RendererCleanupSmoke.kt · 100 lines

package afield.examples.four

import afield.graphics.vulkan.Renderer
import afield.graphics.vulkan.buffers.VulkanBuffer
import afield.graphics.vulkan.buffers.allocators
import afield.graphics.vulkan.device.Fence
import afield.graphics.vulkan.targets.RenderToTexture
import afield.graphics.vulkan.util.AutoDisposer
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.Window
import org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
import org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
import kotlin.system.exitProcess

FIELD_TEST id: four.renderer-cleanup.smoke requiresDisplay: true runner: jvm-main entrypoint: afield.examples.four.RendererCleanupSmoke tags: [smoke, four, gpu, lifecycle] timeoutSec: 180 expect: exitCode: 0 stdoutContains: - "renderer deferred cleanup PASSED" stdoutNotContains: - "VUID-" - "Validation Error" stderrNotContains: - "VUID-" - "Validation Error"

object RendererCleanupSmoke

Close immediately after buffer replacement, before deferred releases can age out.

fun main(args: Array<String>)

body — 64 lines, from line 36
    @JvmStatic
    fun main(args: Array<String>) {
        Options.doVulkanSDK()
        Options.enableVR = false
        Options.validate = true
        val window = Window("Renderer cleanup regression")
        val renderer = Renderer(window)
        val released = mutableListOf<String>()
        val retained = VulkanBuffer(
            renderer, 256L, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
        )
        val fence = Fence(renderer.device, true)
        val surface = RenderToTexture(renderer) {
            width = 32
            height = 32
        }
        surface.setDefaultSampler()

        try {
            check(renderer.instance.validationActive) {
                "This lifecycle regression requires the Vulkan validation layer and debug messenger"
            }
            val buffer = VulkanBuffer(
                renderer, 64L, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
                VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
            )
            buffer.realloc(128L)
            renderer.cleanup += Runnable { released += "retired" }
            // One real frame retires the first allocation, but cannot release it yet.
            window.pollEvents()
            renderer.render()
            check(released.isEmpty())
            check(renderer.cleanupStack.any { it.second.isNotEmpty() })

            buffer.pushCleanup()
            // An explicit cleanup after handing ownership off must be harmless too.
            buffer.cleanup()
            renderer.cleanup += Runnable {
                released += "current"
                renderer.cleanup += Runnable { released += "dependent" }
            }
        } finally {
            try {
                renderer.cleanup()
            } finally {
                window.cleanup()
            }
        }

        check(released == listOf("retired", "current", "dependent")) { released }
        check(renderer.cleanup.isEmpty() && renderer.cleanupStack.isEmpty())
        check(AutoDisposer.ownedCount(renderer.device) == 0)
        check(renderer !in allocators)
        // Documents can retain wrappers after closing. Late explicit cleanup must be harmless.
        retained.cleanup()
        fence.cleanup()
        renderer.cleanup()
        check(runCatching { renderer.render() }.exceptionOrNull() is IllegalStateException)
        java.lang.ref.Reference.reachabilityFence(surface)
        println("renderer deferred cleanup PASSED")
        exitProcess(0)
    }
}