changeset 1052:b8d20f75ecef

add: some sync (not finished), simplify renderpass af
author sam <sam@basx.dev>
date Sat, 30 Mar 2024 20:29:33 +0700
parents d9b5370da3fe
children 6377949df709
files semicongine/core/utils.nim semicongine/core/vulkanapi.nim semicongine/engine.nim semicongine/renderer.nim semicongine/vulkan/buffer.nim semicongine/vulkan/commandbuffer.nim semicongine/vulkan/device.nim semicongine/vulkan/image.nim semicongine/vulkan/renderpass.nim semicongine/vulkan/swapchain.nim
diffstat 10 files changed, 294 insertions(+), 312 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/core/utils.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/core/utils.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -18,7 +18,7 @@
       result = join(str[0 ..< i])
       break
 
-func toCPointer*[T](list: seq[T]): ptr T =
+func toCPointer*[T](list: openArray[T]): ptr T =
   if list.len > 0: addr(list[0]) else: nil
 
 proc staticExecChecked*(command: string, input = ""): string {.compileTime.} =
--- a/semicongine/core/vulkanapi.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/core/vulkanapi.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -402,13 +402,13 @@
   VkShaderCorePropertiesFlagsAMD* = distinct VkFlags
   VkDeviceDiagnosticsConfigFlagsNV* = distinct VkFlags
   VkRefreshObjectFlagsKHR* = distinct VkFlags
-  VkAccessFlags2* = distinct VkFlags
-  VkPipelineStageFlags2* = distinct VkFlags
+  VkAccessFlags2* = distinct VkFlags64
+  VkPipelineStageFlags2* = distinct VkFlags64
   VkAccelerationStructureMotionInfoFlagsNV* = distinct VkFlags
   VkAccelerationStructureMotionInstanceFlagsNV* = distinct VkFlags
-  VkFormatFeatureFlags2* = distinct VkFlags
+  VkFormatFeatureFlags2* = distinct VkFlags64
   VkRenderingFlags* = distinct VkFlags
-  VkMemoryDecompressionMethodFlagsNV* = distinct VkFlags
+  VkMemoryDecompressionMethodFlagsNV* = distinct VkFlags64
   VkBuildMicromapFlagsEXT* = distinct VkFlags
   VkMicromapCreateFlagsEXT* = distinct VkFlags
   VkDirectDriverLoadingFlagsLUNARG* = distinct VkFlags
--- a/semicongine/engine.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/engine.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -217,7 +217,6 @@
 func mouseMove*(engine: Engine): auto = engine.input.mouseMove
 func mouseWheel*(engine: Engine): auto = engine.input.mouseWheel
 func eventsProcessed*(engine: Engine): auto = engine.input.eventsProcessed
-func framesRendered*(engine: Engine): uint64 = (if engine.renderer.isSome: engine.renderer.get.framesRendered else: 0)
 func gpuDevice*(engine: Engine): Device = engine.device
 func getWindow*(engine: Engine): auto = engine.window
 func getAspectRatio*(engine: Engine): float32 = engine.getWindow().size[0] / engine.getWindow().size[1]
--- a/semicongine/renderer.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/renderer.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -6,6 +6,7 @@
 import std/logging
 
 import ./core
+import ./vulkan/commandbuffer
 import ./vulkan/buffer
 import ./vulkan/device
 import ./vulkan/drawable
@@ -37,40 +38,46 @@
     materials: Table[MaterialType, seq[MaterialData]]
   Renderer* = object
     device: Device
-    surfaceFormat: VkSurfaceFormatKHR
     renderPass: RenderPass
     swapchain: Swapchain
     scenedata: Table[Scene, SceneData]
     emptyTexture: VulkanTexture
+    queue: Queue
+    commandBufferPool: CommandBufferPool
+
+proc currentCommandBuffer(renderer: Renderer): VkCommandBuffer =
+  renderer.commandBufferPool.buffers[renderer.swapchain.currentInFlight]
 
 proc initRenderer*(device: Device, shaders: openArray[(MaterialType, ShaderConfiguration)], clearColor = Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling = true): Renderer =
   assert device.vk.valid
 
   result.device = device
-  result.renderPass = device.simpleForwardRenderPass(shaders, clearColor = clearColor, backFaceCulling = backFaceCulling)
-  result.surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat()
-  # use last renderpass as output for swapchain
-  let swapchain = device.createSwapchain(result.renderPass.vk, result.surfaceFormat, device.firstGraphicsQueue().get().family)
+  result.renderPass = device.createRenderPass(shaders, clearColor = clearColor, backFaceCulling = backFaceCulling)
+  let swapchain = device.createSwapchain(
+    result.renderPass.vk,
+    device.physicalDevice.getSurfaceFormats().filterSurfaceFormat(),
+  )
   if not swapchain.isSome:
     raise newException(Exception, "Unable to create swapchain")
 
+  result.queue = device.firstGraphicsQueue().get()
+  result.commandBufferPool = device.createCommandBufferPool(result.queue.family, swapchain.get().inFlightFrames)
   result.swapchain = swapchain.get()
-  result.emptyTexture = device.uploadTexture(EMPTYTEXTURE)
+  result.emptyTexture = device.uploadTexture(result.queue, EMPTYTEXTURE)
 
 func inputs(renderer: Renderer, scene: Scene): seq[ShaderAttribute] =
   var found: Table[string, ShaderAttribute]
-  for i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialType, shaderPipeline) in renderer.renderPass.subpasses[i].shaderPipelines:
-      if scene.usesMaterial(materialType):
-        for input in shaderPipeline.inputs:
-          if found.contains(input.name):
-            assert input.name == found[input.name].name, &"{input.name}: {input.name} != {found[input.name].name}"
-            assert input.theType == found[input.name].theType, &"{input.name}: {input.theType} != {found[input.name].theType}"
-            assert input.arrayCount == found[input.name].arrayCount, &"{input.name}: {input.arrayCount} != {found[input.name].arrayCount}"
-            assert input.memoryPerformanceHint == found[input.name].memoryPerformanceHint, &"{input.name}: {input.memoryPerformanceHint} != {found[input.name].memoryPerformanceHint}"
-          else:
-            result.add input
-            found[input.name] = input
+  for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines:
+    if scene.usesMaterial(materialType):
+      for input in shaderPipeline.inputs:
+        if found.contains(input.name):
+          assert input.name == found[input.name].name, &"{input.name}: {input.name} != {found[input.name].name}"
+          assert input.theType == found[input.name].theType, &"{input.name}: {input.theType} != {found[input.name].theType}"
+          assert input.arrayCount == found[input.name].arrayCount, &"{input.name}: {input.arrayCount} != {found[input.name].arrayCount}"
+          assert input.memoryPerformanceHint == found[input.name].memoryPerformanceHint, &"{input.name}: {input.memoryPerformanceHint} != {found[input.name].memoryPerformanceHint}"
+        else:
+          result.add input
+          found[input.name] = input
 
 proc materialCompatibleWithPipeline(scene: Scene, materialType: MaterialType, shaderPipeline: ShaderPipeline): (bool, string) =
   for uniform in shaderPipeline.uniforms:
@@ -116,14 +123,13 @@
 
   var foundRenderableObject = false
   var materialTypes: seq[MaterialType]
-  for i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialType, shaderPipeline) in renderer.renderPass.subpasses[i].shaderPipelines:
-      materialTypes.add materialType
-      for mesh in scene.meshes:
-        if mesh.material.theType == materialType:
-          foundRenderableObject = true
-          let (error, message) = scene.meshCompatibleWithPipeline(mesh, shaderPipeline)
-          assert not error, &"Mesh '{mesh}' not compatible with assigned shaderPipeline ({materialType}) because: {message}"
+  for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines:
+    materialTypes.add materialType
+    for mesh in scene.meshes:
+      if mesh.material.theType == materialType:
+        foundRenderableObject = true
+        let (error, message) = scene.meshCompatibleWithPipeline(mesh, shaderPipeline)
+        assert not error, &"Mesh '{mesh}' not compatible with assigned shaderPipeline ({materialType}) because: {message}"
 
   if not foundRenderableObject:
     var matTypes: Table[string, MaterialType]
@@ -218,12 +224,11 @@
 
     # fill offsets per shaderPipeline (as sequence corresponds to shader input binding)
     var offsets: Table[VkPipeline, seq[(string, MemoryPerformanceHint, int)]]
-    for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
-      for (materialType, shaderPipeline) in renderer.renderPass.subpasses[subpass_i].shaderPipelines:
-        if scene.usesMaterial(materialType):
-          offsets[shaderPipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]()
-          for attribute in shaderPipeline.inputs:
-            offsets[shaderPipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)])
+    for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines:
+      if scene.usesMaterial(materialType):
+        offsets[shaderPipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]()
+        for attribute in shaderPipeline.inputs:
+          offsets[shaderPipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)])
 
     # create drawables
     let indexed = mesh.indexType != MeshIndexType.None
@@ -246,73 +251,72 @@
       drawable.indexBufferOffset = indexBufferOffset
       drawable.indexType = mesh.indexType
       var (pdata, size) = mesh[].getRawIndexData()
-      scenedata.indexBuffer.setData(pdata, size, indexBufferOffset)
+      scenedata.indexBuffer.setData(renderer.queue, pdata, size, indexBufferOffset)
       indexBufferOffset += size
     scenedata.drawables.add (drawable, mesh)
 
   # setup uniforms and textures (anything descriptor)
   var uploadedTextures: Table[Texture, VulkanTexture]
-  for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialType, shaderPipeline) in renderer.renderPass.subpasses[subpass_i].shaderPipelines:
-      if scene.usesMaterial(materialType):
-        # gather textures
-        scenedata.textures[shaderPipeline.vk] = initTable[string, seq[VulkanTexture]]()
-        for texture in shaderPipeline.samplers:
-          scenedata.textures[shaderPipeline.vk][texture.name] = newSeq[VulkanTexture]()
-          if scene.shaderGlobals.contains(texture.name):
-            for textureValue in scene.shaderGlobals[texture.name][Texture][]:
-              if not uploadedTextures.contains(textureValue):
-                uploadedTextures[textureValue] = renderer.device.uploadTexture(textureValue)
-              scenedata.textures[shaderPipeline.vk][texture.name].add uploadedTextures[textureValue]
-          else:
-            var foundTexture = false
-            for material in scene.getMaterials(materialType):
-              if material.hasMatchingAttribute(texture):
-                foundTexture = true
-                let value = material[texture.name, Texture][]
-                assert value.len == 1, &"Mesh material attribute '{texture.name}' has texture-array, but only single textures are allowed"
-                if not uploadedTextures.contains(value[0]):
-                  uploadedTextures[value[0]] = renderer.device.uploadTexture(value[0])
-                scenedata.textures[shaderPipeline.vk][texture.name].add uploadedTextures[value[0]]
-            assert foundTexture, &"No texture found in shaderGlobals or materials for '{texture.name}'"
-          let nTextures = scenedata.textures[shaderPipeline.vk][texture.name].len
-          assert (texture.arrayCount == 0 and nTextures == 1) or texture.arrayCount >= nTextures, &"Shader assigned to render '{materialType}' expected {texture.arrayCount} textures for '{texture.name}' but got {nTextures}"
-          if texture.arrayCount < nTextures:
-            warn &"Shader assigned to render '{materialType}' expected {texture.arrayCount} textures for '{texture.name}' but got {nTextures}"
+  for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines:
+    if scene.usesMaterial(materialType):
+      # gather textures
+      scenedata.textures[shaderPipeline.vk] = initTable[string, seq[VulkanTexture]]()
+      for texture in shaderPipeline.samplers:
+        scenedata.textures[shaderPipeline.vk][texture.name] = newSeq[VulkanTexture]()
+        if scene.shaderGlobals.contains(texture.name):
+          for textureValue in scene.shaderGlobals[texture.name][Texture][]:
+            if not uploadedTextures.contains(textureValue):
+              uploadedTextures[textureValue] = renderer.device.uploadTexture(renderer.queue, textureValue)
+            scenedata.textures[shaderPipeline.vk][texture.name].add uploadedTextures[textureValue]
+        else:
+          var foundTexture = false
+          for material in scene.getMaterials(materialType):
+            if material.hasMatchingAttribute(texture):
+              foundTexture = true
+              let value = material[texture.name, Texture][]
+              assert value.len == 1, &"Mesh material attribute '{texture.name}' has texture-array, but only single textures are allowed"
+              if not uploadedTextures.contains(value[0]):
+                uploadedTextures[value[0]] = renderer.device.uploadTexture(renderer.queue, value[0])
+              scenedata.textures[shaderPipeline.vk][texture.name].add uploadedTextures[value[0]]
+          assert foundTexture, &"No texture found in shaderGlobals or materials for '{texture.name}'"
+        let nTextures = scenedata.textures[shaderPipeline.vk][texture.name].len
+        assert (texture.arrayCount == 0 and nTextures == 1) or texture.arrayCount >= nTextures, &"Shader assigned to render '{materialType}' expected {texture.arrayCount} textures for '{texture.name}' but got {nTextures}"
+        if texture.arrayCount < nTextures:
+          warn &"Shader assigned to render '{materialType}' expected {texture.arrayCount} textures for '{texture.name}' but got {nTextures}"
 
-        # gather uniform sizes
-        var uniformBufferSize = 0
-        for uniform in shaderPipeline.uniforms:
-          uniformBufferSize += uniform.size
-        if uniformBufferSize > 0:
-          scenedata.uniformBuffers[shaderPipeline.vk] = newSeq[Buffer]()
-          for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
-            scenedata.uniformBuffers[shaderPipeline.vk].add renderer.device.createBuffer(
-              size = uniformBufferSize,
-              usage = [VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
-              requireMappable = true,
-              preferVRAM = true,
-            )
+      # gather uniform sizes
+      var uniformBufferSize = 0
+      for uniform in shaderPipeline.uniforms:
+        uniformBufferSize += uniform.size
+      if uniformBufferSize > 0:
+        scenedata.uniformBuffers[shaderPipeline.vk] = newSeq[Buffer]()
+        for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
+          scenedata.uniformBuffers[shaderPipeline.vk].add renderer.device.createBuffer(
+            size = uniformBufferSize,
+            usage = [VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
+            requireMappable = true,
+            preferVRAM = true,
+          )
 
-        # TODO: rework the whole descriptor/pool/layout stuff, a bit unclear
-        var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, renderer.swapchain.inFlightFrames)]
-        var nTextures = 0
-        for descriptor in shaderPipeline.descriptorSetLayout.descriptors:
-          if descriptor.thetype == ImageSampler:
-            nTextures += descriptor.count
-        if nTextures > 0:
-          poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nTextures * renderer.swapchain.inFlightFrames)
-        scenedata.descriptorPools[shaderPipeline.vk] = renderer.device.createDescriptorSetPool(poolsizes)
+      # TODO: rework the whole descriptor/pool/layout stuff, a bit unclear
+      var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, renderer.swapchain.inFlightFrames)]
+      var nTextures = 0
+      for descriptor in shaderPipeline.descriptorSetLayout.descriptors:
+        if descriptor.thetype == ImageSampler:
+          nTextures += descriptor.count
+      if nTextures > 0:
+        poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nTextures * renderer.swapchain.inFlightFrames)
+      scenedata.descriptorPools[shaderPipeline.vk] = renderer.device.createDescriptorSetPool(poolsizes)
 
-        scenedata.descriptorSets[shaderPipeline.vk] = shaderPipeline.setupDescriptors(
-          scenedata.descriptorPools[shaderPipeline.vk],
-          scenedata.uniformBuffers.getOrDefault(shaderPipeline.vk, @[]),
-          scenedata.textures[shaderPipeline.vk],
-          inFlightFrames = renderer.swapchain.inFlightFrames,
-          emptyTexture = renderer.emptyTexture,
-        )
-        for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
-          scenedata.descriptorSets[shaderPipeline.vk][frame_i].writeDescriptorSet()
+      scenedata.descriptorSets[shaderPipeline.vk] = shaderPipeline.setupDescriptors(
+        scenedata.descriptorPools[shaderPipeline.vk],
+        scenedata.uniformBuffers.getOrDefault(shaderPipeline.vk, @[]),
+        scenedata.textures[shaderPipeline.vk],
+        inFlightFrames = renderer.swapchain.inFlightFrames,
+        emptyTexture = renderer.emptyTexture,
+      )
+      for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
+        scenedata.descriptorSets[shaderPipeline.vk][frame_i].writeDescriptorSet()
 
   renderer.scenedata[scene] = scenedata
 
@@ -324,6 +328,7 @@
 
   let memoryPerformanceHint = renderer.scenedata[scene].attributeLocation[attribute]
   renderer.scenedata[scene].vertexBuffers[memoryPerformanceHint].setData(
+    renderer.queue,
     mesh[].getPointer(attribute),
     mesh[].attributeSize(attribute),
     renderer.scenedata[scene].vertexBufferOffsets[(mesh, attribute)]
@@ -352,60 +357,57 @@
     debug &"Update uniforms because of dirty scene globals: {dirty}"
 
   # loop over all used shaders/pipelines
-  for i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialType, shaderPipeline) in renderer.renderPass.subpasses[i].shaderPipelines:
-      if (
-        scene.usesMaterial(materialType) and
-        renderer.scenedata[scene].uniformBuffers.hasKey(shaderPipeline.vk) and
-        renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk].len != 0
-      ):
-        var dirtyMaterialAttribs: seq[string]
-        for material in renderer.scenedata[scene].materials[materialType].mitems:
-          dirtyMaterialAttribs.add material.dirtyAttributes
-          material.clearDirtyAttributes()
-        assert renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk][renderer.swapchain.currentInFlight].vk.valid
-        if forceAll:
-          for buffer in renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk]:
-            assert buffer.vk.valid
+  for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines:
+    if (
+      scene.usesMaterial(materialType) and
+      renderer.scenedata[scene].uniformBuffers.hasKey(shaderPipeline.vk) and
+      renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk].len != 0
+    ):
+      var dirtyMaterialAttribs: seq[string]
+      for material in renderer.scenedata[scene].materials[materialType].mitems:
+        dirtyMaterialAttribs.add material.dirtyAttributes
+        material.clearDirtyAttributes()
+      assert renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk][renderer.swapchain.currentInFlight].vk.valid
+      if forceAll:
+        for buffer in renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk]:
+          assert buffer.vk.valid
 
-        var offset = 0
-        # loop over all uniforms of the shader-shaderPipeline
-        for uniform in shaderPipeline.uniforms:
-          if dirty.contains(uniform.name) or dirtyMaterialAttribs.contains(uniform.name) or forceAll: # only update uniforms if necessary
-            var value = initDataList(uniform.theType)
-            if scene.shaderGlobals.hasKey(uniform.name):
-              assert scene.shaderGlobals[uniform.name].thetype == uniform.thetype
-              value = scene.shaderGlobals[uniform.name]
-            else:
-              var foundValue = false
-              for material in renderer.scenedata[scene].materials[materialType]:
-                if material.hasMatchingAttribute(uniform):
-                  value.appendValues(material[uniform.name])
-                  foundValue = true
-              assert foundValue, &"Uniform '{uniform.name}' not found in scene shaderGlobals or materials"
-            assert (uniform.arrayCount == 0 and value.len == 1) or value.len <= uniform.arrayCount, &"Uniform '{uniform.name}' found has wrong length (shader declares {uniform.arrayCount} but shaderGlobals and materials provide {value.len})"
-            if value.len <= uniform.arrayCount:
-              debug &"Uniform '{uniform.name}' found has short length (shader declares {uniform.arrayCount} but shaderGlobals and materials provide {value.len})"
-            assert value.size <= uniform.size, &"During uniform update: gathered value has size {value.size} but uniform expects size {uniform.size}"
-            if value.size < uniform.size:
-              debug &"During uniform update: gathered value has size {value.size} but uniform expects size {uniform.size}"
-            debug &"  update uniform '{uniform.name}' with value: {value}"
-            # TODO: technically we would only need to update the uniform buffer of the current
-            # frameInFlight (I think), but we don't track for which frame the shaderglobals are no longer dirty
-            # therefore we have to update the uniform values in all buffers, of all inFlightframes (usually 2)
-            for buffer in renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk]:
-              buffer.setData(value.getPointer(), value.size, offset)
-          offset += uniform.size
+      var offset = 0
+      # loop over all uniforms of the shader-shaderPipeline
+      for uniform in shaderPipeline.uniforms:
+        if dirty.contains(uniform.name) or dirtyMaterialAttribs.contains(uniform.name) or forceAll: # only update uniforms if necessary
+          var value = initDataList(uniform.theType)
+          if scene.shaderGlobals.hasKey(uniform.name):
+            assert scene.shaderGlobals[uniform.name].thetype == uniform.thetype
+            value = scene.shaderGlobals[uniform.name]
+          else:
+            var foundValue = false
+            for material in renderer.scenedata[scene].materials[materialType]:
+              if material.hasMatchingAttribute(uniform):
+                value.appendValues(material[uniform.name])
+                foundValue = true
+            assert foundValue, &"Uniform '{uniform.name}' not found in scene shaderGlobals or materials"
+          assert (uniform.arrayCount == 0 and value.len == 1) or value.len <= uniform.arrayCount, &"Uniform '{uniform.name}' found has wrong length (shader declares {uniform.arrayCount} but shaderGlobals and materials provide {value.len})"
+          if value.len <= uniform.arrayCount:
+            debug &"Uniform '{uniform.name}' found has short length (shader declares {uniform.arrayCount} but shaderGlobals and materials provide {value.len})"
+          assert value.size <= uniform.size, &"During uniform update: gathered value has size {value.size} but uniform expects size {uniform.size}"
+          if value.size < uniform.size:
+            debug &"During uniform update: gathered value has size {value.size} but uniform expects size {uniform.size}"
+          debug &"  update uniform '{uniform.name}' with value: {value}"
+          # TODO: technically we would only need to update the uniform buffer of the current
+          # frameInFlight (I think), but we don't track for which frame the shaderglobals are no longer dirty
+          # therefore we have to update the uniform values in all buffers, of all inFlightframes (usually 2)
+          for buffer in renderer.scenedata[scene].uniformBuffers[shaderPipeline.vk]:
+            buffer.setData(renderer.queue, value.getPointer(), value.size, offset)
+        offset += uniform.size
   scene.clearDirtyShaderGlobals()
 
 proc render*(renderer: var Renderer, scene: Scene) =
   assert scene in renderer.scenedata
 
-  var
-    commandBufferResult = renderer.swapchain.nextFrame()
-    commandBuffer: VkCommandBuffer
+  var currentInFlightFrameIndex = renderer.swapchain.nextFrame()
 
-  if not commandBufferResult.isSome:
+  if not currentInFlightFrameIndex.isSome:
     let res = renderer.swapchain.recreate()
     if res.isSome:
       var oldSwapchain = renderer.swapchain
@@ -414,29 +416,24 @@
       oldSwapchain.destroy()
     return
 
-  commandBuffer = commandBufferResult.get()
-  commandBuffer.beginRenderCommands(renderer.renderPass, renderer.swapchain.currentFramebuffer(), oneTimeSubmit = true)
+  renderer.currentCommandBuffer.beginRenderCommands(renderer.renderPass, renderer.swapchain.currentFramebuffer(), oneTimeSubmit = true)
 
   debug "Scene buffers:"
   for (location, buffer) in renderer.scenedata[scene].vertexBuffers.pairs:
     debug "  ", location, ": ", buffer
   debug "  Index buffer: ", renderer.scenedata[scene].indexBuffer
 
-  for i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialType, shaderPipeline) in renderer.renderPass.subpasses[i].shaderPipelines:
-      if scene.usesMaterial(materialType):
-        debug &"Start shaderPipeline for '{materialType}'"
-        commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, shaderPipeline.vk)
-        commandBuffer.vkCmdBindDescriptorSets(renderer.renderPass.subpasses[i].pipelineBindPoint, shaderPipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[shaderPipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil)
-        for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].material.theType == materialType):
-          drawable.draw(commandBuffer, vertexBuffers = renderer.scenedata[scene].vertexBuffers, indexBuffer = renderer.scenedata[scene].indexBuffer, shaderPipeline.vk)
+  for (materialType, shaderPipeline) in renderer.renderPass.shaderPipelines:
+    if scene.usesMaterial(materialType):
+      debug &"Start shaderPipeline for '{materialType}'"
+      renderer.currentCommandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, shaderPipeline.vk)
+      renderer.currentCommandBuffer.vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, shaderPipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[shaderPipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil)
+      for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].material.theType == materialType):
+        drawable.draw(renderer.currentCommandBuffer, vertexBuffers = renderer.scenedata[scene].vertexBuffers, indexBuffer = renderer.scenedata[scene].indexBuffer, shaderPipeline.vk)
 
-    if i < renderer.renderPass.subpasses.len - 1:
-      commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE)
+  renderer.currentCommandBuffer.endRenderCommands()
 
-  commandBuffer.endRenderCommands()
-
-  if not renderer.swapchain.swap():
+  if not renderer.swapchain.swap(renderer.currentCommandBuffer):
     let res = renderer.swapchain.recreate()
     if res.isSome:
       var oldSwapchain = renderer.swapchain
@@ -444,9 +441,6 @@
       checkVkResult renderer.device.vk.vkDeviceWaitIdle()
       oldSwapchain.destroy()
 
-func framesRendered*(renderer: Renderer): uint64 =
-  renderer.swapchain.framesRendered
-
 func valid*(renderer: Renderer): bool =
   renderer.device.vk.valid
 
@@ -476,6 +470,7 @@
 
 proc destroy*(renderer: var Renderer) =
   checkVkResult renderer.device.vk.vkDeviceWaitIdle()
+
   for scenedata in renderer.scenedata.mvalues:
     for buffer in scenedata.vertexBuffers.mvalues:
       assert buffer.vk.valid
@@ -500,4 +495,5 @@
     renderer.scenedata.del(scene)
   renderer.emptyTexture.destroy()
   renderer.renderPass.destroy()
+  renderer.commandBufferPool.destroy()
   renderer.swapchain.destroy()
--- a/semicongine/vulkan/buffer.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/vulkan/buffer.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -98,7 +98,7 @@
   result.allocateMemory(requireMappable = requireMappable, preferVRAM = preferVRAM, preferAutoFlush = preferAutoFlush)
 
 
-proc copy*(src, dst: Buffer, dstOffset = 0) =
+proc copy*(src, dst: Buffer, queue: Queue, dstOffset = 0) =
   assert src.device.vk.valid
   assert dst.device.vk.valid
   assert src.device == dst.device
@@ -107,7 +107,15 @@
   assert VK_BUFFER_USAGE_TRANSFER_DST_BIT in dst.usage
 
   var copyRegion = VkBufferCopy(size: VkDeviceSize(src.size), dstOffset: VkDeviceSize(dstOffset))
-  withSingleUseCommandBuffer(src.device, true, commandBuffer):
+  withSingleUseCommandBuffer(src.device, queue, true, commandBuffer):
+    let barrier = VkMemoryBarrier2(
+      sType: VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
+      srcStageMask: [VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT].toBits,
+      srcAccessMask: [VK_ACCESS_2_MEMORY_WRITE_BIT].toBits,
+      dstStageMask: [VK_PIPELINE_STAGE_2_VERTEX_ATTRIBUTE_INPUT_BIT].toBits,
+      dstAccessMask: [VK_ACCESS_2_MEMORY_READ_BIT].toBits,
+    )
+    commandBuffer.pipelineBarrier(memoryBarriers = [barrier])
     commandBuffer.vkCmdCopyBuffer(src.vk, dst.vk, 1, addr(copyRegion))
 
 proc destroy*(buffer: var Buffer) =
@@ -126,7 +134,7 @@
     )
   buffer.vk.reset
 
-proc setData*(dst: Buffer, src: pointer, size: int, bufferOffset = 0) =
+proc setData*(dst: Buffer, queue: Queue, src: pointer, size: int, bufferOffset = 0) =
   assert bufferOffset + size <= dst.size
   if dst.memory.canMap:
     copyMem(cast[pointer](cast[int](dst.memory.data) + bufferOffset), src, size)
@@ -134,13 +142,13 @@
       dst.memory.flush()
   else: # use staging buffer, slower but required if memory is not host visible
     var stagingBuffer = dst.device.createBuffer(size, [VK_BUFFER_USAGE_TRANSFER_SRC_BIT], requireMappable = true, preferVRAM = false, preferAutoFlush = true)
-    setData(stagingBuffer, src, size, 0)
-    stagingBuffer.copy(dst, bufferOffset)
+    setData(stagingBuffer, queue, src, size, 0)
+    stagingBuffer.copy(dst, queue, bufferOffset)
     stagingBuffer.destroy()
 
-proc setData*[T: seq](dst: Buffer, src: ptr T, offset = 0'u64) =
-  dst.setData(src, sizeof(get(genericParams(T), 0)) * src[].len, offset = offset)
+proc setData*[T: seq](dst: Buffer, queue: Queue, src: ptr T, offset = 0'u64) =
+  dst.setData(queue, src, sizeof(get(genericParams(T), 0)) * src[].len, offset = offset)
 
-proc setData*[T](dst: Buffer, src: ptr T, offset = 0'u64) =
-  dst.setData(src, sizeof(T), offset = offset)
+proc setData*[T](dst: Buffer, queue: Queue, src: ptr T, offset = 0'u64) =
+  dst.setData(queue, src, sizeof(T), offset = offset)
 
--- a/semicongine/vulkan/commandbuffer.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/vulkan/commandbuffer.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -1,3 +1,5 @@
+import std/strformat
+
 import ../core
 import ./device
 import ./physicaldevice
@@ -18,7 +20,7 @@
   )
   result.family = family
   result.device = device
-  checkVkResult device.vk.vkCreateCommandPool(addr(createInfo), nil, addr(result.vk))
+  checkVkResult device.vk.vkCreateCommandPool(addr createInfo, nil, addr result.vk)
 
   var allocInfo = VkCommandBufferAllocateInfo(
     sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
@@ -27,20 +29,33 @@
     commandBufferCount: uint32(nBuffers),
   )
   result.buffers = newSeq[VkCommandBuffer](nBuffers)
-  checkVkResult device.vk.vkAllocateCommandBuffers(addr(allocInfo), result.buffers.toCPointer)
+  checkVkResult device.vk.vkAllocateCommandBuffers(addr allocInfo, result.buffers.toCPointer)
+
+proc pipelineBarrier*(
+  commandBuffer: VkCommandBuffer,
+  memoryBarriers: openArray[VkMemoryBarrier2] = [],
+  bufferMemoryBarriers: openArray[VkBufferMemoryBarrier2] = [],
+  imageBarriers: openArray[VkImageMemoryBarrier2] = [],
+) =
+  let dependencies = VkDependencyInfo(
+    sType: VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
+    dependencyFlags: VkDependencyFlags(0),
+    memoryBarrierCount: uint32(memoryBarriers.len),
+    pMemoryBarriers: memoryBarriers.toCPointer,
+    bufferMemoryBarrierCount: uint32(bufferMemoryBarriers.len),
+    pBufferMemoryBarriers: bufferMemoryBarriers.toCPointer,
+    imageMemoryBarrierCount: uint32(imageBarriers.len),
+    pImageMemoryBarriers: imageBarriers.toCPointer,
+  )
+
+  vkCmdPipelineBarrier2(commandBuffer, addr dependencies)
 
 
-template withSingleUseCommandBuffer*(device: Device, needsTransfer: bool, commandBuffer, body: untyped): untyped =
+template withSingleUseCommandBuffer*(device: Device, queue: Queue, needsTransfer: bool, commandBuffer, body: untyped): untyped =
   assert device.vk.valid
+  assert queue.vk.valid
 
-  var queue: Queue
-  for q in device.queues.values:
-    if q.family.canDoTransfer or not needsTransfer:
-      queue = q
-      break
-  if not queue.vk.valid:
-    raise newException(Exception, "No queue that supports buffer transfer")
-
+  checkVkResult queue.vk.vkQueueWaitIdle()
   var
     commandBufferPool = createCommandBufferPool(device, queue.family, 1)
     commandBuffer = commandBufferPool.buffers[0]
@@ -48,7 +63,7 @@
       sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
       flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
     )
-  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
+  checkVkResult commandBuffer.vkBeginCommandBuffer(addr beginInfo)
 
   block:
     body
@@ -57,9 +72,9 @@
   var submitInfo = VkSubmitInfo(
     sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
     commandBufferCount: 1,
-    pCommandBuffers: addr(commandBuffer),
+    pCommandBuffers: addr commandBuffer,
   )
-  checkVkResult queue.vk.vkQueueSubmit(1, addr(submitInfo), VkFence(0))
+  checkVkResult queue.vk.vkQueueSubmit(1, addr submitInfo, VkFence(0))
   checkVkResult queue.vk.vkQueueWaitIdle()
   commandBufferPool.destroy()
 
@@ -68,3 +83,4 @@
   assert commandpool.vk.valid
   commandpool.device.vk.vkDestroyCommandPool(commandpool.vk, nil)
   commandpool.vk.reset
+
--- a/semicongine/vulkan/device.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/vulkan/device.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -50,9 +50,14 @@
     )
   var queueList = deviceQueues.values.toSeq
 
+  var features13 = VkPhysicalDeviceVulkan13Features(
+    stype: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
+    synchronization2: true
+  )
   var features2 = VkPhysicalDeviceFeatures2(
     stype: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
     features: result.enabledFeatures,
+    pNext: addr features13,
   )
   var createInfo = VkDeviceCreateInfo(
     sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
--- a/semicongine/vulkan/image.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/vulkan/image.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -79,48 +79,39 @@
   )
   checkVkResult image.device.vk.vkBindImageMemory(image.vk, image.memory.vk, VkDeviceSize(0))
 
-proc transitionImageLayout(image: VulkanImage, oldLayout, newLayout: VkImageLayout) =
-  var barrier = VkImageMemoryBarrier(
-    sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
-    oldLayout: oldLayout,
-    newLayout: newLayout,
-    srcQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
-    dstQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
-    image: image.vk,
-    subresourceRange: VkImageSubresourceRange(
-      aspectMask: toBits [VK_IMAGE_ASPECT_COLOR_BIT],
-      baseMipLevel: 0,
-      levelCount: 1,
-      baseArrayLayer: 0,
-      layerCount: 1,
+proc transitionImageLayout(image: VulkanImage, queue: Queue, oldLayout, newLayout: VkImageLayout) =
+  var barrier = VkImageMemoryBarrier2(
+      sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
+      oldLayout: oldLayout,
+      newLayout: newLayout,
+      srcQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
+      dstQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
+      image: image.vk,
+      subresourceRange: VkImageSubresourceRange(
+        aspectMask: toBits [VK_IMAGE_ASPECT_COLOR_BIT],
+        baseMipLevel: 0,
+        levelCount: 1,
+        baseArrayLayer: 0,
+        layerCount: 1,
     ),
   )
-  var
-    sourceStage, destinationStage: VkPipelineStageFlagBits
   if oldLayout == VK_IMAGE_LAYOUT_UNDEFINED and newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
-    barrier.srcAccessMask = VkAccessFlags(0)
-    barrier.dstAccessMask = toBits [VK_ACCESS_TRANSFER_WRITE_BIT]
-    sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
-    destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT
+    barrier.srcStageMask = [VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT].toBits
+    barrier.srcAccessMask = VkAccessFlags2(0)
+    barrier.dstStageMask = [VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT].toBits
+    barrier.dstAccessMask = [VK_ACCESS_2_TRANSFER_WRITE_BIT].toBits
   elif oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL and newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
-    barrier.srcAccessMask = toBits [VK_ACCESS_TRANSFER_WRITE_BIT]
-    barrier.dstAccessMask = toBits [VK_ACCESS_SHADER_READ_BIT]
-    sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT
-    destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
+    barrier.srcStageMask = [VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT].toBits
+    barrier.srcAccessMask = [VK_ACCESS_2_TRANSFER_WRITE_BIT].toBits
+    barrier.dstStageMask = [VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT].toBits
+    barrier.dstAccessMask = [VK_ACCESS_2_SHADER_READ_BIT].toBits
   else:
     raise newException(Exception, "Unsupported layout transition!")
 
-  withSingleUseCommandBuffer(image.device, false, commandBuffer):
-    vkCmdPipelineBarrier(
-      commandBuffer,
-      toBits [sourceStage], toBits [destinationStage],
-      VkDependencyFlags(0),
-      0, nil,
-      0, nil,
-      1, addr barrier
-    )
+  withSingleUseCommandBuffer(image.device, queue, false, commandBuffer):
+    commandBuffer.pipelineBarrier(imageBarriers = [barrier])
 
-proc copy*(src: Buffer, dst: VulkanImage) =
+proc copy*(src: Buffer, dst: VulkanImage, queue: Queue) =
   assert src.device.vk.valid
   assert dst.device.vk.valid
   assert src.device == dst.device
@@ -140,7 +131,7 @@
     imageOffset: VkOffset3D(x: 0, y: 0, z: 0),
     imageExtent: VkExtent3D(width: uint32(dst.width), height: uint32(dst.height), depth: 1)
   )
-  withSingleUseCommandBuffer(src.device, true, commandBuffer):
+  withSingleUseCommandBuffer(src.device, queue, true, commandBuffer):
     commandBuffer.vkCmdCopyBufferToImage(
       src.vk,
       dst.vk,
@@ -150,7 +141,7 @@
     )
 
 # currently only usable for texture access from shader
-proc createImage(device: Device, width, height: int, depth: PixelDepth, data: pointer): VulkanImage =
+proc createImage(device: Device, queue: Queue, width, height: int, depth: PixelDepth, data: pointer): VulkanImage =
   assert device.vk.valid
   assert width > 0
   assert height > 0
@@ -180,12 +171,12 @@
   )
   checkVkResult device.vk.vkCreateImage(addr imageInfo, nil, addr result.vk)
   result.allocateMemory(requireMappable = false, preferVRAM = true, preferAutoFlush = false)
-  result.transitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
+  result.transitionImageLayout(queue, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
 
   var stagingBuffer = device.createBuffer(size = size, usage = [VK_BUFFER_USAGE_TRANSFER_SRC_BIT], requireMappable = true, preferVRAM = false, preferAutoFlush = true)
-  stagingBuffer.setData(src = data, size = size)
-  stagingBuffer.copy(result)
-  result.transitionImageLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+  stagingBuffer.setData(queue, src = data, size = size)
+  stagingBuffer.copy(result, queue)
+  result.transitionImageLayout(queue, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
   stagingBuffer.destroy()
 
 proc destroy*(image: var VulkanImage) =
@@ -279,12 +270,12 @@
   &"VulkanTexture({texture.image.width}x{texture.image.height})"
 
 
-proc uploadTexture*(device: Device, texture: Texture): VulkanTexture =
+proc uploadTexture*(device: Device, queue: Queue, texture: Texture): VulkanTexture =
   assert device.vk.valid
   if texture.isGrayscale:
-    result.image = createImage(device = device, width = texture.grayImage.width, height = texture.grayImage.height, depth = 1, data = addr texture.grayImage.imagedata[0])
+    result.image = createImage(device = device, queue = queue, width = texture.grayImage.width, height = texture.grayImage.height, depth = 1, data = addr texture.grayImage.imagedata[0])
   else:
-    result.image = createImage(device = device, width = texture.colorImage.width, height = texture.colorImage.height, depth = 4, data = addr texture.colorImage.imagedata[0][0])
+    result.image = createImage(device = device, queue = queue, width = texture.colorImage.width, height = texture.colorImage.height, depth = 4, data = addr texture.colorImage.imagedata[0][0])
   result.imageView = result.image.createImageView()
   result.sampler = result.image.device.createSampler(texture.sampler)
 
--- a/semicongine/vulkan/renderpass.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/vulkan/renderpass.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -10,66 +10,21 @@
 
 type
   Subpass* = object
-    clearColor*: Vec4f
-    pipelineBindPoint*: VkPipelineBindPoint
-    flags: VkSubpassDescriptionFlags
     outputs: seq[VkAttachmentReference]
-    depthStencil: Option[VkAttachmentReference]
-    shaderPipelines*: seq[(MaterialType, ShaderPipeline)]
   RenderPass* = object
     vk*: VkRenderPass
     device*: Device
-    subpasses*: seq[Subpass]
+    shaderPipelines*: seq[(MaterialType, ShaderPipeline)]
+    clearColor*: Vec4f
 
 proc createRenderPass*(
   device: Device,
-  attachments: seq[VkAttachmentDescription],
-  subpasses: seq[Subpass],
-  dependencies: seq[VkSubpassDependency],
+  shaders: openArray[(MaterialType, ShaderConfiguration)],
+  clearColor = Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]),
+  backFaceCulling = true,
+  inFlightFrames = 2,
 ): RenderPass =
   assert device.vk.valid
-  var pAttachments = attachments
-  var pSubpasses = subpasses
-  var pDependencies = dependencies
-
-  var subpassesList: seq[VkSubpassDescription]
-  for subpass in pSubpasses.mitems:
-    subpassesList.add VkSubpassDescription(
-      flags: subpass.flags,
-      pipelineBindPoint: subpass.pipelineBindPoint,
-      inputAttachmentCount: 0,
-      pInputAttachments: nil,
-      colorAttachmentCount: uint32(subpass.outputs.len),
-      pColorAttachments: subpass.outputs.toCPointer,
-      pResolveAttachments: nil,
-      pDepthStencilAttachment: if subpass.depthStencil.isSome: addr(subpass.depthStencil.get) else: nil,
-      preserveAttachmentCount: 0,
-      pPreserveAttachments: nil,
-    )
-
-  var createInfo = VkRenderPassCreateInfo(
-      sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
-      attachmentCount: uint32(pAttachments.len),
-      pAttachments: pAttachments.toCPointer,
-      subpassCount: uint32(subpassesList.len),
-      pSubpasses: subpassesList.toCPointer,
-      dependencyCount: uint32(pDependencies.len),
-      pDependencies: pDependencies.toCPointer,
-    )
-  result.device = device
-  result.subpasses = pSubpasses
-  checkVkResult device.vk.vkCreateRenderPass(addr(createInfo), nil, addr(result.vk))
-
-proc simpleForwardRenderPass*(
-  device: Device,
-  shaders: openArray[(MaterialType, ShaderConfiguration)],
-  inFlightFrames = 2,
-  clearColor = Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]),
-  backFaceCulling = true,
-): RenderPass =
-  assert device.vk.valid
-  for (_, shaderconfig) in shaders:
-    assert shaderconfig.outputs.len == 1
   var
     attachments = @[VkAttachmentDescription(
         format: device.physicalDevice.getSurfaceFormats().filterSurfaceFormat().format,
@@ -81,13 +36,6 @@
         initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
         finalLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
     )]
-    subpasses = @[
-      Subpass(
-        pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS,
-        outputs: @[VkAttachmentReference(attachment: 0, layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)],
-        clearColor: clearColor
-      )
-    ]
     # dependencies seems to be optional, TODO: benchmark difference
     dependencies = @[VkSubpassDependency(
       srcSubpass: VK_SUBPASS_EXTERNAL,
@@ -97,10 +45,45 @@
       dstStageMask: toBits [VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT],
       dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
     )]
-  result = device.createRenderPass(attachments = attachments, subpasses = subpasses, dependencies = dependencies)
+    outputs = @[
+      VkAttachmentReference(
+        attachment: 0,
+        layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+      )
+    ]
+
+  var subpassesList = [
+    VkSubpassDescription(
+      flags: VkSubpassDescriptionFlags(0),
+      pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS,
+      inputAttachmentCount: 0,
+      pInputAttachments: nil,
+      colorAttachmentCount: uint32(outputs.len),
+      pColorAttachments: outputs.toCPointer,
+      pResolveAttachments: nil,
+      pDepthStencilAttachment: nil,
+      preserveAttachmentCount: 0,
+      pPreserveAttachments: nil,
+    )
+  ]
+
+  var createInfo = VkRenderPassCreateInfo(
+      sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+      attachmentCount: uint32(attachments.len),
+      pAttachments: attachments.toCPointer,
+      subpassCount: uint32(subpassesList.len),
+      pSubpasses: subpassesList.toCPointer,
+      dependencyCount: uint32(dependencies.len),
+      pDependencies: dependencies.toCPointer,
+    )
+  result.device = device
+  result.clearColor = clearColor
+  checkVkResult device.vk.vkCreateRenderPass(addr(createInfo), nil, addr(result.vk))
+
+  for (_, shaderconfig) in shaders:
+    assert shaderconfig.outputs.len == 1
   for (materialtype, shaderconfig) in shaders:
-    result.subpasses[0].shaderPipelines.add (materialtype, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling = backFaceCulling))
-
+    result.shaderPipelines.add (materialtype, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling = backFaceCulling))
 
 proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer, oneTimeSubmit: bool) =
   assert commandBuffer.valid
@@ -110,9 +93,7 @@
     w = framebuffer.dimension.x
     h = framebuffer.dimension.y
 
-  var clearColors: seq[VkClearValue]
-  for subpass in renderpass.subpasses:
-    clearColors.add(VkClearValue(color: VkClearColorValue(float32: subpass.clearColor)))
+  var clearColors = [VkClearValue(color: VkClearColorValue(float32: renderpass.clearColor))]
   var
     beginInfo = VkCommandBufferBeginInfo(
       sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@@ -158,7 +139,5 @@
   assert renderPass.vk.valid
   renderPass.device.vk.vkDestroyRenderPass(renderPass.vk, nil)
   renderPass.vk.reset
-  for i in 0 ..< renderPass.subpasses.len:
-    for _, pipeline in renderPass.subpasses[i].shaderPipelines.mitems:
-      pipeline.destroy()
-  renderPass.subpasses = @[]
+  for _, pipeline in renderPass.shaderPipelines.mitems:
+    pipeline.destroy()
--- a/semicongine/vulkan/swapchain.nim	Fri Mar 29 16:28:30 2024 +0700
+++ b/semicongine/vulkan/swapchain.nim	Sat Mar 30 20:29:33 2024 +0700
@@ -7,7 +7,6 @@
 import ./physicaldevice
 import ./image
 import ./framebuffer
-import ./commandbuffer
 import ./syncing
 
 type
@@ -20,15 +19,12 @@
     framebuffers*: seq[Framebuffer]
     currentInFlight*: int
     currentFramebufferIndex: uint32
-    framesRendered*: uint64
     queueFinishedFence*: seq[Fence]
     imageAvailableSemaphore*: seq[Semaphore]
     renderFinishedSemaphore*: seq[Semaphore]
-    commandBufferPool: CommandBufferPool
     # required for recreation:
     renderPass: VkRenderPass
     surfaceFormat: VkSurfaceFormatKHR
-    queueFamily: QueueFamily
     imageCount: uint32
     inFlightFrames*: int
 
@@ -37,7 +33,6 @@
   device: Device,
   renderPass: VkRenderPass,
   surfaceFormat: VkSurfaceFormatKHR,
-  queueFamily: QueueFamily,
   desiredNumberOfImages = 3'u32,
   inFlightFrames = 2,
   oldSwapchain = VkSwapchainKHR(0),
@@ -82,7 +77,6 @@
       surfaceFormat: surfaceFormat,
       dimension: TVec2[uint32]([capabilities.currentExtent.width, capabilities.currentExtent.height]),
       inFlightFrames: inFlightFrames,
-      queueFamily: queueFamily,
       renderPass: renderPass
     )
 
@@ -101,7 +95,6 @@
       swapchain.queueFinishedFence.add device.createFence()
       swapchain.imageAvailableSemaphore.add device.createSemaphore()
       swapchain.renderFinishedSemaphore.add device.createSemaphore()
-    swapchain.commandBufferPool = device.createCommandBufferPool(queueFamily, swapchain.inFlightFrames)
     debug &"Created swapchain with: {nImages} framebuffers, {inFlightFrames} in-flight frames, {swapchain.dimension.x}x{swapchain.dimension.y}"
     result = some(swapchain)
   else:
@@ -112,7 +105,7 @@
   assert swapchain.vk.valid
   swapchain.framebuffers[swapchain.currentFramebufferIndex]
 
-proc nextFrame*(swapchain: var Swapchain): Option[VkCommandBuffer] =
+proc nextFrame*(swapchain: var Swapchain): Option[int] =
   assert swapchain.device.vk.valid
   assert swapchain.vk.valid
 
@@ -129,18 +122,17 @@
 
   if nextImageResult == VK_SUCCESS:
     swapchain.queueFinishedFence[swapchain.currentInFlight].reset()
-    result = some(swapchain.commandBufferPool.buffers[swapchain.currentInFlight])
+    result = some(swapchain.currentInFlight)
   else:
-    result = none(VkCommandBuffer)
+    result = none(int)
 
-proc swap*(swapchain: var Swapchain): bool =
+proc swap*(swapchain: var Swapchain, commandBuffer: VkCommandBuffer): bool =
   assert swapchain.device.vk.valid
   assert swapchain.vk.valid
   assert swapchain.device.firstGraphicsQueue().isSome
   assert swapchain.device.firstPresentationQueue().isSome
 
   var
-    commandBuffer = swapchain.commandBufferPool.buffers[swapchain.currentInFlight]
     waitSemaphores = [swapchain.imageAvailableSemaphore[swapchain.currentInFlight].vk]
     waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
     submitInfo = VkSubmitInfo(
@@ -173,13 +165,11 @@
   if presentResult != VK_SUCCESS:
     return false
 
-  inc swapchain.framesRendered
   return true
 
 
 proc destroy*(swapchain: var Swapchain) =
   assert swapchain.vk.valid
-  assert swapchain.commandBufferPool.vk.valid
 
   for imageview in swapchain.imageviews.mitems:
     assert imageview.vk.valid
@@ -187,7 +177,6 @@
   for framebuffer in swapchain.framebuffers.mitems:
     assert framebuffer.vk.valid
     framebuffer.destroy()
-  swapchain.commandBufferPool.destroy()
   for i in 0 ..< swapchain.inFlightFrames:
     assert swapchain.queueFinishedFence[i].vk.valid
     assert swapchain.imageAvailableSemaphore[i].vk.valid
@@ -206,7 +195,6 @@
     device = swapchain.device,
     renderPass = swapchain.renderPass,
     surfaceFormat = swapchain.surfaceFormat,
-    queueFamily = swapchain.queueFamily,
     desiredNumberOfImages = swapchain.imageCount,
     inFlightFrames = swapchain.inFlightFrames,
     oldSwapchain = swapchain.vk,