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:
...
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:
...
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:
...
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:
...
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:
...
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
- Vulkan API with Kotlin Native - Project Setup
- Vulkan API with Kotlin Native - Platform's Windows
- Vulkan API with Kotlin Native - Instance
- Vulkan API with Kotlin Native - Surface, Devices
- Vulkan API with Kotlin Native - SwapChain, Pipeline
- Vulkan API with Kotlin Native - Draw