Vulkan API with Kotlin Native - Platform's Windows





5.00/5 (2投票s)
适用于 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 了。
历史
- 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