Kotlin Native 与 Vulkan API - 表面,设备





5.00/5 (1投票)
使用 Kotlin Native 创建 Vulkan 表面、物理设备和逻辑设备
引言
好的,现在是时候创建 Vulkan 表面、物理设备和逻辑设备了。Vulkan 表面是对我们已经创建的 Windows、Linux 或 Android 窗口(或其他受支持平台)的一种抽象。物理设备就是字面意思——安装的物理设备。逻辑设备是用户视角的物理设备。此外,在创建逻辑设备时,我们将创建一个命令池和一个队列。但首先,让我介绍一下代码中的一些小改动。由于我们只在一个平台上工作,我们将把 Platform
类更改为单例。在 Kotlin 中,这非常容易——只需将 class
更改为 object
。相应地,这应该对 expect
和 actual
都进行处理。
表面
我们将会在实际的 Platform
类中创建 Vulkan 表面,因为它的创建方式因平台而异。它取决于是否支持相应的扩展。VK_KHR_WIN32_SURFACE_EXTENSION_NAME
- 用于 Windows,VK_KHR_XCB_SURFACE_EXTENSION_NAME
- 用于 Linux,等等。对于 Linux,我们已经知道 xcb_connection_t
和窗口 id
。对于 Windows - HWND
和 HINSTANCE
。
所以,对于 Windows,它看起来像这样
...
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.")
}
...
对于 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.")
}
...
没什么新鲜的,只是结构和函数调用。
物理设备
在这里,我们会更有趣一些。我们将枚举物理设备,选择第一个离散设备,获取驱动程序版本、表面功能、设备扩展。现在让我们寻找并选择系统中支持我们所需功能的显卡。为此,使用我们已经使用的标准方法
...
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.")
}
...
在获得物理设备后,我们可以获取其属性。为此,我们将为表面功能、设备属性、队列属性等创建延迟初始化的属性。例如,如果我们只想获取一个可呈现的队列,它看起来像这样
...
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
}
}
}
...
好的,现在我们拥有了关于物理设备的所有信息。
逻辑设备
现在我们将创建到物理设备的连接。根据 Vulkan 规范:“应用程序必须为它将使用的每个物理设备创建一个单独的逻辑设备。创建的逻辑设备是与物理设备的主要接口。” 所以,让我们这样做,同时创建队列和命令池
...
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
)
...
很好。现在我们拥有了物理设备、逻辑设备、命令池和队列。将它们添加到我们的渲染器类中,并且不要忘记释放资源并释放分配的内存。