changeset 1059:9c364af8d3f0

did: tons of small improvments, on the way to make GPU sync (more) correct I guess
author sam <sam@basx.dev>
date Sun, 31 Mar 2024 22:11:50 +0700
parents 7a0d5fc00f4f
children b3f782703aa5 71a23166ccd6
files config.nims examples/E01_hello_triangle.nim semicongine/core/dynamic_arrays.nim semicongine/engine.nim semicongine/mesh.nim semicongine/platform/linux/window.nim semicongine/platform/windows/window.nim semicongine/renderer.nim semicongine/scene.nim semicongine/vulkan/buffer.nim semicongine/vulkan/commandbuffer.nim semicongine/vulkan/device.nim semicongine/vulkan/swapchain.nim
diffstat 13 files changed, 136 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/config.nims	Sun Mar 31 18:13:46 2024 +0700
+++ b/config.nims	Sun Mar 31 22:11:50 2024 +0700
@@ -2,61 +2,26 @@
 import std/strutils
 import std/os
 
-const BUILDBASE = "build"
-const DEBUG = "debug"
-const RELEASE = "release"
-const LINUX = "linux"
-const WINDOWS = "windows"
+import semicongine/build
 
-const PACKAGETYPE* {.strdefine.}: string = "dir" # dir, zip, exe
-const RESOURCEROOT* {.strdefine.}: string = "resources"
+# TODO: totally update this file!!
 
-switch("d", "nimPreviewHashRef")
-switch("experimental", "strictEffects")
-switch("experimental", "strictFuncs")
 switch("nimblePath", "nimbledeps/pkgs2")
 
-task build, "build":
-  switch("d", "PACKAGETYPE=" & PACKAGETYPE)
-  switch("d", "RESOURCEROOT=" & RESOURCEROOT)
-  var buildType = DEBUG
-  var platformDir = ""
-  if defined(linux):
-    switch("define", "VK_USE_PLATFORM_XLIB_KHR")
-    platformDir = LINUX
-  if defined(windows):
-    switch("define", "VK_USE_PLATFORM_WIN32_KHR")
-    platformDir = WINDOWS
-  if defined(release):
-    switch("app", "gui")
-    buildType = RELEASE
-  else:
-    switch("debugger", "native")
-
-  var outdir = getCurrentDir() / BUILDBASE / buildType / platformDir / projectName()
-  switch("outdir", outdir)
+task build_dev, "build dev":
+  semicongine_build_switches(buildname = "dev")
   setCommand "c"
-  rmDir(outdir)
-  mkDir(outdir)
-  let resourcedir = joinPath(projectDir(), RESOURCEROOT)
-  if dirExists(resourcedir):
-    let outdir_resources = joinPath(outdir, RESOURCEROOT)
-    if PACKAGETYPE == "dir":
-      cpDir(resourcedir, outdir_resources)
-    elif PACKAGETYPE == "zip":
-      mkDir(outdir_resources)
-      for resource in listDirs(resourcedir):
-        let
-          oldcwd = getCurrentDir()
-          outputfile = joinPath(outdir_resources, resource.splitPath().tail & ".zip")
-          inputfile = resource.splitPath().tail
-        cd(resource)
-        if defined(linux):
-          exec &"zip -r {outputfile} ."
-        elif defined(windows):
-          # TODO: test this
-          exec &"powershell Compress-Archive * {outputfile}"
-        cd(oldcwd)
+  let outdir = semicongine_builddir(buildname = "dev")
+  semicongine_pack(outdir, bundleType = "exe", resourceRoot = "resources")
+
+task build_release, "build release":
+  switch "define", "release"
+  switch "app", "gui"
+  semicongine_build_switches(buildname = "release")
+  setCommand "c"
+  let outdir = semicongine_builddir(buildname = "release")
+  semicongine_pack(outdir, bundleType = "exe", resourceRoot = "resources")
+
 
 task build_all_debug, "build all examples for debug":
   for file in listFiles("examples"):
@@ -77,9 +42,6 @@
   exec("nim build -d:BUILD_RESOURCEROOT=tests/resources -d:PACKAGETYPE=zip --run tests/test_resources.nim")
   exec("nim build -d:BUILD_RESOURCEROOT=tests/resources -d:PACKAGETYPE=exe --run tests/test_resources.nim")
 
-task clean, "remove all build files":
-  exec(&"rm -rf {BUILDBASE}")
-
 task publish, "publish all build":
   for file in listDirs("build/debug/linux"):
     exec(&"scp -r {file} sam@mail.basx.dev:/var/www/public.basx.dev/semicongine/debug/linux/")
--- a/examples/E01_hello_triangle.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/examples/E01_hello_triangle.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -1,35 +1,37 @@
 import std/tables
 
-import ../src/semicongine
+import ../semicongine
 
 # shader setup
 const
   shaderConfiguration = createShaderConfiguration(
-    inputs=[
+    inputs = [
       attr[Vec3f]("position"),
       attr[Vec4f]("color"),
     ],
-    intermediates=[attr[Vec4f]("outcolor")],
-    outputs=[attr[Vec4f]("color")],
-    vertexCode="gl_Position = vec4(position, 1.0); outcolor = color;",
-    fragmentCode="color = outcolor;",
+    intermediates = [attr[Vec4f]("outcolor")],
+    outputs = [attr[Vec4f]("color")],
+    vertexCode = "gl_Position = vec4(position, 1.0); outcolor = color;",
+    fragmentCode = "color = outcolor;",
   )
 
 # scene setup
 var
-  triangle = Scene(name: "scene",
+  scene = Scene(name: "scene",
     meshes: @[newMesh(
-      [newVec3f(-0.5, 0.5), newVec3f(0, -0.5), newVec3f(0.5, 0.5)],
-      [newVec4f(1, 0, 0, 1), newVec4f(0, 1, 0, 1), newVec4f(0, 0, 1, 1)],
-      material=Material(name: "default")
+      positions = [newVec3f(-0.5, 0.5), newVec3f(0, -0.5), newVec3f(0.5, 0.5)],
+      colors = [newVec4f(1, 0, 0, 1), newVec4f(0, 1, 0, 1), newVec4f(0, 0, 1, 1)],
+      material = VERTEX_COLORED_MATERIAL.initMaterialData()
     )]
   )
-  myengine = initEngine("Hello triangle")
+  myengine = initEngine("Hello triangle", showFps = true)
 
-myengine.initRenderer({"default": shaderConfiguration}.toTable)
-myengine.addScene(triangle)
+myengine.initRenderer({VERTEX_COLORED_MATERIAL: shaderConfiguration}, vSync = false, inFlightFrames = 1)
+myengine.loadScene(scene)
 
 while myengine.updateInputs() == Running and not myengine.keyWasPressed(Escape):
-  myengine.renderScene(triangle)
+  echo ""
+  transform[Vec3f](scene.meshes[0][], "position", scale(1.001, 1.001))
+  myengine.renderScene(scene)
 
 myengine.destroy()
--- a/semicongine/core/dynamic_arrays.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/core/dynamic_arrays.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -473,6 +473,8 @@
   getValue[t](list, i)
 
 # since we use this often with tables, add this for an easy assignment
+template `[]`*(table: Table[string, DataList], key: string, t: typedesc): ref seq[t] =
+  getValues[t](table[key])
 template `[]=`*[T](table: var Table[string, DataList], key: string, values: openArray[T]) =
   if table.contains(key):
     table[key].setValues(values)
--- a/semicongine/engine.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/engine.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -1,4 +1,7 @@
+import std/algorithm
+import std/monotimes
 import std/options
+import std/strformat
 import std/sequtils
 import std/logging
 import std/os
@@ -19,6 +22,8 @@
 import ./text
 import ./panel
 
+const COUNT_N_RENDERTIMES = 99
+
 type
   EngineState* = enum
     Starting
@@ -37,6 +42,9 @@
     windowWasResized: bool
     mouseWheel: float32
   Engine* = object
+    applicationName: string
+    debug: bool
+    showFps: bool
     state*: EngineState
     device: Device
     debugger: Debugger
@@ -48,6 +56,8 @@
     resizeHandler: proc(engine: var Engine)
     eventHandler: proc(engine: var Engine, event: Event)
     fullscreen: bool
+    lastNRenderTimes: array[COUNT_N_RENDERTIMES, int64]
+    currentRenderTimeI: int = 0
 
 # forward declarations
 func getAspectRatio*(engine: Engine): float32
@@ -66,6 +76,7 @@
 proc initEngine*(
   applicationName: string,
   debug = DEBUG,
+  showFps = DEBUG,
   exitHandler: proc(engine: var Engine) = nil,
   resizeHandler: proc(engine: var Engine) = nil,
   eventHandler: proc(engine: var Engine, event: Event) = nil,
@@ -79,34 +90,34 @@
   result.exitHandler = exitHandler
   result.resizeHandler = resizeHandler
   result.eventHandler = eventHandler
-  result.window = createWindow(applicationName)
+  result.applicationName = applicationName
+  result.debug = debug
+  result.showFps = showFps
+  result.window = createWindow(result.applicationName)
 
   var
     layers = @vulkanLayers
     instanceExtensions: seq[string]
 
-  if debug:
+  if result.debug:
     instanceExtensions.add "VK_EXT_debug_utils"
     layers.add "VK_LAYER_KHRONOS_validation"
     # This stuff might be usefull if we one day to smart GPU memory allocation,
-    # but right now it just clobbers up the console log
+    # but right now it just clobbers up the console log:
     # putEnv("VK_LAYER_ENABLES", "VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT")
     putEnv("VK_LAYER_ENABLES", "VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD,VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_NVIDIA,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXTVK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT")
 
-  if defined(linux) and DEBUG:
-    layers.add "VK_LAYER_MESA_overlay"
   result.instance = result.window.createInstance(
     vulkanVersion = vulkanVersion,
     instanceExtensions = instanceExtensions,
-    layers = layers,
+    layers = layers.deduplicate(),
   )
-  if debug:
+  if result.debug:
     result.debugger = result.instance.createDebugMessenger()
   # create devices
   let selectedPhysicalDevice = result.instance.getPhysicalDevices().filterBestGraphics()
   result.device = result.instance.createDevice(
     selectedPhysicalDevice,
-    enabledLayers = @[],
     enabledExtensions = @[],
     selectedPhysicalDevice.filterForGraphicsPresentationQueues()
   )
@@ -154,11 +165,26 @@
 proc renderScene*(engine: var Engine, scene: var Scene) =
   assert engine.state == Running
   assert engine.renderer.isSome
+  let t0 = getMonoTime()
   scene.setShaderGlobal(ASPECT_RATIO_ATTRIBUTE, engine.getAspectRatio)
   engine.renderer.get.updateMeshData(scene)
   engine.renderer.get.updateUniformData(scene)
   engine.renderer.get.render(scene)
 
+  if engine.showFps:
+    let nanoSecs = getMonoTime().ticks - t0.ticks
+    engine.lastNRenderTimes[engine.currentRenderTimeI] = nanoSecs
+    inc engine.currentRenderTimeI
+    if engine.currentRenderTimeI >= engine.lastNRenderTimes.len:
+      engine.currentRenderTimeI = 0
+      engine.lastNRenderTimes.sort
+      let
+        min = float(engine.lastNRenderTimes[0]) / 1_000_000
+        median = float(engine.lastNRenderTimes[engine.lastNRenderTimes.len div 2]) / 1_000_000
+        max = float(engine.lastNRenderTimes[^1]) / 1_000_000
+      engine.window.setTitle(&"{engine.applicationName} ({min:.2}, {median:.2}, {max:.2})")
+
+
 proc updateInputs*(engine: var Engine): EngineState =
   assert engine.state in [Starting, Running]
 
--- a/semicongine/mesh.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/mesh.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -384,6 +384,7 @@
       mesh.instanceData[attribute][i] = transform * mesh.vertexData[attribute][i, T]
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+  mesh.dirtyAttributes.add attribute
 
 proc applyTransformToVertices*(mesh: var MeshObject, positionAttribute = DEFAULT_POSITION_ATTRIBUTE) =
   for i in 0 ..< mesh.vertexData[positionAttribute].len:
--- a/semicongine/platform/linux/window.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/platform/linux/window.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -77,6 +77,9 @@
   checkXlibResult display.XFreePixmap(pixmap)
   return NativeWindow(display: display, window: window, emptyCursor: empty_cursor)
 
+proc setTitle*(window: NativeWindow, title: string) =
+  checkXlibResult XSetStandardProperties(window.display, window.window, title, "window", 0, nil, 0, nil)
+
 proc fullscreen*(window: var NativeWindow, enable: bool) =
   var
     wm_state = window.display.XInternAtom("_NET_WM_STATE", 0)
@@ -94,8 +97,8 @@
         0,
         0,
         0
-      ]
-    )
+    ]
+  )
   )
   xev.theType = ClientMessage
 
--- a/semicongine/platform/windows/window.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/platform/windows/window.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -100,6 +100,9 @@
   result.g_wpPrev.length = UINT(sizeof(WINDOWPLACEMENT))
   discard result.hwnd.ShowWindow(SW_SHOW)
 
+proc setTitle*(window: NativeWindow, title: string)
+  window.hwnd.SetWindowText(T(title))
+
 # inspired by the one and only, Raymond Chen
 # https://devblogs.microsoft.com/oldnewthing/20100412-00/?p=14353
 proc fullscreen*(window: var NativeWindow, enable: bool) =
--- a/semicongine/renderer.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/renderer.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -362,7 +362,7 @@
 
   if forceAll:
     debug "Update uniforms because 'forceAll' was given"
-  else:
+  elif dirty.len > 0:
     debug &"Update uniforms because of dirty scene globals: {dirty}"
 
   # loop over all used shaders/pipelines
@@ -411,25 +411,31 @@
         offset += uniform.size
   scene.clearDirtyShaderGlobals()
 
+proc startNewFrame(renderer: var Renderer) =
+  # this is kinda important as we will wait for the queue finished fence from the swapchain
+  if not renderer.swapchain.nextFrame():
+    let res = renderer.swapchain.recreate()
+    if not res.isSome:
+      raise newException(Exception, "Unable to recreate swapchain")
+    var oldSwapchain = renderer.swapchain
+    renderer.swapchain = res.get()
+    checkVkResult renderer.device.vk.vkDeviceWaitIdle()
+    oldSwapchain.destroy()
+
 proc render*(renderer: var Renderer, scene: Scene) =
   assert scene in renderer.scenedata
 
-  if not renderer.swapchain.nextFrame():
-    let res = renderer.swapchain.recreate()
-    if res.isSome:
-      var oldSwapchain = renderer.swapchain
-      renderer.swapchain = res.get()
-      checkVkResult renderer.device.vk.vkDeviceWaitIdle()
-      oldSwapchain.destroy()
-    return
-
+  # preparation
+  renderer.startNewFrame()
   renderer.currentFrameCommandBuffer.beginRenderCommands(renderer.renderPass, renderer.swapchain.currentFramebuffer(), oneTimeSubmit = true)
 
+  # debug output
   debug "Scene buffers:"
   for (location, buffer) in renderer.scenedata[scene].vertexBuffers.pairs:
     debug "  ", location, ": ", buffer
   debug "  Index buffer: ", renderer.scenedata[scene].indexBuffer
 
+  # draw all meshes
   for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines:
     if scene.usesMaterial(materialType):
       debug &"Start shaderPipeline for '{materialType}'"
@@ -438,8 +444,10 @@
       for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].material.theType == materialType):
         drawable.draw(renderer.currentFrameCommandBuffer, vertexBuffers = renderer.scenedata[scene].vertexBuffers, indexBuffer = renderer.scenedata[scene].indexBuffer, shaderPipeline.vk)
 
+  # done rendering
   renderer.currentFrameCommandBuffer.endRenderCommands()
 
+  # swap framebuffer
   if not renderer.swapchain.swap(renderer.queue, renderer.currentFrameCommandBuffer):
     let res = renderer.swapchain.recreate()
     if res.isSome:
--- a/semicongine/scene.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/scene.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -58,6 +58,8 @@
   scene.getShaderGlobalArray(name)[][0]
 
 proc setShaderGlobalArray*[T](scene: var Scene, name: string, value: openArray[T]) =
+  if scene.shaderGlobals[name, T][] == @value:
+    return
   scene.shaderGlobals[name] = value
   if not scene.dirtyShaderGlobals.contains(name):
     scene.dirtyShaderGlobals.add name
--- a/semicongine/vulkan/buffer.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/vulkan/buffer.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -99,6 +99,7 @@
 
 
 proc copy*(src, dst: Buffer, queue: Queue, dstOffset = 0) =
+  # TODO? This is super slow, because withSingleUseCommandBuffer uses vkQueueWaitIdle
   assert src.device.vk.valid
   assert dst.device.vk.valid
   assert src.device == dst.device
@@ -108,14 +109,16 @@
 
   var copyRegion = VkBufferCopy(size: VkDeviceSize(src.size), dstOffset: VkDeviceSize(dstOffset))
   withSingleUseCommandBuffer(src.device, queue, true, commandBuffer):
+    # TODO: This barrier is somehow "reversed"
+    # I think we need to check for the queue-finished fence before we
+    # do any buffer updates, otherwise will append buffer updates after the draw calls
     let barrier = VkMemoryBarrier(
       sType: VK_STRUCTURE_TYPE_MEMORY_BARRIER,
-      srcAccessMask: [VK_ACCESS_MEMORY_WRITE_BIT].toBits,
-      dstAccessMask: [VK_ACCESS_MEMORY_READ_BIT].toBits,
+      dstAccessMask: [VK_ACCESS_MEMORY_WRITE_BIT].toBits,
     )
     commandBuffer.pipelineBarrier(
+      [VK_PIPELINE_STAGE_VERTEX_INPUT_BIT],
       [VK_PIPELINE_STAGE_TRANSFER_BIT],
-      [VK_PIPELINE_STAGE_VERTEX_INPUT_BIT],
       memoryBarriers = [barrier]
     )
     commandBuffer.vkCmdCopyBuffer(src.vk, dst.vk, 1, addr(copyRegion))
--- a/semicongine/vulkan/commandbuffer.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/vulkan/commandbuffer.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -55,10 +55,10 @@
 
 
 template withSingleUseCommandBuffer*(device: Device, queue: Queue, needsTransfer: bool, commandBuffer, body: untyped): untyped =
+  # TODO? This is super slow, because we call vkQueueWaitIdle
   assert device.vk.valid
   assert queue.vk.valid
 
-  checkVkResult queue.vk.vkQueueWaitIdle()
   var
     commandBufferPool = createCommandBufferPool(device, queue.family, 1)
     commandBuffer = commandBufferPool.buffers[0]
--- a/semicongine/vulkan/device.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/vulkan/device.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -24,7 +24,6 @@
 proc createDevice*(
   instance: Instance,
   physicalDevice: PhysicalDevice,
-  enabledLayers: seq[string],
   enabledExtensions: seq[string],
   queueFamilies: seq[QueueFamily],
 ): Device =
@@ -37,7 +36,6 @@
   for extension in allExtensions:
     instance.vk.loadExtension(extension)
   var
-    enabledLayersC = allocCStringArray(enabledLayers)
     enabledExtensionsC = allocCStringArray(allExtensions)
     priority = 1'f32
   var deviceQueues: Table[QueueFamily, VkDeviceQueueCreateInfo]
@@ -58,8 +56,8 @@
     sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
     queueCreateInfoCount: uint32(queueList.len),
     pQueueCreateInfos: queueList.toCPointer,
-    enabledLayerCount: uint32(enabledLayers.len),
-    ppEnabledLayerNames: enabledLayersC,
+    enabledLayerCount: 0,
+    ppEnabledLayerNames: nil,
     enabledExtensionCount: uint32(allExtensions.len),
     ppEnabledExtensionNames: enabledExtensionsC,
     pEnabledFeatures: nil,
@@ -72,7 +70,6 @@
     pAllocator = nil,
     pDevice = addr result.vk
   )
-  deallocCStringArray(enabledLayersC)
   deallocCStringArray(enabledExtensionsC)
   for family in deviceQueues.keys:
     var queue: VkQueue
--- a/semicongine/vulkan/swapchain.nim	Sun Mar 31 18:13:46 2024 +0700
+++ b/semicongine/vulkan/swapchain.nim	Sun Mar 31 22:11:50 2024 +0700
@@ -14,18 +14,17 @@
     device*: Device
     vk*: VkSwapchainKHR
     dimension*: TVec2[uint32]
-    nImages*: uint32
-    imageviews*: seq[ImageView]
-    framebuffers*: seq[Framebuffer]
+    nFramebuffers*: uint32
     currentInFlight*: int
     currentFramebufferIndex: uint32
+    framebufferViews*: seq[ImageView]
+    framebuffers*: seq[Framebuffer]
     queueFinishedFence*: seq[Fence]
     imageAvailableSemaphore*: seq[Semaphore]
     renderFinishedSemaphore*: seq[Semaphore]
     # required for recreation:
     renderPass: VkRenderPass
     surfaceFormat: VkSurfaceFormatKHR
-    imageCount: uint32
     inFlightFrames*: int
     presentQueue: Queue
     vSync: bool
@@ -36,7 +35,7 @@
   renderPass: VkRenderPass,
   surfaceFormat: VkSurfaceFormatKHR,
   inFlightFrames: int,
-  desiredNumberOfImages = 3'u32,
+  desiredFramebufferCount = 3'u32,
   oldSwapchain = VkSwapchainKHR(0),
   vSync = false
 ): Option[Swapchain] =
@@ -49,17 +48,17 @@
   if capabilities.currentExtent.width == 0 or capabilities.currentExtent.height == 0:
     return none(Swapchain)
 
-  var imageCount = desiredNumberOfImages
+  var minFramebufferCount = desiredFramebufferCount
 
   # following is according to vulkan specs
-  imageCount = max(imageCount, capabilities.minImageCount)
+  minFramebufferCount = max(minFramebufferCount, capabilities.minImageCount)
   if capabilities.maxImageCount != 0:
-    imageCount = min(imageCount, capabilities.maxImageCount)
+    minFramebufferCount = min(minFramebufferCount, capabilities.maxImageCount)
   let hasTripleBuffering = VK_PRESENT_MODE_MAILBOX_KHR in device.physicalDevice.getSurfacePresentModes()
   var createInfo = VkSwapchainCreateInfoKHR(
     sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
     surface: device.physicalDevice.surface,
-    minImageCount: imageCount,
+    minImageCount: minFramebufferCount,
     imageFormat: surfaceFormat.format,
     imageColorSpace: surfaceFormat.colorSpace,
     imageExtent: capabilities.currentExtent,
@@ -83,22 +82,19 @@
       vSync: vSync
     )
 
-  if device.vk.vkCreateSwapchainKHR(addr(createInfo), nil, addr(swapchain.vk)) == VK_SUCCESS:
-    var nImages: uint32
-    checkVkResult device.vk.vkGetSwapchainImagesKHR(swapChain.vk, addr(nImages), nil)
-    swapchain.nImages = nImages
-    var images = newSeq[VkImage](nImages)
-    checkVkResult device.vk.vkGetSwapchainImagesKHR(swapChain.vk, addr(nImages), images.toCPointer)
-    for vkimage in images:
-      let image = VulkanImage(vk: vkimage, format: surfaceFormat.format, device: device)
-      let imageview = image.createImageView()
-      swapChain.imageviews.add imageview
-      swapChain.framebuffers.add device.createFramebuffer(renderPass, [imageview], swapchain.dimension)
+  if device.vk.vkCreateSwapchainKHR(addr createInfo, nil, addr swapchain.vk) == VK_SUCCESS:
+    checkVkResult device.vk.vkGetSwapchainImagesKHR(swapchain.vk, addr swapchain.nFramebuffers, nil)
+    var framebuffers = newSeq[VkImage](swapchain.nFramebuffers)
+    checkVkResult device.vk.vkGetSwapchainImagesKHR(swapchain.vk, addr swapchain.nFramebuffers, framebuffers.toCPointer)
+    for framebuffer in framebuffers:
+      let framebufferView = VulkanImage(vk: framebuffer, format: surfaceFormat.format, device: device).createImageView()
+      swapchain.framebufferViews.add framebufferView
+      swapchain.framebuffers.add device.createFramebuffer(renderPass, [framebufferView], swapchain.dimension)
     for i in 0 ..< swapchain.inFlightFrames:
       swapchain.queueFinishedFence.add device.createFence()
       swapchain.imageAvailableSemaphore.add device.createSemaphore()
       swapchain.renderFinishedSemaphore.add device.createSemaphore()
-    debug &"Created swapchain with: {nImages} framebuffers, {inFlightFrames} in-flight frames, {swapchain.dimension.x}x{swapchain.dimension.y}"
+    debug &"Created swapchain with: {swapchain.nFramebuffers} framebuffers, {inFlightFrames} in-flight frames, {swapchain.dimension.x}x{swapchain.dimension.y}"
     assert device.firstPresentationQueue().isSome, "No present queue found"
     swapchain.presentQueue = device.firstPresentationQueue().get
     result = some(swapchain)
@@ -122,7 +118,7 @@
     high(uint64),
     swapchain.imageAvailableSemaphore[swapchain.currentInFlight].vk,
     VkFence(0),
-    addr(swapchain.currentFramebufferIndex)
+    addr swapchain.currentFramebufferIndex,
   )
 
   if nextImageResult == VK_SUCCESS:
@@ -142,29 +138,29 @@
     submitInfo = VkSubmitInfo(
       sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
       waitSemaphoreCount: 1,
-      pWaitSemaphores: addr(waitSemaphores[0]),
-      pWaitDstStageMask: addr(waitStages[0]),
+      pWaitSemaphores: waitSemaphores.toCPointer,
+      pWaitDstStageMask: waitStages.toCPointer,
       commandBufferCount: 1,
-      pCommandBuffers: addr(commandBuffer),
+      pCommandBuffers: addr commandBuffer,
       signalSemaphoreCount: 1,
-      pSignalSemaphores: addr(swapchain.renderFinishedSemaphore[swapchain.currentInFlight].vk),
+      pSignalSemaphores: addr swapchain.renderFinishedSemaphore[swapchain.currentInFlight].vk,
     )
   checkVkResult queue.vk.vkQueueSubmit(
-    1,
-    addr(submitInfo),
-    swapchain.queueFinishedFence[swapchain.currentInFlight].vk
+    submitCount = 1,
+    pSubmits = addr submitInfo,
+    fence = swapchain.queueFinishedFence[swapchain.currentInFlight].vk
   )
 
   var presentInfo = VkPresentInfoKHR(
     sType: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
     waitSemaphoreCount: 1,
-    pWaitSemaphores: addr(swapchain.renderFinishedSemaphore[swapchain.currentInFlight].vk),
+    pWaitSemaphores: addr swapchain.renderFinishedSemaphore[swapchain.currentInFlight].vk,
     swapchainCount: 1,
-    pSwapchains: addr(swapchain.vk),
-    pImageIndices: addr(swapchain.currentFramebufferIndex),
+    pSwapchains: addr swapchain.vk,
+    pImageIndices: addr swapchain.currentFramebufferIndex,
     pResults: nil,
   )
-  let presentResult = vkQueuePresentKHR(swapchain.presentQueue.vk, addr(presentInfo))
+  let presentResult = vkQueuePresentKHR(swapchain.presentQueue.vk, addr presentInfo)
   if presentResult != VK_SUCCESS:
     return false
 
@@ -174,7 +170,7 @@
 proc destroy*(swapchain: var Swapchain) =
   assert swapchain.vk.valid
 
-  for imageview in swapchain.imageviews.mitems:
+  for imageview in swapchain.framebufferViews.mitems:
     assert imageview.vk.valid
     imageview.destroy()
   for framebuffer in swapchain.framebuffers.mitems:
@@ -198,7 +194,7 @@
     device = swapchain.device,
     renderPass = swapchain.renderPass,
     surfaceFormat = swapchain.surfaceFormat,
-    desiredNumberOfImages = swapchain.imageCount,
+    desiredFramebufferCount = swapchain.nFramebuffers,
     inFlightFrames = swapchain.inFlightFrames,
     oldSwapchain = swapchain.vk,
     vSync = swapchain.vSync