Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Vulkan API with Kotlin Native - Surface, Devices

5.00/5 (1 vote)
26 Apr 2019GPL32 min read 9.5K  
Vulkan surface, physical and logical devices with Kotlin Native

Introduction

Ok, now it's time to create Vulkan surface, physical and logical devices. Vulkan surface is an abstraction to a Windows, Linux or Android window we already created (or another supported platform). A physical device is what it says - installed physical device. A logical device is a physical device from the user's point of view. Also, while creating a logical device, we'll create a command pool and a queue. But first of all, let me introduce small changes to the code. As we work on one and only one platform, we'll change the Platform class to a singleton. In Kotlin, it's very easy - just change class to object. Correspondingly, it should be done for both expect and actual.

Surface

The Vulkan surface we will create in actual Platform class as its creation is different for each platform. It depends if the corresponding extension is supported. VK_KHR_WIN32_SURFACE_EXTENSION_NAME - for Windows, VK_KHR_XCB_SURFACE_EXTENSION_NAME - for Linux, etc.. For Linux, we also already know xcb_connection_t and a window id. For Windows - HWND and HINSTANCE.

So, for Windows, it will look like this:

Kotlin
...
            val surfaceInfo = alloc<VkWin32SurfaceCreateInfoKHR> {
                sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR
                pNext = null
                flags = 0u
                @Suppress("UNCHECKED_CAST")
                hinstance = sharedData.hInstance!! as vulkan.HINSTANCE
                @Suppress("UNCHECKED_CAST")
                hwnd = sharedData.hwnd!! as vulkan.HWND

            }

            if (!VK_CHECK(vkCreateWin32SurfaceKHR
                  (instance, surfaceInfo.ptr, null, surfaceVar.ptr))) {
                throw RuntimeException("Failed to create surface.")
            }
...

And for Linux:

Kotlin
...
            val surfaceinfo = alloc<VkXcbSurfaceCreateInfoKHR> {
                sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR
                pNext = null
                flags = 0u
                window = sharedData.wnd
                connection = sharedData.connection

            }

            val surface = alloc<VkSurfaceKHRVar>()

            if (!VK_CHECK(vkCreateXcbSurfaceKHR
                 (instance, surfaceinfo.ptr, null, surface.ptr))) {
                throw RuntimeException("Failed to create surface.")
            }

...

Nothing new, just structure and function call.

Physical Device

Here, we'll be a little more interesting. We will enumerate physical devices, select a first discrete one, get a driver version, the surface capabilities, the device extensions. Now let's look for and select a graphics card in the system that supports the features we need. To do it, use the standard way we already used:

Kotlin
...
         var buffer: CArrayPointer<VkPhysicalDeviceVar>? = null

            while (result == VK_INCOMPLETE) {

                if (!VK_CHECK(vkEnumeratePhysicalDevices(instance, gpuCount.ptr, null)))
                    throw RuntimeException("Could not enumerate GPUs.")

                buffer?.let {
                    nativeHeap.free(it)
                    buffer = null
                }

                buffer = nativeHeap.allocArray(gpuCount.value.toInt())

                result = vkEnumeratePhysicalDevices(instance, gpuCount.ptr, buffer)
                if (result != VK_INCOMPLETE && !VK_CHECK(result))
                    throw RuntimeException("Could not enumerate GPUs.")
            }
...

After we have the physical device, we can get its properties. For this, we'll create lazy initialized properties for surface capabilities, device properties, queue properties and so on. For example, if we want to get a presentable queue only, it will look like this:

Kotlin
...
    val presentableQueue by lazy {

        val props = queueProperties.filter {
            (it.queueFlags and VK_QUEUE_GRAPHICS_BIT) > 0u
        }

        memScoped {

            props.mapIndexed { index, it ->
                Pair(index, it)
            }.indexOfFirst {
                val p = alloc<VkBool32Var>()
                vkGetPhysicalDeviceSurfaceSupportKHR(device, it.first.toUInt(), surface, p.ptr)
                if (p.value == 1u) true else false
            }
        }
    }
...

Ok, now we have all information about our physical device.

Logical Device

Now we'll create a connection to our physical device. In accordance with Vulkan specification: "An application must create a separate logical device for each physical device it will use. The created logical device is then the primary interface to the physical device." So, let's do it, along the way also creating a queue and a command pool:

Kotlin
...            
            val queueCreateInfos: ArrayList<VkDeviceQueueCreateInfo> = ArrayList()
            val defaultQueuePriority = allocArrayOf(1.0f)

            val queueInfo = alloc<VkDeviceQueueCreateInfo>().apply {
                sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO
                queueCount = 1u
                pQueuePriorities = defaultQueuePriority

            }

            if (presentable) {     
                val queue = pdevice.presentableQueue

                if (queue < 0)
                    throw RuntimeException("Presentable queue not found")

                _queueFamilyIndices.graphics = queue.toUInt()
                queueInfo.queueFamilyIndex = _queueFamilyIndices.graphics!!
                _poolType = PoolType.GRAPHICS
            else {
                ...
            }

            if (useSwapChain && !enabledExtensions.contains(VK_KHR_SWAPCHAIN_EXTENSION_NAME))
                enabledExtensions.add(VK_KHR_SWAPCHAIN_EXTENSION_NAME)

            var idx = 0
            val queueInfos = allocArray<VkDeviceQueueCreateInfo>(queueCreateInfos.size) {
                val info = queueCreateInfos[idx++]
                this.flags = info.flags
                this.sType = info.sType
                this.flags = info.flags
                this.pNext = info.pNext
                this.pQueuePriorities = info.pQueuePriorities
                this.queueCount = info.queueCount
                this.queueFamilyIndex = info.queueFamilyIndex
            }

            val deviceCreateInfo = alloc<VkDeviceCreateInfo>().apply {
                sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO
                queueCreateInfoCount = queueCreateInfos.size.toUInt()
                pQueueCreateInfos = queueInfos
                pEnabledFeatures = enabledFeatures?.ptr
            }

            if (enabledExtensions.size > 0) {
                deviceCreateInfo.enabledExtensionCount = enabledExtensions.size.toUInt()
                deviceCreateInfo.ppEnabledExtensionNames = 
                                 enabledExtensions.toCStringArray(memScope)
            }

            if (!VK_CHECK(vkCreateDevice
                   (pdevice.device, deviceCreateInfo.ptr, null, _device.ptr)))
                throw RuntimeException("Failed to create _device")
            logDebug("Ok logical device")

            ....
            
            if (!VK_CHECK(vkCreateCommandPool
                  (_device.value, commandPoolCreateInfo.ptr, null, _commandPool.ptr)))
                throw RuntimeException("Failed to create command pool")
            
            vkGetDeviceQueue(
                _device.value, when (_poolType) {
                    PoolType.GRAPHICS -> _queueFamilyIndices.graphics!!
                    PoolType.COMPUTE -> _queueFamilyIndices.compute!!
                    PoolType.TRANSFER -> _queueFamilyIndices.transfer!!
                }, 0u, _deviceQueue.ptr
            )

           
...

Fine. Now we have the physical device, the logical device, the command pool and the queue. Add them to our renderer class and don't forget to dispose resources and free allocated memory.

History

  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. Vulkan API with Kotlin Native - Surface, Devices
  5. Vulkan API with Kotlin Native - SwapChain, Pipeline
  6. Vulkan API with Kotlin Native - Draw

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)