Click here to Skip to main content
14,034,935 members
Click here to Skip to main content
Add your own
alternative version

Stats

3.8K views
2 bookmarked
Posted 31 Mar 2019
Licenced GPL3

Vulkan API with Kotlin Native - Platform's Windows

, 31 Mar 2019
Rate this:
Please Sign up or sign in to vote.
Native windows with Kotlin Native for Linux and Windows platforms

Introduction

In the previous part, Vulkan API with Kotlin Native - Project Setup, we created the project that works on Windows and Linux platforms — it determines on which platform it is and shows the corresponding message. In this part, we will create native windows for Linux and Windows. We will add a possibility to switch to fullscreen mode, for now real switch will be only for Windows platform, for Linux for now we'll just show it maximized and without decorations, later, I'll add real switch for the Linux window.

Windows

Let's start with Windows as it's a little simpler to implement. I'll add two different threads — the first one for the system message loop and the second one for the Vulkan rendering. First of all, we need a shared data to pass between threads. For this, we added global.def file to the native interop in the previous part. Now we'll define the data we need:

/**
 * 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?)

Here, we added all needed data to pass to the system message loop — the most important are: the semaphore to synchronize threads, "onTheRun" variable to stop all processing and reference to the new window class. We also added the class to get a pointer to our shared data.

Now some changes in common code:

internal expect class Platform {

    fun Initialize()

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

Here, we'll expect from each native platform the "Initialize" that will create the window and start rendering, also added constant that will define the name of the surface extension. Now the main function will look like this:

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

    val platform = Platform()
    platform.Initialize()

}

Now, it's time to create the window. Mostly, it's done the same way as in C++ — using standard WinAPI methods and passed the class reference to WndProc to call class methods. First of all, let's get the shared data in class initialization:

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

Then, let's create the native window itself and run it:

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)
        }
    }

And the window message loop:

fun messageLoop() {

        memScoped {

            val msg: MSG = alloc()

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

Quite easy, so, since we have the window, it's time to add threads for the window and for the rendering.

@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 Platform

The Linux window creation and run is much the same as for Window. The differences are in using specific API calls to create the window itself, message loop processing, using specific libraries such as xcb, xkb, etc.

So in the next part, we're already ready to work with Vulkan API.

History

  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. Vulkan API with Kotlin Native - Surface, Devices

Resources

  1. Kotlin Native Samples
  2. WinAPI Documentation
  3. xcb library documentation
  4. xkbcommon documentation

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

Share

About the Author

Igor Kushnarev
Software Developer
Russian Federation Russian Federation
• More than 10 years experience in software development
• 3 years experience in direction of automation department.
• software engineering: experience in the whole life cycle of software development
• languages: Kotlin, Swift,Objective-C, C++, C#, Java, ASP.NET, HTML, XML, JavaScript, Visual FoxPro, MS SQL, T-SQL
• Gathering, specification and the analysis of requirements of the customer to the software.
• The analysis of a subject area.
• Estimations of labour input of development.
And so on...

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web05 | 2.8.190424.1 | Last Updated 1 Apr 2019
Article Copyright 2019 by Igor Kushnarev
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid