changeset 2:213fdf8d31dd

did: hello world triangle, a bit of code organization
author Sam <sam@basx.dev>
date Mon, 19 Dec 2022 10:41:20 +0700
parents bb2a7d3a7003
children 5d54ef652619
files Makefile examples/test.nim src/engine.nim src/glslang/glslang_c_interface.nim src/test src/test.nim src/vulkan.nim src/vulkan_helpers.nim src/xlib_helpers.nim
diffstat 9 files changed, 548 insertions(+), 157 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Fri Dec 16 00:05:41 2022 +0700
+++ b/Makefile	Mon Dec 19 10:41:20 2022 +0700
@@ -1,11 +1,25 @@
 SOURCES := $(shell find src -name '*.nim')
+COMPILE_OPTIONS := --path:src --mm:orc --experimental:strictEffects --threads:on
+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}
-	nim c -o:$@ --gc:orc --debugger:native --checks:on --assertions:on src/test.nim
+	nim c ${COMPILE_OPTIONS} ${DEBUG_OPTIONS} -o:$@ examples/test.nim
 
 build/release/linux:
 	mkdir -p $@
 build/release/linux/test: build/release/linux ${SOURCES}
-	nim c -d:release --gc:orc -o:$@ --checks:off --assertions:off src/test.nim
+	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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/test.nim	Mon Dec 19 10:41:20 2022 +0700
@@ -0,0 +1,7 @@
+import engine
+
+
+when isMainModule:
+  var myengine = igniteEngine()
+  myengine.fullThrottle()
+  myengine.trash()
--- a/src/engine.nim	Fri Dec 16 00:05:41 2022 +0700
+++ b/src/engine.nim	Mon Dec 19 10:41:20 2022 +0700
@@ -1,4 +1,7 @@
+import std/strformat
 import std/enumerate
+import std/logging
+
 
 import ./vulkan
 import ./vulkan_helpers
@@ -6,6 +9,12 @@
 
 import ./glslang/glslang
 
+const MAX_FRAMES_IN_FLIGHT = 2
+
+var logger = newConsoleLogger()
+addHandler(logger)
+
+
 var vertexShaderCode: string = """#version 450
 layout(location = 0) out vec3 fragColor;
 vec3 colors[3] = vec3[](
@@ -39,10 +48,13 @@
 type
   GraphicsPipeline = object
     shaderStages*: seq[VkPipelineShaderStageCreateInfo]
+    layout*: VkPipelineLayout
+    renderPass*: VkRenderPass
+    pipeline*: VkPipeline
   QueueFamily = object
     properties*: VkQueueFamilyProperties
     hasSurfaceSupport*: bool
-  PhyscialDevice = object
+  PhysicalDevice = object
     device*: VkPhysicalDevice
     extensions*: seq[string]
     properties*: VkPhysicalDeviceProperties
@@ -52,61 +64,74 @@
     surfaceFormats: seq[VkSurfaceFormatKHR]
     presentModes: seq[VkPresentModeKHR]
   Vulkan* = object
+    debugMessenger: VkDebugUtilsMessengerEXT
     instance*: VkInstance
-    deviceList*: seq[PhyscialDevice]
-    activePhysicalDevice*: PhyscialDevice
-    activeQueueFamily*: uint32
+    deviceList*: seq[PhysicalDevice]
+    activePhysicalDevice*: PhysicalDevice
+    graphicsQueueFamily*: uint32
+    graphicsQueue*: VkQueue
+    presentationQueueFamily*: uint32
+    presentationQueue*: VkQueue
     device*: VkDevice
-    presentationQueue*: VkQueue
     surface*: VkSurfaceKHR
     selectedSurfaceFormat: VkSurfaceFormatKHR
     selectedPresentationMode: VkPresentModeKHR
-    selectedExtent: VkExtent2D
+    frameDimension: VkExtent2D
     swapChain: VkSwapchainKHR
     swapImages: seq[VkImage]
+    swapFramebuffers: seq[VkFramebuffer]
     swapImageViews: seq[VkImageView]
+    pipeline*: GraphicsPipeline
+    commandPool*: VkCommandPool
+    commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]
+    viewport*: VkViewport
+    scissor*: VkRect2D
+    imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
+    renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
+    inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence]
   Engine* = object
     display*: PDisplay
     window*: x.Window
     vulkan*: Vulkan
-    pipeline*: GraphicsPipeline
 
 
-proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[PhyscialDevice] =
+proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[PhysicalDevice] =
   for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance):
-    var device = PhyscialDevice(device: vulkanPhysicalDevice, extensions: getDeviceExtensions(vulkanPhysicalDevice))
+    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)
 
+    debug(&"Physical device nr {int(vulkanPhysicalDevice)} {cleanString(device.properties.deviceName)}")
     for i, queueFamilyProperty in enumerate(getQueueFamilies(vulkanPhysicalDevice)):
       var hasSurfaceSupport: VkBool32 = VK_FALSE
       checkVkResult vkGetPhysicalDeviceSurfaceSupportKHR(vulkanPhysicalDevice, uint32(i), surface, addr(hasSurfaceSupport))
       device.queueFamilies.add(QueueFamily(properties: queueFamilyProperty, hasSurfaceSupport: bool(hasSurfaceSupport)))
+      debug(&"  Queue family {i} {queueFamilyProperty}")
 
     result.add(device)
 
-proc filterForDevice(devices: seq[PhyscialDevice]): seq[(PhyscialDevice, uint32)] =
+proc filterForDevice(devices: seq[PhysicalDevice]): seq[(PhysicalDevice, uint32, uint32)] =
   for device in devices:
-    if "VK_KHR_swapchain" in device.extensions:
-      for i, queueFamily in enumerate(device.queueFamilies):
-        let hasGraphics = bool(uint32(queueFamily.properties.queueFlags) and ord(VK_QUEUE_GRAPHICS_BIT))
-        if (
-          queueFamily.hasSurfaceSupport and
-          hasGraphics and
-          device.surfaceFormats.len > 0 and
-          device.presentModes.len > 0
-        ):
-          result.add((device, uint32(i)))
+    if not (device.surfaceFormats.len > 0 and device.presentModes.len > 0 and "VK_KHR_swapchain" in device.extensions):
+      continue
+    var graphicsQueueFamily = high(uint32)
+    var presentationQueueFamily = high(uint32)
+    for i, queueFamily in enumerate(device.queueFamilies):
+      if queueFamily.hasSurfaceSupport:
+        presentationQueueFamily = uint32(i)
+      if bool(uint32(queueFamily.properties.queueFlags) and ord(VK_QUEUE_GRAPHICS_BIT)):
+        graphicsQueueFamily = uint32(i)
+    if graphicsQueueFamily != high(uint32) and presentationQueueFamily != high(uint32):
+      result.add((device, graphicsQueueFamily, presentationQueueFamily))
 
-proc filterForSurfaceFormat(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] =
-  for format in formats:
-    if format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
-      result.add(format)
+  for (device, graphicsQueueFamily, presentationQueueFamily) in result:
+    debug(&"Viable device: {cleanString(device.properties.deviceName)} (graphics queue family {graphicsQueueFamily}, presentation queue family {presentationQueueFamily})")
 
-proc getSwapExtent(display: PDisplay, window: Window, capabilities: VkSurfaceCapabilitiesKHR): VkExtent2D =
+
+proc getFrameDimension(display: PDisplay, window: Window, capabilities: VkSurfaceCapabilitiesKHR): VkExtent2D =
   if capabilities.currentExtent.width != high(uint32):
     return capabilities.currentExtent
   else:
@@ -116,37 +141,69 @@
       height: min(max(uint32(height), capabilities.minImageExtent.height), capabilities.maxImageExtent.height),
     )
 
+proc createVulkanSurface(instance: VkInstance, display: PDisplay, window: Window): VkSurfaceKHR =
+  var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR(
+    sType: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
+    dpy: display,
+    window: window,
+  )
+  checkVkResult vkCreateXlibSurfaceKHR(instance, addr(surfaceCreateInfo), nil, addr(result))
+
+proc setupVulkanDeviceAndQueues(instance: VkInstance, surface: VkSurfaceKHR): (PhysicalDevice, uint32, uint32, VkDevice, VkQueue, VkQueue) =
+  let usableDevices = instance.getAllPhysicalDevices(surface).filterForDevice()
+  if len(usableDevices) == 0:
+    raise newException(Exception, "No suitable graphics device found")
+  result[0] = usableDevices[0][0]
+  result[1] = usableDevices[0][1]
+  result[2] = usableDevices[0][2]
+
+  debug(&"Chose device {cleanString(result[0].properties.deviceName)}")
+  
+  (result[3], result[4], result[5]) = getVulcanDevice(
+    result[0].device,
+    result[0].features,
+    result[1],
+    result[2],
+  )
+
 proc igniteEngine*(): Engine =
-  vkLoad1_0()
-  vkLoad1_1()
-  vkLoad1_2()
 
   # init X11 window
   (result.display, result.window) = xlibInit()
 
   # create vulkan instance
+  vkLoad1_0()
+  vkLoad1_1()
+  vkLoad1_2()
   result.vulkan.instance = createVulkanInstance(VULKAN_VERSION)
-
-  # create vulkan-X11 surface
-  var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR(
-    sType: VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
-    dpy: result.display,
-    window: result.window,
-  )
-  checkVkResult vkCreateXlibSurfaceKHR(result.vulkan.instance, addr(surfaceCreateInfo), nil, addr(result.vulkan.surface))
+  when ENABLEVULKANVALIDATIONLAYERS:
+    var createInfo = VkDebugUtilsMessengerCreateInfoEXT(
+      sType: VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
+      messageSeverity: VkDebugUtilsMessageSeverityFlagsEXT(
+        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) or
+        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) or
+        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
+      ),
+      messageType: VkDebugUtilsMessageTypeFlagsEXT(
+        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) or
+        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) or
+        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
+      ),
+      pfnUserCallback: debugCallback,
+      pUserData: nil,
+    )
+    checkVkResult vkCreateDebugUtilsMessengerEXT(result.vulkan.instance, addr(createInfo), nil, addr(result.vulkan.debugMessenger))
 
-  # determine device and queue to use and instantiate
-  result.vulkan.deviceList = result.vulkan.instance.getAllPhysicalDevices(result.vulkan.surface)
-  let usableDevices = result.vulkan.deviceList.filterForDevice()
-  if len(usableDevices) == 0:
-    raise newException(Exception, "No suitable graphics device found")
-  (result.vulkan.activePhysicalDevice, result.vulkan.activeQueueFamily) = usableDevices[0]
-  
-  (result.vulkan.device, result.vulkan.presentationQueue) = getVulcanDevice(
-    result.vulkan.activePhysicalDevice.device,
-    result.vulkan.activePhysicalDevice.features,
-    result.vulkan.activeQueueFamily
-  )
+  result.vulkan.surface = result.vulkan.instance.createVulkanSurface(result.display, result.window)
+
+  (
+    result.vulkan.activePhysicalDevice,
+    result.vulkan.graphicsQueueFamily,
+    result.vulkan.presentationQueueFamily,
+    result.vulkan.device,
+    result.vulkan.graphicsQueue,
+    result.vulkan.presentationQueue
+  ) = result.vulkan.instance.setupVulkanDeviceAndQueues(result.vulkan.surface)
   
   # determine surface format for swapchain
   let usableSurfaceFormats = filterForSurfaceFormat(result.vulkan.activePhysicalDevice.surfaceFormats)
@@ -154,7 +211,7 @@
     raise newException(Exception, "No suitable surface formats found")
   result.vulkan.selectedSurfaceFormat = usableSurfaceFormats[0]
   result.vulkan.selectedPresentationMode = getPresentMode(result.vulkan.activePhysicalDevice.presentModes)
-  result.vulkan.selectedExtent = getSwapExtent(result.display, result.window, result.vulkan.activePhysicalDevice.surfaceCapabilities)
+  result.vulkan.frameDimension = result.display.getFrameDimension(result.window, result.vulkan.activePhysicalDevice.surfaceCapabilities)
 
   # setup swapchain
   var swapchainCreateInfo = VkSwapchainCreateInfoKHR(
@@ -163,7 +220,7 @@
     minImageCount: max(result.vulkan.activePhysicalDevice.surfaceCapabilities.minImageCount + 1, result.vulkan.activePhysicalDevice.surfaceCapabilities.maxImageCount),
     imageFormat: result.vulkan.selectedSurfaceFormat.format,
     imageColorSpace: result.vulkan.selectedSurfaceFormat.colorSpace,
-    imageExtent: result.vulkan.selectedExtent,
+    imageExtent: result.vulkan.frameDimension,
     imageArrayLayers: 1,
     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?)
@@ -174,8 +231,8 @@
     clipped: VK_TRUE,
     oldSwapchain: VkSwapchainKHR(0),
   )
-  checkVkResult vkCreateSwapchainKHR(result.vulkan.device, addr(swapchainCreateInfo), nil, addr(result.vulkan.swapChain))
-  result.vulkan.swapImages = getSwapChainImages(result.vulkan.device, result.vulkan.swapChain)
+  checkVkResult result.vulkan.device.vkCreateSwapchainKHR(addr(swapchainCreateInfo), nil, addr(result.vulkan.swapChain))
+  result.vulkan.swapImages = result.vulkan.device.getSwapChainImages(result.vulkan.swapChain)
 
   # setup swapchian image views
   result.vulkan.swapImageViews = newSeq[VkImageView](result.vulkan.swapImages.len)
@@ -199,77 +256,344 @@
         layerCount: 1,
       ),
     )
-    checkVkResult vkCreateImageView(result.vulkan.device, addr(imageViewCreateInfo), nil, addr(result.vulkan.swapImageViews[i]))
+    checkVkResult result.vulkan.device.vkCreateImageView(addr(imageViewCreateInfo), nil, addr(result.vulkan.swapImageViews[i]))
 
   # init shader system
   checkGlslangResult glslang_initialize_process()
 
   # load shaders
-  result.pipeline.shaderStages.add(createShaderStage(result.vulkan.device, VK_SHADER_STAGE_VERTEX_BIT, vertexShaderCode))
-  result.pipeline.shaderStages.add(createShaderStage(result.vulkan.device, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShaderCode))
+  result.vulkan.pipeline.shaderStages.add(result.vulkan.device.createShaderStage(VK_SHADER_STAGE_VERTEX_BIT, vertexShaderCode))
+  result.vulkan.pipeline.shaderStages.add(result.vulkan.device.createShaderStage(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShaderCode))
+
+  # setup render passes
+  var
+    colorAttachment = VkAttachmentDescription(
+      format: result.vulkan.selectedSurfaceFormat.format,
+      samples: VK_SAMPLE_COUNT_1_BIT,
+      loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR,
+      storeOp: VK_ATTACHMENT_STORE_OP_STORE,
+      stencilLoadOp: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+      stencilStoreOp: VK_ATTACHMENT_STORE_OP_DONT_CARE,
+      initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
+      finalLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+    )
+    colorAttachmentRef = VkAttachmentReference(
+      attachment: 0,
+      layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+    )
+    subpass = VkSubpassDescription(
+      pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS,
+      colorAttachmentCount: 1,
+      pColorAttachments: addr(colorAttachmentRef)
+    )
+    dependency = VkSubpassDependency(
+      srcSubpass: VK_SUBPASS_EXTERNAL,
+      dstSubpass: 0,
+      srcStageMask: VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT),
+      srcAccessMask: VkAccessFlags(0),
+      dstStageMask: VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT),
+      dstAccessMask: VkAccessFlags(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
+    )
+    renderPassCreateInfo = VkRenderPassCreateInfo(
+      sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+      attachmentCount: 1,
+      pAttachments: addr(colorAttachment),
+      subpassCount: 1,
+      pSubpasses: addr(subpass),
+      dependencyCount: 1,
+      pDependencies: addr(dependency),
+    )
+  checkVkResult result.vulkan.device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result.vulkan.pipeline.renderPass))
 
   # create graphis pipeline
-  var dynamicStates = [VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR]
-  var dynamicState = VkPipelineDynamicStateCreateInfo(
-    sType: VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
-    dynamicStateCount: uint32(dynamicStates.len),
-    pDynamicStates: addr(dynamicStates[0]),
-  )
-  var vertexInputInfo = VkPipelineVertexInputStateCreateInfo(
-    sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
-    vertexBindingDescriptionCount: 0,
-    pVertexBindingDescriptions: nil,
-    vertexAttributeDescriptionCount: 0,
-    pVertexAttributeDescriptions: nil,
-  )
-  var inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
-    sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
-    topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
-    primitiveRestartEnable: VK_FALSE,
-  )
+  
+  var
+    # define which parts can be dynamic (pipeline is fixed after setup)
+    dynamicStates = [VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR]
+    dynamicState = VkPipelineDynamicStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+      dynamicStateCount: uint32(dynamicStates.len),
+      pDynamicStates: addr(dynamicStates[0]),
+    )
+
+    # define input data format
+    vertexInputInfo = VkPipelineVertexInputStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+      vertexBindingDescriptionCount: 0,
+      pVertexBindingDescriptions: nil,
+      vertexAttributeDescriptionCount: 0,
+      pVertexAttributeDescriptions: nil,
+    )
+    inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+      topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+      primitiveRestartEnable: VK_FALSE,
+    )
 
   # setup viewport
-  var viewport = VkViewport(
+  result.vulkan.viewport = VkViewport(
     x: 0.0,
     y: 0.0,
-    width: (float) result.vulkan.selectedExtent.width,
-    height: (float) result.vulkan.selectedExtent.height,
+    width: (float) result.vulkan.frameDimension.width,
+    height: (float) result.vulkan.frameDimension.height,
     minDepth: 0.0,
     maxDepth: 1.0,
   )
-  var scissor = VkRect2D(
+  result.vulkan.scissor = VkRect2D(
     offset: VkOffset2D(x: 0, y: 0),
-    extent: result.vulkan.selectedExtent
+    extent: result.vulkan.frameDimension
+  )
+  var viewportState = VkPipelineViewportStateCreateInfo(
+    sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+    viewportCount: 1,
+    pViewports: addr(result.vulkan.viewport),
+    scissorCount: 1,
+    pScissors: addr(result.vulkan.scissor),
   )
 
+  # rasterizerization config
+  var
+    rasterizer = VkPipelineRasterizationStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+      depthClampEnable: VK_FALSE,
+      rasterizerDiscardEnable: VK_FALSE,
+      polygonMode: VK_POLYGON_MODE_FILL,
+      lineWidth: 1.0,
+      cullMode: VkCullModeFlags(VK_CULL_MODE_BACK_BIT),
+      frontFace: VK_FRONT_FACE_CLOCKWISE,
+      depthBiasEnable: VK_FALSE,
+      depthBiasConstantFactor: 0.0,
+      depthBiasClamp: 0.0,
+      depthBiasSlopeFactor: 0.0,
+    )
+    multisampling = VkPipelineMultisampleStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+      sampleShadingEnable: VK_FALSE,
+      rasterizationSamples: VK_SAMPLE_COUNT_1_BIT,
+      minSampleShading: 1.0,
+      pSampleMask: nil,
+      alphaToCoverageEnable: VK_FALSE,
+      alphaToOneEnable: VK_FALSE,
+    )
+    colorBlendAttachment = VkPipelineColorBlendAttachmentState(
+      colorWriteMask: VkColorComponentFlags(
+        ord(VK_COLOR_COMPONENT_R_BIT) or
+        ord(VK_COLOR_COMPONENT_G_BIT) or
+        ord(VK_COLOR_COMPONENT_B_BIT) or
+        ord(VK_COLOR_COMPONENT_A_BIT)
+      ),
+      blendEnable: VK_TRUE,
+      srcColorBlendFactor: VK_BLEND_FACTOR_SRC_ALPHA,
+      dstColorBlendFactor: VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+      colorBlendOp: VK_BLEND_OP_ADD,
+      srcAlphaBlendFactor: VK_BLEND_FACTOR_ONE,
+      dstAlphaBlendFactor: VK_BLEND_FACTOR_ZERO,
+      alphaBlendOp: VK_BLEND_OP_ADD,
+    )
+    colorBlending = VkPipelineColorBlendStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+      logicOpEnable: VK_TRUE,
+      logicOp: VK_LOGIC_OP_COPY,
+      attachmentCount: 1,
+      pAttachments: addr(colorBlendAttachment),
+      blendConstants: [0.0'f, 0.0'f, 0.0'f, 0.0'f],
+    )
+
+    # create pipeline
+    pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+      setLayoutCount: 0,
+      pSetLayouts: nil,
+      pushConstantRangeCount: 0,
+      pPushConstantRanges: nil,
+    )
+  checkVkResult result.vulkan.device.vkCreatePipelineLayout(addr(pipelineLayoutInfo), nil, addr(result.vulkan.pipeline.layout))
+
+  var pipelineInfo = VkGraphicsPipelineCreateInfo(
+    sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+    stageCount: 2,
+    pStages: addr(result.vulkan.pipeline.shaderStages[0]),
+    pVertexInputState: addr(vertexInputInfo),
+    pInputAssemblyState: addr(inputAssembly),
+    pViewportState: addr(viewportState),
+    pRasterizationState: addr(rasterizer),
+    pMultisampleState: addr(multisampling),
+    pDepthStencilState: nil,
+    pColorBlendState: addr(colorBlending),
+    pDynamicState: addr(dynamicState),
+    layout: result.vulkan.pipeline.layout,
+    renderPass: result.vulkan.pipeline.renderPass,
+    subpass: 0,
+    basePipelineHandle: VkPipeline(0),
+    basePipelineIndex: -1,
+  )
+  checkVkResult result.vulkan.device.vkCreateGraphicsPipelines(
+    VkPipelineCache(0),
+    1,
+    addr(pipelineInfo),
+    nil,
+    addr(result.vulkan.pipeline.pipeline)
+  )
+
+  # set up framebuffers
+  result.vulkan.swapFramebuffers  = newSeq[VkFramebuffer](result.vulkan.swapImages.len)
+
+  for i, imageview in enumerate(result.vulkan.swapImageViews):
+    var framebufferInfo = VkFramebufferCreateInfo(
+      sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+      renderPass: result.vulkan.pipeline.renderPass,
+      attachmentCount: 1,
+      pAttachments: addr(result.vulkan.swapImageViews[i]),
+      width: result.vulkan.frameDimension.width,
+      height: result.vulkan.frameDimension.height,
+      layers: 1,
+    )
+    checkVkResult result.vulkan.device.vkCreateFramebuffer(addr(framebufferInfo), nil, addr(result.vulkan.swapFramebuffers[i]))
+  
+  # set up command buffer
+  var poolInfo = VkCommandPoolCreateInfo(
+    sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+    flags: VkCommandPoolCreateFlags(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT),
+    queueFamilyIndex: result.vulkan.graphicsQueueFamily,
+  )
+  checkVkResult result.vulkan.device.vkCreateCommandPool(addr(poolInfo), nil, addr(result.vulkan.commandPool))
+
+  var allocInfo = VkCommandBufferAllocateInfo(
+    sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+    commandPool: result.vulkan.commandPool,
+    level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+    commandBufferCount: result.vulkan.commandBuffers.len.uint32,
+  )
+  checkVkResult result.vulkan.device.vkAllocateCommandBuffers(addr(allocInfo), addr(result.vulkan.commandBuffers[0]))
+
+  # create semaphores for syncing rendering
+  var semaphoreInfo = VkSemaphoreCreateInfo(sType: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO)
+  var fenceInfo = VkFenceCreateInfo(
+    sType: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+    flags: VkFenceCreateFlags(VK_FENCE_CREATE_SIGNALED_BIT)
+  )
+  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
+    checkVkResult result.vulkan.device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(result.vulkan.imageAvailableSemaphores[i]))
+    checkVkResult result.vulkan.device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(result.vulkan.renderFinishedSemaphores[i]))
+    checkVkResult result.vulkan.device.vkCreateFence(addr(fenceInfo), nil, addr(result.vulkan.inFlightFences[i]))
+
 
-proc fullThrottle*(engine: Engine) =
-  var event: XEvent
-  while true:
-    discard XNextEvent(engine.display, addr(event))
-    case event.theType
-    of Expose:
-      discard
-    of ClientMessage:
-      if cast[Atom](event.xclient.data.l[0]) == deleteMessage:
-        break
-    of KeyPress:
-      let key = XLookupKeysym(cast[PXKeyEvent](addr(event)), 0)
-      if key != 0:
-        echo "Key ", key, " pressed"
-    of ButtonPressMask:
-      echo "Mouse button ", event.xbutton.button, " pressed at ",
-          event.xbutton.x, ",", event.xbutton.y
-    else:
-      discard
+proc recordCommandBuffer(vulkan: var Vulkan, commandBuffer: VkCommandBuffer, imageIndex: uint32) =
+  var beginInfo = VkCommandBufferBeginInfo(
+    sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+    pInheritanceInfo: nil,
+  )
+  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
+
+  var
+    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,
+      renderPass: vulkan.pipeline.renderPass,
+      framebuffer: vulkan.swapFramebuffers[imageIndex],
+      renderArea: VkRect2D(
+        offset: VkOffset2D(x: 0, y: 0),
+        extent: vulkan.frameDimension,
+      ),
+      clearValueCount: 1,
+      pClearValues: addr(clearColor),
+    )
+  commandBuffer.vkCmdBeginRenderPass(addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE)
+  commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan.pipeline.pipeline)
+
+  commandBuffer.vkCmdSetViewport(firstViewport=0, viewportCount=1, addr(vulkan.viewport))
+  commandBuffer.vkCmdSetScissor(firstScissor=0, scissorCount=1, addr(vulkan.scissor))
+  commandBuffer.vkCmdDraw(vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0)
+  commandBuffer.vkCmdEndRenderPass()
+  checkVkResult commandBuffer.vkEndCommandBuffer()
+
+proc drawFrame(vulkan: var Vulkan, currentFrame: int) =
+  checkVkResult vulkan.device.vkWaitForFences(1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64))
+  checkVkResult vulkan.device.vkResetFences(1, addr(vulkan.inFlightFences[currentFrame]))
+  var bufferImageIndex: uint32
+  checkVkResult vulkan.device.vkAcquireNextImageKHR(
+    vulkan.swapChain,
+    high(uint64),
+    vulkan.imageAvailableSemaphores[currentFrame],
+    VkFence(0),
+    addr(bufferImageIndex)
+  )
+
+  checkVkResult vkResetCommandBuffer(vulkan.commandBuffers[currentFrame], VkCommandBufferResetFlags(0))
+  recordCommandBuffer(vulkan, vulkan.commandBuffers[currentFrame], bufferImageIndex)
+  var
+    waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]]
+    waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
+    signalSemaphores = [vulkan.renderFinishedSemaphores[currentFrame]]
+    submitInfo = VkSubmitInfo(
+      sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
+      waitSemaphoreCount: 1,
+      pWaitSemaphores: addr(waitSemaphores[0]),
+      pWaitDstStageMask: addr(waitStages[0]),
+      commandBufferCount: 1,
+      pCommandBuffers: addr(vulkan.commandBuffers[currentFrame]),
+      signalSemaphoreCount: 1,
+      pSignalSemaphores: addr(signalSemaphores[0]),
+    )
+  checkVkResult vkQueueSubmit(vulkan.graphicsQueue, 1, addr(submitInfo), vulkan.inFlightFences[currentFrame])
+
+  var presentInfo = VkPresentInfoKHR(
+    sType: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+    waitSemaphoreCount: 1,
+    pWaitSemaphores: addr(signalSemaphores[0]),
+    swapchainCount: 1,
+    pSwapchains: addr(vulkan.swapChain),
+    pImageIndices: addr(bufferImageIndex),
+    pResults: nil,
+  )
+  checkVkResult vkQueuePresentKHR(vulkan.presentationQueue, addr(presentInfo))
+
+
+proc fullThrottle*(engine: var Engine) =
+  var
+    event: XEvent
+    killed = false
+    currentFrame = 0
+
+  while not killed:
+    while engine.display.XPending() > 0 and not killed:
+      discard engine.display.XNextEvent(addr(event))
+      case event.theType
+      of ClientMessage:
+        if cast[Atom](event.xclient.data.l[0]) == deleteMessage:
+          killed = true
+      of KeyPress:
+        let key = XLookupKeysym(cast[PXKeyEvent](addr(event)), 0)
+        if key == XK_Escape:
+          killed = true
+      else:
+        discard
+    drawFrame(engine.vulkan, currentFrame)
+    currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT;
+  checkVkResult engine.vulkan.device.vkDeviceWaitIdle()
+
 
 proc trash*(engine: Engine) =
-  for shaderStage in engine.pipeline.shaderStages:
-    vkDestroyShaderModule(engine.vulkan.device, shaderStage.module, nil);
+  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
+    engine.vulkan.device.vkDestroySemaphore(engine.vulkan.imageAvailableSemaphores[i], nil)
+    engine.vulkan.device.vkDestroySemaphore(engine.vulkan.renderFinishedSemaphores[i], nil)
+    engine.vulkan.device.vkDestroyFence(engine.vulkan.inFlightFences[i], nil)
+
+  engine.vulkan.device.vkDestroyCommandPool(engine.vulkan.commandPool, nil)
+  for framebuffer in engine.vulkan.swapFramebuffers:
+    engine.vulkan.device.vkDestroyFramebuffer(framebuffer, nil)
+
+  engine.vulkan.device.vkDestroyPipeline(engine.vulkan.pipeline.pipeline, nil)
+  engine.vulkan.device.vkDestroyPipelineLayout(engine.vulkan.pipeline.layout, nil)
+  engine.vulkan.device.vkDestroyRenderPass(engine.vulkan.pipeline.renderPass, nil)
+
+  for shaderStage in engine.vulkan.pipeline.shaderStages:
+    engine.vulkan.device.vkDestroyShaderModule(shaderStage.module, nil)
+
   glslang_finalize_process()
-  vkDestroySwapchainKHR(engine.vulkan.device, engine.vulkan.swapChain, nil);
-  vkDestroySurfaceKHR(engine.vulkan.instance, engine.vulkan.surface, nil);
-  vkDestroyDevice(engine.vulkan.device, nil)
-  vkDestroyInstance(engine.vulkan.instance, nil)
+  engine.vulkan.device.vkDestroySwapchainKHR(engine.vulkan.swapChain, nil)
+  engine.vulkan.instance.vkDestroySurfaceKHR(engine.vulkan.surface, nil)
+  engine.vulkan.device.vkDestroyDevice(nil)
+  engine.vulkan.instance.vkDestroyInstance(nil)
   checkXlibResult engine.display.XDestroyWindow(engine.window)
   discard engine.display.XCloseDisplay() # always returns 0
--- a/src/glslang/glslang_c_interface.nim	Fri Dec 16 00:05:41 2022 +0700
+++ b/src/glslang/glslang_c_interface.nim	Mon Dec 19 10:41:20 2022 +0700
@@ -1,9 +1,20 @@
-# required to link the GLSL compiler
+import std/strformat
+
+when defined(linux):
+  const platform = "linux"
+when defined(windows):
+  const platform = "windows"
+
+
 when defined(release):
-  {.passl: "-Lthirdparty/glslang/lib/release" .}
+  const libversion = "release"
 else:
-  {.passl: "-Lthirdparty/glslang/lib/debug" .}
-{.passl: "-Lthirdparty/spirv-tools/lib/" .}
+  const libversion = "debug"
+
+
+# required to link the GLSL compiler
+{.passl: &"-Lthirdparty/lib/glslang/{platform}_{libversion}" .}
+{.passl: &"-Lthirdparty/lib/spirv-tools/{platform}_{libversion}" .}
 
 {.passl: "-lglslang" .}
 {.passl: "-lglslang-default-resource-limits" .}
@@ -14,7 +25,6 @@
 {.passl: "-lOGLCompiler" .}
 {.passl: "-lSPIRV" .}
 {.passl: "-lSPIRV-Tools-opt" .}
-
 {.passl: "-lSPIRV-Tools" .}
 {.passl: "-lSPIRV-Tools-diff" .}
 {.passl: "-lSPIRV-Tools-fuzz" .}
Binary file src/test has changed
--- a/src/test.nim	Fri Dec 16 00:05:41 2022 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-import engine
-
-
-when isMainModule:
-  let myengine = igniteEngine()
-  myengine.fullThrottle()
-  myengine.trash()
--- a/src/vulkan.nim	Fri Dec 16 00:05:41 2022 +0700
+++ b/src/vulkan.nim	Mon Dec 19 10:41:20 2022 +0700
@@ -91,6 +91,7 @@
     VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL = 6
     VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL = 7
     VK_IMAGE_LAYOUT_PREINITIALIZED = 8
+    VK_IMAGE_LAYOUT_PRESENT_SRC_KHR = 1000001002,
   VkAttachmentLoadOp* {.size: int32.sizeof.} = enum
     VK_ATTACHMENT_LOAD_OP_LOAD = 0
     VK_ATTACHMENT_LOAD_OP_CLEAR = 1
@@ -508,7 +509,9 @@
     VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO = 47
     VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO = 48
     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_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT = 1000128004 # added by sam
   VkSubpassContents* {.size: int32.sizeof.} = enum
     VK_SUBPASS_CONTENTS_INLINE = 0
     VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS = 1
@@ -10635,18 +10638,18 @@
   vkCreateMacOSSurfaceMVK = cast[proc(instance: VkInstance, pCreateInfo: ptr VkMacOSSurfaceCreateInfoMVK , pAllocator: ptr VkAllocationCallbacks , pSurface: ptr VkSurfaceKHR ): VkResult {.stdcall.}](vkGetProc("vkCreateMacOSSurfaceMVK"))
 
 # Load VK_EXT_debug_utils
-proc loadVK_EXT_debug_utils*() =
-  vkSetDebugUtilsObjectNameEXT = cast[proc(device: VkDevice, pNameInfo: ptr VkDebugUtilsObjectNameInfoEXT ): VkResult {.stdcall.}](vkGetProc("vkSetDebugUtilsObjectNameEXT"))
-  vkSetDebugUtilsObjectTagEXT = cast[proc(device: VkDevice, pTagInfo: ptr VkDebugUtilsObjectTagInfoEXT ): VkResult {.stdcall.}](vkGetProc("vkSetDebugUtilsObjectTagEXT"))
-  vkQueueBeginDebugUtilsLabelEXT = cast[proc(queue: VkQueue, pLabelInfo: ptr VkDebugUtilsLabelEXT ): void {.stdcall.}](vkGetProc("vkQueueBeginDebugUtilsLabelEXT"))
-  vkQueueEndDebugUtilsLabelEXT = cast[proc(queue: VkQueue): void {.stdcall.}](vkGetProc("vkQueueEndDebugUtilsLabelEXT"))
-  vkQueueInsertDebugUtilsLabelEXT = cast[proc(queue: VkQueue, pLabelInfo: ptr VkDebugUtilsLabelEXT ): void {.stdcall.}](vkGetProc("vkQueueInsertDebugUtilsLabelEXT"))
-  vkCmdBeginDebugUtilsLabelEXT = cast[proc(commandBuffer: VkCommandBuffer, pLabelInfo: ptr VkDebugUtilsLabelEXT ): void {.stdcall.}](vkGetProc("vkCmdBeginDebugUtilsLabelEXT"))
-  vkCmdEndDebugUtilsLabelEXT = cast[proc(commandBuffer: VkCommandBuffer): void {.stdcall.}](vkGetProc("vkCmdEndDebugUtilsLabelEXT"))
-  vkCmdInsertDebugUtilsLabelEXT = cast[proc(commandBuffer: VkCommandBuffer, pLabelInfo: ptr VkDebugUtilsLabelEXT ): void {.stdcall.}](vkGetProc("vkCmdInsertDebugUtilsLabelEXT"))
-  vkCreateDebugUtilsMessengerEXT = cast[proc(instance: VkInstance, pCreateInfo: ptr VkDebugUtilsMessengerCreateInfoEXT , pAllocator: ptr VkAllocationCallbacks , pMessenger: ptr VkDebugUtilsMessengerEXT ): VkResult {.stdcall.}](vkGetProc("vkCreateDebugUtilsMessengerEXT"))
-  vkDestroyDebugUtilsMessengerEXT = cast[proc(instance: VkInstance, messenger: VkDebugUtilsMessengerEXT, pAllocator: ptr VkAllocationCallbacks ): void {.stdcall.}](vkGetProc("vkDestroyDebugUtilsMessengerEXT"))
-  vkSubmitDebugUtilsMessageEXT = cast[proc(instance: VkInstance, messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT, messageTypes: VkDebugUtilsMessageTypeFlagsEXT, pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT ): void {.stdcall.}](vkGetProc("vkSubmitDebugUtilsMessageEXT"))
+proc loadVK_EXT_debug_utils*(instance: VkInstance) =
+  vkSetDebugUtilsObjectNameEXT = cast[proc(device: VkDevice, pNameInfo: ptr VkDebugUtilsObjectNameInfoEXT ): VkResult {.stdcall.}](vkGetInstanceProcAddr(instance, "vkSetDebugUtilsObjectNameEXT"))
+  vkSetDebugUtilsObjectTagEXT = cast[proc(device: VkDevice, pTagInfo: ptr VkDebugUtilsObjectTagInfoEXT ): VkResult {.stdcall.}](vkGetInstanceProcAddr(instance, "vkSetDebugUtilsObjectTagEXT"))
+  vkQueueBeginDebugUtilsLabelEXT = cast[proc(queue: VkQueue, pLabelInfo: ptr VkDebugUtilsLabelEXT ): void {.stdcall.}](vkGetInstanceProcAddr(instance, "vkQueueBeginDebugUtilsLabelEXT"))
+  vkQueueEndDebugUtilsLabelEXT = cast[proc(queue: VkQueue): void {.stdcall.}](vkGetInstanceProcAddr(instance, "vkQueueEndDebugUtilsLabelEXT"))
+  vkQueueInsertDebugUtilsLabelEXT = cast[proc(queue: VkQueue, pLabelInfo: ptr VkDebugUtilsLabelEXT ): void {.stdcall.}](vkGetInstanceProcAddr(instance, "vkQueueInsertDebugUtilsLabelEXT"))
+  vkCmdBeginDebugUtilsLabelEXT = cast[proc(commandBuffer: VkCommandBuffer, pLabelInfo: ptr VkDebugUtilsLabelEXT ): void {.stdcall.}](vkGetInstanceProcAddr(instance, "vkCmdBeginDebugUtilsLabelEXT"))
+  vkCmdEndDebugUtilsLabelEXT = cast[proc(commandBuffer: VkCommandBuffer): void {.stdcall.}](vkGetInstanceProcAddr(instance, "vkCmdEndDebugUtilsLabelEXT"))
+  vkCmdInsertDebugUtilsLabelEXT = cast[proc(commandBuffer: VkCommandBuffer, pLabelInfo: ptr VkDebugUtilsLabelEXT ): void {.stdcall.}](vkGetInstanceProcAddr(instance, "vkCmdInsertDebugUtilsLabelEXT"))
+  vkCreateDebugUtilsMessengerEXT = cast[proc(instance: VkInstance, pCreateInfo: ptr VkDebugUtilsMessengerCreateInfoEXT , pAllocator: ptr VkAllocationCallbacks , pMessenger: ptr VkDebugUtilsMessengerEXT ): VkResult {.stdcall.}](vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"))
+  vkDestroyDebugUtilsMessengerEXT = cast[proc(instance: VkInstance, messenger: VkDebugUtilsMessengerEXT, pAllocator: ptr VkAllocationCallbacks ): void {.stdcall.}](vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"))
+  vkSubmitDebugUtilsMessageEXT = cast[proc(instance: VkInstance, messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT, messageTypes: VkDebugUtilsMessageTypeFlagsEXT, pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT ): void {.stdcall.}](vkGetInstanceProcAddr(instance, "vkSubmitDebugUtilsMessageEXT"))
 
 # Load VK_ANDROID_external_memory_android_hardware_buffer
 proc loadVK_ANDROID_external_memory_android_hardware_buffer*() =
--- a/src/vulkan_helpers.nim	Fri Dec 16 00:05:41 2022 +0700
+++ b/src/vulkan_helpers.nim	Mon Dec 19 10:41:20 2022 +0700
@@ -1,26 +1,43 @@
 import std/tables
 import std/strutils
+import std/strformat
+import std/logging
 
 import ./glslang/glslang
 import ./vulkan
 
-
 when defined(release):
-  const ENABLEVULKANVALIDATIONLAYERS = false
+  const ENABLEVULKANVALIDATIONLAYERS* = false
 else:
-  const ENABLEVULKANVALIDATIONLAYERS = true
+  const ENABLEVULKANVALIDATIONLAYERS* = true
 
 
 template checkVkResult*(call: untyped) =
-  let value = call
-  if value != VK_SUCCESS:
-    raise newException(Exception, "Vulkan error: " & astToStr(call) & " returned " & $value)
+  when defined(release):
+    discard call
+  else:
+    let value = call
+    debug(&"CALLING vulkan: {astToStr(call)}")
+    if value != VK_SUCCESS:
+      raise newException(Exception, "Vulkan error: " & astToStr(call) & " returned " & $value)
 
 
 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
 
 
+proc filterForSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] =
+  for format in formats:
+    if format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
+      result.add(format)
+
+
+proc cleanString*(str: openArray[char]): string =
+  for i in 0 ..< len(str):
+    if str[i] == char(0):
+      result = join(str[0 ..< i])
+      break
+
 proc getInstanceExtensions*(): seq[string] =
   var extensionCount: uint32
   checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr(extensionCount), nil)
@@ -28,7 +45,7 @@
   checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr(extensionCount), addr(extensions[0]))
 
   for extension in extensions:
-    result.add(join(extension.extensionName).strip(chars={char(0)}))
+    result.add(cleanString(extension.extensionName))
 
 
 proc getDeviceExtensions*(device: VkPhysicalDevice): seq[string] =
@@ -38,7 +55,7 @@
   checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(extensionCount), addr(extensions[0]))
 
   for extension in extensions:
-    result.add(join(extension.extensionName).strip(chars={char(0)}))
+    result.add(cleanString(extension.extensionName))
 
 
 proc getValidationLayers*(): seq[string] =
@@ -48,7 +65,7 @@
   checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), addr(layers[0]))
 
   for layer in layers:
-    result.add(join(layer.layerName).strip(chars={char(0)}))
+    result.add(cleanString(layer.layerName))
 
 
 proc getVulkanPhysicalDevices*(instance: VkInstance): seq[VkPhysicalDevice] =
@@ -88,10 +105,10 @@
 
 proc getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR =
   let preferredModes = [
-    VK_PRESENT_MODE_MAILBOX_KHR,
-    VK_PRESENT_MODE_FIFO_RELAXED_KHR,
-    VK_PRESENT_MODE_FIFO_KHR,
-    VK_PRESENT_MODE_IMMEDIATE_KHR,
+    VK_PRESENT_MODE_MAILBOX_KHR, # triple buffering
+    VK_PRESENT_MODE_FIFO_RELAXED_KHR, # double duffering
+    VK_PRESENT_MODE_FIFO_KHR, # double duffering
+    VK_PRESENT_MODE_IMMEDIATE_KHR, # single buffering
   ]
   for preferredMode in preferredModes:
     for mode in modes:
@@ -108,6 +125,7 @@
     "VK_KHR_display".cstring,
     "VK_KHR_surface".cstring,
     "VK_KHR_xlib_surface".cstring,
+    "VK_EXT_debug_utils".cstring,
   ]
   let availableExtensions = getInstanceExtensions()
   for extension in requiredExtensions:
@@ -122,7 +140,9 @@
       if $layer in availableLayers:
         usableLayers.add(layer)
 
+  echo "Available validation layers: ", availableLayers
   echo "Using validation layers: ", usableLayers
+  echo "Available extensions: ", availableExtensions
   echo "Using extensions: ", requiredExtensions
 
   var appinfo = VkApplicationInfo(
@@ -144,33 +164,40 @@
   loadVK_KHR_surface()
   loadVK_KHR_xlib_surface()
   loadVK_KHR_swapchain()
+  when ENABLEVULKANVALIDATIONLAYERS:
+    loadVK_EXT_debug_utils(result)
 
 
 proc getVulcanDevice*(
   physicalDevice: var VkPhysicalDevice,
   features: var VkPhysicalDeviceFeatures,
-  selectedQueueFamily: uint32,
-): (VkDevice, VkQueue) =
+  graphicsQueueFamily: uint32,
+  presentationQueueFamily: uint32,
+): (VkDevice, VkQueue, VkQueue) =
   # setup queue and device
+  # TODO: need check this, possibly wrong logic, see Vulkan tutorial
   var priority = 1.0'f32
-  var queueCreateInfo = VkDeviceQueueCreateInfo(
-    sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
-    queueFamilyIndex: uint32(selectedQueueFamily),
-    queueCount: 1,
-    pQueuePriorities: addr(priority),
-  )
+  var queueCreateInfo = [
+    VkDeviceQueueCreateInfo(
+      sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+      queueFamilyIndex: graphicsQueueFamily,
+      queueCount: 1,
+      pQueuePriorities: addr(priority),
+    ),
+  ]
 
   var requiredExtensions = ["VK_KHR_swapchain".cstring]
   var deviceCreateInfo = VkDeviceCreateInfo(
     sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
-    pQueueCreateInfos: addr(queueCreateInfo),
-    queueCreateInfoCount: 1,
+    queueCreateInfoCount: uint32(queueCreateInfo.len),
+    pQueueCreateInfos: addr(queueCreateInfo[0]),
     pEnabledFeatures: addr(features),
     enabledExtensionCount: requiredExtensions.len.uint32,
     ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions))
   )
   checkVkResult vkCreateDevice(physicalDevice, addr(deviceCreateInfo), nil, addr(result[0]))
-  vkGetDeviceQueue(result[0], selectedQueueFamily, 0'u32, addr(result[1]));
+  vkGetDeviceQueue(result[0], graphicsQueueFamily, 0'u32, addr(result[1]));
+  vkGetDeviceQueue(result[0], presentationQueueFamily, 0'u32, addr(result[2]));
 
 proc createShaderStage*(device: VkDevice, stage: VkShaderStageFlagBits, shader: string): VkPipelineShaderStageCreateInfo =
   const VK_GLSL_MAP = {
@@ -180,15 +207,24 @@
   var code = compileGLSLToSPIRV(VK_GLSL_MAP[stage], shader, "<memory-shader>")
   var createInfo = VkShaderModuleCreateInfo(
     sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
-    codeSize: code.len.uint,
+    codeSize: uint(code.len * sizeof(uint32)),
     pCode: addr(code[0]),
   )
   var shaderModule: VkShaderModule
   checkVkResult vkCreateShaderModule(device, addr(createInfo), nil, addr(shaderModule))
 
-  var vertShaderStageInfo = VkPipelineShaderStageCreateInfo(
+  return VkPipelineShaderStageCreateInfo(
     sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
     stage: stage,
     module: shaderModule,
     pName: "main", # entry point for shader
   )
+
+proc debugCallback*(
+  messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT,
+  messageTypes: VkDebugUtilsMessageTypeFlagsEXT,
+  pCallbackData: VkDebugUtilsMessengerCallbackDataEXT,
+  userData: pointer
+): VkBool32 {.cdecl.} =
+  echo &"{messageSeverity}: {VkDebugUtilsMessageTypeFlagBitsEXT(messageTypes)}: {pCallbackData.pMessage}"
+  return VK_FALSE
--- a/src/xlib_helpers.nim	Fri Dec 16 00:05:41 2022 +0700
+++ b/src/xlib_helpers.nim	Mon Dec 19 10:41:20 2022 +0700
@@ -1,7 +1,10 @@
 import
   x11/xlib,
   x11/xutil,
-  x11/x
+  x11/x,
+  x11/keysym
+
+export keysym
 
 var deleteMessage*: Atom
 
@@ -11,6 +14,7 @@
     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"
@@ -26,7 +30,7 @@
   checkXlibResult XSelectInput(display, window, ButtonPressMask or KeyPressMask or ExposureMask)
   checkXlibResult XMapWindow(display, window)
 
-  deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", false.XBool)
+  deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false))
   checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1)
 
   return (display, window)