65.9K
CodeProject 正在变化。 阅读更多。
Home

Vulkan API with Kotlin Native - Project Setup

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2019年3月27日

GPL3

7分钟阅读

viewsIcon

18291

使用 Vulkan API 和 Kotlin Native 示例。

引言

通过这篇文章,我将开始一个系列,介绍如何使用 Kotlin Native 来处理 Vulkan API。什么是 Kotlin Native?它旨在将您的代码编译成不需要 Java 虚拟机或其他虚拟机的二进制文件。因此,它应该可以生成平台独立的代码(除了真正依赖于平台的部分)。Kotlin Native 可以生成 arm32arm64x86_64wasm32 等。我们主要关注 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 中,创建一个新的“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")
    }
}

就是这样。我们所有的准备工作都完成了。现在,在为每个平台实现原生窗口后,我们将只处理通用代码。

历史

  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. Kotlin Native 与 Vulkan API - 表面,设备
  5. Kotlin Native 与 Vulkan API - 交换链,管道
  6. Vulkan API with Kotlin Native - Draw

资源

© . All rights reserved.