changeset 466:1dd9e2393a9e

add: structure code for crossplatform, add some input handling + bugfixes
author Sam <sam@basx.dev>
date Thu, 22 Dec 2022 00:06:40 +0700
parents 2fcb9268072b
children 2ee12af72659
files Makefile src/engine.nim src/events.nim src/notes src/platform/linux/symkey_map.nim src/platform/linux/xlib.nim src/platform/windows/win32.nim src/vulkan.nim src/vulkan_helpers.nim src/window.nim src/xlib_helpers.nim
diffstat 11 files changed, 367 insertions(+), 200 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Tue Dec 20 00:28:05 2022 +0700
+++ b/Makefile	Thu Dec 22 00:06:40 2022 +0700
@@ -3,43 +3,67 @@
 DEBUG_OPTIONS := --debugger:native --checks:on --assertions:on
 RELEASE_OPTIONS := -d:release --checks:off --assertions:off
 
-build/debug/linux:
-	mkdir -p $@
-build/debug/linux/test: build/debug/linux ${SOURCES}
+# build
+build/debug/linux/test: ${SOURCES}
+	mkdir -p $$( dirname $@ )
 	nim c ${COMPILE_OPTIONS} ${DEBUG_OPTIONS} -o:$@ examples/test.nim
-
-build/release/linux:
-	mkdir -p $@
-build/release/linux/test: build/release/linux ${SOURCES}
+build/release/linux/test: ${SOURCES}
+	mkdir -p $$( dirname $@ )
+	nim c ${COMPILE_OPTIONS} ${RELEASE_OPTIONS} -o:$@ examples/test.nim
+build/debug/windows/test:  ${SOURCES}
+	mkdir -p $$( dirname $@ )
+	nim c ${COMPILE_OPTIONS} ${DEBUG_OPTIONS} -o:$@ examples/test.nim
+build/release/windows/test: ${SOURCES}
+	mkdir -p $$( dirname $@ )
 	nim c ${COMPILE_OPTIONS} ${RELEASE_OPTIONS} -o:$@ examples/test.nim
 
-# not working yet, need to implement windows window-API
-# build/debug/windows:
-	# mkdir -p $@
-# build/debug/windows/test: build/debug/windows ${SOURCES}
-	# nim c ${COMPILE_OPTIONS} ${DEBUG_OPTIONS} -d:mingw -o:$@ examples/test.nim
-# build/release/windows:
-	# mkdir -p $@
-# build/release/windows/test: build/release/windows ${SOURCES}
-	# nim c ${COMPILE_OPTIONS} ${DEBUG_OPTIONS} -d:mingw -o:$@ examples/test.nim
-thirdparty:
-	echo https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/windows-msvc-2017-release/continuous/1885/20221216-081805/install.zip
-	echo https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/windows-msvc-2017-release/continuous/1885/20221216-081805/install.zip
+build_all: build/debug/linux/test build/release/linux/test build/debug/windows/test build/release/windows/test
+
+# publish
+publish_linux_debug: build/debug/linux/test
+	scp $< basx.dev:/var/www/public.basx.dev/joni/linux/debug/
+publish_linux_release: build/release/linux/test
+	scp $< basx.dev:/var/www/public.basx.dev/joni/linux/release/
+publish_windows_debug: build/debug/linux/test
+	scp $< basx.dev:/var/www/public.basx.dev/joni/windows/debug/
+publish_windows_release: build/release/linux/test
+	scp $< basx.dev:/var/www/public.basx.dev/joni/windows/release/
+
+publish_all: publish_linux_debug publish_linux_release publish_windows_debug publish_windows_release
+
+
+# download thirdparty-libraries
 
-SPIRV_TOOLS_LINUX_DEBUG:
-	wget https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/linux-gcc-release/continuous/1889/20221216-081754/install.tgz
-SPIRV_TOOLS_LINUX_DEBUG:
-	wget https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/linux-gcc-debug/continuous/1899/20221216-081758/install.tgz
-SPIRV_TOOLS_WINDOWS_DEBUG:
-	wget https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/windows-msvc-2017-debug/continuous/1599/20221216-081803/install.zip
-SPIRV_TOOLS_WINDOWS_RELEASE:
-	wget https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/windows-msvc-2017-release/continuous/1885/20221216-081805/install.zip
+thirdparty/lib/glslang/linux_debug:
+	mkdir -p $@
+	wget --directory-prefix=$@ https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-linux-Debug.zip
+	uzip glslang-master-linux-Debug.zip
+thirdparty/lib/glslang/linux_release:
+	mkdir -p $@
+	wget --directory-prefix=$@ https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-linux-Release.zip
+	unzip glslang-master-linux-Release.zip
+thirdparty/lib/glslang/windows_debug:
+	mkdir -p $@
+	wget --directory-prefix=$@ https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-windows-x64-Debug.zip
+	unzip glslang-master-windows-x64-Debug.zip
+thirdparty/lib/glslang/windows_release:
+	mkdir -p $@
+	wget --directory-prefix=$@ https://github.com/KhronosGroup/glslang/releases/download/master-tot/glslang-master-windows-x64-Release.zip
+	unzip glslang-master-windows-x64-Release.zip
 
-GLSL_LINUX_DEBUG:
-	wget
-GLSL_LINUX_RELEASE:
-	wget
-GLSL_WINDOWS_DEBUG:
-	wget
-GLSL_WINDOWS_RELEASE:
-	wget
+thirdparty/lib/spirv-tools/linux_debug:
+	mkdir -p $@
+	wget --directory-prefix=$@ https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/linux-gcc-debug/continuous/1899/20221216-081758/install.tgz
+	tar -xf $@/install.tgz
+thirdparty/lib/spirv-tools/linux_release:
+	mkdir -p $@
+	wget --directory-prefix=$@ https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/linux-gcc-release/continuous/1889/20221216-081754/install.tgz
+	tar -xf $@/install.tgz
+thirdparty/lib/spirv-tools/windows_debug:
+	mkdir -p $@
+	wget --directory-prefix=$@ https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/windows-msvc-2017-debug/continuous/1599/20221216-081803/install.zip
+	unzip $@/install.zip
+thirdparty/lib/spirv-tools/windows_release:
+	mkdir -p $@
+	wget --directory-prefix=$@ https://storage.googleapis.com/spirv-tools/artifacts/prod/graphics_shader_compiler/spirv-tools/windows-msvc-2017-release/continuous/1885/20221216-081805/install.zip
+	unzip $@/install.zip
--- a/src/engine.nim	Tue Dec 20 00:28:05 2022 +0700
+++ b/src/engine.nim	Thu Dec 22 00:06:40 2022 +0700
@@ -6,7 +6,8 @@
 
 import ./vulkan
 import ./vulkan_helpers
-import ./xlib_helpers
+import ./window
+import ./events
 
 import ./glslang/glslang
 
@@ -40,10 +41,6 @@
   outColor = vec4(fragColor, 1.0);
 }"""
 
-import
-  x11/xlib,
-  x11/x
-
 const VULKAN_VERSION = VK_MAKE_API_VERSION(0'u32, 1'u32, 2'u32, 0'u32)
 
 type
@@ -62,8 +59,6 @@
     shaderStages*: seq[VkPipelineShaderStageCreateInfo]
     layout*: VkPipelineLayout
     pipeline*: VkPipeline
-    viewport*: VkViewport
-    scissor*: VkRect2D
   QueueFamily = object
     properties*: VkQueueFamilyProperties
     hasSurfaceSupport*: bool
@@ -73,8 +68,7 @@
     properties*: VkPhysicalDeviceProperties
     features*: VkPhysicalDeviceFeatures
     queueFamilies*: seq[QueueFamily]
-    surfaceCapabilities*: VkSurfaceCapabilitiesKHR
-    surfaceFormats: seq[VkSurfaceFormatKHR]
+    formats: seq[VkSurfaceFormatKHR]
     presentModes: seq[VkPresentModeKHR]
   Vulkan* = object
     debugMessenger: VkDebugUtilsMessengerEXT
@@ -93,22 +87,17 @@
     imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
     renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
     inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence]
-  Window* = object
-    display*: PDisplay
-    window*: x.Window
   Engine* = object
     vulkan*: Vulkan
-    window: Window
-
+    window: NativeWindow
 
 proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[PhysicalDevice] =
   for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance):
     var device = PhysicalDevice(device: vulkanPhysicalDevice, extensions: getDeviceExtensions(vulkanPhysicalDevice))
     vkGetPhysicalDeviceProperties(vulkanPhysicalDevice, addr(device.properties))
     vkGetPhysicalDeviceFeatures(vulkanPhysicalDevice, addr(device.features))
-    checkVkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vulkanPhysicalDevice, surface, addr(device.surfaceCapabilities))
-    device.surfaceFormats = getDeviceSurfaceFormats(vulkanPhysicalDevice, surface)
-    device.presentModes = getDeviceSurfacePresentModes(vulkanPhysicalDevice, surface)
+    device.formats = vulkanPhysicalDevice.getDeviceSurfaceFormats(surface)
+    device.presentModes = vulkanPhysicalDevice.getDeviceSurfacePresentModes(surface)
 
     debug(&"Physical device nr {int(vulkanPhysicalDevice)} {cleanString(device.properties.deviceName)}")
     for i, queueFamilyProperty in enumerate(getQueueFamilies(vulkanPhysicalDevice)):
@@ -121,7 +110,7 @@
 
 proc filterForDevice(devices: seq[PhysicalDevice]): seq[(PhysicalDevice, uint32, uint32)] =
   for device in devices:
-    if not (device.surfaceFormats.len > 0 and device.presentModes.len > 0 and "VK_KHR_swapchain" in device.extensions):
+    if not (device.formats.len > 0 and device.presentModes.len > 0 and "VK_KHR_swapchain" in device.extensions):
       continue
     var graphicsQueueFamily = high(uint32)
     var presentationQueueFamily = high(uint32)
@@ -137,11 +126,12 @@
     debug(&"Viable device: {cleanString(device.properties.deviceName)} (graphics queue family {graphicsQueueFamily}, presentation queue family {presentationQueueFamily})")
 
 
-proc getFrameDimension(window: Window, capabilities: VkSurfaceCapabilitiesKHR): VkExtent2D =
+proc getFrameDimension(window: NativeWindow, device: VkPhysicalDevice, surface: VkSurfaceKHR): VkExtent2D =
+  let capabilities = device.getSurfaceCapabilities(surface)
   if capabilities.currentExtent.width != high(uint32):
     return capabilities.currentExtent
   else:
-    let (width, height) = window.display.xlibFramebufferSize(window.window)
+    let (width, height) = window.size()
     return VkExtent2D(
       width: min(max(uint32(width), capabilities.minImageExtent.width), capabilities.maxImageExtent.width),
       height: min(max(uint32(height), capabilities.minImageExtent.height), capabilities.maxImageExtent.height),
@@ -165,14 +155,6 @@
   )
   checkVkResult instance.vkCreateDebugUtilsMessengerEXT(addr(createInfo), nil, addr(result))
 
-proc createVulkanSurface(instance: VkInstance, window: Window): VkSurfaceKHR =
-  var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR(
-    sType: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
-    dpy: window.display,
-    window: window.window,
-  )
-  checkVkResult vkCreateXlibSurfaceKHR(instance, addr(surfaceCreateInfo), nil, addr(result))
-
 proc setupVulkanDeviceAndQueues(instance: VkInstance, surface: VkSurfaceKHR): Device =
   let usableDevices = instance.getAllPhysicalDevices(surface).filterForDevice()
   if len(usableDevices) == 0:
@@ -191,12 +173,14 @@
   )
 
 proc setupSwapChain(device: VkDevice, physicalDevice: PhysicalDevice, surface: VkSurfaceKHR, dimension: VkExtent2D, surfaceFormat: VkSurfaceFormatKHR): Swapchain =
+
+  let capabilities = physicalDevice.device.getSurfaceCapabilities(surface)
   var selectedPresentationMode = getPresentMode(physicalDevice.presentModes)
   # setup swapchain
   var swapchainCreateInfo = VkSwapchainCreateInfoKHR(
     sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
     surface: surface,
-    minImageCount: max(physicalDevice.surfaceCapabilities.minImageCount + 1, physicalDevice.surfaceCapabilities.maxImageCount),
+    minImageCount: max(capabilities.minImageCount + 1, capabilities.maxImageCount),
     imageFormat: surfaceFormat.format,
     imageColorSpace: surfaceFormat.colorSpace,
     imageExtent: dimension,
@@ -204,7 +188,7 @@
     imageUsage: VkImageUsageFlags(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
     # VK_SHARING_MODE_CONCURRENT no supported (i.e cannot use different queue families for  drawing to swap surface?)
     imageSharingMode: VK_SHARING_MODE_EXCLUSIVE,
-    preTransform: physicalDevice.surfaceCapabilities.currentTransform,
+    preTransform: capabilities.currentTransform,
     compositeAlpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
     presentMode: selectedPresentationMode,
     clipped: VK_TRUE,
@@ -308,24 +292,10 @@
     )
 
   # setup viewport
-  result.viewport = VkViewport(
-    x: 0.0,
-    y: 0.0,
-    width: (float) frameDimension.width,
-    height: (float) frameDimension.height,
-    minDepth: 0.0,
-    maxDepth: 1.0,
-  )
-  result.scissor = VkRect2D(
-    offset: VkOffset2D(x: 0, y: 0),
-    extent: frameDimension
-  )
   var viewportState = VkPipelineViewportStateCreateInfo(
     sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
     viewportCount: 1,
-    pViewports: addr(result.viewport),
     scissorCount: 1,
-    pScissors: addr(result.scissor),
   )
 
   # rasterizerization config
@@ -426,13 +396,18 @@
     )
     checkVkResult device.vkCreateFramebuffer(addr(framebufferInfo), nil, addr(result[i]))
   
+proc trash(device: VkDevice, swapchain: Swapchain, framebuffers: seq[VkFramebuffer]) =
+  for framebuffer in framebuffers:
+    device.vkDestroyFramebuffer(framebuffer, nil)
+  for imageview in swapchain.imageviews:
+    device.vkDestroyImageView(imageview, nil)
+  device.vkDestroySwapchainKHR(swapchain.swapchain, nil)
+
 proc recreateSwapchain(vulkan: Vulkan): (Swapchain, seq[VkFramebuffer]) =
+  debug(&"Recreate swapchain with dimension {vulkan.frameDimension}")
   checkVkResult vulkan.device.device.vkDeviceWaitIdle()
-  for framebuffer in vulkan.framebuffers:
-    vulkan.device.device.vkDestroyFramebuffer(framebuffer, nil)
-  for imageview in vulkan.swapchain.imageviews:
-    vulkan.device.device.vkDestroyImageView(imageview, nil)
-  vulkan.device.device.vkDestroySwapchainKHR(vulkan.swapchain.swapchain, nil)
+
+  vulkan.device.device.trash(vulkan.swapchain, vulkan.framebuffers)
 
   result[0] = vulkan.device.device.setupSwapChain(
     vulkan.device.physicalDevice,
@@ -483,8 +458,7 @@
 
 proc igniteEngine*(): Engine =
 
-  # init X11 window
-  (result.window.display, result.window.window) = xlibInit()
+  result.window = createWindow("Hello triangle")
 
   # setup vulkan functions
   vkLoad1_0()
@@ -500,8 +474,8 @@
   result.vulkan.device = result.vulkan.instance.setupVulkanDeviceAndQueues(result.vulkan.surface)
 
   # get basic frame information
-  result.vulkan.surfaceFormat = result.vulkan.device.physicalDevice.surfaceFormats.getSuitableSurfaceFormat()
-  result.vulkan.frameDimension = result.window.getFrameDimension(result.vulkan.device.physicalDevice.surfaceCapabilities)
+  result.vulkan.surfaceFormat = result.vulkan.device.physicalDevice.formats.getSuitableSurfaceFormat()
+  result.vulkan.frameDimension = result.window.getFrameDimension(result.vulkan.device.physicalDevice.device, result.vulkan.surface)
 
   # setup swapchain and render pipeline
   result.vulkan.swapchain = result.vulkan.device.device.setupSwapChain(
@@ -530,14 +504,12 @@
   ) = result.vulkan.device.device.setupSyncPrimitives()
 
 
-proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer) =
-  var beginInfo = VkCommandBufferBeginInfo(
-    sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
-    pInheritanceInfo: nil,
-  )
-  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
-
+proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: VkPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D) =
   var
+    beginInfo = VkCommandBufferBeginInfo(
+      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+      pInheritanceInfo: nil,
+    )
     clearColor = VkClearValue(color: VkClearColorValue(float32: [0.2'f, 0.2'f, 0.2'f, 1.0'f]))
     renderPassInfo = VkRenderPassBeginInfo(
       sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
@@ -545,26 +517,34 @@
       framebuffer: framebuffer,
       renderArea: VkRect2D(
         offset: VkOffset2D(x: 0, y: 0),
-        extent: VkExtent2D(
-          width: uint32(pipeline.viewport.width),
-          height: uint32(pipeline.viewport.height)
-        ),
+        extent: frameDimension,
       ),
       clearValueCount: 1,
       pClearValues: addr(clearColor),
     )
+    viewport = VkViewport(
+      x: 0.0,
+      y: 0.0,
+      width: (float) frameDimension.width,
+      height: (float) frameDimension.height,
+      minDepth: 0.0,
+      maxDepth: 1.0,
+    )
+    scissor = VkRect2D(
+      offset: VkOffset2D(x: 0, y: 0),
+      extent: frameDimension
+    )
+  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
   commandBuffer.vkCmdBeginRenderPass(addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE)
-  commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline)
-
-  commandBuffer.vkCmdSetViewport(firstViewport=0, viewportCount=1, addr(pipeline.viewport))
-  commandBuffer.vkCmdSetScissor(firstScissor=0, scissorCount=1, addr(pipeline.scissor))
+  commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline)
+  commandBuffer.vkCmdSetViewport(firstViewport=0, viewportCount=1, addr(viewport))
+  commandBuffer.vkCmdSetScissor(firstScissor=0, scissorCount=1, addr(scissor))
   commandBuffer.vkCmdDraw(vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0)
   commandBuffer.vkCmdEndRenderPass()
   checkVkResult commandBuffer.vkEndCommandBuffer()
 
-proc drawFrame(window: Window, vulkan: var Vulkan, currentFrame: int) =
+proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool) =
   checkVkResult vulkan.device.device.vkWaitForFences(1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64))
-  checkVkResult vulkan.device.device.vkResetFences(1, addr(vulkan.inFlightFences[currentFrame]))
   var bufferImageIndex: uint32
   let nextImageResult = vulkan.device.device.vkAcquireNextImageKHR(
     vulkan.swapchain.swapchain,
@@ -574,12 +554,15 @@
     addr(bufferImageIndex)
   )
   if nextImageResult == VK_ERROR_OUT_OF_DATE_KHR:
-    vulkan.frameDimension = window.getFrameDimension(vulkan.device.physicalDevice.surfaceCapabilities)
+    vulkan.frameDimension = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
     (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
     return
+  elif not (nextImageResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]):
+    raise newException(Exception, "Vulkan error: vkAcquireNextImageKHR returned " & $nextImageResult)
+  checkVkResult vulkan.device.device.vkResetFences(1, addr(vulkan.inFlightFences[currentFrame]))
 
   checkVkResult vulkan.commandBuffers[currentFrame].vkResetCommandBuffer(VkCommandBufferResetFlags(0))
-  vulkan.renderPass.recordCommandBuffer(vulkan.pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex])
+  vulkan.renderPass.recordCommandBuffer(vulkan.pipeline.pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension)
   var
     waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]]
     waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
@@ -607,50 +590,45 @@
   )
   let presentResult = vkQueuePresentKHR(vulkan.device.presentationQueue, addr(presentInfo))
 
-  if presentResult == VK_ERROR_OUT_OF_DATE_KHR or presentResult == VK_SUBOPTIMAL_KHR:
-    vulkan.frameDimension = window.getFrameDimension(vulkan.device.physicalDevice.surfaceCapabilities)
+  if presentResult == VK_ERROR_OUT_OF_DATE_KHR or presentResult == VK_SUBOPTIMAL_KHR or resized:
+    vulkan.frameDimension = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
     (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
-    return
 
 
 proc fullThrottle*(engine: var Engine) =
   var
-    event: XEvent
     killed = false
     currentFrame = 0
+    resized = false
 
   while not killed:
-    while engine.window.display.XPending() > 0 and not killed:
-      discard engine.window.display.XNextEvent(addr(event))
-      case event.theType
-      of ClientMessage:
-        if cast[Atom](event.xclient.data.l[0]) == deleteMessage:
+    for event in engine.window.pendingEvents():
+      case event.eventType:
+        of Quit:
           killed = true
-      of KeyPress:
-        let key = XLookupKeysym(cast[PXKeyEvent](addr(event)), 0)
-        if key == XK_Escape:
-          killed = true
-      of ConfigureNotify:
-        engine.vulkan.frameDimension = engine.window.getFrameDimension(engine.vulkan.device.physicalDevice.surfaceCapabilities)
-        (engine.vulkan.swapchain, engine.vulkan.framebuffers) = engine.vulkan.recreateSwapchain()
-      else:
-        discard
-    engine.window.drawFrame(engine.vulkan, currentFrame)
+        of ResizedWindow:
+          resized = true
+        of KeyDown:
+          echo event
+          if event.key == Escape:
+            killed = true
+        else:
+          discard
+    engine.window.drawFrame(engine.vulkan, currentFrame, resized)
+    resized = false
     currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT;
   checkVkResult engine.vulkan.device.device.vkDeviceWaitIdle()
 
+proc trash*(engine: Engine) =
+  engine.vulkan.device.device.trash(engine.vulkan.swapchain, engine.vulkan.framebuffers)
+  checkVkResult engine.vulkan.device.device.vkDeviceWaitIdle()
 
-proc trash*(engine: Engine) =
-  checkVkResult engine.vulkan.device.device.vkDeviceWaitIdle()
   for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
     engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.imageAvailableSemaphores[i], nil)
     engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.renderFinishedSemaphores[i], nil)
     engine.vulkan.device.device.vkDestroyFence(engine.vulkan.inFlightFences[i], nil)
 
   engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.commandPool, nil)
-  for framebuffer in engine.vulkan.framebuffers:
-    engine.vulkan.device.device.vkDestroyFramebuffer(framebuffer, nil)
-
   engine.vulkan.device.device.vkDestroyPipeline(engine.vulkan.pipeline.pipeline, nil)
   engine.vulkan.device.device.vkDestroyPipelineLayout(engine.vulkan.pipeline.layout, nil)
   engine.vulkan.device.device.vkDestroyRenderPass(engine.vulkan.renderPass, nil)
@@ -658,14 +636,10 @@
   for shaderStage in engine.vulkan.pipeline.shaderStages:
     engine.vulkan.device.device.vkDestroyShaderModule(shaderStage.module, nil)
 
-  for imageview in engine.vulkan.swapchain.imageviews:
-    engine.vulkan.device.device.vkDestroyImageView(imageview, nil)
-  engine.vulkan.device.device.vkDestroySwapchainKHR(engine.vulkan.swapchain.swapchain, nil)
   engine.vulkan.instance.vkDestroySurfaceKHR(engine.vulkan.surface, nil)
   engine.vulkan.device.device.vkDestroyDevice(nil)
   when ENABLEVULKANVALIDATIONLAYERS:
     engine.vulkan.instance.vkDestroyDebugUtilsMessengerEXT(engine.vulkan.debugMessenger, nil)
   glslang_finalize_process()
   engine.vulkan.instance.vkDestroyInstance(nil)
-  checkXlibResult engine.window.display.XDestroyWindow(engine.window.window)
-  discard engine.window.display.XCloseDisplay() # always returns 0
+  engine.window.trash()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/events.nim	Thu Dec 22 00:06:40 2022 +0700
@@ -0,0 +1,20 @@
+type
+  EventType* = enum
+    Quit
+    ResizedWindow
+    KeyDown
+    KeyUp
+  Key* = enum
+    UNKNOWN
+    A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z
+    a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z
+    `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`, `9`, `0`
+    Minus, Plus, Underscore, Equals, Space, Enter, Backspace, Tab
+    Comma, Period, Semicolon, Colon,
+    Escape, CtrlL, ShirtL, AltL, CtrlR, ShirtR, AltR
+  Event* = object
+    case eventType*: EventType
+    of KeyDown, KeyUp:
+      key*: Key
+    else:
+      discard
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/notes	Thu Dec 22 00:06:40 2022 +0700
@@ -0,0 +1,2 @@
+For implementation of font rendering:
+https://developer.apple.com/fonts/TrueType-Reference-Manual/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/platform/linux/symkey_map.nim	Thu Dec 22 00:06:40 2022 +0700
@@ -0,0 +1,16 @@
+import std/tables
+export tables
+
+import x11/keysym
+import x11/x
+
+import ../../events
+
+const KeyTypeMap* = {
+  XK_A: A, XK_B: B, XK_C: C, XK_D: D, XK_E: E, XK_F: F, XK_G: G, XK_H: H, XK_I: I, XK_J: J, XK_K: K, XK_L: L, XK_M: M, XK_N: N, XK_O: O, XK_P: P, XK_Q: Q, XK_R: R, XK_S: S, XK_T: T, XK_U: U, XK_V: V, XK_W: W, XK_X: X, XK_Y: Y, XK_Z: Z,
+  XK_a: a, XK_b: b, XK_c: c, XK_d: d, XK_e: e, XK_f: f, XK_g: g, XK_h: h, XK_i: i, XK_j: j, XK_k: k, XK_l: l, XK_m: m, XK_n: n, XK_o: o, XK_p: p, XK_q: q, XK_r: r, XK_s: s, XK_t: t, XK_u: u, XK_v: v, XK_w: w, XK_x: Key.x, XK_y: y, XK_z: z,
+  XK_1: `1`, XK_2: `2`, XK_3: `3`, XK_4: `4`, XK_5: `5`, XK_6: `6`, XK_7: `7`, XK_8: `8`, XK_9: `9`, XK_0: `0`,
+  XK_minus: Minus, XK_plus: Plus, XK_underscore: Underscore, XK_equal: Equals, XK_space: Space, XK_Return: Enter, XK_BackSpace: Backspace, XK_Tab: Tab,
+  XK_comma: Comma, XK_period: Period, XK_semicolon: Semicolon, XK_colon: Colon,
+  XK_Escape: Escape, XK_Control_L: CtrlL, XK_Shift_L: ShirtL, XK_Alt_L: AltL, XK_Control_R: CtrlR, XK_Shift_R: ShirtR, XK_Alt_R: AltR
+}.toTable
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/platform/linux/xlib.nim	Thu Dec 22 00:06:40 2022 +0700
@@ -0,0 +1,73 @@
+import
+  x11/xlib,
+  x11/xutil,
+  x11/keysym
+import x11/x
+
+import ../../events
+
+import ./symkey_map
+
+export keysym
+
+var deleteMessage*: Atom
+
+type
+  NativeWindow* = object
+    display*: PDisplay
+    window*: Window
+
+template checkXlibResult*(call: untyped) =
+  let value = call
+  if value == 0:
+    raise newException(Exception, "Xlib error: " & astToStr(call) & " returned " & $value)
+
+proc createWindow*(title: string): NativeWindow =
+  checkXlibResult XInitThreads()
+  let display = XOpenDisplay(nil)
+  if display == nil:
+    quit "Failed to open display"
+
+  let
+    screen = XDefaultScreen(display)
+    rootWindow = XRootWindow(display, screen)
+    foregroundColor = XBlackPixel(display, screen)
+    backgroundColor = XWhitePixel(display, screen)
+
+  let window = XCreateSimpleWindow(display, rootWindow, -1, -1, 800, 600, 0, foregroundColor, backgroundColor)
+  checkXlibResult XSetStandardProperties(display, window, title, "window", 0, nil, 0, nil)
+  checkXlibResult XSelectInput(display, window, ButtonPressMask or KeyPressMask or ExposureMask)
+  checkXlibResult XMapWindow(display, window)
+
+  deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false))
+  checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1)
+
+  return NativeWindow(display: display, window: window)
+
+proc trash*(window: NativeWindow) =
+  checkXlibResult window.display.XDestroyWindow(window.window)
+  discard window.display.XCloseDisplay() # always returns 0
+
+proc size*(window: NativeWindow): (int, int) =
+  var attribs: XWindowAttributes
+  checkXlibResult XGetWindowAttributes(window.display, window.window, addr(attribs))
+  return (int(attribs.width), int(attribs.height))
+
+proc pendingEvents*(window: NativeWindow): seq[Event] =
+  var event: XEvent
+  while window.display.XPending() > 0:
+    discard window.display.XNextEvent(addr(event))
+    case event.theType
+    of ClientMessage:
+      if cast[Atom](event.xclient.data.l[0]) == deleteMessage:
+        result.add(Event(eventType: Quit))
+    of KeyPress:
+      let xkey: KeySym = XLookupKeysym(cast[PXKeyEvent](addr(event)), 0)
+      result.add(Event(eventType: KeyDown, key: KeyTypeMap.getOrDefault(xkey, UNKNOWN)))
+    of KeyRelease:
+      let xkey: KeySym = XLookupKeysym(cast[PXKeyEvent](addr(event)), 0)
+      result.add(Event(eventType: KeyUp, key: KeyTypeMap.getOrDefault(xkey, UNKNOWN)))
+    of ConfigureNotify:
+      result.add(Event(eventType: ResizedWindow))
+    else:
+      discard
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/platform/windows/win32.nim	Thu Dec 22 00:06:40 2022 +0700
@@ -0,0 +1,58 @@
+import winim
+
+import ../../events
+
+type
+  NativeWindow* = object
+    hinstance*: HINSTANCE
+    hwnd*: HWND
+
+template checkWin32Result*(call: untyped) =
+  let value = call
+  if value != 0:
+    raise newException(Exception, "Win32 error: " & astToStr(call) & " returned " & $value)
+
+proc WindowHandler(hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM): LRESULT {.stdcall.} =
+  case uMsg
+  of WM_DESTROY:
+    discard
+  else:
+    return DefWindowProc(hwnd, uMsg, wParam, lParam)
+
+
+proc createWindow*(title: string): NativeWindow =
+  result.hInstance = HINSTANCE(GetModuleHandle(nil))
+  var
+    windowClassName = T"EngineWindowClass"
+    windowName = T(title)
+    windowClass = WNDCLASS(
+      lpfnWndProc: WindowHandler,
+      hInstance: result.hInstance,
+      lpszClassName: windowClassName,
+    )
+  RegisterClass(addr(windowClass))
+
+  result.hwnd = CreateWindowEx(
+      DWORD(0),
+      windowClassName,
+      windowName,
+      DWORD(WS_OVERLAPPEDWINDOW),
+      CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+      HMENU(0),
+      HINSTANCE(0),
+      result.hInstance,
+      nil
+    )
+
+  discard ShowWindow(result.hwnd, 0)
+
+proc trash*(window: NativeWindow) =
+  PostQuitMessage(0)
+
+proc size*(window: NativeWindow): (int, int) =
+  var rect: RECT
+  checkWin32Result GetWindowRect(window.hwnd, addr(rect))
+  (int(rect.right - rect.left), int(rect.bottom - rect.top))
+
+proc pendingEvents*(window: NativeWindow): seq[Event] =
+  result
--- a/src/vulkan.nim	Tue Dec 20 00:28:05 2022 +0700
+++ b/src/vulkan.nim	Thu Dec 22 00:06:40 2022 +0700
@@ -4,8 +4,12 @@
 ## ====
 ## WARNING: This is a generated file. Do not edit
 ## Any edits will be overwritten by the generator.
-import x11/xlib
-import x11/x
+
+when defined(linux):
+  import x11/x
+  import x11/xlib
+when defined(windows):
+  import winim
 
 var vkGetProc: proc(procName: cstring): pointer {.cdecl.}
 
@@ -13,6 +17,7 @@
 
 when defined(windows):
   {. emit: """#define VK_USE_PLATFORM_WIN32_KHR""" .}
+  # {.passl: gorge("pkg-config --libs vulkan").}
   const vkDLL = "vulkan-1.dll"
 elif defined(linux):
   {.passl: gorge("pkg-config --libs vulkan").}
@@ -511,6 +516,7 @@
     VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR = 1000001000 # added by sam
     VK_STRUCTURE_TYPE_PRESENT_INFO_KHR = 1000001001 # added by sam
     VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000 # added by sam
+    VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000 # added by sam
     VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT = 1000128004 # added by sam
   VkSubpassContents* {.size: int32.sizeof.} = enum
     VK_SUBPASS_CONTENTS_INLINE = 0
@@ -1191,20 +1197,26 @@
 
 # Types
 
+# stub types if we are on the wrong platform, so we don't need to "when" all platform functions
+when not defined(linux):
+  type
+    Display* = ptr object
+    VisualID* = ptr object
+    Window* = ptr object
+when not defined(windows):
+  type
+    HINSTANCE* = ptr object
+    HWND* = ptr object
+    HMONITOR* = ptr object
+    HANDLE* = ptr object
+    SECURITY_ATTRIBUTES* = ptr object
+    DWORD* = ptr object
+    LPCWSTR* = ptr object
+
 type
-  # Display* = ptr object
-  VisualID* = ptr object
-  # Window* = ptr object
   RROutput* = ptr object
   wl_display* = ptr object
   wl_surface* = ptr object
-  HINSTANCE* = ptr object
-  HWND* = ptr object
-  HMONITOR* = ptr object
-  HANDLE* = ptr object
-  SECURITY_ATTRIBUTES* = ptr object
-  DWORD* = ptr object
-  LPCWSTR* = ptr object
   xcb_connection_t* = ptr object
   xcb_visualid_t* = ptr object
   xcb_window_t* = ptr object
--- a/src/vulkan_helpers.nim	Tue Dec 20 00:28:05 2022 +0700
+++ b/src/vulkan_helpers.nim	Thu Dec 22 00:06:40 2022 +0700
@@ -5,6 +5,7 @@
 
 import ./glslang/glslang
 import ./vulkan
+import ./window
 
 when defined(release):
   const ENABLEVULKANVALIDATIONLAYERS* = false
@@ -21,6 +22,8 @@
     if value != VK_SUCCESS:
       raise newException(Exception, "Vulkan error: " & astToStr(call) & " returned " & $value)
 
+func addrOrNil[T](obj: var openArray[T]): ptr T =
+  if obj.len > 0: addr(obj[0]) else: nil
 
 proc VK_MAKE_API_VERSION*(variant: uint32, major: uint32, minor: uint32, patch: uint32): uint32 {.compileTime.} =
   (variant shl 29) or (major shl 22) or (minor shl 12) or patch
@@ -48,7 +51,7 @@
   var extensionCount: uint32
   checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr(extensionCount), nil)
   var extensions = newSeq[VkExtensionProperties](extensionCount)
-  checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr(extensionCount), addr(extensions[0]))
+  checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr(extensionCount), addrOrNil(extensions))
 
   for extension in extensions:
     result.add(cleanString(extension.extensionName))
@@ -58,7 +61,7 @@
   var extensionCount: uint32
   checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(extensionCount), nil)
   var extensions = newSeq[VkExtensionProperties](extensionCount)
-  checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(extensionCount), addr(extensions[0]))
+  checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(extensionCount), addrOrNil(extensions))
 
   for extension in extensions:
     result.add(cleanString(extension.extensionName))
@@ -68,7 +71,7 @@
   var n_layers: uint32
   checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), nil)
   var layers = newSeq[VkLayerProperties](n_layers)
-  checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), addr(layers[0]))
+  checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), addrOrNil(layers))
 
   for layer in layers:
     result.add(cleanString(layer.layerName))
@@ -78,35 +81,35 @@
   var n_devices: uint32
   checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), nil)
   result = newSeq[VkPhysicalDevice](n_devices)
-  checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), addr(result[0]))
+  checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), addrOrNil(result))
 
 
 proc getQueueFamilies*(device: VkPhysicalDevice): seq[VkQueueFamilyProperties] =
   var n_queuefamilies: uint32
   vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), nil)
   result = newSeq[VkQueueFamilyProperties](n_queuefamilies)
-  vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), addr(result[0]))
+  vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), addrOrNil(result))
 
 
 proc getDeviceSurfaceFormats*(device: VkPhysicalDevice, surface: VkSurfaceKHR): seq[VkSurfaceFormatKHR] =
   var n_formats: uint32
   checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), nil);
   result = newSeq[VkSurfaceFormatKHR](n_formats)
-  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), addr(result[0]))
+  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), addrOrNil(result))
 
 
 proc getDeviceSurfacePresentModes*(device: VkPhysicalDevice, surface: VkSurfaceKHR): seq[VkPresentModeKHR] =
   var n_modes: uint32
   checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(n_modes), nil);
   result = newSeq[VkPresentModeKHR](n_modes)
-  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(n_modes), addr(result[0]))
+  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(n_modes), addrOrNil(result))
 
 
 proc getSwapChainImages*(device: VkDevice, swapChain: VkSwapchainKHR): seq[VkImage] =
   var n_images: uint32
   checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), nil);
   result = newSeq[VkImage](n_images)
-  checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), addr(result[0]));
+  checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), addrOrNil(result));
 
 
 proc getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR =
@@ -161,7 +164,7 @@
     sType: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
     pApplicationInfo: addr(appinfo),
     enabledLayerCount: usableLayers.len.uint32,
-    ppEnabledLayerNames: cast[ptr UncheckedArray[cstring]](addr(usableLayers[0])),
+    ppEnabledLayerNames: cast[ptr UncheckedArray[cstring]](addrOrNil(usableLayers)),
     enabledExtensionCount: requiredExtensions.len.uint32,
     ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions))
   )
@@ -196,7 +199,7 @@
   var deviceCreateInfo = VkDeviceCreateInfo(
     sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
     queueCreateInfoCount: uint32(queueCreateInfo.len),
-    pQueueCreateInfos: addr(queueCreateInfo[0]),
+    pQueueCreateInfos: addrOrNil(queueCreateInfo),
     pEnabledFeatures: addr(features),
     enabledExtensionCount: requiredExtensions.len.uint32,
     ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions))
@@ -214,7 +217,7 @@
   var createInfo = VkShaderModuleCreateInfo(
     sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
     codeSize: uint(code.len * sizeof(uint32)),
-    pCode: addr(code[0]),
+    pCode: addrOrNil(code),
   )
   var shaderModule: VkShaderModule
   checkVkResult vkCreateShaderModule(device, addr(createInfo), nil, addr(shaderModule))
@@ -234,3 +237,23 @@
 ): VkBool32 {.cdecl.} =
   echo &"{messageSeverity}: {VkDebugUtilsMessageTypeFlagBitsEXT(messageTypes)}: {pCallbackData.pMessage}"
   return VK_FALSE
+
+proc getSurfaceCapabilities*(device: VkPhysicalDevice, surface: VkSurfaceKHR): VkSurfaceCapabilitiesKHR =
+    checkVkResult device.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(surface, addr(result))
+
+when defined(linux):
+  proc createVulkanSurface*(instance: VkInstance, window: NativeWindow): VkSurfaceKHR =
+    var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR(
+      sType: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
+      dpy: window.display,
+      window: window.window,
+    )
+    checkVkResult vkCreateXlibSurfaceKHR(instance, addr(surfaceCreateInfo), nil, addr(result))
+when defined(windows):
+  proc createVulkanSurface*(instance: VkInstance, window: NativeWindow): VkSurfaceKHR =
+    var surfaceCreateInfo = VkWin32SurfaceCreateInfoKHR(
+      sType: VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR,
+      hinstance: window.hinstance,
+      hwnd: window.hwnd,
+    )
+    checkVkResult vkCreateWin32SurfaceKHR(instance, addr(surfaceCreateInfo), nil, addr(result))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/window.nim	Thu Dec 22 00:06:40 2022 +0700
@@ -0,0 +1,6 @@
+when defined(linux):
+  import ./platform/linux/xlib
+  export xlib
+elif defined(windows):
+  import ./platform/windows/win32
+  export win32
--- a/src/xlib_helpers.nim	Tue Dec 20 00:28:05 2022 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-import
-  x11/xlib,
-  x11/xutil,
-  x11/x,
-  x11/keysym
-
-export keysym
-
-var deleteMessage*: Atom
-
-template checkXlibResult*(call: untyped) =
-  let value = call
-  if value == 0:
-    raise newException(Exception, "Xlib error: " & astToStr(call) & " returned " & $value)
-
-proc xlibInit*(): (PDisplay, Window) =
-  checkXlibResult XInitThreads()
-  let display = XOpenDisplay(nil)
-  if display == nil:
-    quit "Failed to open display"
-
-  let
-    screen = XDefaultScreen(display)
-    rootWindow = XRootWindow(display, screen)
-    foregroundColor = XBlackPixel(display, screen)
-    backgroundColor = XWhitePixel(display, screen)
-
-  let window = XCreateSimpleWindow(display, rootWindow, -1, -1, 800, 600, 0, foregroundColor, backgroundColor)
-  checkXlibResult XSetStandardProperties(display, window, "Nim X11", "window", 0, nil, 0, nil)
-  checkXlibResult XSelectInput(display, window, ButtonPressMask or KeyPressMask or ExposureMask)
-  checkXlibResult XMapWindow(display, window)
-
-  deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false))
-  checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1)
-
-  return (display, window)
-
-proc xlibFramebufferSize*(display: PDisplay, window: Window): (int, int) =
-  var attribs: XWindowAttributes
-  checkXlibResult XGetWindowAttributes(display, window, addr(attribs))
-  return (int(attribs.width), int(attribs.height))