65.9K
CodeProject 正在变化。 阅读更多。
Home

Vulkan API with Kotlin Native - Instance

starIconstarIconemptyStarIconemptyStarIconemptyStarIcon

2.00/5 (4投票s)

2019 年 4 月 3 日

GPL3

3分钟阅读

viewsIcon

7178

使用 Kotlin Native 创建 Vulkan 实例。

引言

在之前的章节中,我们已经完成了所有准备工作,所以现在终于可以开始使用 Vulkan API 了。让我们总结一下我们目前所拥有的:

  • 该项目编译成适用于 Windows 和 Linux 平台的本机代码。
  • 与 C 库的互操作。
  • 适用于两个平台的原生窗口,我们可以移动、调整大小、切换到全屏模式等等。
  • 我们可以检查我们在哪个平台上,并定义相应的 Vulkan 扩展来使用。
  • 添加了到所有需要的共享库的链接,包括 Vulkan 加载器、调试层库等。
  • 添加了从源代码编译着色器的任务
  • Vulkan API 被声称为一个跨平台的图形 API(不仅仅是,它也用于计算),具有 GPU 直接控制。因此,我们还创建了项目的公共部分,我们将在其中使用它。

Vulkan API 初看似乎很复杂。但一步一步地沉浸其中,理解就会变得更容易。最重要的是——结构体,结构体,还是结构体... 结构体用于定义你想要获得的行为。使用 Vulkan API 的第一件事是 Vulkan 实例。所以,作为使用 Kotlin Native 和 Vulkan API 的首次尝试,让我们详细了解实例的创建。

Vulkan 实例

正如我上面提到的,要做的第一件事——我们需要创建一个 Vulkan 实例。但是我们应该告诉 API 它应该如何创建。例如,我们应该说我们将使用哪个平台表面,每个平台都不同。为了说明这一点,我们必须检查驱动程序支持的扩展。在这里,我们将获得可用的扩展。

 private fun setupExtensions(scope: MemScope): MutableList<String> {

        val availableInstanceExtensions: MutableList<String> = ArrayList()

        with(scope) {

            val extensionsCount = alloc<UIntVar>()
            extensionsCount.value = 0u
            var result: VkResult

            // Enumerate _instance extsensions and check if they're available
            do {
                result = vkEnumerateInstanceExtensionProperties(null, extensionsCount.ptr, null)

                if (!VK_CHECK(result)) throw RuntimeException
                                 ("Could not enumerate _instance extensions.")

                if (extensionsCount.value == 0u) break

                val buffer = allocArray<VkExtensionProperties>(extensionsCount.value.toInt())
                result = vkEnumerateInstanceExtensionProperties(null, extensionsCount.ptr, buffer)

                for (i in 0 until extensionsCount.value.toInt()) {
                    val ext = buffer[i].extensionName.toKString()
                    if (!availableInstanceExtensions.contains(ext))
                        availableInstanceExtensions.add(ext)
                }

            } while (result == VK_INCOMPLETE)
        }

        return availableInstanceExtensions
    }

在这里,我们传递了之前在初始化函数中创建的内存作用域。 内存作用域定义了在其中分配的内存的生命周期。然后,我们使用 vkEnumerateInstanceExtensionProperties API 函数来获取可用的扩展并将它们添加到列表中。

另外,我们需要获取可用的调试层

     /**
     * Prepare debug layers
     */
    private fun prepareLayers(scope: MemScope, instanceCreateInfo: 
                              VkInstanceCreateInfo): MutableList<String> {

        logInfo("Preparing Debug Layers")
        val availableLayers = mutableListOf<String>()

        with(scope) {

            // Layers optimal order: 
            // <a href='https://vulkan.lunarg.com/doc/view/1.0.13.0/windows/layers.html'/>
            val layers = arrayOf(
                "VK_LAYER_GOOGLE_threading",
                "VK_LAYER_LUNARG_parameter_validation",
                "VK_LAYER_LUNARG_object_tracker",
                "VK_LAYER_LUNARG_core_validation",
                "VK_LAYER_GOOGLE_unique_objects",
                "VK_LAYER_LUNARG_standard_validation"
            )

            val layersCount = alloc<UIntVar>()

            var result: VkResult

            run failure@{

                do {

                    // Enumerate available layers
                    result = vkEnumerateInstanceLayerProperties(layersCount.ptr, null)
                    if (!VK_CHECK(result)) {
                        logError("Failed to enumerate debug layers")
                        availableLayers.clear()
                        return@failure // failed to get layers break the loop

                    } else {

                        val buffer = allocArray<VkLayerProperties>(layersCount.value.toInt())

                        result = vkEnumerateInstanceLayerProperties(layersCount.ptr, buffer)
                        if (!VK_CHECK(result)) {
                            logError("Filed to enumerate Debug Layers to buffer")
                            availableLayers.clear()
                            return@failure // failed to get layers break the loop

                        }

                        for (i in 0 until layersCount.value.toInt()) {

                            val layer = buffer[i].layerName.toKString()
                            logDebug("Found $layer layer")
                            if (!availableLayers.contains(layer) && layers.contains(layer)) {
                                availableLayers.add(layer)
                                logDebug("$layer added")
                            }
                        }
                    }

                } while (result == VK_INCOMPLETE)
            }

            // Setting debug layers it they're available
            if (availableLayers.size > 0) {

                if (availableLayers.contains("VK_LAYER_LUNARG_standard_validation"))
                    availableLayers.removeAll {
                        it != "VK_LAYER_LUNARG_standard_validation"
                    }
                else
                // sort available layers in accordance with recommended order
                    availableLayers.sortBy {
                        layers.indexOf(it)
                    }

                logInfo("Setting up Layers:")
                availableLayers.forEach {
                    logInfo(it)
                }

                instanceCreateInfo.enabledLayerCount = availableLayers.size.toUInt()
                instanceCreateInfo.ppEnabledLayerNames =
                        availableLayers.toCStringArray(scope)
            }
        }

        return availableLayers
    }

同样,通过使用 vkEnumerateInstanceLayerProperties,我们创建了可用调试层的列表。我应该稍微解释一下最后一部分。 在 layers 中,我定义了标准调试层 - 它们与 VK_LAYER_LUNARG_standard_validation 层相同。 但有时,它返回此列表或仅返回 VK_LAYER_LUNARG_standard_validation,所以目前,我们将仅使用标准层,我们将检查是否仅保留 VK_LAYER_LUNARG_standard_validation 或列表。 最后。 我们将准备好的层设置为 Instance Create Info 结构,转换为 C string 数组。

现在是创建实例的时候了

            ...
             
            // Application info
            val appInfo = alloc<VkApplicationInfo>().apply {
                sType = VK_STRUCTURE_TYPE_APPLICATION_INFO
                pNext = null
                apiVersion = VK_MAKE_VERSION(1u, 0u, 0u)
                applicationVersion = VK_MAKE_VERSION(1u, 0u, 0u)
                engineVersion = VK_MAKE_VERSION(1u, 0u, 0u)
                pApplicationName = "kvarc".cstr.ptr
                pEngineName = "kvarc".cstr.ptr
                apiVersion = VK_API_VERSION_1_0.toUInt()
            }

            var instanceExt: Array<String> =
                arrayOf(VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_PLATFORM_SURFACE_EXTENSION_NAME)

            val debugSupported = availableInstanceExtensions.contains("VK_EXT_debug_report")
            if (debug && debugSupported) instanceExt += "VK_EXT_debug_report"

            // Debug layers will be added a little later if needed
            val instanceCreateInfo = alloc<VkInstanceCreateInfo>().apply {
                sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO
                pNext = null
                pApplicationInfo = appInfo.ptr
                enabledExtensionCount = instanceExt.size.toUInt()
                ppEnabledExtensionNames = instanceExt.toCStringArray(memScope)
                enabledLayerCount = 0u
                ppEnabledLayerNames = null
            }

            logInfo("Debug: $debug, DebugSupported: $debugSupported")
            val availableLayers =
                if (debug && debugSupported) prepareLayers(this, instanceCreateInfo) else ArrayList()

            logInfo("Creating _instance")
            if (!VK_CHECK(vkCreateInstance(instanceCreateInfo.ptr, null, _instance.ptr)))
                throw RuntimeException("Failed to create _instance")

            ...

在这里,我们定义了应用程序信息结构——将其类型、所需的 API 版本、应用程序信息、转换为 C 字符串的应用程序名称等传递给它......同样,我们定义了实例创建信息。最后,通过 vkCreateInstance 调用创建实例。

最后一件事是设置调试回调。 这里最有趣的部分是——设置回调本身

                ...
                pfnCallback = staticCFunction { flags, _, _, _, msgCode, pLayerPrefix, pMsg, _ ->

                    var prefix = "kvarc-"

                    when {

                        flags and VK_DEBUG_REPORT_ERROR_BIT_EXT > 0u -> prefix += "ERROR:"
                        flags and VK_DEBUG_REPORT_WARNING_BIT_EXT > 0u -> prefix += "WARNING:"
                        flags and VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT > 0u -> prefix += "PERFORMANCE:"
                        flags and VK_DEBUG_REPORT_INFORMATION_BIT_EXT > 0u -> prefix += "INFO:"
                        flags and VK_DEBUG_REPORT_DEBUG_BIT_EXT > 0u -> prefix += "DEBUG:"

                    }

                    val debugMessage =
                        "$prefix [${pLayerPrefix?.toKString() ?: ""}] Code $msgCode:${pMsg?.toKString() ?: ""}"

                    if (flags and VK_DEBUG_REPORT_ERROR_BIT_EXT > 0.toUInt()) {
                        logError(debugMessage)
                    } else {
                        logDebug(debugMessage)
                    }

                    // abort/not
                    VK_FALSE.toUInt()
               }
               ...

只需使用 staticCFunction 分配即可完成。 在它的主体中,我们只需编写回调参数和所需的代码。

所以,我们刚刚使用 Kotlin Native 创建了第一个 Vulkan 实例。 很简单,不是吗? 现在是使用它的时候了。 让我们创建一个 renderer

@ExperimentalUnsignedTypes
internal class Renderer : DisposableContainer() {

    private var _instance: Instance? = null

    fun initialize() {
        _instance = Instance()
        _instance!!.initialize(true)
    }

    override fun dispose() {
        _instance?.dispose()
        super.dispose()
    }
}

为什么不是在 init{} 中,而是 initialize? 因为我们会有很多需要在最后处理的资源——实例、设备等... 如果出现问题,我们将没有 renderer 对象,并且无法处理它。

现在,我们应该将渲染器调用添加到两个平台

    var renderer = Renderer()
    try {        
        renderer.initialize()
    } catch (exc: Exception) {
        logError(exc.message ?: "Failed to create renderer")
    }
    renderer.dispose() 

渲染循环将在稍后添加... 目前就这些...

历史

  1. Vulkan API with Kotlin Native - Project Setup
  2. Vulkan API with Kotlin Native - Platform's Windows
  3. Vulkan API with Kotlin Native - Instance
  4. Kotlin Native 与 Vulkan API - 表面,设备
  5. Kotlin Native 与 Vulkan API - 交换链,管道
  6. Vulkan API with Kotlin Native - Draw
© . All rights reserved.