changeset 113:7b695fb335ed

did: first final implementation of scene-graph <-> pipeline connection, not working yet
author Sam <sam@basx.dev>
date Sun, 02 Apr 2023 01:22:09 +0700
parents 0c5a74885796
children 056e08dfad10
files src/semicongine/gpu_data.nim src/semicongine/math/vector.nim src/semicongine/mesh.nim src/semicongine/platform/linux/xlib.nim src/semicongine/scene.nim src/semicongine/vulkan/buffer.nim src/semicongine/vulkan/renderpass.nim src/semicongine/vulkan/swapchain.nim src/semicongine/vulkan/utils.nim tests/test_vulkan_wrapper.nim
diffstat 10 files changed, 236 insertions(+), 55 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/gpu_data.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/gpu_data.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -1,7 +1,9 @@
+import std/typetraits
 import std/strformat
 import std/tables
 
 import ./vulkan/api
+import ./math
 
 type
   CountType = 1'u32 .. 4'u32
@@ -26,6 +28,16 @@
   AttributeGroup* = object
     attributes*: seq[Attribute]
 
+func vertexInputs*(group: AttributeGroup): seq[Attribute] =
+  for attr in group.attributes:
+    if attr.perInstance == false:
+      result.add attr
+
+func instanceInputs*(group: AttributeGroup): seq[Attribute] =
+  for attr in group.attributes:
+    if attr.perInstance == false:
+      result.add attr
+
 func attr*(name: string, thetype: DataType, components=CountType(1), rows=CountType(1), perInstance=false): auto =
   Attribute(name: name, thetype: thetype, components: components, rows: rows, perInstance: perInstance)
 
@@ -52,6 +64,47 @@
   for attribute in thetype.attributes:
     result += attribute.size
 
+func getDataType*[T: SomeNumber](value: T): DataType =
+  when T is float32: Float32
+  elif T is float64: Float64
+  elif T is int8: Int8
+  elif T is int16: Int16
+  elif T is int32: Int32
+  elif T is int64: Int64
+  elif T is uint8: UInt8
+  elif T is uint16: UInt16
+  elif T is uint32: UInt32
+  elif T is uint64: UInt64
+  elif T is int and sizeof(int) == sizeof(int64): Int64
+  elif T is int and sizeof(int) == sizeof(int32): Int32
+  elif T is uint and sizeof(uint) == sizeof(uint64): UInt64
+  elif T is uint and sizeof(uint) == sizeof(uint32): UInt32
+  elif T is float and sizeof(float) == sizeof(float32): Float32
+  elif T is float and sizeof(float) == sizeof(float64): Float64
+  else:
+    static:
+      raise newException(Exception, &"Unsupported data type for GPU data: {name(T)}" )
+
+func asAttribute*[T: SomeNumber|TMat|TVec](value: T, name: string): Attribute =
+  when not (T is SomeNumber):
+    let nonScalarDatatype = getDataType(default(get(genericParams(typeof(value)), 0)))
+
+  when T is SomeNumber: attr(name, getDataType(default(T)))
+  elif T is TMat22: attr(name, nonScalarDatatype, 2, 2)
+  elif T is TMat23: attr(name, nonScalarDatatype, 3, 2)
+  elif T is TMat32: attr(name, nonScalarDatatype, 3, 2)
+  elif T is TMat33: attr(name, nonScalarDatatype, 3, 3)
+  elif T is TMat34: attr(name, nonScalarDatatype, 4, 3)
+  elif T is TMat43: attr(name, nonScalarDatatype, 3, 4)
+  elif T is TMat44: attr(name, nonScalarDatatype, 4, 4)
+  elif T is TVec2: attr(name, nonScalarDatatype, 2)
+  elif T is TVec3: attr(name, nonScalarDatatype, 3)
+  elif T is TVec4: attr(name, nonScalarDatatype, 4)
+  else: {.error "Unsupported attribute type for GPU data".}
+
+func asAttribute*[T: SomeNumber|TMat|TVec](): Attribute =
+  asAttribute(default(T), name(T))
+
 const TYPEMAP = {
   CountType(1): {
     UInt8: VK_FORMAT_R8_UINT,
--- a/src/semicongine/math/vector.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/math/vector.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -12,12 +12,12 @@
   TVec3*[T: SomeNumber] = array[3, T]
   TVec4*[T: SomeNumber] = array[4, T]
   TVec* = TVec2|TVec3|TVec4
-  Vec2* = TVec2[float32]
-  Vec3* = TVec3[float32]
-  Vec4* = TVec4[float32]
-  Vec2I* = TVec2[uint32]
-  Vec3I* = TVec3[uint32]
-  Vec4I* = TVec4[uint32]
+  Vec2f* = TVec2[float32]
+  Vec3f* = TVec3[float32]
+  Vec4f* = TVec4[float32]
+  Vec2i* = TVec2[uint32]
+  Vec3i* = TVec3[uint32]
+  Vec4i* = TVec4[uint32]
 
 converter toVec2*[T: SomeNumber](orig: TVec3[T]|TVec4[T]): TVec2[T] =
   TVec2[T]([orig[0], orig[1]])
@@ -36,12 +36,12 @@
 func ConstG[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(0), T(1), T(0)])
 func ConstB[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(0), T(0), T(1)])
 
-func newVec2*(x=0'f32, y=0'f32): auto =
-  Vec2([x, y])
-func newVec3*(x=0'f32, y=0'f32, z=0'f32): auto =
-  Vec3([x, y, z])
-func newVec4*(x=0'f32, y=0'f32, z=0'f32, a=0'f32): auto =
-  Vec4([x, y, z, a])
+func newVec2f*(x=0'f32, y=0'f32): auto =
+  Vec2f([x, y])
+func newVec3f*(x=0'f32, y=0'f32, z=0'f32): auto =
+  Vec3f([x, y, z])
+func newVec4f*(x=0'f32, y=0'f32, z=0'f32, a=0'f32): auto =
+  Vec4f([x, y, z, a])
 
 # generates constants: Xf, Xf32, Xf64, Xi, Xi8, Xi16, Xi32, Xi64
 # Also for Y, Z, R, G, B and One
@@ -65,12 +65,12 @@
 
 generateAllConsts()
 
-const X* = ConstX[float]()
-const Y* = ConstY[float]()
-const Z* = ConstZ[float]()
-const One2* = ConstOne2[float]()
-const One3* = ConstOne3[float]()
-const One4* = ConstOne4[float]()
+const X* = ConstX[float32]()
+const Y* = ConstY[float32]()
+const Z* = ConstZ[float32]()
+const One2* = ConstOne2[float32]()
+const One3* = ConstOne3[float32]()
+const One4* = ConstOne4[float32]()
 
 func newVec2*[T](x, y: T): auto = TVec2([x, y])
 func newVec3*[T](x, y, z: T): auto = TVec3([x, y, z])
--- a/src/semicongine/mesh.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/mesh.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -1,17 +1,34 @@
+import std/typetraits
+import std/tables
 import std/enumerate
 import std/strformat
 import std/sequtils
 
+import ./vulkan/utils
+import ./gpu_data
 import ./entity
 import ./math
 
 type
+  SurfaceDataType = enum
+    Position, Color, Normal, Tangent, BiTangent, TextureCoordinate, Index, BigIndex
   MeshIndexType* = enum
     None
     Small # up to 2^16 vertices
     Big # up to 2^32 vertices
+  MeshData = object
+    case thetype*: SurfaceDataType
+      of Position: position: seq[Vec3f]
+      of Color: color: seq[Vec3f]
+      of Normal: normal: seq[Vec3f]
+      of Tangent: tangent: seq[Vec3f]
+      of BiTangent: bitangent: seq[Vec3f]
+      of TextureCoordinate: texturecoord: seq[Vec2f]
+      of Index: index: seq[uint16]
+      of BigIndex: bigindex: seq[uint32]
   Mesh* = ref object of Component
-    vertices: seq[Vec3]
+    vertexCount*: uint32
+    data: Table[Attribute, MeshData]
     case indexType*: MeshIndexType
     of None:
       discard
@@ -20,29 +37,78 @@
     of Big:
       bigIndices*: seq[array[3, uint32]]
 
+
 method `$`*(mesh: Mesh): string =
-  &"Mesh ({mesh.vertices.len})"
+  &"Mesh ({mesh.vertexCount})"
 
-func newMesh*(vertices: openArray[Vec3]): auto =
-  Mesh(vertices: vertices.toSeq, indexType: None)
+func newMesh*(vertices: openArray[Vec3f]): auto =
+  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: vertices.toSeq)}.toTable
+  Mesh(vertexCount: uint32(vertices.len), data: meshdata, indexType: None)
 
-func newMesh*(vertices: openArray[Vec3], indices: openArray[array[3, uint32|int32]]): auto =
+func newMesh*(vertices: openArray[Vec3f], indices: openArray[array[3, uint32|int32]]): auto =
+  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: vertices.toSeq)}.toTable
   if uint16(vertices.len) < high(uint16):
     var smallIndices = newSeq[array[3, uint16]](indices.len)
     for i, tri in enumerate(indices):
       smallIndices[i] = [uint16(tri[0]), uint16(tri[1]), uint16(tri[3])]
-    Mesh(vertices: vertices.toSeq, indexType: Small, smallIndices: smallIndices)
+    Mesh(vertexCount: uint32(vertices.len), data: meshdata, indexType: Small, smallIndices: smallIndices)
   else:
     var bigIndices = newSeq[array[3, uint32]](indices.len)
     for i, tri in enumerate(indices):
       bigIndices[i] = [uint32(tri[0]), uint32(tri[1]), uint32(tri[3])]
-    Mesh(vertices: vertices.toSeq, indexType: Big, bigIndices: bigIndices)
+    Mesh(vertexCount: uint32(vertices.len), data: meshdata, indexType: Big, bigIndices: bigIndices)
 
-func newMesh*(vertices: openArray[Vec3], indices: openArray[array[3, uint16|int16]]): auto =
+func newMesh*(vertices: openArray[Vec3f], indices: openArray[array[3, uint16|int16]]): auto =
+  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: vertices.toSeq)}.toTable
   var smallIndices = newSeq[array[3, uint16]](indices.len)
   for i, tri in enumerate(indices):
     smallIndices[i] = [uint16(tri[0]), uint16(tri[1]), uint16(tri[3])]
-  Mesh(vertices: vertices.toSeq, indexType: Small, smallIndices: smallIndices)
+  Mesh(vertexCount: vertices.len, data: meshdata, indexType: Small, smallIndices: smallIndices)
+
+
+func size*(meshdata: MeshData): uint64 =
+  case meshdata.thetype:
+    of Position: meshdata.position.size
+    of Color: meshdata.color.size
+    of Normal: meshdata.normal.size
+    of Tangent: meshdata.tangent.size
+    of BiTangent: meshdata.bitangent.size
+    of TextureCoordinate: meshdata.texturecoord.size
+    of Index: meshdata.index.size
+    of BigIndex: meshdata.bigindex.size
+
+func size*(mesh: Mesh, attribute: Attribute): uint64 =
+  mesh.data[attribute].size
+
+func size*(mesh: Mesh): uint64 =
+  for d in mesh.data.values:
+    result += d.size
+
+proc rawData[T: seq](value: var T): (pointer, uint64) =
+  (pointer(addr(value)), uint64(sizeof(get(genericParams(typeof(value)), 0)) * value.len))
+
+proc getRawData(data: var MeshData): (pointer, uint64) =
+  case data.thetype:
+    of Position: rawData(data.position)
+    of Color: rawData(data.color)
+    of Normal: rawData(data.normal)
+    of Tangent: rawData(data.tangent)
+    of BiTangent: rawData(data.bitangent)
+    of TextureCoordinate: rawData(data.texturecoord)
+    of Index: rawData(data.index)
+    of BigIndex: rawData(data.bigindex)
+
+proc hasDataFor*(mesh: Mesh, attribute: Attribute): bool =
+  assert attribute.perInstance == false, "Mesh data cannot handle per-instance attributes"
+  attribute in mesh.data
+
+proc getRawData*(mesh: Mesh, attribute: Attribute): (pointer, uint64) =
+  assert attribute.perInstance == false, "Mesh data cannot handle per-instance attributes"
+  mesh.data[attribute].getRawData()
+
+proc getData*(mesh: Mesh, attribute: Attribute): MeshData =
+  assert attribute.perInstance == false, "Mesh data cannot handle per-instance attributes"
+  mesh.data[attribute]
 
 #[
 
@@ -76,8 +142,7 @@
   for name, value in mesh.vertexData.fieldPairs:
     assert value.data.len > 0
     var flags = if value.useOnDeviceMemory: {TransferSrc} else: {VertexBuffer}
-    var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize,
-        flags, {HostVisible, HostCoherent})
+    var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize, flags, {HostVisible, HostCoherent})
     copyMem(stagingBuffer.data, addr(value.data[0]), value.datasize)
 
     if value.useOnDeviceMemory:
--- a/src/semicongine/platform/linux/xlib.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/platform/linux/xlib.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -124,7 +124,7 @@
         result.add events[0]
 
 
-proc getMousePosition*(window: NativeWindow): Option[Vec2] =
+proc getMousePosition*(window: NativeWindow): Option[Vec2f] =
   var
     root: x.Window
     win: x.Window
@@ -144,7 +144,6 @@
       addr(winY),
       addr(mask),
     )
-  if onscreen == 0:
-    return none(Vec2)
-  return some(Vec2([float32(winX), float32(winY)]))
+  if onscreen != 0:
+    result = some(Vec2f([float32(winX), float32(winY)]))
 
--- a/src/semicongine/scene.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/scene.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -1,15 +1,17 @@
 import std/tables
+import std/strformat
 
 import ./vulkan/api
 import ./vulkan/buffer
 import ./vulkan/pipeline
 import ./vulkan/renderpass
+import ./gpu_data
 import ./entity
 import ./mesh
 
 type
   Drawable* = object
-    buffers*: seq[(Buffer, int)] # buffer + offset from buffer
+    buffers*: seq[(Buffer, uint64)] # buffer + offset from buffer
     elementCount*: uint32 # number of vertices or indices
     instanceCount*: uint32 # number of instance
     case indexed*: bool
@@ -24,23 +26,51 @@
     root*: Entity
     drawables: Table[VkPipeline, seq[Drawable]]
 
+func `$`*(drawable: Drawable): string =
+  if drawable.indexed:
+    &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffers: {drawable.buffers}, indexType: {drawable.indexType})"
+  else:
+    &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffers: {drawable.buffers})"
+
 proc setupDrawables(scene: var Scene, pipeline: Pipeline) =
-  var meshes: seq[Mesh]
-  var smallIMeshes: seq[Mesh]
-  var bigIMeshes: seq[Mesh]
+  assert pipeline.device.vk.valid
+  assert not (pipeline.vk in scene.drawables)
+
+  scene.drawables[pipeline.vk] = @[]
+
+  var
+    nonIMeshes: seq[Mesh]
+    smallIMeshes: seq[Mesh]
+    bigIMeshes: seq[Mesh]
   for mesh in allPartsOfType[Mesh](scene.root):
+    for inputAttr in pipeline.inputs.vertexInputs:
+      assert mesh.hasDataFor(inputAttr), &"{mesh} missing data for {inputAttr}"
     case mesh.indexType:
-      of None: meshes.add mesh
+      of None: nonIMeshes.add mesh
       of Small: smallIMeshes.add mesh
       of Big: bigIMeshes.add mesh
-  echo pipeline.inputs
-
-  # one drawable per mesh list
-    # one buffer per pipeline.input
-      # how to find data for pipeline.inputs attribute-buffer?
-      # position: get from mesh, mark attribute
-      # color/UVs: material component?
-  scene.drawables[pipeline.vk] = @[]
+  
+  if nonIMeshes.len > 0:
+    var
+      bufferSize = 0'u64
+      vertexCount = 0'u32
+    for mesh in nonIMeshes:
+      bufferSize += mesh.size
+      vertexCount += mesh.vertexCount
+    var buffer = pipeline.device.createBuffer(
+        size=bufferSize,
+        usage=[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT],
+        memoryFlags=[VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT],
+      )
+    var offset = 0'u64
+    var drawable = Drawable(elementCount: vertexCount, indexed: false, instanceCount: 1)
+    for inputAttr in pipeline.inputs.vertexInputs:
+      drawable.buffers.add (buffer, offset)
+      for mesh in nonIMeshes:
+        var (pdata, size) = mesh.getRawData(inputAttr)
+        buffer.setData(pdata, size, offset)
+        offset += size
+    scene.drawables[pipeline.vk].add drawable
 
 #[
 proc createVertexBuffers*[M: Mesh](
@@ -76,3 +106,12 @@
 
 proc getDrawables*(scene: Scene, pipeline: Pipeline): seq[Drawable] =
   scene.drawables.getOrDefault(pipeline.vk, @[])
+
+proc destroy*(scene: var Scene) =
+  for drawables in scene.drawables.mvalues:
+    for drawable in drawables.mitems:
+      for (buffer, offset) in drawable.buffers.mitems:
+        # if buffer.vk.valid: # required because we allow duplicates in drawable.buffers
+        buffer.destroy()
+      if drawable.indexed:
+        drawable.indexBuffer.destroy()
--- a/src/semicongine/vulkan/buffer.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/vulkan/buffer.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -1,3 +1,5 @@
+import std/strformat
+import std/typetraits
 import std/sequtils
 import std/tables
 
@@ -19,6 +21,18 @@
         memory*: DeviceMemory
         data*: pointer
 
+func `$`*(buffer: Buffer): string = &"Buffer(size: {buffer.size}, usage: {buffer.usage})"
+
+proc setData*(dst: Buffer, src: pointer, len: uint64, offset=0'u64) =
+  assert offset + len <= dst.size
+  copyMem(cast[pointer](cast[uint64](dst.data) + offset), src, len)
+
+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](dst: Buffer, src: ptr T, offset=0'u64) =
+  dst.setData(src, sizeof(T), offset=offset)
+
 proc allocateMemory(buffer: var Buffer, flags: openArray[VkMemoryPropertyFlagBits]) =
   assert buffer.device.vk.valid
   assert buffer.hasMemory == false
@@ -33,9 +47,9 @@
 proc createBuffer*(
   device: Device,
   size: uint64,
-  flags: openArray[VkBufferCreateFlagBits],
   usage: openArray[VkBufferUsageFlagBits],
-  memoryFlags: openArray[VkMemoryPropertyFlagBits],
+  flags: openArray[VkBufferCreateFlagBits] = @[],
+  memoryFlags: openArray[VkMemoryPropertyFlagBits] = @[],
 ): Buffer =
   assert device.vk.valid
   assert size > 0
--- a/src/semicongine/vulkan/renderpass.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -12,7 +12,7 @@
 
 type
   Subpass* = object
-    clearColor*: Vec4
+    clearColor*: Vec4f
     pipelineBindPoint*: VkPipelineBindPoint
     flags: VkSubpassDescriptionFlags
     inputs: seq[VkAttachmentReference]
@@ -199,7 +199,7 @@
   pipeline.descriptorSets = pipeline.descriptorPool.allocateDescriptorSet(pipeline.descriptorSetLayout, renderPass.inFlightFrames)
   renderPass.subpasses[subpass].pipelines.add pipeline
 
-proc simpleForwardRenderPass*(device: Device, format: VkFormat, vertexShader: Shader, fragmentShader: Shader, inFlightFrames: int, clearColor=Vec4([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): RenderPass =
+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 =
   assert device.vk.valid
   var
     attachments = @[VkAttachmentDescription(
--- a/src/semicongine/vulkan/swapchain.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -1,4 +1,5 @@
 import std/options
+import std/logging
 
 import ./api
 import ./utils
@@ -156,6 +157,7 @@
 
 proc draw*(commandBuffer: VkCommandBuffer, drawables: seq[Drawable], scene: Scene) =
   for drawable in drawables:
+    debug "Draw ", drawable
     var buffers: seq[VkBuffer]
     var offsets: seq[VkDeviceSize]
     for (buffer, offset) in drawable.buffers:
--- a/src/semicongine/vulkan/utils.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/src/semicongine/vulkan/utils.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -1,3 +1,4 @@
+import std/typetraits
 import std/strutils
 import std/strformat
 
@@ -18,4 +19,5 @@
     raise newException(Exception, &"Running '{command}' produced exit code: {exitcode}" & output)
   return output
 
-
+func size*[T: seq](list: T): uint64 =
+  uint64(list.len * sizeof(get(genericParams(typeof(list)), 0)))
--- a/tests/test_vulkan_wrapper.nim	Sat Apr 01 00:40:02 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Sun Apr 02 01:22:09 2023 +0700
@@ -58,12 +58,12 @@
     selectedPhysicalDevice.filterForGraphicsPresentationQueues()
   )
 
-  const inputs = AttributeGroup(attributes: @[attr(name="pos", thetype=Float32, components=3)])
+  const inputs = AttributeGroup(attributes: @[attr(name="position", thetype=Float32, components=3)])
   const uniforms = AttributeGroup()
   const outputs = AttributeGroup(attributes: @[attr(name="fragpos", thetype=Float32, components=3)])
   const fragOutput = AttributeGroup(attributes: @[attr(name="color", thetype=Float32, components=4)])
-  const vertexBinary = shaderCode(inputs=inputs, uniforms=uniforms, outputs=outputs, stage=VK_SHADER_STAGE_VERTEX_BIT, version=450, entrypoint="main", "fragpos = pos;")
-  const fragmentBinary = shaderCode(inputs=outputs, uniforms=uniforms, outputs=fragOutput, stage=VK_SHADER_STAGE_FRAGMENT_BIT, version=450, entrypoint="main", "color = vec4(1, 1, 1, 0);")
+  const vertexBinary = shaderCode(inputs=inputs, uniforms=uniforms, outputs=outputs, stage=VK_SHADER_STAGE_VERTEX_BIT, version=450, entrypoint="main", "fragpos = position;")
+  const fragmentBinary = shaderCode(inputs=outputs, uniforms=uniforms, outputs=fragOutput, stage=VK_SHADER_STAGE_FRAGMENT_BIT, version=450, entrypoint="main", "color = vec4(1, 1, 1, 1);")
   var
     vertexshader = device.createShader(inputs, uniforms, outputs, VK_SHADER_STAGE_VERTEX_BIT, "main", vertexBinary)
     fragmentshader = device.createShader(inputs, uniforms, outputs, VK_SHADER_STAGE_FRAGMENT_BIT, "main", fragmentBinary)
@@ -73,7 +73,13 @@
   if res != VK_SUCCESS:
     raise newException(Exception, "Unable to create swapchain")
 
-  var thescene = Scene(name: "main", root: newEntity("triangle", newMesh([newVec3(-1, -1), newVec3(0, 1), newVec3(1, -1)])))
+  var thescene = Scene(
+    name: "main",
+    root: newEntity("root",
+      newEntity("triangle1", newMesh([newVec3f(-0.5, -0.5), newVec3f(0.5, 0.5), newVec3f(0.5, -0.5)])),
+      newEntity("triangle2", newMesh([newVec3f(-0.5, -0.5), newVec3f(0.5, -0.5), newVec3f(0.5, 0.5)])),
+    )
+  )
   thescene.setupDrawables(renderPass)
 
   echo "Setup successfull, start rendering"
@@ -85,6 +91,7 @@
 
   # cleanup
   checkVkResult device.vk.vkDeviceWaitIdle()
+  thescene.destroy()
   vertexshader.destroy()
   fragmentshader.destroy()
   renderPass.destroy()