Vulkan API with Kotlin Native - Project Setup





5.00/5 (1投票)
使用 Vulkan API 和 Kotlin Native 示例。
引言
通过这篇文章,我将开始一个系列,介绍如何使用 Kotlin Native 来处理 Vulkan API。什么是 Kotlin Native?它旨在将您的代码编译成不需要 Java 虚拟机或其他虚拟机的二进制文件。因此,它应该可以生成平台独立的代码(除了真正依赖于平台的部分)。Kotlin Native 可以生成 arm32
、arm64
、x86_64
、wasm32
等。我们主要关注 arm 和 x86 二进制文件。Kotlin Native 可以通过所谓的 cinterop 与 C 和 C++ 库协同工作。它仍处于开发阶段,因此在使用过程中存在一些困难。首先,目前没有 IDE 对其提供良好的支持。我尝试过:IntelliJ Clion 试用版,我可以创建项目,但无法获得一个可用的多平台项目。Android Studio - 对 Android 来说工作正常,但对其他平台不行。 IntelliJ IDEA 社区版 - 是唯一一个能正常工作但有一些限制的版本。而且,没有一个能调试项目。因此,如果能有 Vulkan 调试层和 RenderDoc 的帮助会很好。目前编译过程还不够快。还有一些其他问题……我将在本文中介绍它们的解决方法。总之,当它变得成熟时,它似乎是与 C++ 一起开发图形、数学、科学等库的一个不错的选择。首先,我们将处理 Windows 和 Linux 平台,并在系列文章的最后,我们将添加 Android 支持。至于 Vulkan API,我推荐 Sascha Willems 提供的 C++ 优秀示例,或者 Kronos 的初学者示例集。
首先,几段引言
如果您对 Kotlin/Native 的性能感兴趣,您真的应该选择其他方案。Kotlin/Native 的“原生”并非是为了性能。它适用于您无法承担 JVM(如 iOS 应用)并希望在平台之间共享逻辑,或者只是想编写简洁易读的代码的情况。Kotlin/JVM 比 K/N 更快,而 Rust 比两者都快。
ark1 JetBrains 团队
如果我们比较开发时间和上市时间,我会说 Kotlin/Native 应该快得多 :)。开玩笑。这确实取决于工作负载,对于真正繁重的计算工作负载,如图像处理和大量加密,我建议两者都不用,而依赖 C。对于 Kotlin/Native 设计的任务,性能应该是可接受的,甚至是出色的,尤其是当衡量标准不是原始吞吐量,而是感知性能时。
Nikolay_Igotti Kotlin 团队
正如您将在此项目中看到的,Kotlin 是一种优秀的语言。它易于学习,易于使用。我希望 Jetbrains 团队和 Kotlin 团队都能改变他们对 Kotlin Native 性能的看法,Kotlin Native 能够朝这个方向发展。
那么,我们能用 Kotlin/Native 做什么?
- 编译到 arm 和 x86 平台。所有变体都可从预设中获得。
- 生成可执行文件。
- 生成静态或动态库。
- 生成 Apple 框架。
- 使用外部静态/动态库或 Apple 框架。
我们不能做什么?
- 调试项目。
- 使用 C/C++ 部分的宏。
- 我找不到检查是否处于调试模式或运行时的方法。
- 没有内建函数或内联汇编 - 仅在 C/C++ 部分。
- 尚无协程。
让我们开始...
工具
- IntelliJ IDEA
- Vulkan SDK
- RenderDoc
- MINGW(用于 Windows)
- Android Studio
- Android NDK
- 对于 Linux - 必备构建工具,gcc-multilib,g++-multilib,xcb,xkb,xkbcommon,xkbcommon-x11,xcb-xkb,Xxf86vm,xkb 扩展,Mesa 库。
设置项目
在 IntelliJ IDEA 中,创建一个新的“Native”项目(New project -> Kotlin -> Kotlin Native,并勾选“Use Auto-Import”,这样在 Gradle 文件更改时就不会每次都询问)。不要使用“Multiplatform”项目!使用它,您将无法使用通用的原生代码。IDE 会创建一个项目,其目标平台取决于您所在的平台。别担心,我们将使用项目的 Gradle 文件手动添加所有必需的平台。如果您在 Windows 平台,您会看到示例包和包含 main
函数的 SampleMingw
文件,将它们重构为您想要的名称,对于我们的项目,我将使用 kv(Kotlin Vulkan)缩写作为包名,并将 MainMingw
作为 Windows 的主文件名。对于其他平台,请按照与 Windows 相同的步骤操作,只需替换“sample”和“platform”名称。
让我们看一下项目的 Gradle 文件。在 runProgram
块中,将黄色的 "args
''''更改为 "args []
",这样它就不会再烦扰我们了,并将其重命名为 runReleaseLinuxProgram
。然后,复制/粘贴此块,将其重命名为 runDebugLinuxProgram
,并将 builldType
更改为 "DEBUG
"。我们将使用这个。
现在,如果您打开 IDE 左侧的“Gradle panel”,您将看到两个可以运行的任务:
runDebugLinuxProgram
runReleaseLinuxProgram
右键单击并运行。现在您可以泡杯咖啡,等待 cinerops 完成。幸运的是,这只需要一次,之后 IDE 将使用已编译的二进制文件。结果,您将在 IDE 底部的“Run”选项卡中看到“Hello, Kotlin/Native!
”的输出。
要访问 Vulkan SDK 的包含文件和库,请添加 "VULKAN_SDK_ROOT
" 环境变量,对于 Windows 还要添加 "MINGW_ROOT
" 环境变量。将它们的值设置为相应的文件夹。现在我们可以在 Gradle 文件中使用它们了。
// MINGW and Vulkan SDK paths
def ENV = System.getenv()
project.ext.set("MINGW", ENV['MINGW_ROOT'])
project.ext.set("VULKAN", ENV['VULKAN_SDK_ROOT'])
现在我们可以添加与 C 库的互操作。为此,在项目源文件夹中创建一个 nativeInterop/cinterop 文件夹,并添加以下文件:
Lvulkan.def (用于 Linux) - 在这里,我们将添加所有必需的包含文件,并将 xkb_event
定义为我们无法在 Kotlin 中添加联合体。
package = vulkan
headers = vulkan/vulkan.h X11/Xlib.h X11/Xatom.h X11/keysym.h X11/extensions/xf86vmode.h X11/
XKBlib.h X11/extensions/XKBproto.h stdio.h stdlib.h locale.h unistd.h termios.h xcb/xkb.h
xkbcommon/xkbcommon-x11.h xkbcommon/xkbcommon-compose.h
---
union xkb_event {
struct {
uint8_t response_type;
uint8_t xkbType;
uint16_t sequence;
xcb_timestamp_t time;
uint8_t deviceID;
} any;
xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify;
xcb_xkb_map_notify_event_t map_notify;
xcb_xkb_state_notify_event_t state_notify;
};
vulkan.def (用于 Windows)
package = vulkan
headers = vulkan/vulkan.h
以及 global.def (用于在线程之间传递数据,取自 Kotlin Native 示例)。
package = kvn.global
---
typedef struct {
void* kotlinObject;
} SharedDataStruct;
SharedDataStruct sharedData;
现在要使用它们,请像这样修改 Gradle 文件 - 我们将添加 Kotlin targets 部分。
对于 Linux
configure([linux]) {
// Comment to generate Kotlin/Native library (KLIB) instead of executable file:
compilations.main.outputKinds('EXECUTABLE')
// fully qualified name of the application's entry point:
compilations.main.entryPoint = 'kvarc.main'
compilations.main.linkerOpts "-L${project.VULKAN}lib -L/usr/lib/x86_64-linux-gnu
-lVkLayer_threading -lVkLayer_core_validation -lVkLayer_object_tracker
-lVkLayer_parameter_validation -lVkLayer_unique_objects -lvulkan -lX11 -lxcb
-lxkbcommon -lxkbcommon-x11 -lxcb-xkb -lXxf86vm -lxcb"
compilations.main.cinterops {
lvulkan {
includeDirs "/usr/include", "${project.VULKAN}/include"
compilerOpts "-DVK_USE_PLATFORM_XCB_KHR"
}
global
}
}
适用于 Windows
configure([mingw]) {
// Comment to generate Kotlin/Native library (KLIB) instead of executable file:
compilations.main.outputKinds('EXECUTABLE')
// Fully qualified name of the application's entry point:
compilations.main.entryPoint = 'kvarc.main'
compilations.main.linkerOpts "-L${project.VULKAN}lib -lvulkan-1
-lVkLayer_threading -lVkLayer_core_validation -lVkLayer_object_tracker
-lVkLayer_parameter_validation -lVkLayer_unique_objects"
compilations.main.cinterops {
vulkan {
compilerOpts "-DVK_USE_PLATFORM_WIN32_KHR"
includeDirs "${project.MINGW}\\include", "${project.VULKAN}\\include"
}
global
}
}
发生了什么?在 "outputKinds
" 中,我们指定需要一个可执行文件。程序的入口点是 "kvarc.main
" 函数 - "kvarc
" 是包名,就像 Java 一样。在 "linkerOpts
" 中,我们设置了库的路径和所需的库。
现在,如果您尝试运行程序,它将在加载库时崩溃。在 Windows 上,只需将路径添加到 PATH
变量;在 Linux 上,添加到 LD_LIBRARY_PATH
。
在那之后,让我们对 Gradle 构建文件做一些更多的修改。但首先,让我们将着色器添加到项目中。将 shaders 文件夹添加到项目中,并在那里添加三角形的顶点和片段着色器(感谢 Sascha Willems)。
顶点着色器
#version 450
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inColor;
layout (binding = 0) uniform UBO
{
mat4 projectionMatrix;
mat4 modelMatrix;
mat4 viewMatrix;
} ubo;
layout (location = 0) out vec3 outColor;
out gl_PerVertex
{
vec4 gl_Position;
};
void main()
{
outColor = inColor;
gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPos.xyz, 1.0);
}
片段着色器
#version 450
layout (location = 0) in vec3 inColor;
layout (location = 0) out vec4 outFragColor;
void main()
{
outFragColor = vec4(inColor, 1.0);
}
现在是一个用于运行平台相关构建的强大函数。
// Task create function to compile project, compile shaders and run program
def createRunTask(def buildType, def platform ) {
return tasks.create("tmpRunProgram${buildType.capitalize()}${platform}") {
// get link executable task name
def depends = platform.substring(0,1).capitalize() + platform.substring(1)
dependsOn "link${buildType.capitalize()}Executable$depends"
doLast {
def folder = new File
( "${projectDir}/build/bin/$platform/main/$buildType/executable/assets/shaders" )
// check if a folder for assets exists and create if not
if( !folder.exists() ) {
folder.mkdirs()
}
def shaders = fileTree("${projectDir}/src/shaders").filter { it.isFile() }.files.name
shaders.each { shader ->
exec {
workingDir "."
commandLine "${project.VULKAN}/bin/glslangValidator", "-V",
"$projectDir/src/shaders/$shader", "-o", "$folder/${shader}.spv"
//store the output instead of printing to the console:
standardOutput = new ByteArrayOutputStream()
//extension method to obtain the output:
ext.output = {
return standardOutput.toString()
}
}
}
def programFile = kotlin.targets."${platform}".compilations.main.getBinary
('EXECUTABLE', buildType)
exec {
executable programFile
args []
environment 'LD_LIBRARY_PATH' : "${project.VULKAN}lib:/usr/lib:"
}
}
}
}
这是什么?它会创建一个任务,任务名称取决于选定的平台和构建类型。然后,它会遍历源文件夹中的所有着色器文件,将它们编译并保存到 assets 文件夹中。之后,它会设置 "LD_LIBRARY_PATH
" 为 Vulkan SDK 库路径来运行应用程序。
以及我们针对不同平台和发布/调试构建的任务。
// Linux debug build
task runLinuxDebugProgram {
dependsOn createRunTask("debug", "linux")
}
// Linux release build
task runLinuxReleaseProgram {
dependsOn createRunTask("release", "linux")
}
// Windows debug build
task runWindowsDebugProgram {
dependsOn createRunTask("debug", "mingw")
}
// Windows release build
task runWindowsReleaseProgram {
dependsOn createRunTask("release", "mingw")
}
最后一步 - 添加通用代码,这样我们就不必再返回 Gradle 构建文件了 - 目前,我们只需在项目源文件夹中创建一个 common/kvarc 文件夹。然后再次修改 Gradle 构建文件,在 sourceSets 中。
// Windows build
mingwMain {
kotlin.srcDirs += file("src/common")
}
// Linux build
linuxMain {
kotlin.srcDirs += file("src/common")
}
注意:不要按照他们的说明启用通用源集,在这种情况下,您将无法使用特定于平台的原生库。
我们刚刚添加的通用代码存在一个问题:每次打开项目或更改 Gradle 构建文件时,您都需要更改项目结构 - 为您正在处理的非平台移除通用代码,并为每个模块设置“use project properties”。转到 Preferences->Build, Execution, Deployment->Compiler->Kotlin Compiler,并在 Additional command line parameters 中追加键 "-Xmulti-platform
"。
现在是通用代码部分。我们需要在运行时知道我们在哪个平台。为此,我们将使用一个 enum
类来列出平台。
package kvarc.utils
/**
* Supported platforms
*/
internal enum class PlatformEnum {
WINDOWS,
LINUX,
ANDROID //TODO
}
现在我们将添加一个通用的平台类。
package kvarc
import kvarc.utils.PlatformEnum
internal expect class Platform {
companion object {
val type: PlatformEnum
}
}
这里,"expect
" 关键字表示我们的类期望 "actual
" 类由每个平台实现。现在是它们,改变一下。
MainLinux.kt:
package kvarc
import kvarc.utils.PlatformEnum
internal actual class Platform {
actual companion object {
actual val type = PlatformEnum.LINUX
}
}
MainMingw.kt:
package kvarc
import kvarc.utils.PlatformEnum
internal actual class Platform {
actual companion object {
actual val type = PlatformEnum.WINDOWS
}
}
最后一步 - 通用的 "main
" 函数。
package kvarc
import kvarc.utils.PlatformEnum
fun main(args: Array<String>) {
when (Platform.type) {
PlatformEnum.WINDOWS -> println("Windows platform")
PlatformEnum.LINUX -> println("Linux platform")
PlatformEnum.ANDROID -> println("Android platform")
}
}
就是这样。我们所有的准备工作都完成了。现在,在为每个平台实现原生窗口后,我们将只处理通用代码。
历史
- Vulkan API with Kotlin Native - Project Setup
- Vulkan API with Kotlin Native - Platform's Windows
- Vulkan API with Kotlin Native - Instance
- Kotlin Native 与 Vulkan API - 表面,设备
- Kotlin Native 与 Vulkan API - 交换链,管道
- Vulkan API with Kotlin Native - Draw
资源
- Kotlin Native 概述
- IntelliJ IDEA for Windows (请使用 2018.3.6 版本,2019.1 会显示虚假错误)
- IntelliJ IDEA for Linux (请使用 2018.3.6 版本,2019.1 会显示虚假错误)
- Vulkan SDK - 请使用不晚于 1.1.92.1 的版本。在后续版本中,调试层发生了一些变化,我还没有检查。
- MinGW
- RenderDoc
- Android Studio
- Android NDK
- Vulkan 文档
- Sascha Willems - 新 Vulkan API 的示例和演示
- Vulkan 入门指南
- Kotlin Native 仓库