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

Vulkan API with Kotlin Native - Platform's Windows

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2019 年 4 月 1 日

GPL3

2分钟阅读

viewsIcon

10761

适用于 Linux 和 Windows 平台的 Kotlin Native 原生窗口。

引言

在上一部分,Kotlin Native 与 Vulkan API - 项目设置,我们创建了一个在 Windows 和 Linux 平台上运行的项目——它确定了当前平台并显示相应的消息。 在这一部分,我们将为 Linux 和 Windows 创建原生窗口。 我们将添加切换到全屏模式的可能性,目前真正的切换仅适用于 Windows 平台,对于 Linux,我们现在只会将其显示为最大化且没有装饰,稍后,我将为 Linux 窗口添加真正的切换。

Windows

让我们从 Windows 开始,因为它实现起来稍微简单一些。 我将添加两个不同的线程——一个用于系统消息循环,另一个用于 Vulkan 渲染。 首先,我们需要共享数据在线程之间传递。 为此,我们在上一部分的 native interop 中添加了 global.def 文件。 现在我们将定义我们需要的数据

/**
 * Data shared between threads
 */
internal class CommonData @ExperimentalUnsignedTypes constructor(
    val semaphore: sem_tVar,
    var hInstance: HINSTANCE? = null,
    var hwnd: HWND? = null,
    var showWindowCentered: Boolean = true,
    var showWindowFullscreen: Boolean = false,
    var onTheRun: Boolean = true,
    var windowsSurface: WindowsSurface? = null
)

internal data class SharedCommonData(val userdata: COpaquePointer?)

这里,我们添加了所有需要传递给系统消息循环的数据——最重要的包括:用于同步线程的信号量,“onTheRun”变量以停止所有处理,以及对新窗口类的引用。 我们还添加了一个类来获取指向我们共享数据的指针。

现在对公共代码进行一些更改

internal expect class Platform {

    fun Initialize()

    companion object {
        val type: PlatformEnum
        val VK_KHR_PLATFORM_SURFACE_EXTENSION_NAME: String
    }
}

在这里,我们将期望每个原生平台提供一个“Initialize”,它将创建窗口并启动渲染,还添加了一个常量来定义表面扩展的名称。 现在主函数将如下所示

@ExperimentalUnsignedTypes
fun main(args: Array<String>) {

    val platform = Platform()
    platform.Initialize()

}

现在,是时候创建窗口了。 大部分情况下,它与在 C++ 中完成的方式相同——使用标准的 WinAPI 方法并将类引用传递给 WndProc 以调用类方法。 首先,让我们在类初始化中获取共享数据

init {
        val kotlinObject = DetachedObjectGraph<SharedCommonData>(sharedData.kotlinObject).attach()
        val sharedData = kotlinObject.userdata!!.asStableRef<CommonData>().get()
        sharedData.windowsSurface = this
    }

然后,让我们创建原生窗口并运行它

fun initialize() {

        memScoped {

            val hInstance = GetModuleHandleW(null)
            val hBrush = CreateSolidBrush(0x00000000)

            val wc: WNDCLASSEXW = alloc()
            wc.cbSize = sizeOf<WNDCLASSEX>().convert()
            wc.style = (CS_HREDRAW or CS_VREDRAW or CS_OWNDC).convert()

            wc.lpfnWndProc = staticCFunction { hwnd, msg, wParam, lParam ->

                when (msg.toInt()) {
                    WM_CLOSE -> {
                        val kotlinObject = DetachedObjectGraph<SharedCommonData>
                                           (sharedData.kotlinObject).attach()
                        val sharedData = kotlinObject.userdata!!.asStableRef<CommonData>().get()
                        sharedData.onTheRun = false
                        DestroyWindow(hwnd)
                    }
                    WM_DESTROY -> {
                        PostQuitMessage(0)
                    }

                    ....

                    WM_KEYDOWN -> {
                        if (GetAsyncKeyState(VK_ESCAPE) != 0.toShort()) {
                            val kotlinObject = DetachedObjectGraph<SharedCommonData>
                                               (sharedData.kotlinObject).attach()
                            val sharedData = kotlinObject.userdata!!.asStableRef<CommonData>().get()
                            if(sharedData.showWindowFullscreen){
                                sharedData.windowsSurface?.changeFullscreen(false)
                            }
                            PostMessageW(hwnd, WM_CLOSE, 0, 0)
                        }
                    }
                    WM_SYSKEYDOWN -> {
                        if (wParam == VK_F4.toULong()) {
                            return@staticCFunction 1
                        }
                    }
                    else -> {
                        return@staticCFunction DefWindowProcW(hwnd, msg, wParam, lParam)
                    }
                }                
            }

            wc.hInstance = hInstance 

            ...

            val failure: ATOM = 0u
            if (RegisterClassExW(wc.ptr) == failure) {
                throw RuntimeException("Failed to create native window!")
            }

            hwnd = CreateWindowExW(
                WS_EX_CLIENTEDGE,
                "kvarc",
                "kvarc",
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                _width,
                _height,
                null,
                null,
                hInstance,
                null
            )

            if (hwnd == null) {
                MessageBoxW(
                    null, "Failed to create native window!",
                    "kvarc", (MB_OK).convert()
                )
                throw RuntimeException("Failed to create native window!")
            }
            
            ...

            ShowWindow(hwnd, SW_SHOWNORMAL)
            UpdateWindow(hwnd)
        }
    }

以及窗口消息循环

fun messageLoop() {

        memScoped {

            val msg: MSG = alloc()

            while (GetMessageW(msg.ptr, null, 0u, 0u) > 0) {
                TranslateMessage(msg.ptr)
                DispatchMessageW(msg.ptr)
            }
        }
}

很简单,所以,既然我们有了窗口,就该添加用于窗口和渲染的线程了。

@ExperimentalUnsignedTypes
internal actual class Platform {

    actual fun Initialize() {

        try {

            val arena = Arena()

            val semaphore = arena.alloc<sem_tVar>()
            sem_init(semaphore.ptr, 0, 0)

            ...

            memScoped {

               val winThread = alloc<pthread_tVar>()

                // It lies about redundant lambda arrow
                pthread_create(winThread.ptr, null, staticCFunction { _: COpaquePointer? ->

                    initRuntimeIfNeeded()

                    val kotlinObject = DetachedObjectGraph<SharedCommonData>
                                       (sharedData.kotlinObject).attach()
                    val data = kotlinObject.userdata!!.asStableRef<CommonData>().get()

                    val win = WindowsSurface(data.showWindowFullscreen)
                    win.initialize()

                    ...

                    @Suppress("UNCHECKED_CAST")
                    data.hwnd = win.hwnd!!

                    sem_post(data.semaphore.ptr)

                    win.messageLoop()
                    win.dispose()

                    null as COpaquePointer? //keep it lies not needed
                }, null)
                    .ensureUnixCallResult("pthread_create")
            }

            val vkThread = alloc<pthread_tVar>()

            // It lies about redundant lambda arrow
                pthread_create(vkThread.ptr, null, staticCFunction { _: COpaquePointer? ->

                    initRuntimeIfNeeded()

                    val kotlinObject = DetachedObjectGraph<SharedCommonData>
                                       (sharedData.kotlinObject).attach()
                    val data = kotlinObject.userdata!!.asStableRef<CommonData>().get()
                    sem_wait(data.semaphore.ptr)

                    //TODO Vulkan loop
                    null as COpaquePointer? //keep it lies not needed

                }, null)
                    .ensureUnixCallResult("pthread_create")

                pthread_join(vkThread.value, null).ensureUnixCallResult("pthread_join")
                pthread_join(winThread.value, null).ensureUnixCallResult("pthread_join")

                sem_destroy(semaphore.ptr)
                commonDataStableRef.dispose()
                arena.clear()
        }
        catch (ex: Exception) {
            logError("Failed to start with exception: ${ex.message}")
        }
    }
}

Linux 平台

Linux 窗口创建和运行与 Windows 非常相似。 区别在于使用特定的 API 调用来创建窗口本身、消息循环处理,以及使用特定的库,例如 xcb、xkb 等。

所以在下一部分,我们已经准备好使用 Vulkan API 了。

历史

  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

资源

  1. Kotlin Native 示例
  2. WinAPI 文档
  3. xcb 库文档
  4. xkbcommon 文档
© . All rights reserved.