Vulkan API with Kotlin Native - Instance





2.00/5 (4投票s)
使用 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()
渲染循环将在稍后添加... 目前就这些...