changeset 326:4ec852355750

fix: many issues, better mesh-handling, still need to cope with different binding numbers when using different pipelines...
author Sam <sam@basx.dev>
date Fri, 25 Aug 2023 00:29:51 +0700
parents 0c3f4f6f1aa2
children a63bd8f29252
files src/semicongine/collision.nim src/semicongine/core/gpu_data.nim src/semicongine/core/imagetypes.nim src/semicongine/core/matrix.nim src/semicongine/engine.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources.nim src/semicongine/resources/font.nim src/semicongine/resources/image.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/text.nim src/semicongine/vulkan/buffer.nim src/semicongine/vulkan/descriptor.nim src/semicongine/vulkan/drawable.nim src/semicongine/vulkan/image.nim src/semicongine/vulkan/pipeline.nim src/semicongine/vulkan/shader.nim tests/test_vulkan_wrapper.nim
diffstat 20 files changed, 555 insertions(+), 740 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/collision.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/collision.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -1,15 +1,15 @@
 import std/sequtils
 
 import ./core
-import ./scene
 
 const MAX_COLLISON_DETECTION_ITERATIONS = 20
 const MAX_COLLISON_POINT_CALCULATION_ITERATIONS = 20
 
 type
-  HitBox* = ref object of Component
+  HitBox* = object
     transform*: Mat4
-  HitSphere* = ref object of Component
+  HitSphere* = object
+    transform*: Mat4
     radius*: float32
 
 func between(value, b1, b2: float32): bool =
@@ -18,7 +18,7 @@
 func contains*(hitbox: HitBox, x: Vec3f): bool =
   # from https://math.stackexchange.com/questions/1472049/check-if-a-point-is-inside-a-rectangular-shaped-area-3d
   let
-    t = hitbox.entity.getModelTransform() * hitbox.transform
+    t = hitbox.transform
     P1 = t * newVec3f(0, 0, 0) # origin
     P2 = t * Z
     P4 = t * X
@@ -53,10 +53,10 @@
 
 func findFurthestPoint(hitsphere: HitSphere, direction: Vec3f): Vec3f =
   let directionNormalizedToSphere = ((direction / direction.length) * hitsphere.radius)
-  return hitsphere.entity.getModelTransform() * directionNormalizedToSphere
+  return hitsphere.transform * directionNormalizedToSphere
 
 func findFurthestPoint(hitbox: HitBox, direction: Vec3f): Vec3f =
-  let transform = hitbox.entity.getModelTransform() * hitbox.transform
+  let transform = hitbox.transform
   return findFurthestPoint(
     [
       transform * newVec3f(0, 0, 0),
@@ -383,7 +383,7 @@
     scaleY = (maxY - minY)
     scaleZ = (maxZ - minZ)
 
-  HitBox(transform: translate3d(minX, minY, minZ) * scale3d(scaleX, scaleY, scaleZ))
+  HitBox(transform: translate(minX, minY, minZ) * scale(scaleX, scaleY, scaleZ))
 
 func calculateHitsphere*(points: seq[Vec3f]): HitSphere =
   result = HitSphere()
--- a/src/semicongine/core/gpu_data.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/core/gpu_data.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -1,5 +1,6 @@
 import std/strformat
 import std/tables
+import std/hashes
 
 import ./vulkanapi
 import ./vector
@@ -98,7 +99,7 @@
     of Mat4F64: mat4f64: TMat4[float64]
     of Sampler2D: discard
   DataList* = object
-    len*: uint32
+    len*: int
     case thetype*: DataType
     of Float32: float32: ref seq[float32]
     of Float64: float64: ref seq[float64]
@@ -148,11 +149,105 @@
   ShaderAttribute* = object
     name*: string
     thetype*: DataType
-    arrayCount*: uint32
+    arrayCount*: int
     perInstance*: bool
     noInterpolation: bool
     memoryPerformanceHint*: MemoryPerformanceHint
 
+func hash*(value: DataList): Hash =
+  case value.thetype
+    of Float32: hash(cast[pointer](value.float32))
+    of Float64: hash(cast[pointer](value.float64))
+    of Int8: hash(cast[pointer](value.int8))
+    of Int16: hash(cast[pointer](value.int16))
+    of Int32: hash(cast[pointer](value.int32))
+    of Int64: hash(cast[pointer](value.int64))
+    of UInt8: hash(cast[pointer](value.uint8))
+    of UInt16: hash(cast[pointer](value.uint16))
+    of UInt32: hash(cast[pointer](value.uint32))
+    of UInt64: hash(cast[pointer](value.uint64))
+    of Vec2I32: hash(cast[pointer](value.vec2i32))
+    of Vec2I64: hash(cast[pointer](value.vec2i64))
+    of Vec3I32: hash(cast[pointer](value.vec3i32))
+    of Vec3I64: hash(cast[pointer](value.vec3i64))
+    of Vec4I32: hash(cast[pointer](value.vec4i32))
+    of Vec4I64: hash(cast[pointer](value.vec4i64))
+    of Vec2U32: hash(cast[pointer](value.vec2u32))
+    of Vec2U64: hash(cast[pointer](value.vec2u64))
+    of Vec3U32: hash(cast[pointer](value.vec3u32))
+    of Vec3U64: hash(cast[pointer](value.vec3u64))
+    of Vec4U32: hash(cast[pointer](value.vec4u32))
+    of Vec4U64: hash(cast[pointer](value.vec4u64))
+    of Vec2F32: hash(cast[pointer](value.vec2f32))
+    of Vec2F64: hash(cast[pointer](value.vec2f64))
+    of Vec3F32: hash(cast[pointer](value.vec3f32))
+    of Vec3F64: hash(cast[pointer](value.vec3f64))
+    of Vec4F32: hash(cast[pointer](value.vec4f32))
+    of Vec4F64: hash(cast[pointer](value.vec4f64))
+    of Mat2F32: hash(cast[pointer](value.mat2f32))
+    of Mat2F64: hash(cast[pointer](value.mat2f64))
+    of Mat23F32: hash(cast[pointer](value.mat23f32))
+    of Mat23F64: hash(cast[pointer](value.mat23f64))
+    of Mat32F32: hash(cast[pointer](value.mat32f32))
+    of Mat32F64: hash(cast[pointer](value.mat32f64))
+    of Mat3F32: hash(cast[pointer](value.mat3f32))
+    of Mat3F64: hash(cast[pointer](value.mat3f64))
+    of Mat34F32: hash(cast[pointer](value.mat34f32))
+    of Mat34F64: hash(cast[pointer](value.mat34f64))
+    of Mat43F32: hash(cast[pointer](value.mat43f32))
+    of Mat43F64: hash(cast[pointer](value.mat43f64))
+    of Mat4F32: hash(cast[pointer](value.mat4f32))
+    of Mat4F64: hash(cast[pointer](value.mat4f64))
+    of Sampler2D: raise newException(Exception, "hash not defined for Sampler2D")
+
+func `==`*(a, b: DataList): bool =
+  if a.thetype != b.thetype:
+    return false
+  case a.thetype
+    of Float32: return a.float32 == b.float32
+    of Float64: return a.float64 == b.float64
+    of Int8: return a.int8 == b.int8
+    of Int16: return a.int16 == b.int16
+    of Int32: return a.int32 == b.int32
+    of Int64: return a.int64 == b.int64
+    of UInt8: return a.uint8 == b.uint8
+    of UInt16: return a.uint16 == b.uint16
+    of UInt32: return a.uint32 == b.uint32
+    of UInt64: return a.uint64 == b.uint64
+    of Vec2I32: return a.vec2i32 == b.vec2i32
+    of Vec2I64: return a.vec2i64 == b.vec2i64
+    of Vec3I32: return a.vec3i32 == b.vec3i32
+    of Vec3I64: return a.vec3i64 == b.vec3i64
+    of Vec4I32: return a.vec4i32 == b.vec4i32
+    of Vec4I64: return a.vec4i64 == b.vec4i64
+    of Vec2U32: return a.vec2u32 == b.vec2u32
+    of Vec2U64: return a.vec2u64 == b.vec2u64
+    of Vec3U32: return a.vec3u32 == b.vec3u32
+    of Vec3U64: return a.vec3u64 == b.vec3u64
+    of Vec4U32: return a.vec4u32 == b.vec4u32
+    of Vec4U64: return a.vec4u64 == b.vec4u64
+    of Vec2F32: return a.vec2f32 == b.vec2f32
+    of Vec2F64: return a.vec2f64 == b.vec2f64
+    of Vec3F32: return a.vec3f32 == b.vec3f32
+    of Vec3F64: return a.vec3f64 == b.vec3f64
+    of Vec4F32: return a.vec4f32 == b.vec4f32
+    of Vec4F64: return a.vec4f64 == b.vec4f64
+    of Mat2F32: return a.mat2f32 == b.mat2f32
+    of Mat2F64: return a.mat2f64 == b.mat2f64
+    of Mat23F32: return a.mat23f32 == b.mat23f32
+    of Mat23F64: return a.mat23f64 == b.mat23f64
+    of Mat32F32: return a.mat32f32 == b.mat32f32
+    of Mat32F64: return a.mat32f64 == b.mat32f64
+    of Mat3F32: return a.mat3f32 == b.mat3f32
+    of Mat3F64: return a.mat3f64 == b.mat3f64
+    of Mat34F32: return a.mat34f32 == b.mat34f32
+    of Mat34F64: return a.mat34f64 == b.mat34f64
+    of Mat43F32: return a.mat43f32 == b.mat43f32
+    of Mat43F64: return a.mat43f64 == b.mat43f64
+    of Mat4F32: return a.mat4f32 == b.mat4f32
+    of Mat4F64: return a.mat4f64 == b.mat4f64
+    of Sampler2D: raise newException(Exception, "'==' not defined for Sampler2D")
+
 func vertexInputs*(attributes: seq[ShaderAttribute]): seq[ShaderAttribute] =
   for attr in attributes:
     if attr.perInstance == false:
@@ -163,14 +258,14 @@
     if attr.perInstance == false:
       result.add attr
 
-func numberOfVertexInputAttributeDescriptors*(thetype: DataType): uint32 =
+func numberOfVertexInputAttributeDescriptors*(thetype: DataType): int =
   case thetype:
     of Mat2F32, Mat2F64, Mat23F32, Mat23F64: 2
     of Mat32F32, Mat32F64, Mat3F32, Mat3F64, Mat34F32, Mat34F64: 3
     of Mat43F32, Mat43F64, Mat4F32, Mat4F64: 4
     else: 1
 
-func size*(thetype: DataType): uint32 =
+func size*(thetype: DataType): int =
   case thetype:
     of Float32: 4
     of Float64: 8
@@ -216,7 +311,7 @@
     of Mat4F64: 128
     of Sampler2D: 0
 
-func size*(attribute: ShaderAttribute, perDescriptor=false): uint32 =
+func size*(attribute: ShaderAttribute, perDescriptor=false): int =
   if perDescriptor:
     attribute.thetype.size div attribute.thetype.numberOfVertexInputAttributeDescriptors
   else:
@@ -225,14 +320,14 @@
     else:
       attribute.thetype.size * attribute.arrayCount
 
-func size*(thetype: seq[ShaderAttribute]): uint32 =
+func size*(thetype: seq[ShaderAttribute]): int =
   for attribute in thetype:
     result += attribute.size
 
-func size*(value: DataValue): uint32 =
+func size*(value: DataValue): int =
   value.thetype.size
 
-func size*(value: DataList): uint32 =
+func size*(value: DataList): int =
   value.thetype.size * value.len
 
 func getDataType*[T: GPUType|int|uint|float](): DataType =
@@ -292,7 +387,7 @@
 func attr*[T: GPUType](
   name: string,
   perInstance=false,
-  arrayCount=0'u32,
+  arrayCount=0,
   noInterpolation=false,
   memoryPerformanceHint=PreferFastRead,
 ): auto =
@@ -357,7 +452,7 @@
   else: {.error: "Virtual datatype has no value" .}
 
 func setValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) =
-  value.len = uint32(data.len)
+  value.len = data.len
   when T is float32: value.float32[] = data
   elif T is float64: value.float64[] = data
   elif T is int8: value.int8[] = data
@@ -519,7 +614,7 @@
   elif T is TMat4[float64]: value.mat4f64
   else: {. error: "Virtual datatype has no values" .}
 
-func getRawData*(value: var DataValue): (pointer, uint32) =
+func getRawData*(value: DataValue): (pointer, int) =
   result[1] = value.thetype.size
   case value.thetype
     of Float32: result[0] = addr value.float32
@@ -566,9 +661,9 @@
     of Mat4F64: result[0] = addr value.mat4f64
     of Sampler2D: result[0] = nil
 
-func getRawData*(value: var DataList): (pointer, uint32) =
+func getRawData*(value: DataList): (pointer, int) =
   if value.len == 0:
-    return (nil, 0'u32)
+    return (nil, 0)
   result[1] = value.thetype.size * value.len
   case value.thetype
     of Float32: result[0] = addr value.float32[][0]
@@ -615,7 +710,7 @@
     of Mat4F64: result[0] = addr value.mat4f64[][0]
     of Sampler2D: result[0] = nil
 
-func initData*(value: var DataList, len: uint32) =
+func initData*(value: var DataList, len: int) =
   value.len = len
   case value.thetype
     of Float32: value.float32[].setLen(len)
@@ -714,7 +809,7 @@
   else: {.error: "Virtual datatype has no value" .}
 
 func appendValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) =
-  value.len += uint32(data.len)
+  value.len += data.len
   when T is float32: value.float32[].add data
   elif T is float64: value.float64[].add data
   elif T is int8: value.int8[].add data
@@ -861,7 +956,7 @@
   of Mat4F64: value.mat4f64[].add data.mat4f64
   else: raise newException(Exception, &"Unsupported data type for GPU data:" )
 
-func setValue*[T: GPUType|int|uint|float](value: var DataList, i: uint32, data: T) =
+func setValue*[T: GPUType|int|uint|float](value: var DataList, i: int, data: T) =
   assert i < value.len
   when T is float32: value.float32[][i] = data
   elif T is float64: value.float64[][i] = data
@@ -962,7 +1057,7 @@
   TYPEMAP[thetype]
 
 # from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
-func nLocationSlots*(thetype: DataType): uint32 =
+func nLocationSlots*(thetype: DataType): int =
   #[
   single location:
     16-bit scalar and vector types, and
@@ -1063,7 +1158,7 @@
 func glslInput*(group: openArray[ShaderAttribute]): seq[string] =
   if group.len == 0:
     return @[]
-  var i = 0'u32
+  var i = 0
   for attribute in group:
     assert attribute.arrayCount == 0, "arrays not supported for shader vertex attributes"
     let flat = if attribute.noInterpolation: "flat " else: ""
@@ -1097,7 +1192,7 @@
 func glslOutput*(group: openArray[ShaderAttribute]): seq[string] =
   if group.len == 0:
     return @[]
-  var i = 0'u32
+  var i = 0
   for attribute in group:
     assert attribute.arrayCount == 0, "arrays not supported for outputs"
     let flat = if attribute.noInterpolation: "flat " else: ""
--- a/src/semicongine/core/imagetypes.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/core/imagetypes.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -3,8 +3,8 @@
 type
   Pixel* = array[4, uint8]
   ImageObject* = object
-    width*: uint32
-    height*: uint32
+    width*: int
+    height*: int
     imagedata*: seq[Pixel]
   Sampler* = object
     magnification*: VkFilter
@@ -23,26 +23,26 @@
       wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT,
     )
 
-proc `[]`*(image: Image, x, y: uint32): Pixel =
+proc `[]`*(image: Image, x, y: int): Pixel =
   assert x < image.width
   assert y < image.height
 
   image[].imagedata[y * image.width + x]
 
-proc `[]=`*(image: var Image, x, y: uint32, value: Pixel) =
+proc `[]=`*(image: var Image, x, y: int, value: Pixel) =
   assert x < image.width
   assert y < image.height
 
   image[].imagedata[y * image.width + x] = value
 
 const EMPTYPIXEL = [0'u8, 0'u8, 0'u8, 0'u8]
-proc newImage*(width, height: uint32, imagedata: seq[Pixel] = @[], fill=EMPTYPIXEL): Image =
+proc newImage*(width, height: int, imagedata: seq[Pixel] = @[], fill=EMPTYPIXEL): Image =
   assert width > 0 and height > 0
-  assert uint32(imagedata.len) == width * height or imagedata.len == 0
+  assert imagedata.len == width * height or imagedata.len == 0
 
   result = new Image
   result.imagedata = (if imagedata.len == 0: newSeq[Pixel](width * height) else: imagedata)
-  assert width * height == uint32(result.imagedata.len)
+  assert width * height == result.imagedata.len
 
   result.width = width
   result.height = height
--- a/src/semicongine/core/matrix.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/core/matrix.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -376,19 +376,21 @@
   sin(angle), cos(angle), T(0),
   T(0), T(0), T(1),
 ])
-func translate3d*(x=0'f32, y=0'f32, z=0'f32): TMat4[float32] = Mat4(data: [
+func translate*(x=0'f32, y=0'f32, z=0'f32): TMat4[float32] = Mat4(data: [
   1'f32, 0'f32, 0'f32, x,
   0'f32, 1'f32, 0'f32, y,
   0'f32, 0'f32, 1'f32, z,
   0'f32, 0'f32, 0'f32, 1'f32,
 ])
-func scale3d*(x=1'f32, y=1'f32, z=1'f32): Mat4 = Mat4(data: [
+func translate*[T: TVec3](v: T): TMat4[float32] = translate(v[0], v[1], v[2])
+func scale*(x=1'f32, y=1'f32, z=1'f32): Mat4 = Mat4(data: [
   x,     0'f32, 0'f32, 0'f32,
   0'f32, y,     0'f32, 0'f32,
   0'f32, 0'f32, z,     0'f32,
   0'f32, 0'f32, 0'f32, 1'f32,
 ])
-func rotate3d*(angle: float32, a: Vec3f): Mat4 =
+func scale*[T: TVec3](v: T): TMat4[float32] = scale(v[0], v[1], v[2])
+func rotate*(angle: float32, a: Vec3f): Mat4 =
   let
     cosa = cos(angle)
     sina = sin(angle)
--- a/src/semicongine/engine.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/engine.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -107,7 +107,7 @@
   assert not engine.renderer.isSome
   engine.renderer = some(engine.device.initRenderer(shaders=shaders, clearColor=clearColor))
 
-proc addScene*(engine: var Engine, scene: Scene) =
+proc addScene*(engine: var Engine, scene: var Scene) =
   assert engine.renderer.isSome
   engine.renderer.get.setupDrawableBuffers(scene)
 
@@ -118,11 +118,6 @@
   engine.renderer.get.updateUniformData(scene)
   engine.renderer.get.render(scene)
 
-proc updateAnimations*(engine: var Engine, scene: var Scene, dt: float32) =
-  assert engine.state == Running
-  assert engine.renderer.isSome
-  engine.renderer.get.updateAnimations(scene, dt)
-
 proc updateInputs*(engine: var Engine): EngineState =
   assert engine.state in [Starting, Running]
 
--- a/src/semicongine/mesh.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/mesh.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -8,7 +8,6 @@
 import std/sequtils
 
 import ./core
-import ./scene
 import ./collision
 
 type
@@ -17,18 +16,17 @@
     Tiny # up to 2^8 vertices # TODO: need to check and enable support for this
     Small # up to 2^16 vertices
     Big # up to 2^32 vertices
-  Mesh* = ref object of Component
-    vertexCount*: uint32
-    instanceCount*: uint32
-    instanceTransforms*: seq[Mat4] # this should not reside in instanceData["transform"], as we will use instanceData["transform"] to store the final transformation matrix (as derived from the scene-tree)
-    material*: Material
+  Mesh* = object
+    vertexCount*: int
     case indexType*: MeshIndexType
       of None: discard
       of Tiny: tinyIndices: seq[array[3, uint8]]
       of Small: smallIndices: seq[array[3, uint16]]
       of Big: bigIndices: seq[array[3, uint32]]
-    visible: bool = true
-    dirtyInstanceTransforms: bool
+    material*: Material
+    transform*: Mat4 = Unit4F32
+    instanceTransforms*: seq[Mat4]
+    transformCache: seq[Mat4]
     vertexData: Table[string, DataList]
     instanceData: Table[string, DataList]
     dirtyAttributes: seq[string]
@@ -38,27 +36,8 @@
     constants*: Table[string, DataValue]
     textures*: Table[string, Texture]
 
-proc hash*(material: Material): Hash =
-  hash(cast[int64](material))
-
-converter toVulkan*(indexType: MeshIndexType): VkIndexType =
-  case indexType:
-    of None: VK_INDEX_TYPE_NONE_KHR
-    of Tiny: VK_INDEX_TYPE_UINT8_EXT
-    of Small: VK_INDEX_TYPE_UINT16
-    of Big: VK_INDEX_TYPE_UINT32
-
-func indicesCount*(mesh: Mesh): uint32 =
-  (
-    case mesh.indexType
-    of None: 0'u32
-    of Tiny: uint32(mesh.tinyIndices.len)
-    of Small: uint32(mesh.smallIndices.len)
-    of Big: uint32(mesh.bigIndices.len)
-  ) * 3
-
-method `$`*(mesh: Mesh): string =
-  &"Mesh, vertexCount: {mesh.vertexCount}, vertexData: {mesh.vertexData.keys().toSeq()}, indexType: {mesh.indexType}"
+func `$`*(mesh: Mesh): string =
+  &"Mesh(vertexCount: {mesh.vertexCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})"
 
 proc `$`*(material: Material): string =
   var constants: seq[string]
@@ -69,37 +48,75 @@
     textures.add &"{key}"
   return &"""{material.name} | Values: {constants.join(", ")} | Textures: {textures.join(", ")}"""
 
-func prettyData*(mesh: Mesh): string =
-  for attr, data in mesh.vertexData.pairs:
-    result &= &"{attr}: {data}\n"
-  result &= (case mesh.indexType
-    of None: ""
-    of Tiny: &"indices: {mesh.tinyIndices}"
-    of Small: &"indices: {mesh.smallIndices}"
-    of Big: &"indices: {mesh.bigIndices}")
+func vertexAttributes*(mesh: Mesh): seq[string] =
+  mesh.vertexData.keys.toSeq
+
+func instanceAttributes*(mesh: Mesh): seq[string] =
+  mesh.instanceData.keys.toSeq
+
+func attributes*(mesh: Mesh): seq[string] =
+  mesh.vertexAttributes & mesh.instanceAttributes
+
+func hash*(material: Material): Hash =
+  hash(cast[pointer](material))
+
+func instanceCount*(mesh: Mesh): int =
+  mesh.instanceTransforms.len
+
+converter toVulkan*(indexType: MeshIndexType): VkIndexType =
+  case indexType:
+    of None: VK_INDEX_TYPE_NONE_KHR
+    of Tiny: VK_INDEX_TYPE_UINT8_EXT
+    of Small: VK_INDEX_TYPE_UINT16
+    of Big: VK_INDEX_TYPE_UINT32
 
-proc setMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) =
-  assert not (attribute in mesh.vertexData)
-  mesh.vertexData[attribute] = newDataList(data)
+func indicesCount*(mesh: Mesh): int =
+  (
+    case mesh.indexType
+    of None: 0
+    of Tiny: mesh.tinyIndices.len
+    of Small: mesh.smallIndices.len
+    of Big: mesh.bigIndices.len
+  ) * 3
 
-proc setMeshData*(mesh: Mesh, attribute: string, data: DataList) =
-  assert not (attribute in mesh.vertexData)
-  mesh.vertexData[attribute] = data
+func initVertexAttribute*[T](mesh: var Mesh, attribute: string, value: seq[T]) =
+  assert not mesh.vertexData.contains(attribute)
+  mesh.vertexData[attribute] = newDataList(thetype=getDataType[T]())
+  mesh.vertexData[attribute].initData(mesh.vertexCount)
+  mesh.vertexData[attribute].setValues(value)
+func initVertexAttribute*[T](mesh: var Mesh, attribute: string, value: T) =
+  initVertexAttribute(mesh, attribute, newSeqWith(mesh.vertexCount, value))
+func initVertexAttribute*[T](mesh: var Mesh, attribute: string) =
+  initVertexAttribute(mesh=mesh, attribute=attribute, value=default(T))
+func initVertexAttribute*(mesh: var Mesh, attribute: string, datatype: DataType) =
+  assert not mesh.vertexData.contains(attribute)
+  mesh.vertexData[attribute] = newDataList(thetype=datatype)
+  mesh.vertexData[attribute].initData(mesh.vertexCount)
 
-proc setInstanceData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) =
-  assert uint32(data.len) == mesh.instanceCount
-  assert not (attribute in mesh.instanceData)
-  mesh.instanceData[attribute] = newDataList(data)
+func initInstanceAttribute*[T](mesh: var Mesh, attribute: string, value: seq[T]) =
+  assert not mesh.instanceData.contains(attribute)
+  mesh.instanceData[attribute] = newDataList(thetype=getDataType[T]())
+  mesh.instanceData[attribute].initData(mesh.instanceCount)
+  mesh.instanceData[attribute].setValues(value)
+func initInstanceAttribute*[T](mesh: var Mesh, attribute: string, value: T) =
+  initInstanceAttribute(mesh, attribute, newSeqWith(mesh.instanceCount, value))
+func initInstanceAttribute*[T](mesh: var Mesh, attribute: string) =
+  initInstanceAttribute(mesh=mesh, attribute=attribute, value=default(T))
+func initInstanceAttribute*(mesh: var Mesh, attribute: string, datatype: DataType) =
+  assert not mesh.instanceData.contains(attribute)
+  mesh.instanceData[attribute] = newDataList(thetype=datatype)
+  mesh.instanceData[attribute].initData(mesh.instanceCount)
 
 func newMesh*(
   positions: openArray[Vec3f],
-  indices: openArray[array[3, uint32|int32|uint16|int16|int]],
+  indices: openArray[array[3, uint32|uint16|uint8]],
   colors: openArray[Vec4f]=[],
   uvs: openArray[Vec2f]=[],
+  transform: Mat4=Unit4F32,
+  instanceTransforms: openArray[Mat4]=[Unit4F32],
   material: Material=nil,
-  instanceCount=1'u32,
-  autoResize=true
-): auto =
+  autoResize=true,
+): Mesh =
   assert colors.len == 0 or colors.len == positions.len
   assert uvs.len == 0 or uvs.len == positions.len
 
@@ -113,22 +130,22 @@
       indexType = Small
 
   result = Mesh(
-    instanceCount: instanceCount,
-    instanceTransforms: newSeqWith(int(instanceCount), Unit4F32),
     indexType: indexType,
-    vertexCount: uint32(positions.len)
+    vertexCount: positions.len,
+    instanceTransforms: @instanceTransforms,
+    transform: transform,
+    material: material,
   )
-  result.material = material
 
-  setMeshData(result, "position", positions.toSeq)
-  if colors.len > 0: setMeshData(result, "color", colors.toSeq)
-  if uvs.len > 0: setMeshData(result, "uv", uvs.toSeq)
+  result.initVertexAttribute("position", positions.toSeq)
+  if colors.len > 0: result.initVertexAttribute("color", colors.toSeq)
+  if uvs.len > 0: result.initVertexAttribute("uv", uvs.toSeq)
 
   # assert all indices are valid
   for i in indices:
-    assert uint32(i[0]) < result.vertexCount
-    assert uint32(i[1]) < result.vertexCount
-    assert uint32(i[2]) < result.vertexCount
+    assert int(i[0]) < result.vertexCount
+    assert int(i[1]) < result.vertexCount
+    assert int(i[2]) < result.vertexCount
 
   # cast index values to appropiate type
   if result.indexType == Tiny and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support
@@ -140,31 +157,26 @@
   elif result.indexType == Big:
     for i, tri in enumerate(indices):
       result.bigIndices.add [uint32(tri[0]), uint32(tri[1]), uint32(tri[2])]
-  setInstanceData(result, "transform", newSeqWith(int(instanceCount), Unit4F32))
 
 func newMesh*(
   positions: openArray[Vec3f],
   colors: openArray[Vec4f]=[],
   uvs: openArray[Vec2f]=[],
-  instanceCount=1'u32,
+  transform: Mat4=Unit4F32,
+  instanceTransforms: openArray[Mat4]=[Unit4F32],
   material: Material=nil,
-): auto =
+): Mesh =
   newMesh(
     positions=positions,
-    indices=newSeq[array[3, int]](),
+    indices=newSeq[array[3, uint16]](),
     colors=colors,
     uvs=uvs,
+    transform=transform,
+    instanceTransforms=instanceTransforms,
     material=material,
-    instanceCount=instanceCount,
   )
 
-func vertexAttributes*(mesh: Mesh): seq[string] =
-  mesh.vertexData.keys.toSeq
-
-func instanceAttributes*(mesh: Mesh): seq[string] =
-  mesh.instanceData.keys.toSeq
-
-func attributeSize*(mesh: Mesh, attribute: string): uint32 =
+func attributeSize*(mesh: Mesh, attribute: string): int =
   if mesh.vertexData.contains(attribute):
     mesh.vertexData[attribute].size
   elif mesh.instanceData.contains(attribute):
@@ -180,35 +192,32 @@
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
 
-func indexSize*(mesh: Mesh): uint32 =
+func indexSize*(mesh: Mesh): int =
   case mesh.indexType
-    of None: 0'u32
-    of Tiny: uint32(mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0)))
-    of Small: uint32(mesh.smallIndices.len * sizeof(get(genericParams(typeof(mesh.smallIndices)), 0)))
-    of Big: uint32(mesh.bigIndices.len * sizeof(get(genericParams(typeof(mesh.bigIndices)), 0)))
+    of None: 0
+    of Tiny: mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0))
+    of Small: mesh.smallIndices.len * sizeof(get(genericParams(typeof(mesh.smallIndices)), 0))
+    of Big: mesh.bigIndices.len * sizeof(get(genericParams(typeof(mesh.bigIndices)), 0))
 
-func rawData[T: seq](value: var T): (pointer, uint32) =
-  (pointer(addr(value[0])), uint32(sizeof(get(genericParams(typeof(value)), 0)) * value.len))
+func rawData[T: seq](value: T): (pointer, int) =
+  (pointer(addr(value[0])), sizeof(get(genericParams(typeof(value)), 0)) * value.len)
 
-func getRawIndexData*(mesh: Mesh): (pointer, uint32) =
+func getRawIndexData*(mesh: Mesh): (pointer, int) =
   case mesh.indexType:
     of None: raise newException(Exception, "Trying to get index data for non-indexed mesh")
     of Tiny: rawData(mesh.tinyIndices)
     of Small: rawData(mesh.smallIndices)
     of Big: rawData(mesh.bigIndices)
 
-func hasAttribute*(mesh: Mesh, attribute: string): bool =
-  mesh.vertexData.contains(attribute) or mesh.instanceData.contains(attribute)
-
-func getRawData*(mesh: Mesh, attribute: string): (pointer, uint32) =
+func getRawData*(mesh: Mesh, attribute: string): (pointer, int) =
   if mesh.vertexData.contains(attribute):
     mesh.vertexData[attribute].getRawData()
   elif mesh.instanceData.contains(attribute):
     mesh.instanceData[attribute].getRawData()
   else:
-    (nil, 0)
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
 
-proc getMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string): ref seq[T] =
+proc getAttribute*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string): ref seq[T] =
   if mesh.vertexData.contains(attribute):
     getValues[T](mesh.vertexData[attribute])
   elif mesh.instanceData.contains(attribute):
@@ -216,36 +225,18 @@
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
 
-proc initAttribute*(mesh: Mesh, attribute: ShaderAttribute) =
-  if attribute.perInstance:
-    mesh.instanceData[attribute.name] = newDataList(thetype=attribute.thetype)
-    mesh.instanceData[attribute.name].initData(mesh.instanceCount)
-  else:
-    mesh.vertexData[attribute.name] = newDataList(thetype=attribute.thetype)
-    mesh.vertexData[attribute.name].initData(mesh.vertexCount)
-
-proc initAttribute*[T](mesh: Mesh, attribute: ShaderAttribute, value: T) =
-  if attribute.perInstance:
-    mesh.instanceData[attribute.name] = newDataList(thetype=attribute.thetype)
-    mesh.instanceData[attribute.name].initData(mesh.instanceCount)
-    mesh.instanceData[attribute.name].setValues(newSeqWith(int(mesh.instanceCount), value))
-  else:
-    mesh.vertexData[attribute.name] = newDataList(thetype=attribute.thetype)
-    mesh.vertexData[attribute.name].initData(mesh.vertexCount)
-    mesh.instanceData[attribute.name].setValues(newSeqWith(int(mesh.vertexCount), value))
-
-proc updateAttributeData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) =
+proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) =
   if mesh.vertexData.contains(attribute):
-    assert data.len < mesh.vertexData[attribute].len
+    assert data.len == mesh.vertexCount
     setValues(mesh.vertexData[attribute], data)
   elif mesh.instanceData.contains(attribute):
-    assert data.len < mesh.instanceData[attribute].len
+    assert data.len == mesh.instanceCount
     setValues(mesh.instanceData[attribute], data)
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
   mesh.dirtyAttributes.add attribute
 
-proc updateAttributeData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, i: uint32, value: T) =
+proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, i: int, value: T) =
   if mesh.vertexData.contains(attribute):
     assert i < mesh.vertexData[attribute].len
     setValue(mesh.vertexData[attribute], i, value)
@@ -256,17 +247,7 @@
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
   mesh.dirtyAttributes.add attribute
 
-proc updateInstanceData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) =
-  assert uint32(data.len) == mesh.instanceCount
-  if mesh.vertexData.contains(attribute):
-    setValues(mesh.vertexData[attribute], data)
-  elif mesh.instanceData.contains(attribute):
-    setValues(mesh.instanceData[attribute], data)
-  else:
-    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
-  mesh.dirtyAttributes.add attribute
-
-proc appendAttributeData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) =
+proc appendAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) =
   if mesh.vertexData.contains(attribute):
     appendValues(mesh.vertexData[attribute], data)
   elif mesh.instanceData.contains(attribute):
@@ -276,7 +257,7 @@
   mesh.dirtyAttributes.add attribute
 
 # currently only used for loading from files, shouls
-proc appendAttributeData*(mesh: Mesh, attribute: string, data: DataList) =
+proc appendAttributeData*(mesh: var Mesh, attribute: string, data: DataList) =
   if mesh.vertexData.contains(attribute):
     assert data.thetype == mesh.vertexData[attribute].thetype
     appendValues(mesh.vertexData[attribute], data)
@@ -287,17 +268,23 @@
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
   mesh.dirtyAttributes.add attribute
 
-proc appendIndicesData*(mesh: Mesh, v1, v2, v3: uint32) =
+proc appendIndicesData*(mesh: var Mesh, v1, v2, v3: int) =
   case mesh.indexType
   of None: raise newException(Exception, "Mesh does not support indexed data")
   of Tiny: mesh.tinyIndices.add([uint8(v1), uint8(v2), uint8(v3)])
   of Small: mesh.smallIndices.add([uint16(v1), uint16(v2), uint16(v3)])
-  of Big: mesh.bigIndices.add([v1, v2, v3])
+  of Big: mesh.bigIndices.add([uint32(v1), uint32(v2), uint32(v3)])
 
-func hasDataChanged*(mesh: Mesh, attribute: string): bool =
-  attribute in mesh.dirtyAttributes
+proc updateInstanceTransforms*(mesh: var Mesh, attribute: string) =
+  let currentTransforms = mesh.instanceTransforms.mapIt(mesh.transform * it)
+  if currentTransforms != mesh.transformCache:
+    mesh.updateAttributeData(attribute, currentTransforms)
+    mesh.transformCache = currentTransforms
 
-proc clearDataChanged*(mesh: Mesh) =
+func dirtyAttributes*(mesh: Mesh): seq[string] =
+  mesh.dirtyAttributes
+
+proc clearDirtyAttributes*(mesh: var Mesh) =
   mesh.dirtyAttributes = @[]
 
 proc transform*[T: GPUType](mesh: Mesh, attribute: string, transform: Mat4) =
@@ -313,10 +300,9 @@
 func rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh =
   result = Mesh(
     vertexCount: 4,
-    instanceCount: 1,
+    instanceTransforms: @[Unit4F32],
     indexType: Small,
     smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]],
-    instanceTransforms: @[Unit4F32]
   )
 
   let
@@ -325,24 +311,23 @@
     pos = @[newVec3f(-half_w, -half_h), newVec3f( half_w, -half_h), newVec3f( half_w,  half_h), newVec3f(-half_w,  half_h)]
     c = hexToColorAlpha(color)
 
-  setMeshData(result, "position", pos)
-  setMeshData(result, "color", @[c, c, c, c])
-  setMeshData(result, "uv", @[newVec2f(0, 0), newVec2f(1, 0), newVec2f(1, 1), newVec2f(0, 1)])
-  setInstanceData(result, "transform", @[Unit4F32])
+  result.initVertexAttribute("position", pos)
+  result.initVertexAttribute("color", @[c, c, c, c])
+  result.initVertexAttribute("uv", @[newVec2f(0, 0), newVec2f(1, 0), newVec2f(1, 1), newVec2f(0, 1)])
 
 func tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh =
-  result = Mesh(vertexCount: 3, instanceCount: 1, instanceTransforms: @[Unit4F32])
+  result = Mesh(vertexCount: 3, instanceTransforms: @[Unit4F32])
   let
     half_w = width / 2
     half_h = height / 2
     colorVec = hexToColorAlpha(color)
-  setMeshData(result, "position", @[newVec3f(0, -half_h), newVec3f( half_w, half_h), newVec3f(-half_w,  half_h)])
-  setMeshData(result, "color", @[colorVec, colorVec, colorVec])
-  setInstanceData(result, "transform", @[Unit4F32])
+
+  result.initVertexAttribute("position", @[newVec3f(0, -half_h), newVec3f( half_w, half_h), newVec3f(-half_w,  half_h)])
+  result.initVertexAttribute("color", @[colorVec, colorVec, colorVec])
 
-func circle*(width=1'f32, height=1'f32, nSegments=12'u16, color="ffffffff"): Mesh =
+func circle*(width=1'f32, height=1'f32, nSegments=12, color="ffffffff"): Mesh =
   assert nSegments >= 3
-  result = Mesh(vertexCount: 3 + nSegments, instanceCount: 1, indexType: Small, instanceTransforms: @[Unit4F32])
+  result = Mesh(vertexCount: 3 + nSegments, instanceTransforms: @[Unit4F32], indexType: Small)
 
   let
     half_w = width / 2
@@ -352,36 +337,14 @@
   var
     pos = @[newVec3f(0, 0), newVec3f(0, half_h)]
     col = @[c, c]
-  for i in 0'u16 .. nSegments:
+  for i in 0 .. nSegments:
     pos.add newVec3f(cos(float32(i) * step) * half_w, sin(float32(i) * step) * half_h)
     col.add c
-    result.smallIndices.add [0'u16, i + 1, i + 2]
-
-  setMeshData(result, "position", pos)
-  setMeshData(result, "color", col)
-  setInstanceData(result, "transform", @[Unit4F32])
-
-proc areInstanceTransformsDirty*(mesh: Mesh): bool =
-  result = mesh.dirtyInstanceTransforms
-  mesh.dirtyInstanceTransforms = false
+    result.smallIndices.add [uint16(0), uint16(i + 1), uint16(i + 2)]
 
-proc setInstanceTransform*(mesh: Mesh, i: uint32, mat: Mat4) =
-  assert 0 <= i and i < mesh.instanceCount
-  mesh.instanceTransforms[i] = mat
-  mesh.dirtyInstanceTransforms = true
-
-proc setInstanceTransforms*(mesh: Mesh, mat: seq[Mat4]) =
-  mesh.instanceTransforms = mat
-  mesh.dirtyInstanceTransforms = true
-
-func getInstanceTransform*(mesh: Mesh, i: uint32): Mat4 =
-  assert 0 <= i and i < mesh.instanceCount
-  mesh.instanceTransforms[i]
-
-func getInstanceTransforms*(mesh: Mesh): seq[Mat4] =
-  mesh.instanceTransforms
+  result.initVertexAttribute("position", pos)
+  result.initVertexAttribute("color", col)
 
 func getCollisionPoints*(mesh: Mesh, positionAttribute="position"): seq[Vec3f] =
-  for p in getMeshData[Vec3f](mesh, positionAttribute)[]:
-    result.add mesh.entity.getModelTransform() * p
-
+  for p in getAttribute[Vec3f](mesh, positionAttribute)[]:
+    result.add p
--- a/src/semicongine/renderer.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/renderer.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -1,8 +1,9 @@
 import std/options
 import std/tables
 import std/strformat
+import std/sequtils
 import std/logging
-import std/sequtils
+import std/enumerate
 
 import ./core
 import ./vulkan/buffer
@@ -19,21 +20,21 @@
 import ./scene
 import ./mesh
 
+const MATERIALINDEXATTRIBUTE = "materialIndex"
+const TRANSFORMATTRIBUTE = "transform"
+
 type
   SceneData = object
-    drawables*: OrderedTable[Mesh, Drawable]
+    drawables*: seq[tuple[drawable: Drawable, meshIndex: int]]
     vertexBuffers*: Table[MemoryPerformanceHint, Buffer]
     indexBuffer*: Buffer
     uniformBuffers*: Table[VkPipeline, seq[Buffer]] # one per frame-in-flight
     textures*: Table[string, seq[VulkanTexture]] # per frame-in-flight
     attributeLocation*: Table[string, MemoryPerformanceHint]
     attributeBindingNumber*: Table[string, int]
-    transformAttribute: string # name of attribute that is used for per-instance mesh transformation
-    materialIndexAttribute: string # name of attribute that is used for material selection
-    materials: seq[Material]
-    entityTransformationCache: Table[Mesh, Mat4] # remembers last transformation, avoid to send GPU-updates if no changes
     descriptorPools*: Table[VkPipeline, DescriptorPool]
     descriptorSets*: Table[VkPipeline, seq[DescriptorSet]]
+    materials: seq[Material]
   Renderer* = object
     device: Device
     surfaceFormat: VkSurfaceFormatKHR
@@ -42,11 +43,8 @@
     scenedata: Table[Scene, SceneData]
     emptyTexture: VulkanTexture
 
-func usesMaterialType(scenedata: SceneData, materialType: string): bool =
-  for drawable in scenedata.drawables.values:
-    if drawable.mesh.material.materialType == materialType:
-      return true
-  return false
+func usesMaterialType(scene: Scene, materialType: string): bool =
+  return scene.meshes.anyIt(it.material.materialType == materialType)
 
 proc initRenderer*(device: Device, shaders: Table[string, ShaderConfiguration], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): Renderer =
   assert device.vk.valid
@@ -62,47 +60,34 @@
   result.swapchain = swapchain.get()
   result.emptyTexture = device.uploadTexture(EMPTYTEXTURE)
 
-func inputs(renderer: Renderer): seq[ShaderAttribute] =
+func inputs(renderer: Renderer, scene: Scene): seq[ShaderAttribute] =
+  var found: Table[string, ShaderAttribute]
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for pipeline in renderer.renderPass.subpasses[i].pipelines.values:
-      result.add pipeline.inputs
+    for materialType, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
+      if scene.usesMaterialType(materialType):
+        for input in pipeline.inputs:
+          if found.contains(input.name):
+            assert input == found[input.name]
+          else:
+            result.add input
+            found[input.name] = input
 
-func samplers(renderer: Renderer): seq[ShaderAttribute] =
+func samplers(renderer: Renderer, scene: Scene): seq[ShaderAttribute] =
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for pipeline in renderer.renderPass.subpasses[i].pipelines.values:
-      result.add pipeline.samplers
+    for materialType, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
+      if scene.usesMaterialType(materialType):
+        result.add pipeline.samplers
 
-proc setupDrawableBuffers*(renderer: var Renderer, scene: Scene) =
+proc setupDrawableBuffers*(renderer: var Renderer, scene: var Scene) =
   assert not (scene in renderer.scenedata)
   const VERTEX_ATTRIB_ALIGNMENT = 4 # used for buffer alignment
 
   let
-    inputs = renderer.inputs
-    samplers = renderer.samplers
+    inputs = renderer.inputs(scene)
+    samplers = renderer.samplers(scene)
   var scenedata = SceneData()
 
-  # if mesh transformation are handled through the scenegraph-transformation, set it up here
-  if scene.transformAttribute != "":
-    var hasTransformAttribute = false
-    for input in inputs:
-      if input.name == scene.transformAttribute:
-        assert input.perInstance == true, $input
-        assert getDataType[Mat4]() == input.thetype, $input
-        hasTransformAttribute = true
-    assert hasTransformAttribute
-    scenedata.transformAttribute = scene.transformAttribute
-
-  # check if we have support for material indices, if required
-  if scene.materialIndexAttribute != "":
-    var hasMaterialIndexAttribute = false
-    for input in inputs:
-      if input.name == scene.materialIndexAttribute:
-        assert getDataType[uint16]() == input.thetype
-        hasMaterialIndexAttribute = true
-    assert hasMaterialIndexAttribute
-    scenedata.materialIndexAttribute = scene.materialIndexAttribute
-
-  for mesh in allComponentsOfType[Mesh](scene.root):
+  for mesh in scene.meshes:
     if mesh.material != nil and not scenedata.materials.contains(mesh.material):
       scenedata.materials.add mesh.material
       for textureName, texture in mesh.material.textures.pairs:
@@ -111,30 +96,33 @@
         scenedata.textures[textureName].add renderer.device.uploadTexture(texture)
 
   # find all meshes, populate missing attribute values for shader
-  var allMeshes: seq[Mesh]
-  for mesh in allComponentsOfType[Mesh](scene.root):
-    allMeshes.add mesh
+  for mesh in scene.meshes.mitems:
     for inputAttr in inputs:
-      if scenedata.materialIndexAttribute != "" and inputAttr.name == scenedata.materialIndexAttribute:
-        assert mesh.material != nil, "Missing material specification for mesh. Either set the 'materials' attribute or pass the argument 'materialIndexAttribute=\"\"' when calling 'addScene'"
+      if inputAttr.name == TRANSFORMATTRIBUTE:
+        mesh.initInstanceAttribute(inputAttr.name, inputAttr.thetype)
+      elif inputAttr.name == MATERIALINDEXATTRIBUTE:
+        assert mesh.material != nil, "Missing material specification for mesh. Set material attribute on mesh"
         let matIndex = scenedata.materials.find(mesh.material)
         if matIndex < 0:
           raise newException(Exception, &"Required material '{mesh.material}' not available in scene (available are: {scenedata.materials})")
-        mesh.initAttribute(inputAttr, uint16(matIndex))
-      elif not mesh.hasAttribute(inputAttr.name):
+        mesh.initInstanceAttribute(inputAttr.name, uint16(matIndex))
+      elif not mesh.attributes.contains(inputAttr.name):
         warn(&"Mesh is missing data for shader attribute {inputAttr.name}, auto-filling with empty values")
-        mesh.initAttribute(inputAttr)
+        if inputAttr.perInstance:
+          mesh.initInstanceAttribute(inputAttr.name, inputAttr.thetype)
+        else:
+          mesh.initVertexAttribute(inputAttr.name, inputAttr.thetype)
       assert mesh.attributeType(inputAttr.name) == inputAttr.thetype, &"mesh attribute {inputAttr.name} has type {mesh.attributeType(inputAttr.name)} but shader expects {inputAttr.thetype}"
   
   # create index buffer if necessary
-  var indicesBufferSize = 0'u64
-  for mesh in allMeshes:
+  var indicesBufferSize = 0
+  for mesh in scene.meshes:
     if mesh.indexType != MeshIndexType.None:
       let indexAlignment = case mesh.indexType
-        of MeshIndexType.None: 0'u64
-        of Tiny: 1'u64
-        of Small: 2'u64
-        of Big: 4'u64
+        of MeshIndexType.None: 0
+        of Tiny: 1
+        of Small: 2
+        of Big: 4
       # index value alignment required by Vulkan
       if indicesBufferSize mod indexAlignment != 0:
         indicesBufferSize += indexAlignment - (indicesBufferSize mod indexAlignment)
@@ -150,8 +138,8 @@
   # create vertex buffers and calculcate offsets
   # trying to use one buffer per memory type
   var
-    perLocationOffsets: Table[MemoryPerformanceHint, uint64]
-    perLocationSizes: Table[MemoryPerformanceHint, uint64]
+    perLocationOffsets: Table[MemoryPerformanceHint, int]
+    perLocationSizes: Table[MemoryPerformanceHint, int]
     bindingNumber = 0
   for hint in MemoryPerformanceHint:
     perLocationOffsets[hint] = 0
@@ -161,7 +149,7 @@
     scenedata.attributeBindingNumber[attribute.name] = bindingNumber
     inc bindingNumber
     # setup one buffer per attribute-location-type
-    for mesh in allMeshes:
+    for mesh in scene.meshes:
       # align size to VERTEX_ATTRIB_ALIGNMENT bytes (the important thing is the correct alignment of the offsets, but
       # we need to expand the buffer size as well, therefore considering alignment already here as well
       if perLocationSizes[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0:
@@ -177,9 +165,9 @@
       )
 
   # fill vertex buffers
-  var indexBufferOffset = 0'u64
-  for mesh in allMeshes:
-    var offsets: seq[(string, MemoryPerformanceHint, uint64)]
+  var indexBufferOffset = 0
+  for (meshIndex, mesh) in enumerate(scene.meshes):
+    var offsets: seq[(string, MemoryPerformanceHint, int)]
     for attribute in inputs:
       offsets.add (attribute.name, attribute.memoryPerformanceHint, perLocationOffsets[attribute.memoryPerformanceHint])
       var (pdata, size) = mesh.getRawData(attribute.name)
@@ -191,7 +179,6 @@
 
     let indexed = mesh.indexType != MeshIndexType.None
     var drawable = Drawable(
-      mesh: mesh,
       elementCount: if indexed: mesh.indicesCount else: mesh.vertexCount,
       bufferOffsets: offsets,
       instanceCount: mesh.instanceCount,
@@ -199,10 +186,10 @@
     )
     if indexed:
       let indexAlignment = case mesh.indexType
-        of MeshIndexType.None: 0'u64
-        of Tiny: 1'u64
-        of Small: 2'u64
-        of Big: 4'u64
+        of MeshIndexType.None: 0
+        of Tiny: 1
+        of Small: 2
+        of Big: 4
       # index value alignment required by Vulkan
       if indexBufferOffset mod indexAlignment != 0:
         indexBufferOffset += indexAlignment - (indexBufferOffset mod indexAlignment)
@@ -211,95 +198,75 @@
       var (pdata, size) = mesh.getRawIndexData()
       scenedata.indexBuffer.setData(pdata, size, indexBufferOffset)
       indexBufferOffset += size
-    scenedata.drawables[mesh] = drawable
+    scenedata.drawables.add (drawable, meshIndex)
 
   # setup uniforms and samplers
   for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
-    for material, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs:
-      var uniformBufferSize = 0'u64
-      for uniform in pipeline.uniforms:
-        uniformBufferSize += uniform.size
-      if uniformBufferSize > 0:
-        scenedata.uniformBuffers[pipeline.vk] = newSeq[Buffer]()
+    for materialType, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs:
+      if scene.usesMaterialType(materialType):
+        var uniformBufferSize = 0
+        for uniform in pipeline.uniforms:
+          uniformBufferSize += uniform.size
+        if uniformBufferSize > 0:
+          scenedata.uniformBuffers[pipeline.vk] = newSeq[Buffer]()
+          for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
+            scenedata.uniformBuffers[pipeline.vk].add renderer.device.createBuffer(
+              size=uniformBufferSize,
+              usage=[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
+              requireMappable=true,
+              preferVRAM=true,
+            )
+            
+        var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, renderer.swapchain.inFlightFrames)]
+        if samplers.len > 0:
+          var samplercount = 0
+          for sampler in samplers:
+            samplercount += (if sampler.arrayCount == 0: 1 else: sampler.arrayCount)
+          poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, renderer.swapchain.inFlightFrames * samplercount * 2)
+      
+        scenedata.descriptorPools[pipeline.vk] = renderer.device.createDescriptorSetPool(poolsizes)
+    
+        scenedata.descriptorSets[pipeline.vk] = pipeline.setupDescriptors(
+          scenedata.descriptorPools[pipeline.vk],
+          scenedata.uniformBuffers.getOrDefault(pipeline.vk, @[]),
+          scenedata.textures,
+          inFlightFrames=renderer.swapchain.inFlightFrames,
+          emptyTexture=renderer.emptyTexture,
+        )
         for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
-          scenedata.uniformBuffers[pipeline.vk].add renderer.device.createBuffer(
-            size=uniformBufferSize,
-            usage=[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
-            requireMappable=true,
-            preferVRAM=true,
-          )
-          
-      var poolsizes = @[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, uint32(renderer.swapchain.inFlightFrames))]
-      if samplers.len > 0:
-        var samplercount = 0'u32
-        for sampler in samplers:
-          samplercount += (if sampler.arrayCount == 0: 1'u32 else: sampler.arrayCount)
-        poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, uint32(renderer.swapchain.inFlightFrames) * samplercount * 2)
-    
-      scenedata.descriptorPools[pipeline.vk] = renderer.device.createDescriptorSetPool(poolsizes)
-  
-      scenedata.descriptorSets[pipeline.vk] = pipeline.setupDescriptors(
-        scenedata.descriptorPools[pipeline.vk],
-        scenedata.uniformBuffers.getOrDefault(pipeline.vk, @[]),
-        scenedata.textures,
-        inFlightFrames=renderer.swapchain.inFlightFrames,
-        emptyTexture=renderer.emptyTexture,
-      )
-      for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
-        scenedata.descriptorSets[pipeline.vk][frame_i].writeDescriptorSet()
+          scenedata.descriptorSets[pipeline.vk][frame_i].writeDescriptorSet()
 
   renderer.scenedata[scene] = scenedata
 
-proc refreshMeshAttributeData(sceneData: var SceneData, mesh: Mesh, attribute: string) =
-  debug &"Refreshing data on mesh {mesh} for {attribute}"
+proc refreshMeshAttributeData(renderer: Renderer, scene: Scene, drawable: Drawable, meshIndex: int, attribute: string) =
+  debug &"Refreshing data on mesh {scene.meshes[meshIndex]} for {attribute}"
   # ignore attributes that are not used in this shader
-  if not (attribute in sceneData.attributeLocation):
+  if not (attribute in renderer.scenedata[scene].attributeLocation):
     return
-  var (pdata, size) = mesh.getRawData(attribute)
-  let memoryPerformanceHint = sceneData.attributeLocation[attribute]
-  let bindingNumber = sceneData.attributeBindingNumber[attribute]
-  sceneData.vertexBuffers[memoryPerformanceHint].setData(pdata, size, sceneData.drawables[mesh].bufferOffsets[bindingNumber][2])
+  var (pdata, size) = scene.meshes[meshIndex].getRawData(attribute)
+  let memoryPerformanceHint = renderer.scenedata[scene].attributeLocation[attribute]
+  let bindingNumber = renderer.scenedata[scene].attributeBindingNumber[attribute]
+  renderer.scenedata[scene].vertexBuffers[memoryPerformanceHint].setData(pdata, size, drawable.bufferOffsets[bindingNumber][2])
 
-proc updateMeshData*(renderer: var Renderer, scene: Scene) =
+proc updateMeshData*(renderer: var Renderer, scene: var Scene) =
   assert scene in renderer.scenedata
 
-  for mesh in allComponentsOfType[Mesh](scene.root):
-    # if mesh transformation attribute is enabled, update the model matrix
-    if renderer.scenedata[scene].transformAttribute != "":
-      let transform = mesh.entity.getModelTransform()
-      if not (mesh in renderer.scenedata[scene].entityTransformationCache) or renderer.scenedata[scene].entityTransformationCache[mesh] != transform or mesh.areInstanceTransformsDirty :
-        var updatedTransform = newSeq[Mat4](int(mesh.instanceCount))
-        for i in 0 ..< mesh.instanceCount:
-          updatedTransform[i] = transform * mesh.getInstanceTransform(i)
-        debug &"Update mesh transformation"
-        mesh.updateInstanceData(renderer.scenedata[scene].transformAttribute, updatedTransform)
-        renderer.scenedata[scene].entityTransformationCache[mesh] = transform
-
-    # update any changed mesh attributes
-    for attribute in mesh.vertexAttributes:
-      if mesh.hasDataChanged(attribute):
-        renderer.scenedata[scene].refreshMeshAttributeData(mesh, attribute)
-        debug &"Update mesh vertex attribute {attribute}"
-    for attribute in mesh.instanceAttributes:
-      if mesh.hasDataChanged(attribute):
-        renderer.scenedata[scene].refreshMeshAttributeData(mesh, attribute)
-        debug &"Update mesh instance attribute {attribute}"
-    var m = mesh
-    m.clearDataChanged()
-
-proc updateAnimations*(renderer: var Renderer, scene: var Scene, dt: float32) =
-  for animation in allComponentsOfType[EntityAnimation](scene.root):
-    debug &"Update animation {animation}"
-    animation.update(dt)
+  for (drawable, meshIndex) in renderer.scenedata[scene].drawables.mitems:
+    if scene.meshes[meshIndex].attributes.contains(TRANSFORMATTRIBUTE):
+      scene.meshes[meshIndex].updateInstanceTransforms(TRANSFORMATTRIBUTE)
+    for attribute in scene.meshes[meshIndex].dirtyAttributes:
+      renderer.refreshMeshAttributeData(scene, drawable, meshIndex, attribute)
+      debug &"Update mesh attribute {attribute}"
+    scene.meshes[meshIndex].clearDirtyAttributes()
 
 proc updateUniformData*(renderer: var Renderer, scene: var Scene) =
   assert scene in renderer.scenedata
 
   for i in 0 ..< renderer.renderPass.subpasses.len:
     for materialType, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
-      if renderer.scenedata[scene].usesMaterialType(materialType) and renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0:
+      if scene.usesMaterialType(materialType) and renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0:
         assert renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].vk.valid
-        var offset = 0'u64
+        var offset = 0
         for uniform in pipeline.uniforms:
           if not scene.shaderGlobals.hasKey(uniform.name):
             raise newException(Exception, &"Uniform '{uniform.name}' not found in scene shaderGlobals")
@@ -310,7 +277,7 @@
           renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].setData(pdata, size, offset)
           offset += size
 
-proc render*(renderer: var Renderer, scene: var Scene) =
+proc render*(renderer: var Renderer, scene: Scene) =
   assert scene in renderer.scenedata
 
   var
@@ -336,13 +303,13 @@
 
   for i in 0 ..< renderer.renderPass.subpasses.len:
     for materialType, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
-      if renderer.scenedata[scene].usesMaterialType(materialType):
+      if scene.usesMaterialType(materialType):
         debug &"Start pipeline for {materialType}"
         commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.vk)
         commandBuffer.vkCmdBindDescriptorSets(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[pipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil)
 
-        for drawable in renderer.scenedata[scene].drawables.values:
-          if drawable.mesh.material != nil and drawable.mesh.material.materialType == materialType:
+        for (drawable, meshIndex) in renderer.scenedata[scene].drawables:
+          if scene.meshes[meshIndex].material != nil and scene.meshes[meshIndex].material.materialType == materialType:
             drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer)
 
     if i < renderer.renderPass.subpasses.len - 1:
--- a/src/semicongine/resources.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/resources.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -9,8 +9,7 @@
 import ./resources/audio
 import ./resources/mesh
 import ./resources/font
-
-from ./scene import Entity, Scene
+import ./mesh
 
 export image
 export audio
@@ -134,15 +133,10 @@
   let defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=+[{]};:,<.>/? ".toRunes()
   loadResource_intern(path).readTrueType(name, defaultCharset, color, resolution)
 
-proc loadMesh*(path: string): Entity =
-  loadResource_intern(path).readglTF()[0].root
+proc loadFirstMesh*(path: string): Mesh =
+  loadResource_intern(path).readglTF()[0].children[0].mesh
 
-proc loadScene*(path: string, name=""): Scene =
-  result = loadResource_intern(path).readglTF()[0]
-  if name != "":
-    result.name = name
-
-proc loadScenes*(path: string): seq[Scene] =
+proc loadMeshes*(path: string): seq[MeshTree] =
   loadResource_intern(path).readglTF()
 
 proc modList*(): seq[string] =
--- a/src/semicongine/resources/font.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/resources/font.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -33,7 +33,7 @@
   result.resolution = resolution
   result.fontscale = float32(stbtt_ScaleForPixelHeight(addr fontinfo, cfloat(resolution)))
   var
-    offsetX: uint32
+    offsetX = 0
     bitmaps: Table[Rune, (cstring, cint, cint)]
     topOffsets: Table[Rune, int]
   for codePoint in codePoints:
@@ -49,13 +49,13 @@
       )
     bitmaps[codePoint] = (data, width, height)
     result.maxHeight = max(result.maxHeight, int(height))
-    offsetX += uint32(width + 1)
+    offsetX += width + 1
     topOffsets[codePoint] = offY
 
   result.name = name
   result.fontAtlas = Texture(
     name: name & "_texture",
-    image: newImage(offsetX, uint32(result.maxHeight + 1)),
+    image: newImage(offsetX, result.maxHeight + 1),
     sampler: FONTSAMPLER_SOFT
   )
 
@@ -63,8 +63,8 @@
   for codePoint in codePoints:
     let
       bitmap = bitmaps[codePoint][0]
-      width = uint32(bitmaps[codePoint][1])
-      height = uint32(bitmaps[codePoint][2])
+      width = bitmaps[codePoint][1]
+      height = bitmaps[codePoint][2]
 
     # bitmap data
     for y in 0 ..< height:
--- a/src/semicongine/resources/image.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/resources/image.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -101,7 +101,7 @@
       data[row_mult * dibHeader.width + col]= pixel
     stream.setPosition(stream.getPosition() + padding)
 
-  result = newImage(width=uint32(dibHeader.width), height=uint32(abs(dibHeader.height)), imagedata=data)
+  result = newImage(width=dibHeader.width, height=abs(dibHeader.height), imagedata=data)
 
 {.compile: currentSourcePath.parentDir() & "/lodepng.c" .}
 
@@ -122,4 +122,4 @@
 
   free(data)
 
-  result = newImage(width=w, height=h, imagedata=imagedata)
+  result = newImage(width=int(w), height=int(h), imagedata=imagedata)
--- a/src/semicongine/resources/mesh.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/resources/mesh.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -6,7 +6,6 @@
 import std/strformat
 import std/streams
 
-import ../scene
 import ../mesh
 import ../core
 
@@ -20,6 +19,9 @@
   glTFData = object
     structuredContent: JsonNode
     binaryBufferData: seq[uint8]
+  MeshTree* = ref object
+    mesh*: Mesh
+    children*: seq[MeshTree]
 
 const
   JSON_CHUNK = 0x4E4F534A
@@ -95,7 +97,7 @@
 
 proc getAccessorData(root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): DataList =
   result = newDataList(thetype=accessor.getGPUType())
-  result.initData(uint32(accessor["count"].getInt()))
+  result.initData(accessor["count"].getInt())
 
   let bufferView = root["bufferViews"][accessor["bufferView"].getInt()]
   assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported"
@@ -113,7 +115,7 @@
     # we don't support stride, have to convert stuff here... does this even work?
     for i in 0 ..< int(result.len):
       copyMem(dstPointer, addr mainBuffer[bufferOffset + i * bufferView["byteStride"].getInt()], int(result.thetype.size))
-      dstPointer = cast[pointer](cast[uint64](dstPointer) + result.thetype.size)
+      dstPointer = cast[pointer](cast[int](dstPointer) + result.thetype.size)
   else:
     copyMem(dstPointer, addr mainBuffer[bufferOffset], length)
 
@@ -212,7 +214,7 @@
   if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4:
     raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode")
 
-  var vertexCount = 0'u32
+  var vertexCount = 0
   for attribute, accessor in primitiveNode["attributes"].pairs:
     let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer)
     mesh.appendAttributeData(attribute.toLowerAscii, data)
@@ -221,7 +223,7 @@
   var materialId = 0'u16
   if primitiveNode.hasKey("material"):
     materialId = uint16(primitiveNode["material"].getInt())
-  mesh.appendAttributeData("materialIndex", newSeqWith[uint8](int(vertexCount), materialId))
+  mesh.appendAttributeData("materialIndex", newSeqWith[uint8](vertexCount, materialId))
   let material = loadMaterial(root, root["materials"][int(materialId)], mainBuffer, materialId)
   # if mesh.material != nil and mesh.material[] != material[]:
     # raise newException(Exception, &"Only one material per mesh supported at the moment")
@@ -231,18 +233,18 @@
     assert mesh.indexType != None
     let data = root.getAccessorData(root["accessors"][primitiveNode["indices"].getInt()], mainBuffer)
     let baseIndex = mesh.indicesCount
-    var tri: seq[uint32]
+    var tri: seq[int]
     case data.thetype
       of UInt16:
         for entry in getValues[uint16](data)[]:
-          tri.add uint32(entry) + baseIndex
+          tri.add int(entry) + baseIndex
           if tri.len == 3:
             # FYI gltf uses counter-clockwise indexing
             mesh.appendIndicesData(tri[0], tri[2], tri[1])
             tri.setLen(0)
       of UInt32:
         for entry in getValues[uint32](data)[]:
-          tri.add uint32(entry)
+          tri.add int(entry)
           if tri.len == 3:
             # FYI gltf uses counter-clockwise indexing
             mesh.appendIndicesData(tri[0], tri[2], tri[1])
@@ -265,7 +267,7 @@
     else:
       indexType = Big
 
-  result = Mesh(instanceCount: 1, instanceTransforms: newSeqWith(1, Unit4F32), indexType: indexType)
+  result = Mesh(instanceTransforms: @[Unit4F32], indexType: indexType)
 
   # check we have the same attributes for all primitives
   let attributes = meshNode["primitives"][0]["attributes"].keys.toSeq
@@ -274,37 +276,34 @@
 
   # prepare mesh attributes
   for attribute, accessor in meshNode["primitives"][0]["attributes"].pairs:
-    result.setMeshData(attribute.toLowerAscii, newDataList(thetype=root["accessors"][accessor.getInt()].getGPUType()))
-  result.setMeshData("materialIndex", newDataList(theType=UInt16))
+    result.initVertexAttribute(attribute.toLowerAscii, root["accessors"][accessor.getInt()].getGPUType())
+  result.initInstanceAttribute("materialIndex", 0'u16)
 
   # add all mesh data
   for primitive in meshNode["primitives"]:
     result.addPrimitive(root, primitive, mainBuffer)
 
-  setInstanceData(result, "transform", newSeqWith(int(result.instanceCount), Unit4F32))
-
-proc loadNode(root: JsonNode, node: JsonNode, mainBuffer: var seq[uint8]): Entity =
-  var name = "<Unknown>"
-  if node.hasKey("name"):
-    name = node["name"].getStr()
-  result = newEntity(name)
+proc loadNode(root: JsonNode, node: JsonNode, mainBuffer: var seq[uint8]): MeshTree =
+  # mesh
+  if node.hasKey("mesh"):
+    result.mesh = loadMesh(root, root["meshes"][node["mesh"].getInt()], mainBuffer)
 
   # transformation
   if node.hasKey("matrix"):
     var mat: Mat4
     for i in 0 ..< node["matrix"].len:
       mat[i] = node["matrix"][i].getFloat()
-    result.transform = mat
+    result.mesh.transform = mat
   else:
     var (t, r, s) = (Unit4F32, Unit4F32, Unit4F32)
     if node.hasKey("translation"):
-      t = translate3d(
+      t = translate(
         float32(node["translation"][0].getFloat()),
         float32(node["translation"][1].getFloat()),
         float32(node["translation"][2].getFloat())
       )
     if node.hasKey("rotation"):
-      t = rotate3d(
+      t = rotate(
         float32(node["rotation"][3].getFloat()),
         newVec3f(
           float32(node["rotation"][0].getFloat()),
@@ -313,32 +312,25 @@
         )
       )
     if node.hasKey("scale"):
-      t = scale3d(
+      t = scale(
         float32(node["scale"][0].getFloat()),
         float32(node["scale"][1].getFloat()),
         float32(node["scale"][2].getFloat())
       )
-    result.transform = t * r * s
+    result.mesh.transform = t * r * s
 
   # children
   if node.hasKey("children"):
     for childNode in node["children"]:
-      result.add loadNode(root, root["nodes"][childNode.getInt()], mainBuffer)
-
-  # mesh
-  if node.hasKey("mesh"):
-    result["mesh"] = loadMesh(root, root["meshes"][node["mesh"].getInt()], mainBuffer)
+      result.children.add loadNode(root, root["nodes"][childNode.getInt()], mainBuffer)
 
-proc loadScene(root: JsonNode, scenenode: JsonNode, mainBuffer: var seq[uint8]): Scene =
-  var rootEntity = newEntity("<root>")
+proc loadMeshTree(root: JsonNode, scenenode: JsonNode, mainBuffer: var seq[uint8]): MeshTree =
+  result = MeshTree()
   for nodeId in scenenode["nodes"]:
-    var node = loadNode(root, root["nodes"][nodeId.getInt()], mainBuffer)
-    rootEntity.add node
-
-  newScene(scenenode["name"].getStr(), rootEntity)
+    result.children.add loadNode(root, root["nodes"][nodeId.getInt()], mainBuffer)
 
 
-proc readglTF*(stream: Stream): seq[Scene] =
+proc readglTF*(stream: Stream): seq[MeshTree] =
   var
     header: glTFHeader
     data: glTFData
@@ -368,4 +360,4 @@
   debug "Loading mesh: ", data.structuredContent.pretty
 
   for scenedata in data.structuredContent["scenes"]:
-    result.add data.structuredContent.loadScene(scenedata, data.binaryBufferData)
+    result.add data.structuredContent.loadMeshTree(scenedata, data.binaryBufferData)
--- a/src/semicongine/scene.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/scene.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -6,76 +6,15 @@
 
 import ./core
 import ./animation
+import ./mesh
 
 type
   Scene* = object
     name*: string
-    root*: Entity
     shaderGlobals*: Table[string, DataList]
     transformAttribute*: string = "transform"
     materialIndexAttribute*: string = "materialIndex"
-
-  Component* = ref object of RootObj
-    entity*: Entity
-
-  Entity* = ref object of RootObj
-    name*: string
-    internal_transform: Mat4 # todo: cache transform + only update VBO when transform changed
-    parent: Entity
-    children: seq[Entity]
-    components: Table[string, Component]
-
-  EntityAnimation* = ref object of Component
-    player: AnimationPlayer[Mat4]
-
-func newEntityAnimation*(animation: Animation[Mat4]): EntityAnimation =
-  result = EntityAnimation(player: newAnimator(animation))
-  result.player.currentValue = Unit4
-
-func setAnimation*(entityAnimation: EntityAnimation, animation: Animation[Mat4]) =
-  entityAnimation.player.animation = animation
-  entityAnimation.player.resetPlayer()
-
-func start*(animation: EntityAnimation) =
-  animation.player.start()
-
-func stop*(animation: EntityAnimation) =
-  animation.player.stop()
-
-func reset*(animation: EntityAnimation) =
-  animation.player.stop()
-  animation.player.resetPlayer()
-
-func playing*(animation: EntityAnimation): bool =
-  animation.player.playing
-
-func update*(animation: EntityAnimation, dt: float32) =
-  animation.player.advance(dt)
-
-func parent(entity: Entity): Entity =
-  entity.parent
-
-# TODO: this is wrong: transfrom setter + getter are not "symetric"
-func transform*(entity: Entity): Mat4 =
-  result = entity.internal_transform
-  for component in entity.components.mvalues:
-    if component of EntityAnimation and EntityAnimation(component).player.playing:
-      result = result * EntityAnimation(component).player.currentValue
-
-func `transform=`*(entity: Entity, value: Mat4) =
-  entity.internal_transform = value
-
-# TODO: position-setter
-func position*(entity: Entity): Vec3f =
-  return entity.transform.col(3)
-
-func originalTransform*(entity: Entity): Mat4 =
-  entity.internal_transform
-
-func getModelTransform*(entity: Entity): Mat4 =
-  result = entity.transform
-  if not entity.parent.isNil:
-    result = entity.transform * entity.parent.getModelTransform()
+    meshes*: seq[Mesh]
 
 func addShaderGlobal*[T](scene: var Scene, name: string, data: T) =
   scene.shaderGlobals[name] = newDataList(thetype=getDataType[T]())
@@ -100,137 +39,8 @@
 func appendShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) =
   appendValues[T](scene.shaderGlobals[name], value)
 
-func newScene*(name: string, root: Entity, transformAttribute="transform", materialIndexAttribute="materialIndex"): Scene =
-  Scene(name: name, root: root, transformAttribute: transformAttribute, materialIndexAttribute: materialIndexAttribute)
-
 func hash*(scene: Scene): Hash =
   hash(scene.name)
 
 func `==`*(a, b: Scene): bool =
   a.name == b.name
-
-func hash*(entity: Entity): Hash =
-  hash(cast[pointer](entity))
-
-func hash*(component: Component): Hash =
-  hash(cast[pointer](component))
-
-method `$`*(entity: Entity): string {.base.} = entity.name
-method `$`*(component: Component): string {.base.} =
-  "Unknown Component"
-method `$`*(animation: EntityAnimation): string =
-  &"Entity animation: {animation.player.animation}"
-
-proc add*(entity: Entity, child: Entity) =
-  child.parent = entity
-  entity.children.add child
-proc `[]=`*[T](entity: Entity, index: int, child: var T) =
-  child.parent = entity
-  entity.children[index] = child
-proc `[]`*(entity: Entity, index: int): Entity =
-  entity.children[index]
-
-proc `[]=`*[T](entity: Entity, name: string, component: T) =
-  component.entity = entity
-  entity.components[name] = component
-proc `[]`*[T](entity: Entity, name: string, component: T): T =
-  T(entity.components[name])
-
-func newEntity*(name: string, components: openArray[(string, Component)] = [], children: varargs[Entity]): Entity =
-  result = new Entity
-  for child in children:
-    result.add child
-  result.name = name
-  for (name, comp) in components:
-    result[name] = comp
-  if result.name == "":
-    result.name = &"Entity[{$(cast[uint](result))}]"
-  result.internal_transform = Unit4
-
-iterator allEntitiesOfType*[T: Entity](root: Entity): T =
-  var queue = @[root]
-  while queue.len > 0:
-    var entity = queue.pop
-    if entity of T:
-      yield T(entity)
-    for i in countdown(entity.children.len - 1, 0):
-      queue.add entity.children[i]
-
-iterator allComponentsOfType*[T: Component](root: Entity): var T =
-  var queue = @[root]
-  while queue.len > 0:
-    let entity = queue.pop
-    for component in entity.components.mvalues:
-      if component of T:
-        yield T(component)
-    for i in countdown(entity.children.len - 1, 0):
-      queue.add entity.children[i]
-
-func firstWithName*(root: Entity, name: string): Entity =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      if child.name == name:
-        return child
-      queue.add child
-
-func `[]`*(scene: Scene, name: string): Entity =
-  return scene.root.firstWithName(name)
-
-func firstComponentWithName*[T: Component](root: Entity, name: string): T =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      if child.name == name:
-        for component in child.components:
-          if component of T:
-            return T(component)
-      queue.add child
-
-func allWithName*(root: Entity, name: string): seq[Entity] =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      if child.name == name:
-        result.add child
-      queue.add child
-
-func allComponentsWithName*[T: Component](root: Entity, name: string): seq[T] =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      if child.name == name:
-        for component in child.components:
-          if component of T:
-            result.add T(component)
-      queue.add child
-
-iterator allEntities*(root: Entity): Entity =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      queue.add child
-    yield next
-
-proc prettyRecursive*(entity: Entity): seq[string] =
-  var compList: seq[string]
-  for (name, comp) in entity.components.pairs:
-    compList.add name & ": " & $comp
-
-  var trans = entity.transform.col(3)
-  var pos = entity.getModelTransform().col(3)
-  result.add "- " & $entity & " [" & $trans.x & ", " & $trans.y & ", " & $trans.z & "] ->  [" & $pos.x & ", " & $pos.y & ", " & $pos.z & "]"
-  if compList.len > 0:
-    result.add "  [" & compList.join(", ") & "]"
-
-  for child in entity.children:
-    for childLine in child.prettyRecursive:
-      result.add "  " & childLine
-
-proc pretty*(entity: Entity): string =
-  entity.prettyRecursive.join("\n")
--- a/src/semicongine/text.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/text.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -1,8 +1,6 @@
-import std/sequtils
 import std/tables
 import std/unicode
 
-import ./scene
 import ./mesh
 import ./core/vector
 import ./core/matrix
@@ -13,8 +11,8 @@
     Left
     Center
     Right
-  Textbox* = ref object of Entity
-    maxLen*: uint32
+  Textbox* = object
+    maxLen*: int
     text: seq[Rune]
     dirty: bool
     alignment*: TextAlignment
@@ -25,9 +23,9 @@
 
   # pre-calculate text-width
   var width = 0'f32
-  for i in 0 ..< min(uint32(textbox.text.len), textbox.maxLen):
+  for i in 0 ..< min(textbox.text.len, textbox.maxLen):
     width += textbox.font.glyphs[textbox.text[i]].advance
-    if i < uint32(textbox.text.len - 1):
+    if i < textbox.text.len - 1:
       width += textbox.font.kerning[(textbox.text[i], textbox.text[i + 1])]
 
   let centerX = width / 2
@@ -36,7 +34,7 @@
   var offsetX = 0'f32
   for i in 0 ..< textbox.maxLen:
     let vertexOffset = i * 4
-    if i < uint32(textbox.text.len):
+    if i < textbox.text.len:
       let
         glyph = textbox.font.glyphs[textbox.text[i]]
         left = offsetX + glyph.leftOffset
@@ -55,7 +53,7 @@
       textbox.mesh.updateAttributeData("uv", vertexOffset + 3, glyph.uvs[3])
 
       offsetX += glyph.advance
-      if i < uint32(textbox.text.len - 1):
+      if i < textbox.text.len - 1:
         offsetX += textbox.font.kerning[(textbox.text[i], textbox.text[i + 1])]
     else:
       textbox.mesh.updateAttributeData("position", vertexOffset + 0, newVec3f())
@@ -69,27 +67,23 @@
 
 proc `text=`*(textbox: var Textbox, text: seq[Rune]) =
   textbox.text = text
-  textbox.name = $text
   textbox.updateMesh()
 
-proc newTextbox*(maxLen: uint32, font: Font, text=toRunes("")): Textbox =
+proc newTextbox*(maxLen: int, font: Font, text=toRunes("")): Textbox =
   var
     positions = newSeq[Vec3f](int(maxLen * 4))
-    indices: seq[array[3, uint32]]
+    indices: seq[array[3, uint16]]
     uvs = newSeq[Vec2f](int(maxLen * 4))
   for i in 0 ..< maxLen:
     let offset = i * 4
-    indices.add [[offset + 0, offset + 1, offset + 2], [offset + 2, offset + 3, offset + 0]]
+    indices.add [
+      [uint16(offset + 0), uint16(offset + 1), uint16(offset + 2)],
+      [uint16(offset + 2), uint16(offset + 3), uint16(offset + 0)],
+    ]
 
   result = Textbox(maxLen: maxLen, text: text, font: font, dirty: true)
   result.mesh = newMesh(positions = positions, indices = indices, uvs = uvs)
-  result.mesh.setInstanceTransforms(@[Unit4F32])
-  result.name = $text
-  Entity(result).transform = Unit4F32
 
   # wrap the text mesh in a new entity to preserve the font-scaling
-  var box = newEntity("box", {"mesh": Component(result.mesh)})
-  # box.transform = scale3d(font.fontscale * 0.002, font.fontscale * 0.002)
-  box.transform = scale3d(1 / font.resolution, 1 / font.resolution)
-  result.add box
+  result.mesh.transform = scale(1 / font.resolution, 1 / font.resolution)
   result.updateMesh()
--- a/src/semicongine/vulkan/buffer.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/vulkan/buffer.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -14,7 +14,7 @@
   Buffer* = object
     device*: Device
     vk*: VkBuffer
-    size*: uint64
+    size*: int
     usage*: seq[VkBufferUsageFlagBits]
     case memoryAllocated*: bool
       of false: discard
@@ -67,7 +67,7 @@
 # (shardingMode = VK_SHARING_MODE_CONCURRENT not supported)
 proc createBuffer*(
   device: Device,
-  size: uint64,
+  size: int,
   usage: openArray[VkBufferUsageFlagBits],
   requireMappable: bool,
   preferVRAM: bool,
@@ -84,7 +84,7 @@
   var createInfo = VkBufferCreateInfo(
     sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
     flags: VkBufferCreateFlags(0),
-    size: size,
+    size: uint64(size),
     usage: toBits(result.usage),
     sharingMode: VK_SHARING_MODE_EXCLUSIVE,
   )
@@ -98,7 +98,7 @@
   result.allocateMemory(requireMappable=requireMappable, preferVRAM=preferVRAM, preferAutoFlush=preferAutoFlush)
 
 
-proc copy*(src, dst: Buffer, dstOffset=0'u64) =
+proc copy*(src, dst: Buffer, dstOffset=0) =
   assert src.device.vk.valid
   assert dst.device.vk.valid
   assert src.device == dst.device
@@ -106,7 +106,7 @@
   assert VK_BUFFER_USAGE_TRANSFER_SRC_BIT in src.usage
   assert VK_BUFFER_USAGE_TRANSFER_DST_BIT in dst.usage
 
-  var copyRegion = VkBufferCopy(size: VkDeviceSize(src.size), dstOffset: dstOffset)
+  var copyRegion = VkBufferCopy(size: VkDeviceSize(src.size), dstOffset: VkDeviceSize(dstOffset))
   withSingleUseCommandBuffer(src.device, true, commandBuffer):
     commandBuffer.vkCmdCopyBuffer(src.vk, dst.vk, 1, addr(copyRegion))
 
@@ -126,10 +126,10 @@
     )
   buffer.vk.reset
 
-proc setData*(dst: Buffer, src: pointer, size: uint64, bufferOffset=0'u64) =
+proc setData*(dst: Buffer, src: pointer, size: int, bufferOffset=0) =
   assert bufferOffset + size <= dst.size
   if dst.memory.canMap:
-    copyMem(cast[pointer](cast[uint64](dst.memory.data) + bufferOffset), src, size)
+    copyMem(cast[pointer](cast[int](dst.memory.data) + bufferOffset), src, size)
     if dst.memory.needsFlushing:
       dst.memory.flush()
   else: # use staging buffer, slower but required if memory is not host visible
--- a/src/semicongine/vulkan/descriptor.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/vulkan/descriptor.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -11,13 +11,13 @@
     Uniform, ImageSampler
   Descriptor* = object # "fields" of a DescriptorSetLayout
     name*: string
-    count*: uint32
+    count*: int
     stages*: seq[VkShaderStageFlagBits]
     case thetype*: DescriptorType
     of Uniform:
       buffer*: Buffer
-      offset*: uint64
-      size*: uint64
+      offset*: int
+      size*: int
     of ImageSampler:
       imageviews*: seq[ImageView]
       samplers*: seq[VulkanSampler]
@@ -31,8 +31,8 @@
   DescriptorPool* = object # required for allocation of DescriptorSet
     device: Device
     vk*: VkDescriptorPool
-    maxSets*: uint32 # maximum number of allocatable descriptor sets
-    counts*: seq[(VkDescriptorType, uint32)] # maximum number for each descriptor type to allocate
+    maxSets*: int # maximum number of allocatable descriptor sets
+    counts*: seq[(VkDescriptorType, int)] # maximum number for each descriptor type to allocate
 
 const DESCRIPTOR_TYPE_MAP = {
   Uniform: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
@@ -53,7 +53,7 @@
     layoutbindings.add VkDescriptorSetLayoutBinding(
       binding: uint32(i),
       descriptorType: descriptor.vkType,
-      descriptorCount: descriptor.count,
+      descriptorCount: uint32(descriptor.count),
       stageFlags: toBits descriptor.stages,
       pImmutableSamplers: nil,
     )
@@ -71,7 +71,7 @@
   descriptorSetLayout.vk.reset
 
 
-proc createDescriptorSetPool*(device: Device, counts: seq[(VkDescriptorType, uint32)], maxSets = 1000'u32): DescriptorPool =
+proc createDescriptorSetPool*(device: Device, counts: seq[(VkDescriptorType, int)], maxSets = 1000): DescriptorPool =
   assert device.vk.valid
 
   result.device = device
@@ -80,12 +80,12 @@
 
   var poolSizes: seq[VkDescriptorPoolSize]
   for (thetype, count) in result.counts:
-    poolSizes.add VkDescriptorPoolSize(thetype: thetype, descriptorCount: count)
+    poolSizes.add VkDescriptorPoolSize(thetype: thetype, descriptorCount: uint32(count))
   var poolInfo = VkDescriptorPoolCreateInfo(
     sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
     poolSizeCount: uint32(poolSizes.len),
     pPoolSizes: poolSizes.toCPointer,
-    maxSets: result.maxSets,
+    maxSets: uint32(result.maxSets),
   )
   checkVkResult vkCreateDescriptorPool(result.device.vk, addr(poolInfo), nil, addr(result.vk))
 
@@ -139,8 +139,8 @@
       assert descriptor.buffer.vk.valid
       bufferInfos.add VkDescriptorBufferInfo(
         buffer: descriptor.buffer.vk,
-        offset: descriptor.offset,
-        range: descriptor.size,
+        offset: uint64(descriptor.offset),
+        range: uint64(descriptor.size),
       )
       descriptorSetWrites.add VkWriteDescriptorSet(
           sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
@@ -148,7 +148,7 @@
           dstBinding: i,
           dstArrayElement: 0,
           descriptorType: descriptor.vkType,
-          descriptorCount: descriptor.count,
+          descriptorCount: uint32(descriptor.count),
           pBufferInfo: addr bufferInfos[^1],
         )
     elif descriptor.thetype == ImageSampler:
@@ -168,7 +168,7 @@
           dstBinding: i,
           dstArrayElement: 0,
           descriptorType: descriptor.vkType,
-          descriptorCount: descriptor.count,
+          descriptorCount: uint32(descriptor.count),
           pImageInfo: imgInfos[^1].toCPointer,
         )
     inc i
--- a/src/semicongine/vulkan/drawable.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/vulkan/drawable.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -3,20 +3,17 @@
 import std/logging
 
 import ../core
-import ../mesh
-import ../scene
 import ./buffer
 
 type
   Drawable* = object
-    mesh*: Mesh
-    elementCount*: uint32 # number of vertices or indices
-    bufferOffsets*: seq[(string, MemoryPerformanceHint, uint64)] # list of buffers and list of offset for each attribute in that buffer
-    instanceCount*: uint32 # number of instance
+    elementCount*: int # number of vertices or indices
+    bufferOffsets*: seq[(string, MemoryPerformanceHint, int)] # list of buffers and list of offset for each attribute in that buffer
+    instanceCount*: int # number of instance
     case indexed*: bool
     of true:
       indexType*: VkIndexType
-      indexBufferOffset*: uint64
+      indexBufferOffset*: int
     of false:
       discard
 
@@ -27,8 +24,6 @@
     &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, bufferOffsets: {drawable.bufferOffsets})"
 
 proc draw*(drawable: Drawable, commandBuffer: VkCommandBuffer, vertexBuffers: Table[MemoryPerformanceHint, Buffer], indexBuffer: Buffer) =
-    if drawable.mesh.entity.transform == Mat4():
-      return
     debug "Draw ", drawable
 
     var buffers: seq[VkBuffer]
@@ -47,16 +42,16 @@
     if drawable.indexed:
       commandBuffer.vkCmdBindIndexBuffer(indexBuffer.vk, VkDeviceSize(drawable.indexBufferOffset), drawable.indexType)
       commandBuffer.vkCmdDrawIndexed(
-        indexCount=drawable.elementCount,
-        instanceCount=drawable.instanceCount,
+        indexCount=uint32(drawable.elementCount),
+        instanceCount=uint32(drawable.instanceCount),
         firstIndex=0,
         vertexOffset=0,
         firstInstance=0
       )
     else:
       commandBuffer.vkCmdDraw(
-        vertexCount=drawable.elementCount,
-        instanceCount=drawable.instanceCount,
+        vertexCount=uint32(drawable.elementCount),
+        instanceCount=uint32(drawable.instanceCount),
         firstVertex=0,
         firstInstance=0
       )
--- a/src/semicongine/vulkan/image.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/vulkan/image.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -9,12 +9,12 @@
 import ./commandbuffer
 
 type
-  PixelDepth = 1'u32 .. 4'u32
+  PixelDepth = 1 .. 4
   VulkanImage* = object
     device*: Device
     vk*: VkImage
-    width*: uint32 # pixel
-    height*: uint32 # pixel
+    width*: int # pixel
+    height*: int # pixel
     depth*: PixelDepth
     format*: VkFormat
     usage*: seq[VkImageUsageFlagBits]
@@ -137,7 +137,7 @@
       layerCount: 1,
     ),
     imageOffset: VkOffset3D(x: 0, y: 0, z: 0),
-    imageExtent: VkExtent3D(width: dst.width, height: dst.height, depth: 1)
+    imageExtent: VkExtent3D(width: uint32(dst.width), height: uint32(dst.height), depth: 1)
   )
   withSingleUseCommandBuffer(src.device, true, commandBuffer):
     commandBuffer.vkCmdCopyBufferToImage(
@@ -149,7 +149,7 @@
     )
 
 # currently only usable for texture access from shader
-proc createImage*(device: Device, width, height: uint32, depth: PixelDepth, data: pointer): VulkanImage =
+proc createImage*(device: Device, width, height: int, depth: PixelDepth, data: pointer): VulkanImage =
   assert device.vk.valid
   assert width > 0
   assert height > 0
@@ -167,7 +167,7 @@
   var imageInfo = VkImageCreateInfo(
     sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
     imageType: VK_IMAGE_TYPE_2D,
-    extent: VkExtent3D(width: width, height: height, depth: 1),
+    extent: VkExtent3D(width: uint32(width), height: uint32(height), depth: 1),
     mipLevels: 1,
     arrayLayers: 1,
     format: result.format,
--- a/src/semicongine/vulkan/pipeline.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -34,11 +34,11 @@
   result = descriptorPool.allocateDescriptorSet(pipeline.descriptorSetLayout, inFlightFrames)
   
   for i in 0 ..< inFlightFrames:
-    var offset = 0'u64
+    var offset = 0
     # first descriptor is always uniform for globals, match should be better somehow
     for descriptor in result[i].layout.descriptors.mitems:
       if descriptor.thetype == Uniform and buffers.len > 0:
-        let size = VkDeviceSize(descriptor.size)
+        let size = descriptor.size
         descriptor.buffer = buffers[i]
         descriptor.offset = offset
         descriptor.size = size
@@ -76,7 +76,7 @@
     descriptors.add Descriptor(
       name: sampler.name,
       thetype: ImageSampler,
-      count: (if sampler.arrayCount == 0: 1'u32 else: sampler.arrayCount),
+      count: (if sampler.arrayCount == 0: 1 else: sampler.arrayCount),
       stages: @[VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT],
     )
   result.descriptorSetLayout = device.createDescriptorSetLayout(descriptors)
--- a/src/semicongine/vulkan/shader.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/src/semicongine/vulkan/shader.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -180,7 +180,7 @@
   for attribute in shaderConfiguration.inputs:
     bindings.add VkVertexInputBindingDescription(
       binding: binding,
-      stride: attribute.size,
+      stride: uint32(attribute.size),
       inputRate: if attribute.perInstance: VK_VERTEX_INPUT_RATE_INSTANCE else: VK_VERTEX_INPUT_RATE_VERTEX,
     )
     # allows to submit larger data structures like Mat44, for most other types will be 1
@@ -189,9 +189,9 @@
         binding: binding,
         location: location,
         format: attribute.thetype.getVkFormat,
-        offset: i * attribute.size(perDescriptor=true),
+        offset: uint32(i * attribute.size(perDescriptor=true)),
       )
-      location += attribute.thetype.nLocationSlots
+      location += uint32(attribute.thetype.nLocationSlots)
     inc binding
 
   return VkPipelineVertexInputStateCreateInfo(
--- a/tests/test_vulkan_wrapper.nim	Mon Aug 21 00:17:16 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Fri Aug 25 00:29:51 2023 +0700
@@ -41,114 +41,120 @@
     name: "mat3",
     materialType: "my_special_material",
     constants: {
-      "color": toGPUValue(newVec4f(0.5, 0.5, 0))
+      "color": toGPUValue(newVec4f(0, 1, 0, 1))
     }.toTable
   )
 
-proc scene_different_mesh_types(): Entity =
-  result = newEntity("root", [],
-    newEntity("triangle1", {"mesh": Component(newMesh(
+proc scene_different_mesh_types(): seq[Mesh] =
+  @[
+    newMesh(
       positions=[newVec3f(0.0, -0.5), newVec3f(0.5, 0.5), newVec3f(-0.5, 0.5)],
       colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
       material=mat,
-    ))}),
-    newEntity("triangle1b", {"mesh": Component(newMesh(
+      transform=translate(-0.7, -0.5),
+    ),
+    newMesh(
       positions=[newVec3f(0.0, -0.4), newVec3f(0.4, 0.4), newVec3f(-0.4, 0.5)],
       colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
       material=mat,
-    ))}),
-    newEntity("triangle2a", {"mesh": Component(newMesh(
+      transform=translate(0, -0.5),
+    ),
+    newMesh(
       positions=[newVec3f(0.0, 0.5), newVec3f(0.5, -0.5), newVec3f(-0.5, -0.5)],
       colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
       indices=[[0'u16, 2'u16, 1'u16]],
       material=mat2,
-    ))}),
-    newEntity("triangle2b", {"mesh": Component(newMesh(
+      transform=translate(0.7, -0.5),
+    ),
+    newMesh(
       positions=[newVec3f(0.0, 0.4), newVec3f(0.4, -0.4), newVec3f(-0.4, -0.4)],
       colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
       indices=[[0'u16, 2'u16, 1'u16]],
       material=mat2,
-    ))}),
-    newEntity("triangle3a", {"mesh": Component(newMesh(
-      positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
-      colors=[newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1)],
-      indices=[[0'u32, 2'u32, 1'u32]],
-      autoResize=false,
-      material=mat2,
-    ))}),
-    newEntity("triangle3b", {"mesh": Component(newMesh(
+      transform=translate(-0.7, 0.5),
+    ),
+    newMesh(
       positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
       colors=[newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1)],
       indices=[[0'u32, 2'u32, 1'u32]],
       autoResize=false,
       material=mat2,
-    ))}),
-  )
-  for mesh in allComponentsOfType[Mesh](result):
-    mesh.setInstanceData("translate", @[newVec3f()])
-  result[0]["mesh", Mesh()].updateInstanceData("translate", @[newVec3f(-0.6, -0.6)])
-  result[1]["mesh", Mesh()].updateInstanceData("translate", @[newVec3f(-0.6, 0.6)])
-  result[2]["mesh", Mesh()].updateInstanceData("translate", @[newVec3f(0.6, -0.6)])
-  result[3]["mesh", Mesh()].updateInstanceData("translate", @[newVec3f(0.6, 0.6)])
+      transform=translate(0, 0.5),
+    ),
+    newMesh(
+      positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
+      colors=[newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1)],
+      indices=[[0'u32, 2'u32, 1'u32]],
+      autoResize=false,
+      material=mat2,
+      transform=translate(0.7, 0.5),
+    ),
+  ]
 
-proc scene_simple(): Entity =
-  var mymesh1 = newMesh(
-    positions=[newVec3f(0.0, -0.3), newVec3f(0.3, 0.3), newVec3f(-0.3, 0.3)],
-    colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
-    material=mat,
-  )
-  var mymesh2 = newMesh(
-    positions=[newVec3f(0.0, -0.5), newVec3f(0.5, 0.5), newVec3f(-0.5, 0.5)],
-    colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
-    material=mat,
-  )
-  var mymesh3 = newMesh(
-    positions=[newVec3f(0.0, -0.6), newVec3f(0.6, 0.6), newVec3f(-0.6, 0.6)],
-    colors=[newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1)],
-    indices=[[0'u32, 1'u32, 2'u32]],
-    autoResize=false,
-    material=mat,
-  )
-  var mymesh4 = newMesh(
-    positions=[newVec3f(0.0, -0.8), newVec3f(0.8, 0.8), newVec3f(-0.8, 0.8)],
-    colors=[newVec4f(0.0, 0.0, 1.0, 1), newVec4f(0.0, 0.0, 1.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
-    indices=[[0'u16, 1'u16, 2'u16]],
-    instanceCount=2,
-    material=mat,
-  )
-  mymesh1.setInstanceData("translate", @[newVec3f( 0.4,  0.4)])
-  mymesh2.setInstanceData("translate", @[newVec3f( 0.4, -0.4)])
-  mymesh3.setInstanceData("translate", @[newVec3f(-0.4, -0.4)])
-  mymesh4.setInstanceData("translate", @[newVec3f(-0.4,  0.4), newVec3f(0.0, 0.0)])
-  result = newEntity("root", [], newEntity("triangle", {"mesh1": Component(mymesh4), "mesh2": Component(mymesh3), "mesh3": Component(mymesh2), "mesh4": Component(mymesh1)}))
+proc scene_simple(): seq[Mesh] =
+  @[
+    newMesh(
+      positions=[newVec3f(0.0, -0.3), newVec3f(0.3, 0.3), newVec3f(-0.3, 0.3)],
+      colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
+      material=mat,
+      transform=translate(0.4, 0.4),
+    ),
+    newMesh(
+      positions=[newVec3f(0.0, -0.5), newVec3f(0.5, 0.5), newVec3f(-0.5, 0.5)],
+      colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
+      material=mat,
+      transform=translate(0.4, -0.4),
+    ),
+    newMesh(
+      positions=[newVec3f(0.0, -0.6), newVec3f(0.6, 0.6), newVec3f(-0.6, 0.6)],
+      colors=[newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1)],
+      indices=[[0'u32, 1'u32, 2'u32]],
+      autoResize=false,
+      material=mat,
+      transform=translate(-0.4, 0.4),
+    ),
+    newMesh(
+      positions=[newVec3f(0.0, -0.8), newVec3f(0.8, 0.8), newVec3f(-0.8, 0.8)],
+      colors=[newVec4f(0.0, 0.0, 1.0, 1), newVec4f(0.0, 0.0, 1.0, 1), newVec4f(0.0, 0.0, 1.0, 1)],
+      indices=[[0'u16, 1'u16, 2'u16]],
+      instanceTransforms=[Unit4F32, Unit4F32],
+      material=mat,
+      transform=translate(-0.4, -0.4),
+    )
+  ]
 
-proc scene_primitives(): Entity =
+proc scene_primitives(): seq[Mesh] =
   var r = rect(color="ff0000")
   var t = tri(color="0000ff")
   var c = circle(color="00ff00")
   r.material = mat
   t.material = mat
   c.material = mat
-
-  r.setInstanceData("translate", @[newVec3f(0.5, -0.3)])
-  t.setInstanceData("translate", @[newVec3f(0.3,  0.3)])
-  c.setInstanceData("translate", @[newVec3f(-0.3,  0.1)])
-  result = newEntity("root", {"mesh1": Component(t), "mesh2": Component(r), "mesh3": Component(c)})
+  r.transform = translate(newVec3f(0.5, -0.3))
+  t.transform = translate(newVec3f(0.3,  0.3))
+  c.transform = translate(newVec3f(-0.3,  0.1))
+  result = @[r, c, t]
 
-proc scene_flag(): Entity =
-  var r = rect(color="ffffff")
-  r.material = mat
-  result = newEntity("root", {"mesh": Component(r)})
+proc scene_flag(): seq[Mesh] =
+  @[
+    newMesh(
+      positions=[newVec3f(-1.0, -1.0), newVec3f(1.0, -1.0), newVec3f(1.0, 1.0), newVec3f(-1.0, 1.0)],
+      colors=[newVec4f(-1, -1, 1, 1), newVec4f(1, -1, 1, 1), newVec4f(1, 1, 1, 1), newVec4f(-1, 1, 1, 1)],
+      indices=[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]],
+      material=mat,
+      transform=scale(0.5, 0.5)
+    )
+  ]
 
-proc scene_multi_material(): Entity =
+proc scene_multi_material(): seq[Mesh] =
   var
     r1 = rect(color="ffffff")
     r2 = rect(color="000000")
   r1.material = mat
   r2.material = mat3
-  r1.setInstanceData("translate", @[newVec3f(-0.5)])
-  r2.setInstanceData("translate", @[newVec3f(+0.5)])
-  result = newEntity("root", {"mesh1": Component(r1), "mesh2": Component(r2)})
+  r1.transform = translate(newVec3f(-0.5))
+  r2.transform = translate(newVec3f(+0.5))
+  result = @[r1, r2]
 
 proc main() =
   var engine = initEngine("Test")
@@ -159,7 +165,7 @@
       inputs=[
         attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead),
         attr[Vec4f]("color", memoryPerformanceHint=PreferFastWrite),
-        attr[Vec3f]("translate", perInstance=true),
+        attr[Mat4]("transform", perInstance=true),
         attr[uint16]("materialIndex", perInstance=true),
       ],
       intermediates=[
@@ -171,19 +177,19 @@
       samplers=[
         attr[Sampler2DType]("my_little_texture", arrayCount=2)
       ],
-      vertexCode="""gl_Position = vec4(position + translate, 1.0); outcolor = color; materialIndexOut = materialIndex;""",
+      vertexCode="""gl_Position = vec4(position, 1.0) * transform; outcolor = color; materialIndexOut = materialIndex;""",
       fragmentCode="color = texture(my_little_texture[materialIndexOut], outcolor.xy) * 0.5 + outcolor * 0.5;",
     )
     shaderConfiguration2 = createShaderConfiguration(
       inputs=[
         attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead),
-        attr[Vec3f]("translate", perInstance=true),
+        attr[Mat4]("transform", perInstance=true),
       ],
       intermediates=[attr[Vec4f]("outcolor")],
       outputs=[attr[Vec4f]("color")],
       uniforms=[attr[Vec4f]("color")],
-      vertexCode="""gl_Position = vec4(position + translate, 1.0); outcolor = Uniforms.color;""",
-      fragmentCode="color = outcolor;",
+      vertexCode="""gl_Position = vec4(position, 1.0) * transform; outcolor = Uniforms.color;""",
+      fragmentCode="color = vec4(0, 0, 1, 1);",
     )
   engine.initRenderer({
     "my_material": shaderConfiguration1,
@@ -192,22 +198,24 @@
 
   # INIT SCENES
   var scenes = [
-    newScene("simple", scene_simple(), transformAttribute=""),
-    newScene("different mesh types", scene_different_mesh_types(), transformAttribute=""),
-    newScene("primitives", scene_primitives(), transformAttribute=""),
-    newScene("flag", scene_multi_material(), transformAttribute=""),
+    # Scene(name: "simple", meshes: scene_simple()),
+    # Scene(name: "different mesh types", meshes: scene_different_mesh_types()),
+    # Scene(name: "primitives", meshes: scene_primitives()),
+    # Scene(name: "flag", meshes: scene_flag()),
+    # Scene(name: "multimaterial", meshes: scene_multi_material(), materialIndexAttribute: ""),
+    Scene(name: "multimaterial", meshes: scene_multi_material()),
   ]
 
-  scenes[3].addShaderGlobal("color", newVec4f(1, 0, 1))
+  scenes[0].addShaderGlobal("color", newVec4f(1, 0, 0, 1))
   for scene in scenes.mitems:
     scene.addShaderGlobal("time", 0.0'f32)
     engine.addScene(scene)
 
-
   # MAINLOOP
   echo "Setup successfull, start rendering"
   for i in 0 ..< 3:
     for scene in scenes.mitems:
+      echo "rendering scene ", scene.name
       for i in 0 ..< 1000:
         if engine.updateInputs() != Running or engine.keyIsDown(Escape):
           engine.destroy()