changeset 589:b434feaf2b67

did: finish refactoring of render pipeline, yipi! :)
author Sam <sam@basx.dev>
date Mon, 17 Apr 2023 18:02:19 +0700
parents 008592db0442
children 1edf3e16144e
files src/semicongine.nim src/semicongine/entity.nim src/semicongine/renderer.nim src/semicongine/scene.nim src/semicongine/vulkan/pipeline.nim src/semicongine/vulkan/renderpass.nim src/semicongine/vulkan/shader.nim src/semicongine/vulkan/swapchain.nim tests/test_vulkan_wrapper.nim
diffstat 9 files changed, 228 insertions(+), 235 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine.nim	Wed Apr 12 01:20:53 2023 +0700
+++ b/src/semicongine.nim	Mon Apr 17 18:02:19 2023 +0700
@@ -6,8 +6,8 @@
 import semicongine/gpu_data
 import semicongine/math
 import semicongine/mesh
+import semicongine/renderer
 import semicongine/platform/window
-import semicongine/scene
 import semicongine/vulkan
 
 export color
@@ -18,6 +18,6 @@
 export gpu_data
 export math
 export mesh
+export renderer
 export window
-export scene
 export vulkan
--- a/src/semicongine/entity.nim	Wed Apr 12 01:20:53 2023 +0700
+++ b/src/semicongine/entity.nim	Mon Apr 17 18:02:19 2023 +0700
@@ -1,4 +1,5 @@
 import std/strformat
+import std/hashes
 import std/typetraits
 
 import ./math/matrix
@@ -27,6 +28,8 @@
   value.setValue(data)
   ShaderGlobal(name: name, value: value)
 
+func hash*(entity: Entity): Hash =
+  hash(cast[pointer](entity))
 
 method `$`*(entity: Entity): string {.base.} = entity.name
 method `$`*(component: Component): string {.base.} =
@@ -81,11 +84,11 @@
     result = currentEntity.transform * result
     currentEntity = currentEntity.parent
 
-iterator allComponentsOfType*[T: Component](root: Entity): T =
+iterator allComponentsOfType*[T: Component](root: Entity): var T =
   var queue = @[root]
   while queue.len > 0:
     let entity = queue.pop
-    for component in entity.components:
+    for component in entity.components.mitems:
       if component of T:
         yield T(component)
     for i in countdown(entity.children.len - 1, 0):
--- a/src/semicongine/renderer.nim	Wed Apr 12 01:20:53 2023 +0700
+++ b/src/semicongine/renderer.nim	Mon Apr 17 18:02:19 2023 +0700
@@ -1,27 +1,171 @@
 import std/options
+import std/sequtils
+import std/tables
+import std/strformat
+import std/logging
 
 import ./vulkan/api
+import ./vulkan/buffer
 import ./vulkan/device
+import ./vulkan/drawable
+import ./vulkan/framebuffer
+import ./vulkan/pipeline
 import ./vulkan/physicaldevice
 import ./vulkan/renderpass
 import ./vulkan/swapchain
 
+import ./entity
+import ./mesh
+import ./gpu_data
+
 type
-  Renderer = object
+  SceneData = object
+    drawables*: seq[Drawable]
+    vertexBuffers*: Table[MemoryLocation, Buffer]
+    indexBuffer*: Buffer
+  Renderer* = object
+    device: Device
     surfaceFormat: VkSurfaceFormatKHR
     renderPasses: seq[RenderPass]
     swapchain: Swapchain
+    scenedata: Table[Entity, SceneData]
 
 
-proc initRenderer(device: Device, renderPasses: seq[RenderPass]): Renderer =
+proc initRenderer*(device: Device, renderPasses: openArray[RenderPass]): Renderer =
   assert device.vk.valid
   assert renderPasses.len > 0
   for renderPass in renderPasses:
     assert renderPass.vk.valid
 
-  result.renderPasses = renderPasses
+  result.device = device
+  result.renderPasses = renderPasses.toSeq
   result.surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat()
-  let (swapchain, res) = device.createSwapchain(renderPasses[^1], result.surfaceFormat, device.firstGraphicsQueue().get().family, 2)
+  let (swapchain, res) = device.createSwapchain(result.renderPasses[^1], result.surfaceFormat, device.firstGraphicsQueue().get().family, 2)
   if res != VK_SUCCESS:
     raise newException(Exception, "Unable to create swapchain")
   result.swapchain = swapchain
+
+proc setupDrawableBuffers*(renderer: var Renderer, tree: Entity, inputs: seq[ShaderAttribute]) =
+  assert not (tree in renderer.scenedata)
+  var data = SceneData()
+
+  var allMeshes: seq[Mesh]
+  for mesh in allComponentsOfType[Mesh](tree):
+    allMeshes.add mesh
+    for inputAttr in inputs:
+      assert mesh.hasDataFor(inputAttr.name), &"{mesh} missing data for {inputAttr}"
+  
+  var indicesBufferSize = 0'u64
+  for mesh in allMeshes:
+    if mesh.indexType != None:
+      let indexAlignment = case mesh.indexType
+        of None: 0'u64
+        of Tiny: 1'u64
+        of Small: 2'u64
+        of Big: 4'u64
+      # index value alignment required by Vulkan
+      if indicesBufferSize mod indexAlignment != 0:
+        indicesBufferSize += indexAlignment - (indicesBufferSize mod indexAlignment)
+      indicesBufferSize += mesh.indexDataSize
+  if indicesBufferSize > 0:
+    data.indexBuffer = renderer.device.createBuffer(
+      size=indicesBufferSize,
+      usage=[VK_BUFFER_USAGE_INDEX_BUFFER_BIT],
+      useVRAM=true,
+      mappable=false,
+    )
+
+  # one vertex data buffer per memory location
+  var perLocationOffsets: Table[MemoryLocation, uint64]
+  for location, attributes in inputs.groupByMemoryLocation().pairs:
+    # setup one buffer per attribute-location-type
+    var bufferSize = 0'u64
+    for mesh in allMeshes:
+      for attribute in attributes:
+        bufferSize += mesh.dataSize(attribute.name)
+    if bufferSize > 0:
+      data.vertexBuffers[location] = renderer.device.createBuffer(
+        size=bufferSize,
+        usage=[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT],
+        useVRAM=location in [VRAM, VRAMVisible],
+        mappable=location in [VRAMVisible, RAM],
+      )
+      perLocationOffsets[location] = 0
+
+  var indexBufferOffset = 0'u64
+  for mesh in allMeshes:
+    var offsets: Table[MemoryLocation, seq[uint64]]
+    for location, attributes in inputs.groupByMemoryLocation().pairs:
+      for attribute in attributes:
+        if not (location in offsets):
+          offsets[location] = @[]
+        offsets[location].add perLocationOffsets[location]
+        var (pdata, size) = mesh.getRawData(attribute.name)
+        data.vertexBuffers[location].setData(pdata, size, perLocationOffsets[location])
+        perLocationOffsets[location] += size
+
+    let indexed = mesh.indexType != None
+    var drawable = Drawable(
+      elementCount: if indexed: mesh.indicesCount else: mesh.vertexCount,
+      bufferOffsets: offsets,
+      instanceCount: mesh.instanceCount,
+      indexed: indexed,
+    )
+    if indexed:
+      let indexAlignment = case mesh.indexType
+        of None: 0'u64
+        of Tiny: 1'u64
+        of Small: 2'u64
+        of Big: 4'u64
+      # index value alignment required by Vulkan
+      if indexBufferOffset mod indexAlignment != 0:
+        indexBufferOffset += indexAlignment - (indexBufferOffset mod indexAlignment)
+      drawable.indexBufferOffset = indexBufferOffset
+      drawable.indexType = mesh.indexType
+      var (pdata, size) = mesh.getRawIndexData()
+      data.indexBuffer.setData(pdata, size, indexBufferOffset)
+      indexBufferOffset += size
+    data.drawables.add drawable
+
+  renderer.scenedata[tree] = data
+
+proc render*(renderer: var Renderer, entity: Entity): bool =
+  var commandBuffer = renderer.swapchain.nextFrame()
+
+  commandBuffer.beginRenderCommands(renderer.swapchain.renderPass, renderer.swapchain.currentFramebuffer())
+
+  for i in 0 ..< renderer.swapchain.renderPass.subpasses.len:
+    let subpass = renderer.swapchain.renderPass.subpasses[i]
+    for pipeline in subpass.pipelines:
+      var mpipeline = pipeline
+      commandBuffer.vkCmdBindPipeline(subpass.pipelineBindPoint, mpipeline.vk)
+      commandBuffer.vkCmdBindDescriptorSets(subpass.pipelineBindPoint, mpipeline.layout, 0, 1, addr(mpipeline.descriptorSets[renderer.swapchain.currentInFlight].vk), 0, nil)
+      mpipeline.updateUniforms(entity, renderer.swapchain.currentInFlight)
+
+      debug "Scene buffers:"
+      for (location, buffer) in renderer.scenedata[entity].vertexBuffers.pairs:
+        echo "  ", location, ": ", buffer
+      echo "  Index buffer: ", renderer.scenedata[entity].indexBuffer
+
+      for drawable in renderer.scenedata[entity].drawables:
+        commandBuffer.draw(drawable, vertexBuffers=renderer.scenedata[entity].vertexBuffers, indexBuffer=renderer.scenedata[entity].indexBuffer)
+
+    if i < renderer.swapchain.renderPass.subpasses.len - 1:
+      commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE)
+
+  commandBuffer.endRenderCommands()
+
+  return renderer.swapchain.swap()
+
+func framesRendered*(renderer: Renderer): uint64 =
+  renderer.swapchain.framesRendered
+
+proc destroy*(renderer: var Renderer) =
+  for data in renderer.scenedata.mvalues:
+    for buffer in data.vertexBuffers.mvalues:
+      buffer.destroy()
+    if data.indexBuffer.vk.valid:
+      data.indexBuffer.destroy()
+  for renderpass in renderer.renderPasses.mitems:
+    renderpass.destroy()
+  renderer.swapchain.destroy()
--- a/src/semicongine/scene.nim	Wed Apr 12 01:20:53 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-import std/tables
-import std/strformat
-
-import ./vulkan/api
-import ./vulkan/buffer
-import ./vulkan/device
-import ./vulkan/drawable
-
-import ./gpu_data
-import ./entity
-import ./mesh
-
-type
-  Scene* = object
-    name*: string
-    root*: Entity
-    drawables*: seq[Drawable]
-    vertexBuffers*: Table[MemoryLocation, Buffer]
-    indexBuffer*: Buffer
-
-proc setupDrawableBuffers*(scene: var Scene, device: Device, inputs: seq[ShaderAttribute]) =
-  assert scene.drawables.len == 0
-  var allMeshes: seq[Mesh]
-  for mesh in allComponentsOfType[Mesh](scene.root):
-    allMeshes.add mesh
-    for inputAttr in inputs:
-      assert mesh.hasDataFor(inputAttr.name), &"{mesh} missing data for {inputAttr}"
-  
-  var indicesBufferSize = 0'u64
-  for mesh in allMeshes:
-    if mesh.indexType != None:
-      let indexAlignment = case mesh.indexType
-        of None: 0'u64
-        of Tiny: 1'u64
-        of Small: 2'u64
-        of Big: 4'u64
-      # index value alignment required by Vulkan
-      if indicesBufferSize mod indexAlignment != 0:
-        indicesBufferSize += indexAlignment - (indicesBufferSize mod indexAlignment)
-      indicesBufferSize += mesh.indexDataSize
-  if indicesBufferSize > 0:
-    scene.indexBuffer = device.createBuffer(
-      size=indicesBufferSize,
-      usage=[VK_BUFFER_USAGE_INDEX_BUFFER_BIT],
-      useVRAM=true,
-      mappable=false,
-    )
-
-  # one vertex data buffer per memory location
-  var perLocationOffsets: Table[MemoryLocation, uint64]
-  for location, attributes in inputs.groupByMemoryLocation().pairs:
-    # setup one buffer per attribute-location-type
-    var bufferSize = 0'u64
-    for mesh in allMeshes:
-      for attribute in attributes:
-        bufferSize += mesh.dataSize(attribute.name)
-    if bufferSize > 0:
-      scene.vertexBuffers[location] = device.createBuffer(
-        size=bufferSize,
-        usage=[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT],
-        useVRAM=location in [VRAM, VRAMVisible],
-        mappable=location in [VRAMVisible, RAM],
-      )
-      perLocationOffsets[location] = 0
-
-  var indexBufferOffset = 0'u64
-  for mesh in allMeshes:
-    var offsets: Table[MemoryLocation, seq[uint64]]
-    for location, attributes in inputs.groupByMemoryLocation().pairs:
-      for attribute in attributes:
-        if not (location in offsets):
-          offsets[location] = @[]
-        offsets[location].add perLocationOffsets[location]
-        var (pdata, size) = mesh.getRawData(attribute.name)
-        scene.vertexBuffers[location].setData(pdata, size, perLocationOffsets[location])
-        perLocationOffsets[location] += size
-
-    let indexed = mesh.indexType != None
-    var drawable = Drawable(
-      elementCount: if indexed: mesh.indicesCount else: mesh.vertexCount,
-      bufferOffsets: offsets,
-      instanceCount: mesh.instanceCount,
-      indexed: indexed,
-    )
-    if indexed:
-      let indexAlignment = case mesh.indexType
-        of None: 0'u64
-        of Tiny: 1'u64
-        of Small: 2'u64
-        of Big: 4'u64
-      # index value alignment required by Vulkan
-      if indexBufferOffset mod indexAlignment != 0:
-        indexBufferOffset += indexAlignment - (indexBufferOffset mod indexAlignment)
-      drawable.indexBufferOffset = indexBufferOffset
-      drawable.indexType = mesh.indexType
-      var (pdata, size) = mesh.getRawIndexData()
-      scene.indexBuffer.setData(pdata, size, indexBufferOffset)
-      indexBufferOffset += size
-    scene.drawables.add drawable
-
-proc destroy*(scene: var Scene) =
-  for buffer in scene.vertexBuffers.mvalues:
-    buffer.destroy()
-  if scene.indexBuffer.vk.valid:
-    scene.indexBuffer.destroy
-
--- a/src/semicongine/vulkan/pipeline.nim	Wed Apr 12 01:20:53 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Mon Apr 17 18:02:19 2023 +0700
@@ -54,9 +54,13 @@
     pipeline.uniformBuffers.add buffer
     pipeline.descriptorSets[i].setDescriptorSet(buffer)
 
-proc createPipeline*(device: Device, renderPass: VkRenderPass, vertexShader: Shader, fragmentShader: Shader, inFlightFrames: int, subpass = 0'u32): Pipeline =
+proc createPipeline*(device: Device, renderPass: VkRenderPass, vertexCode: ShaderCode, fragmentCode: ShaderCode, inFlightFrames: int, subpass = 0'u32): Pipeline =
   assert renderPass.valid
   assert device.vk.valid
+
+  var
+    vertexShader = device.createShaderModule(vertexCode)
+    fragmentShader = device.createShaderModule(fragmentCode)
   assert vertexShader.stage == VK_SHADER_STAGE_VERTEX_BIT
   assert fragmentShader.stage == VK_SHADER_STAGE_FRAGMENT_BIT
   assert vertexShader.outputs == fragmentShader.inputs
@@ -210,6 +214,9 @@
   
   if pipeline.descriptorPool.vk.valid:
     pipeline.descriptorPool.destroy()
+
+  for shader in pipeline.shaders.mitems:
+    shader.destroy()
   pipeline.descriptorSetLayout.destroy()
   pipeline.device.vk.vkDestroyPipelineLayout(pipeline.layout, nil)
   pipeline.device.vk.vkDestroyPipeline(pipeline.vk, nil)
--- a/src/semicongine/vulkan/renderpass.nim	Wed Apr 12 01:20:53 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Mon Apr 17 18:02:19 2023 +0700
@@ -1,5 +1,4 @@
 import std/options
-import std/tables
 import std/logging
 
 import ./api
@@ -7,13 +6,9 @@
 import ./device
 import ./pipeline
 import ./shader
-import ./drawable
-import ./buffer
 import ./framebuffer
 
 import ../math
-import ../entity
-import ../gpu_data
 
 type
   Subpass* = object
@@ -70,7 +65,7 @@
   result.subpasses = pSubpasses
   checkVkResult device.vk.vkCreateRenderPass(addr(createInfo), nil, addr(result.vk))
 
-proc simpleForwardRenderPass*(device: Device, format: VkFormat, vertexShader: Shader, fragmentShader: Shader, inFlightFrames: int, clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): RenderPass =
+proc simpleForwardRenderPass*(device: Device, format: VkFormat, vertexCode: ShaderCode, fragmentCode: ShaderCode, inFlightFrames: int, clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): RenderPass =
   assert device.vk.valid
   var
     attachments = @[VkAttachmentDescription(
@@ -100,7 +95,7 @@
       dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
     )]
   result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies)
-  result.subpasses[0].pipelines.add device.createPipeline(result.vk, vertexShader, fragmentShader, inFlightFrames, 0)
+  result.subpasses[0].pipelines.add device.createPipeline(result.vk, vertexCode, fragmentCode, inFlightFrames, 0)
 
 proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer) =
   assert commandBuffer.valid
@@ -151,36 +146,6 @@
   commandBuffer.vkCmdEndRenderPass()
   checkVkResult commandBuffer.vkEndCommandBuffer()
 
-proc draw*(
-  commandBuffer: VkCommandBuffer,
-  renderPass: var RenderPass,
-  framebuffer: Framebuffer,
-  rootEntity: Entity,
-  drawables: seq[Drawable],
-  vertexBuffers: Table[MemoryLocation, Buffer],
-  indexBuffer: Buffer,
-  currentInFlight: int
-) =
-  commandBuffer.beginRenderCommands(renderpass, framebuffer)
-  for i in 0 ..< renderpass.subpasses.len:
-    let subpass = renderpass.subpasses[i]
-    for pipeline in subpass.pipelines:
-      var mpipeline = pipeline
-      commandBuffer.vkCmdBindPipeline(subpass.pipelineBindPoint, mpipeline.vk)
-      commandBuffer.vkCmdBindDescriptorSets(subpass.pipelineBindPoint, mpipeline.layout, 0, 1, addr(mpipeline.descriptorSets[currentInFlight].vk), 0, nil)
-      mpipeline.updateUniforms(rootEntity, currentInFlight)
-
-      debug "Scene buffers:"
-      for (location, buffer) in vertexBuffers.pairs:
-        echo "  ", location, ": ", buffer
-      echo "  Index buffer: ", indexBuffer
-
-      for drawable in drawables:
-        commandBuffer.draw(drawable, vertexBuffers=vertexBuffers, indexBuffer=indexBuffer)
-
-    if i < renderpass.subpasses.len - 1:
-      commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE)
-  commandBuffer.endRenderCommands()
 
 proc destroy*(renderPass: var RenderPass) =
   assert renderPass.device.vk.valid
--- a/src/semicongine/vulkan/shader.nim	Wed Apr 12 01:20:53 2023 +0700
+++ b/src/semicongine/vulkan/shader.nim	Mon Apr 17 18:02:19 2023 +0700
@@ -20,7 +20,7 @@
 addHandler(logger)
 
 type
-  ShaderCode = object
+  ShaderCode* = object # compiled shader code with some meta data
     stage: VkShaderStageFlagBits
     entrypoint: string
     binary: seq[uint32]
--- a/src/semicongine/vulkan/swapchain.nim	Wed Apr 12 01:20:53 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Mon Apr 17 18:02:19 2023 +0700
@@ -11,7 +11,6 @@
 import ./commandbuffer
 import ./syncing
 
-import ../scene
 import ../math
 
 type
@@ -25,7 +24,8 @@
     imageviews*: seq[ImageView]
     framebuffers*: seq[Framebuffer]
     currentInFlight*: int
-    framesRendered*: int
+    currentFramebufferIndex: uint32
+    framesRendered*: uint64
     queueFinishedFence: seq[Fence]
     imageAvailableSemaphore*: seq[Semaphore]
     renderFinishedSemaphore*: seq[Semaphore]
@@ -103,8 +103,10 @@
 
   return (swapchain, createResult)
 
+proc currentFramebuffer*(swapchain: Swapchain): Framebuffer =
+  swapchain.framebuffers[swapchain.currentFramebufferIndex]
 
-proc drawScene*(swapchain: var Swapchain, scene: Scene): bool =
+proc nextFrame*(swapchain: var Swapchain): VkCommandBuffer =
   assert swapchain.device.vk.valid
   assert swapchain.vk.valid
   assert swapchain.device.firstGraphicsQueue().isSome
@@ -113,24 +115,23 @@
   swapchain.currentInFlight = (swapchain.currentInFlight + 1) mod swapchain.inFlightFrames
   swapchain.queueFinishedFence[swapchain.currentInFlight].wait()
 
-  var currentFramebufferIndex: uint32
-
   let nextImageResult = swapchain.device.vk.vkAcquireNextImageKHR(
     swapchain.vk,
     high(uint64),
     swapchain.imageAvailableSemaphore[swapchain.currentInFlight].vk,
     VkFence(0),
-    addr(currentFramebufferIndex)
+    addr(swapchain.currentFramebufferIndex)
   )
   if not (nextImageResult in [VK_SUCCESS, VK_TIMEOUT, VK_NOT_READY, VK_SUBOPTIMAL_KHR]):
-    return false
+    return
 
   swapchain.queueFinishedFence[swapchain.currentInFlight].reset()
 
-  var commandBuffer = swapchain.commandBufferPool.buffers[swapchain.currentInFlight]
-  commandBuffer.draw(renderPass=swapchain.renderPass, framebuffer=swapchain.framebuffers[currentFramebufferIndex], rootEntity=scene.root, drawables=scene.drawables, vertexBuffers=scene.vertexBuffers, indexBuffer=scene.indexBuffer, currentInFlight=swapchain.currentInFlight)
+  return swapchain.commandBufferPool.buffers[swapchain.currentInFlight]
 
+proc swap*(swapchain: var Swapchain): bool =
   var
+    commandBuffer = swapchain.commandBufferPool.buffers[swapchain.currentInFlight]
     waitSemaphores = [swapchain.imageAvailableSemaphore[swapchain.currentInFlight].vk]
     waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
     submitInfo = VkSubmitInfo(
@@ -156,7 +157,7 @@
     pWaitSemaphores: addr(swapchain.renderFinishedSemaphore[swapchain.currentInFlight].vk),
     swapchainCount: 1,
     pSwapchains: addr(swapchain.vk),
-    pImageIndices: addr(currentFramebufferIndex),
+    pImageIndices: addr(swapchain.currentFramebufferIndex),
     pResults: nil,
   )
   let presentResult = vkQueuePresentKHR(swapchain.device.firstPresentationQueue().get().vk, addr(presentInfo))
@@ -164,7 +165,6 @@
     return false
 
   inc swapchain.framesRendered
-
   return true
 
 
--- a/tests/test_vulkan_wrapper.nim	Wed Apr 12 01:20:53 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Mon Apr 17 18:02:19 2023 +0700
@@ -33,44 +33,43 @@
     for format in device.getSurfaceFormats():
       echo "    " & $format
 
-proc scene_different_mesh_types(): Scene =
-  result = Scene(
-    name: "main",
-    root: newEntity("root",
-      newEntity("triangle1", newMesh(
-        positions=[newVec3f(0.0, -0.5), newVec3f(0.5, 0.5), newVec3f(-0.5, 0.5)],
-        colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
-      )),
-      newEntity("triangle1b", newMesh(
-        positions=[newVec3f(0.0, -0.4), newVec3f(0.4, 0.4), newVec3f(-0.4, 0.5)],
-        colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
-      )),
-      newEntity("triangle2a", newMesh(
-        positions=[newVec3f(0.0, 0.5), newVec3f(0.5, -0.5), newVec3f(-0.5, -0.5)],
-        colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
-        indices=[[0'u16, 2'u16, 1'u16]]
-      )),
-      newEntity("triangle2b", newMesh(
-        positions=[newVec3f(0.0, 0.4), newVec3f(0.4, -0.4), newVec3f(-0.4, -0.4)],
-        colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
-        indices=[[0'u16, 2'u16, 1'u16]]
-      )),
-      newEntity("triangle3a", newMesh(
-        positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
-        colors=[newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0)],
-        indices=[[0'u32, 2'u32, 1'u32]],
-        autoResize=false
-      )),
-      newEntity("triangle3b", newMesh(
-        positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
-        colors=[newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0)],
-        indices=[[0'u32, 2'u32, 1'u32]],
-        autoResize=false
-      )),
-    )
+proc scene_different_mesh_types(): Entity =
+  result = newEntity("root",
+    newEntity("triangle1", newMesh(
+      positions=[newVec3f(0.0, -0.5), newVec3f(0.5, 0.5), newVec3f(-0.5, 0.5)],
+      colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
+    )),
+    newEntity("triangle1b", newMesh(
+      positions=[newVec3f(0.0, -0.4), newVec3f(0.4, 0.4), newVec3f(-0.4, 0.5)],
+      colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
+    )),
+    newEntity("triangle2a", newMesh(
+      positions=[newVec3f(0.0, 0.5), newVec3f(0.5, -0.5), newVec3f(-0.5, -0.5)],
+      colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
+      indices=[[0'u16, 2'u16, 1'u16]]
+    )),
+    newEntity("triangle2b", newMesh(
+      positions=[newVec3f(0.0, 0.4), newVec3f(0.4, -0.4), newVec3f(-0.4, -0.4)],
+      colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
+      indices=[[0'u16, 2'u16, 1'u16]]
+    )),
+    newEntity("triangle3a", newMesh(
+      positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
+      colors=[newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0)],
+      indices=[[0'u32, 2'u32, 1'u32]],
+      autoResize=false
+    )),
+    newEntity("triangle3b", newMesh(
+      positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
+      colors=[newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0)],
+      indices=[[0'u32, 2'u32, 1'u32]],
+      autoResize=false
+    )),
   )
+  for mesh in allComponentsOfType[Mesh](result):
+    mesh.setInstanceData("translate", @[newVec3f()])
 
-proc scene_simple(): Scene =
+proc scene_simple(): Entity =
   var mymesh1 = newMesh(
     positions=[newVec3f(0.0, -0.3), newVec3f(0.3, 0.3), newVec3f(-0.3, 0.3)],
     colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
@@ -95,22 +94,16 @@
   mymesh2.setInstanceData("translate", @[newVec3f(0.0, 0.3)])
   mymesh3.setInstanceData("translate", @[newVec3f(-0.3, 0.0)])
   mymesh4.setInstanceData("translate", @[newVec3f(0.0, -0.3), newVec3f(0.0, 0.5)])
-  result = Scene(
-    name: "main",
-    root: newEntity("root", newEntity("triangle", mymesh4, mymesh3, mymesh2, mymesh1))
-  )
+  result = newEntity("root", newEntity("triangle", mymesh4, mymesh3, mymesh2, mymesh1))
 
-proc scene_primitives(): Scene =
+proc scene_primitives(): Entity =
   var r = rect(color="ff0000")
   var t = tri(color="0000ff")
   var c = circle(color="00ff00")
   setInstanceData[Vec3f](r, "translate", @[newVec3f(0.5, -0.3)])
   setInstanceData[Vec3f](t, "translate", @[newVec3f(0.3,  0.3)])
   setInstanceData[Vec3f](c, "translate", @[newVec3f(-0.3,  0.1)])
-  result = Scene(
-    name: "main",
-    root: newEntity("root", t, r, c)
-  )
+  result = newEntity("root", t, r, c)
 
 when isMainModule:
   var engine = initEngine("Test")
@@ -140,42 +133,29 @@
       main="color = vec4(outcolor, 1);"
     )
   var
-    vertexshader = engine.gpuDevice.createShaderModule(vertexCode)
-    fragmentshader = engine.gpuDevice.createShaderModule(fragmentCode)
     surfaceFormat = engine.gpuDevice.physicalDevice.getSurfaceFormats().filterSurfaceFormat()
-    renderPass = engine.gpuDevice.simpleForwardRenderPass(surfaceFormat.format, vertexshader, fragmentshader, 2)
-    (swapchain, res) = engine.gpuDevice.createSwapchain(renderPass, surfaceFormat, engine.gpuDevice.firstGraphicsQueue().get().family, 2)
-  if res != VK_SUCCESS:
-    raise newException(Exception, "Unable to create swapchain")
+    renderPass = engine.gpuDevice.simpleForwardRenderPass(surfaceFormat.format, vertexCode, fragmentCode, 2)
+    renderer = engine.gpuDevice.initRenderer([renderPass])
 
   # INIT SCENE
 
-  # var thescene = scene_simple()
-  # var thescene = scene_different_mesh_types()
-  var thescene = scene_primitives()
+  var scenes = [scene_simple(), scene_different_mesh_types(), scene_primitives()]
   var time = initShaderGlobal("time", 0.0'f32)
-  thescene.root.components.add time
-  thescene.setupDrawableBuffers(engine.gpuDevice, vertexInput)
+  for scene in scenes.mitems:
+    scene.components.add time
+    renderer.setupDrawableBuffers(scene, vertexInput)
 
   # MAINLOOP
   echo "Setup successfull, start rendering"
-  for i in 0 ..< 10000:
-    setValue[float32](time.value, get[float32](time.value) + 0.0005)
-    discard swapchain.drawScene(thescene)
-  echo "Rendered ", swapchain.framesRendered, " frames"
+  for i in 0 ..< 3:
+    for scene in scenes:
+      for i in 0 ..< 1000:
+        setValue[float32](time.value, get[float32](time.value) + 0.0005)
+        discard renderer.render(scene)
+  echo "Rendered ", renderer.framesRendered, " frames"
 
   # cleanup
   echo "Start cleanup"
   checkVkResult engine.gpuDevice.vk.vkDeviceWaitIdle()
-
-  # destroy scene
-  thescene.destroy()
-
-  # destroy renderer
-  vertexshader.destroy()
-  fragmentshader.destroy()
-  renderPass.destroy()
-  swapchain.destroy()
-
-  # destroy engine
+  renderer.destroy()
   engine.destroy()