changeset 792:d65b62812d34

did: undid using meshes as values, ref is much better, fix a few things, fix a few huge performance issues
author Sam <sam@basx.dev>
date Sun, 03 Sep 2023 17:34:29 +0700
parents 43432dad4797
children c31e42d72253
files src/semicongine/engine.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/text.nim src/semicongine/vulkan/swapchain.nim tests/test_collision.nim tests/test_font.nim tests/test_vulkan_wrapper.nim
diffstat 11 files changed, 193 insertions(+), 141 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/engine.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/src/semicongine/engine.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -130,12 +130,15 @@
 proc updateInputs*(engine: var Engine): EngineState =
   assert engine.state in [Starting, Running]
 
+  # reset input states
   engine.input.keyWasPressed = {}
   engine.input.keyWasReleased = {}
   engine.input.mouseWasPressed = {}
   engine.input.mouseWasReleased = {}
   engine.input.mouseWheel = 0
   engine.input.mouseMove = newVec2f()
+  engine.input.windowWasResized = false
+
   if engine.state == Starting:
     engine.input.windowWasResized = true
     var mpos = engine.window.getMousePosition()
--- a/src/semicongine/mesh.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/src/semicongine/mesh.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -16,7 +16,7 @@
     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* = object
+  MeshObject* = object
     vertexCount*: int
     case indexType*: MeshIndexType
       of None: discard
@@ -30,6 +30,7 @@
     vertexData: Table[string, DataList]
     instanceData: Table[string, DataList]
     dirtyAttributes: seq[string]
+  Mesh* = ref MeshObject
   Material* = object
     name*: string
     constants*: Table[string, DataList]
@@ -39,8 +40,10 @@
   name: "empty material"
 )
 
+func `$`*(mesh: MeshObject): string =
+  &"Mesh(vertexCount: {mesh.vertexCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.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})"
+  $mesh[]
 
 proc `$`*(material: Material): string =
   var constants: seq[string]
@@ -51,19 +54,22 @@
     textures.add &"{key}"
   return &"""{material.name} | Values: {constants.join(", ")} | Textures: {textures.join(", ")}"""
 
-func vertexAttributes*(mesh: Mesh): seq[string] =
+func vertexAttributes*(mesh: MeshObject): seq[string] =
   mesh.vertexData.keys.toSeq
 
-func instanceAttributes*(mesh: Mesh): seq[string] =
+func instanceAttributes*(mesh: MeshObject): seq[string] =
   mesh.instanceData.keys.toSeq
 
-func attributes*(mesh: Mesh): seq[string] =
+func attributes*(mesh: MeshObject): seq[string] =
   mesh.vertexAttributes & mesh.instanceAttributes
 
 func hash*(material: Material): Hash =
   hash(material.name)
 
-func instanceCount*(mesh: Mesh): int =
+func hash*(mesh: Mesh): Hash =
+  hash(cast[ptr MeshObject](mesh))
+
+func instanceCount*(mesh: MeshObject): int =
   mesh.instanceTransforms.len
 
 converter toVulkan*(indexType: MeshIndexType): VkIndexType =
@@ -73,7 +79,7 @@
     of Small: VK_INDEX_TYPE_UINT16
     of Big: VK_INDEX_TYPE_UINT32
 
-func indicesCount*(mesh: Mesh): int =
+func indicesCount*(mesh: MeshObject): int =
   (
     case mesh.indexType
     of None: 0
@@ -82,35 +88,35 @@
     of Big: mesh.bigIndices.len
   ) * 3
 
-func initVertexAttribute*[T](mesh: var Mesh, attribute: string, value: seq[T]) =
+func initVertexAttribute*[T](mesh: var MeshObject, 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) =
+func initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: T) =
   initVertexAttribute(mesh, attribute, newSeqWith(mesh.vertexCount, value))
-func initVertexAttribute*[T](mesh: var Mesh, attribute: string) =
+func initVertexAttribute*[T](mesh: var MeshObject, attribute: string) =
   initVertexAttribute(mesh=mesh, attribute=attribute, value=default(T))
-func initVertexAttribute*(mesh: var Mesh, attribute: string, datatype: DataType) =
+func initVertexAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) =
   assert not mesh.vertexData.contains(attribute)
   mesh.vertexData[attribute] = newDataList(thetype=datatype)
   mesh.vertexData[attribute].initData(mesh.vertexCount)
 
-func initInstanceAttribute*[T](mesh: var Mesh, attribute: string, value: seq[T]) =
+func initInstanceAttribute*[T](mesh: var MeshObject, 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) =
+func initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: T) =
   initInstanceAttribute(mesh, attribute, newSeqWith(mesh.instanceCount, value))
-func initInstanceAttribute*[T](mesh: var Mesh, attribute: string) =
+func initInstanceAttribute*[T](mesh: var MeshObject, attribute: string) =
   initInstanceAttribute(mesh=mesh, attribute=attribute, value=default(T))
-func initInstanceAttribute*(mesh: var Mesh, attribute: string, datatype: DataType) =
+func initInstanceAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) =
   assert not mesh.instanceData.contains(attribute)
   mesh.instanceData[attribute] = newDataList(thetype=datatype)
   mesh.instanceData[attribute].initData(mesh.instanceCount)
 
-func newMesh*(
+proc newMesh*(
   positions: openArray[Vec3f],
   indices: openArray[array[3, uint32|uint16|uint8]],
   colors: openArray[Vec4f]=[],
@@ -140,28 +146,28 @@
     material: material,
   )
 
-  result.initVertexAttribute("position", positions.toSeq)
-  if colors.len > 0: result.initVertexAttribute("color", colors.toSeq)
-  if uvs.len > 0: result.initVertexAttribute("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 int(i[0]) < result.vertexCount
-    assert int(i[1]) < result.vertexCount
-    assert int(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
+  if result[].indexType == Tiny and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support
     for i, tri in enumerate(indices):
-      result.tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])]
-  elif result.indexType == Small and uint32(positions.len) < uint32(high(uint16)):
+      result[].tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])]
+  elif result[].indexType == Small and uint32(positions.len) < uint32(high(uint16)):
     for i, tri in enumerate(indices):
-      result.smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])]
-  elif result.indexType == Big:
+      result[].smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])]
+  elif result[].indexType == Big:
     for i, tri in enumerate(indices):
-      result.bigIndices.add [uint32(tri[0]), uint32(tri[1]), uint32(tri[2])]
+      result[].bigIndices.add [uint32(tri[0]), uint32(tri[1]), uint32(tri[2])]
 
-func newMesh*(
+proc newMesh*(
   positions: openArray[Vec3f],
   colors: openArray[Vec4f]=[],
   uvs: openArray[Vec2f]=[],
@@ -179,7 +185,7 @@
     material=material,
   )
 
-func attributeSize*(mesh: Mesh, attribute: string): int =
+func attributeSize*(mesh: MeshObject, attribute: string): int =
   if mesh.vertexData.contains(attribute):
     mesh.vertexData[attribute].size
   elif mesh.instanceData.contains(attribute):
@@ -187,7 +193,7 @@
   else:
     0
 
-func attributeType*(mesh: Mesh, attribute: string): DataType =
+func attributeType*(mesh: MeshObject, attribute: string): DataType =
   if mesh.vertexData.contains(attribute):
     mesh.vertexData[attribute].theType
   elif mesh.instanceData.contains(attribute):
@@ -195,7 +201,7 @@
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
 
-func indexSize*(mesh: Mesh): int =
+func indexSize*(mesh: MeshObject): int =
   case mesh.indexType
     of None: 0
     of Tiny: mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0))
@@ -205,14 +211,14 @@
 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, int) =
+func getRawIndexData*(mesh: MeshObject): (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 getRawData*(mesh: var Mesh, attribute: string): (pointer, int) =
+func getRawData*(mesh: var MeshObject, attribute: string): (pointer, int) =
   if mesh.vertexData.contains(attribute):
     mesh.vertexData[attribute].getRawData()
   elif mesh.instanceData.contains(attribute):
@@ -220,7 +226,7 @@
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
 
-proc getAttribute*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string): seq[T] =
+proc getAttribute*[T: GPUType|int|uint|float](mesh: MeshObject, attribute: string): seq[T] =
   if mesh.vertexData.contains(attribute):
     getValues[T](mesh.vertexData[attribute])
   elif mesh.instanceData.contains(attribute):
@@ -228,7 +234,7 @@
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
 
-proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) =
+proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) =
   if mesh.vertexData.contains(attribute):
     assert data.len == mesh.vertexCount
     setValues(mesh.vertexData[attribute], data)
@@ -237,9 +243,10 @@
     setValues(mesh.instanceData[attribute], data)
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
-  mesh.dirtyAttributes.add attribute
+  if not mesh.dirtyAttributes.contains(attribute):
+    mesh.dirtyAttributes.add attribute
 
-proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, i: int, value: T) =
+proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, i: int, value: T) =
   if mesh.vertexData.contains(attribute):
     assert i < mesh.vertexData[attribute].len
     setValue(mesh.vertexData[attribute], i, value)
@@ -248,19 +255,21 @@
     setValue(mesh.instanceData[attribute], i, value)
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
-  mesh.dirtyAttributes.add attribute
+  if not mesh.dirtyAttributes.contains(attribute):
+    mesh.dirtyAttributes.add attribute
 
-proc appendAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) =
+proc appendAttributeData*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) =
   if mesh.vertexData.contains(attribute):
     appendValues(mesh.vertexData[attribute], data)
   elif mesh.instanceData.contains(attribute):
     appendValues(mesh.instanceData[attribute], data)
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
-  mesh.dirtyAttributes.add attribute
+  if not mesh.dirtyAttributes.contains(attribute):
+    mesh.dirtyAttributes.add attribute
 
 # currently only used for loading from files, shouls
-proc appendAttributeData*(mesh: var Mesh, attribute: string, data: DataList) =
+proc appendAttributeData*(mesh: var MeshObject, attribute: string, data: DataList) =
   if mesh.vertexData.contains(attribute):
     assert data.thetype == mesh.vertexData[attribute].thetype
     appendValues(mesh.vertexData[attribute], data)
@@ -269,28 +278,29 @@
     appendValues(mesh.instanceData[attribute], data)
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
-  mesh.dirtyAttributes.add attribute
+  if not mesh.dirtyAttributes.contains(attribute):
+    mesh.dirtyAttributes.add attribute
 
-proc appendIndicesData*(mesh: var Mesh, v1, v2, v3: int) =
+proc appendIndicesData*(mesh: var MeshObject, 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([uint32(v1), uint32(v2), uint32(v3)])
 
-proc updateInstanceTransforms*(mesh: var Mesh, attribute: string) =
+proc updateInstanceTransforms*(mesh: var MeshObject, attribute: string) =
   let currentTransforms = mesh.instanceTransforms.mapIt(mesh.transform * it)
   if currentTransforms != mesh.transformCache:
     mesh.updateAttributeData(attribute, currentTransforms)
     mesh.transformCache = currentTransforms
 
-func dirtyAttributes*(mesh: Mesh): seq[string] =
+func dirtyAttributes*(mesh: MeshObject): seq[string] =
   mesh.dirtyAttributes
 
-proc clearDirtyAttributes*(mesh: var Mesh) =
-  mesh.dirtyAttributes = @[]
+proc clearDirtyAttributes*(mesh: var MeshObject) =
+  mesh.dirtyAttributes.reset
 
-proc transform*[T: GPUType](mesh: Mesh, attribute: string, transform: Mat4) =
+proc transform*[T: GPUType](mesh: MeshObject, attribute: string, transform: Mat4) =
   if mesh.vertexData.contains(attribute):
     for i in 0 ..< mesh.vertexData[attribute].len:
       setValue(mesh.vertexData[attribute], i, transform * getValue[T](mesh.vertexData[attribute], i))
@@ -300,7 +310,7 @@
   else:
     raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
 
-func rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh =
+proc rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh =
   result = Mesh(
     vertexCount: 4,
     instanceTransforms: @[Unit4F32],
@@ -314,21 +324,21 @@
     pos = @[newVec3f(-half_w, -half_h), newVec3f( half_w, -half_h), newVec3f( half_w,  half_h), newVec3f(-half_w,  half_h)]
     c = hexToColorAlpha(color)
 
-  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)])
+  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 =
+proc tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh =
   result = Mesh(vertexCount: 3, instanceTransforms: @[Unit4F32])
   let
     half_w = width / 2
     half_h = height / 2
     colorVec = hexToColorAlpha(color)
 
-  result.initVertexAttribute("position", @[newVec3f(0, -half_h), newVec3f( half_w, half_h), newVec3f(-half_w,  half_h)])
-  result.initVertexAttribute("color", @[colorVec, colorVec, colorVec])
+  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, color="ffffffff"): Mesh =
+proc circle*(width=1'f32, height=1'f32, nSegments=12, color="ffffffff"): Mesh =
   assert nSegments >= 3
   result = Mesh(vertexCount: 3 + nSegments, instanceTransforms: @[Unit4F32], indexType: Small)
 
@@ -343,11 +353,11 @@
   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 [uint16(0), uint16(i + 1), uint16(i + 2)]
+    result[].smallIndices.add [uint16(0), uint16(i + 1), uint16(i + 2)]
 
-  result.initVertexAttribute("position", pos)
-  result.initVertexAttribute("color", col)
+  result[].initVertexAttribute("position", pos)
+  result[].initVertexAttribute("color", col)
 
-func getCollisionPoints*(mesh: Mesh, positionAttribute="position"): seq[Vec3f] =
+func getCollisionPoints*(mesh: MeshObject, positionAttribute="position"): seq[Vec3f] =
   for p in getAttribute[Vec3f](mesh, positionAttribute):
     result.add mesh.transform * p
--- a/src/semicongine/renderer.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/src/semicongine/renderer.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -3,7 +3,6 @@
 import std/strformat
 import std/sequtils
 import std/logging
-import std/enumerate
 
 import ./core
 import ./vulkan/buffer
@@ -25,13 +24,13 @@
 
 type
   SceneData = object
-    drawables*: seq[tuple[drawable: Drawable, meshIndex: int]]
+    drawables*: seq[tuple[drawable: Drawable, mesh: Mesh]]
     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]
-    vertexBufferOffsets*: Table[(int, string), int]
+    vertexBufferOffsets*: Table[(Mesh, string), int]
     descriptorPools*: Table[VkPipeline, DescriptorPool]
     descriptorSets*: Table[VkPipeline, seq[DescriptorSet]]
     materials: seq[Material]
@@ -106,11 +105,11 @@
   for input in pipeline.inputs:
     if input.name == TRANSFORMATTRIBUTE: # will be populated automatically
       continue
-    if not (input.name in mesh.attributes):
+    if not (input.name in mesh[].attributes):
       return (true, &"Shader input '{input.name}' is not available for mesh '{mesh}'")
-    if input.theType != mesh.attributeType(input.name):
-      return (true, &"Shader input '{input.name}' expects type {input.theType}, but mesh '{mesh}' has {mesh.attributeType(input.name)}")
-    if input.perInstance != mesh.instanceAttributes.contains(input.name):
+    if input.theType != mesh[].attributeType(input.name):
+      return (true, &"Shader input '{input.name}' expects type {input.theType}, but mesh '{mesh}' has {mesh[].attributeType(input.name)}")
+    if input.perInstance != mesh[].instanceAttributes.contains(input.name):
       return (true, &"Shader input '{input.name}' expects to be per instance, but mesh '{mesh}' has is not as instance attribute")
 
   return materialCompatibleWithPipeline(scene, mesh.material, pipeline)
@@ -159,20 +158,20 @@
   for mesh in scene.meshes.mitems:
     for inputAttr in inputs:
       if inputAttr.name == TRANSFORMATTRIBUTE:
-        mesh.initInstanceAttribute(inputAttr.name, inputAttr.thetype)
-      elif not mesh.attributes.contains(inputAttr.name):
+        mesh[].initInstanceAttribute(inputAttr.name, inputAttr.thetype)
+      elif not mesh[].attributes.contains(inputAttr.name):
         warn(&"Mesh is missing data for shader attribute {inputAttr.name}, auto-filling with empty values")
         if inputAttr.perInstance:
-          mesh.initInstanceAttribute(inputAttr.name, inputAttr.thetype)
+          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}"
+          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
   for mesh in scene.meshes:
-    if mesh.indexType != MeshIndexType.None:
-      let indexAlignment = case mesh.indexType
+    if mesh[].indexType != MeshIndexType.None:
+      let indexAlignment = case mesh[].indexType
         of MeshIndexType.None: 0
         of Tiny: 1
         of Small: 2
@@ -180,7 +179,7 @@
       # index value alignment required by Vulkan
       if indicesBufferSize mod indexAlignment != 0:
         indicesBufferSize += indexAlignment - (indicesBufferSize mod indexAlignment)
-      indicesBufferSize += mesh.indexSize
+      indicesBufferSize += mesh[].indexSize
   if indicesBufferSize > 0:
     scenedata.indexBuffer = renderer.device.createBuffer(
       size=indicesBufferSize,
@@ -202,7 +201,7 @@
       # 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:
         perLocationSizes[attribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationSizes[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT)
-      perLocationSizes[attribute.memoryPerformanceHint] += mesh.attributeSize(attribute.name)
+      perLocationSizes[attribute.memoryPerformanceHint] += mesh[].attributeSize(attribute.name)
 
   # create vertex buffers
   for memoryPerformanceHint, bufferSize in perLocationSizes.pairs:
@@ -220,10 +219,10 @@
   for hint in MemoryPerformanceHint:
     perLocationOffsets[hint] = 0
 
-  for (meshIndex, mesh) in enumerate(scene.meshes.mitems):
+  for mesh in scene.meshes:
     for attribute in inputs:
-      scenedata.vertexBufferOffsets[(meshIndex, attribute.name)] = perLocationOffsets[attribute.memoryPerformanceHint]
-      let size = mesh.getRawData(attribute.name)[1]
+      scenedata.vertexBufferOffsets[(mesh, attribute.name)] = perLocationOffsets[attribute.memoryPerformanceHint]
+      let size = mesh[].getRawData(attribute.name)[1]
       perLocationOffsets[attribute.memoryPerformanceHint] += size
       if perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0:
         perLocationOffsets[attribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT)
@@ -235,14 +234,14 @@
         if scene.usesMaterial(materialName):
           offsets[pipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]()
           for attribute in pipeline.inputs:
-            offsets[pipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(meshIndex, attribute.name)])
+            offsets[pipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)])
 
     # create drawables
     let indexed = mesh.indexType != MeshIndexType.None
     var drawable = Drawable(
-      elementCount: if indexed: mesh.indicesCount else: mesh.vertexCount,
+      elementCount: if indexed: mesh[].indicesCount else: mesh[].vertexCount,
       bufferOffsets: offsets,
-      instanceCount: mesh.instanceCount,
+      instanceCount: mesh[].instanceCount,
       indexed: indexed,
     )
     if indexed:
@@ -256,10 +255,10 @@
         indexBufferOffset += indexAlignment - (indexBufferOffset mod indexAlignment)
       drawable.indexBufferOffset = indexBufferOffset
       drawable.indexType = mesh.indexType
-      var (pdata, size) = mesh.getRawIndexData()
+      var (pdata, size) = mesh[].getRawIndexData()
       scenedata.indexBuffer.setData(pdata, size, indexBufferOffset)
       indexBufferOffset += size
-    scenedata.drawables.add (drawable, meshIndex)
+    scenedata.drawables.add (drawable, mesh)
 
   # setup uniforms and samplers
   for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
@@ -299,39 +298,58 @@
 
   renderer.scenedata[scene] = scenedata
 
-proc refreshMeshAttributeData(renderer: Renderer, scene: var Scene, drawable: Drawable, meshIndex: int, attribute: string) =
-  debug &"Refreshing data on mesh {scene.meshes[meshIndex]} for {attribute}"
+proc refreshMeshAttributeData(renderer: Renderer, scene: var Scene, drawable: Drawable, mesh: Mesh, attribute: string) =
+  debug &"Refreshing data on mesh mesh for {attribute}"
   # ignore attributes that are not used in this shader
   if not (attribute in renderer.scenedata[scene].attributeLocation):
     return
-  let (pdata, size) = scene.meshes[meshIndex].getRawData(attribute)
+  let (pdata, size) = mesh[].getRawData(attribute)
   let memoryPerformanceHint = renderer.scenedata[scene].attributeLocation[attribute]
 
-  renderer.scenedata[scene].vertexBuffers[memoryPerformanceHint].setData(pdata, size, renderer.scenedata[scene].vertexBufferOffsets[(meshIndex, attribute)])
+  renderer.scenedata[scene].vertexBuffers[memoryPerformanceHint].setData(pdata, size, renderer.scenedata[scene].vertexBufferOffsets[(mesh, attribute)])
 
 proc updateMeshData*(renderer: var Renderer, scene: var Scene, forceAll=false) =
   assert scene in renderer.scenedata
 
-  for (drawable, meshIndex) in renderer.scenedata[scene].drawables.mitems:
-    if scene.meshes[meshIndex].attributes.contains(TRANSFORMATTRIBUTE):
-      scene.meshes[meshIndex].updateInstanceTransforms(TRANSFORMATTRIBUTE)
-    let attrs = (if forceAll: scene.meshes[meshIndex].attributes else: scene.meshes[meshIndex].dirtyAttributes)
+  for (drawable, mesh) in renderer.scenedata[scene].drawables.mitems:
+    if mesh[].attributes.contains(TRANSFORMATTRIBUTE):
+      mesh[].updateInstanceTransforms(TRANSFORMATTRIBUTE)
+    let attrs = (if forceAll: mesh[].attributes else: mesh[].dirtyAttributes)
     for attribute in attrs:
-      renderer.refreshMeshAttributeData(scene, drawable, meshIndex, attribute)
+      renderer.refreshMeshAttributeData(scene, drawable, mesh, attribute)
       debug &"Update mesh attribute {attribute}"
-    scene.meshes[meshIndex].clearDirtyAttributes()
+    mesh[].clearDirtyAttributes()
 
-# TODO: only update if uniform values changed
-proc updateUniformData*(renderer: var Renderer, scene: var Scene, forceAll=true) =
+proc updateUniformData*(renderer: var Renderer, scene: var Scene, forceAll=false) =
   assert scene in renderer.scenedata
+  # TODO: maybe check for dirty materials too, but atm we copy materials into the
+  # renderers scenedata, so they will are immutable after initialization, would 
+  # need to allow updates of materials too in order to make sense
+
+  let dirty = scene.dirtyShaderGlobals
+  if not forceAll and dirty.len == 0:
+    return
+
+  if forceAll:
+    debug "Update uniforms because 'forceAll' was given"
+  else:
+    debug &"Update uniforms because of dirty scene globals: {dirty}"
 
   # loop over all used shaders/pipelines
   for i in 0 ..< renderer.renderPass.subpasses.len:
     for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
-      if scene.usesMaterial(materialName) and renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0:
+      if (
+        scene.usesMaterial(materialName) 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
+        if forceAll:
+          for buffer in renderer.scenedata[scene].uniformBuffers[pipeline.vk]:
+            assert buffer.vk.valid
+
         var offset = 0
-        # loop over all uniforms of the shader
+        # loop over all uniforms of the shader-pipeline
         for uniform in pipeline.uniforms:
           var foundValue = false
           var value: DataList
@@ -349,10 +367,16 @@
               if foundValue: break
           if not foundValue:
             raise newException(Exception, &"Uniform '{uniform.name}' not found in scene shaderGlobals or materials")
-          debug &"Update uniform {uniform.name}"
+          debug &"  update uniform {uniform.name} with value: {value}"
           let (pdata, size) = value.getRawData()
-          renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].setData(pdata, size, offset)
+          if dirty.contains(uniform.name) or forceAll: # only update if necessary
+            if forceAll: # need to update all in-flight frames, when forced to do all
+              for buffer in renderer.scenedata[scene].uniformBuffers[pipeline.vk]:
+                buffer.setData(pdata, size, offset)
+            else:
+              renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].setData(pdata, size, offset)
           offset += size
+  scene.clearDirtyShaderGlobals()
 
 proc render*(renderer: var Renderer, scene: Scene) =
   assert scene in renderer.scenedata
@@ -385,8 +409,8 @@
         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, meshIndex) in renderer.scenedata[scene].drawables:
-          if scene.meshes[meshIndex].material.name == materialName:
+        for (drawable, mesh) in renderer.scenedata[scene].drawables:
+          if mesh.material.name == materialName:
             drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer, pipeline.vk)
 
     if i < renderer.renderPass.subpasses.len - 1:
--- a/src/semicongine/resources.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/src/semicongine/resources.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -133,9 +133,6 @@
   let defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=+[{]};:,<.>/? ".toRunes()
   loadResource_intern(path).readTrueType(name, defaultCharset, color, resolution)
 
-proc loadFirstMesh*(path: string): Mesh =
-  loadResource_intern(path).readglTF()[0].children[0].mesh
-
 proc loadMeshes*(path: string): seq[MeshTree] =
   loadResource_intern(path).readglTF()
 
--- a/src/semicongine/resources/mesh.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/src/semicongine/resources/mesh.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -20,7 +20,7 @@
     structuredContent: JsonNode
     binaryBufferData: seq[uint8]
   MeshTree* = ref object
-    mesh*: Mesh
+    mesh*: MeshObject
     children*: seq[MeshTree]
 
 const
@@ -210,7 +210,7 @@
     setValue(result.constants["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)])
 
 
-proc addPrimitive(mesh: var Mesh, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) =
+proc addPrimitive(mesh: var MeshObject, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) =
   if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4:
     raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode")
 
@@ -253,7 +253,7 @@
         raise newException(Exception, &"Unsupported index data type: {data.thetype}")
 
 # TODO: use one mesh per primitive?? right now we are merging primitives... check addPrimitive below
-proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: seq[uint8]): Mesh =
+proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: seq[uint8]): MeshObject =
 
   # check if and how we use indexes
   var indexCount = 0
@@ -267,7 +267,7 @@
     else:
       indexType = Big
 
-  result = Mesh(instanceTransforms: @[Unit4F32], indexType: indexType)
+  result = MeshObject(instanceTransforms: @[Unit4F32], indexType: indexType)
 
   # check we have the same attributes for all primitives
   let attributes = meshNode["primitives"][0]["attributes"].keys.toSeq
--- a/src/semicongine/scene.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/src/semicongine/scene.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -9,14 +9,29 @@
     name*: string
     shaderGlobals*: Table[string, DataList]
     meshes*: seq[Mesh]
+    dirtyShaderGlobals: seq[string]
+
+proc add*(scene: var Scene, mesh: MeshObject) =
+  var tmp = new Mesh
+  tmp[] = mesh
+  scene.meshes.add tmp
+
+proc add*(scene: var Scene, mesh: Mesh) =
+  scene.meshes.add mesh
+
+# generic way to add objects that have a mesh-attribute
+proc add*[T](scene: var Scene, obj: T) =
+  scene.meshes.add obj.mesh
 
 func addShaderGlobal*[T](scene: var Scene, name: string, data: T) =
   scene.shaderGlobals[name] = newDataList(thetype=getDataType[T]())
   setValues(scene.shaderGlobals[name], @[data])
+  scene.dirtyShaderGlobals.add name
 
 func addShaderGlobalArray*[T](scene: var Scene, name: string, data: seq[T]) =
   scene.shaderGlobals[name] = newDataList(thetype=getDataType[T]())
   setValues(scene.shaderGlobals[name], data)
+  scene.dirtyShaderGlobals.add name
 
 func getShaderGlobal*[T](scene: Scene, name: string): T =
   getValues[T](scene.shaderGlobals[name])[0]
@@ -26,12 +41,24 @@
 
 func setShaderGlobal*[T](scene: var Scene, name: string, value: T) =
   setValues[T](scene.shaderGlobals[name], @[value])
+  if not scene.dirtyShaderGlobals.contains(name):
+    scene.dirtyShaderGlobals.add name
 
 func setShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) =
   setValues[T](scene.shaderGlobals[name], value)
+  if not scene.dirtyShaderGlobals.contains(name):
+    scene.dirtyShaderGlobals.add name
 
 func appendShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) =
   appendValues[T](scene.shaderGlobals[name], value)
+  if not scene.dirtyShaderGlobals.contains(name):
+    scene.dirtyShaderGlobals.add name
+
+func dirtyShaderGlobals*(scene: Scene): seq[string] =
+  scene.dirtyShaderGlobals
+
+func clearDirtyShaderGlobals*(scene: var Scene) =
+  scene.dirtyShaderGlobals.reset
 
 func hash*(scene: Scene): Hash =
   hash(scene.name)
--- a/src/semicongine/text.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/src/semicongine/text.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -22,20 +22,15 @@
   TEXT_MATERIAL* = "default-text"
   TEXT_SHADER* = createShaderConfiguration(
     inputs=[
-      attr[Mat4]("transform", memoryPerformanceHint=PreferFastWrite),
+      attr[Mat4]("transform", memoryPerformanceHint=PreferFastWrite, perInstance=true),
       attr[Vec3f]("position", memoryPerformanceHint=PreferFastWrite),
       attr[Vec2f]("uv", memoryPerformanceHint=PreferFastWrite),
     ],
     intermediates=[attr[Vec2f]("uvFrag")],
     outputs=[attr[Vec4f]("color")],
     samplers=[attr[Sampler2DType]("fontAtlas")],
-    uniforms=[
-      attr[Mat4]("perspective"),
-    ],
-    vertexCode="""gl_Position = vec4(position, 1.0) * (transform * Uniforms.perspective); uvFrag = uv;""",
-    # vertexCode="""gl_Position = vec4(position, 1.0);""",
+    vertexCode="""gl_Position = vec4(position, 1.0) * transform; uvFrag = uv;""",
     fragmentCode="""color = texture(fontAtlas, uvFrag);""",
-    # fragmentCode="""color = vec4(1, 0, 0, 1);""",
   )
 
 proc updateMesh(textbox: var Textbox) =
@@ -61,24 +56,24 @@
         top = glyph.topOffset
         bottom = glyph.topOffset + glyph.dimension.y
 
-      textbox.mesh.updateAttributeData("position", vertexOffset + 0, newVec3f(left - centerX, bottom + centerY))
-      textbox.mesh.updateAttributeData("position", vertexOffset + 1, newVec3f(left - centerX, top + centerY))
-      textbox.mesh.updateAttributeData("position", vertexOffset + 2, newVec3f(right - centerX, top + centerY))
-      textbox.mesh.updateAttributeData("position", vertexOffset + 3, newVec3f(right - centerX, bottom + centerY))
+      textbox.mesh[].updateAttributeData("position", vertexOffset + 0, newVec3f(left - centerX, bottom + centerY))
+      textbox.mesh[].updateAttributeData("position", vertexOffset + 1, newVec3f(left - centerX, top + centerY))
+      textbox.mesh[].updateAttributeData("position", vertexOffset + 2, newVec3f(right - centerX, top + centerY))
+      textbox.mesh[].updateAttributeData("position", vertexOffset + 3, newVec3f(right - centerX, bottom + centerY))
 
-      textbox.mesh.updateAttributeData("uv", vertexOffset + 0, glyph.uvs[0])
-      textbox.mesh.updateAttributeData("uv", vertexOffset + 1, glyph.uvs[1])
-      textbox.mesh.updateAttributeData("uv", vertexOffset + 2, glyph.uvs[2])
-      textbox.mesh.updateAttributeData("uv", vertexOffset + 3, glyph.uvs[3])
+      textbox.mesh[].updateAttributeData("uv", vertexOffset + 0, glyph.uvs[0])
+      textbox.mesh[].updateAttributeData("uv", vertexOffset + 1, glyph.uvs[1])
+      textbox.mesh[].updateAttributeData("uv", vertexOffset + 2, glyph.uvs[2])
+      textbox.mesh[].updateAttributeData("uv", vertexOffset + 3, glyph.uvs[3])
 
       offsetX += glyph.advance
       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())
-      textbox.mesh.updateAttributeData("position", vertexOffset + 1, newVec3f())
-      textbox.mesh.updateAttributeData("position", vertexOffset + 2, newVec3f())
-      textbox.mesh.updateAttributeData("position", vertexOffset + 3, newVec3f())
+      textbox.mesh[].updateAttributeData("position", vertexOffset + 0, newVec3f())
+      textbox.mesh[].updateAttributeData("position", vertexOffset + 1, newVec3f())
+      textbox.mesh[].updateAttributeData("position", vertexOffset + 2, newVec3f())
+      textbox.mesh[].updateAttributeData("position", vertexOffset + 3, newVec3f())
 
 
 func text*(textbox: Textbox): seq[Rune] =
@@ -104,10 +99,7 @@
   result.mesh = newMesh(positions = positions, indices = indices, uvs = uvs)
   result.mesh.material = Material(
     name: TEXT_MATERIAL,
-    # constants: {"glyphCoordinates": }.toTable,
     textures: {"fontAtlas": font.fontAtlas}.toTable,
   )
 
-  # wrap the text mesh in a new entity to preserve the font-scaling
-  result.mesh.transform = scale(1 / font.resolution, 1 / font.resolution)
   result.updateMesh()
--- a/src/semicongine/vulkan/swapchain.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -77,7 +77,6 @@
     imageCount = max(imageCount, capabilities.minImageCount)
     if capabilities.maxImageCount != 0:
       imageCount = min(imageCount, capabilities.maxImageCount)
-
   var createInfo = VkSwapchainCreateInfoKHR(
     sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
     surface: device.physicalDevice.surface,
--- a/tests/test_collision.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/tests/test_collision.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -5,9 +5,9 @@
 proc main() =
   var scene = Scene(name: "main")
 
-  scene.meshes.add rect(color="f00f")
-  scene.meshes.add rect()
-  scene.meshes.add circle(color="0f0f")
+  scene.add rect(color="f00f")
+  scene.add rect()
+  scene.add circle(color="0f0f")
   scene.meshes[1].transform = scale(0.8, 0.8)
   scene.meshes[2].transform = scale(0.1, 0.1)
   scene.addShaderGlobal("perspective", Unit4F32)
--- a/tests/test_font.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/tests/test_font.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -7,16 +7,19 @@
   var font = loadFont("DejaVuSans.ttf", color=newVec4f(1, 0.5, 0.5, 1), resolution=20)
   
   var textbox = initTextbox(32, font, "".toRunes)
-  var scene = Scene(name: "main", meshes: @[textbox.mesh])
+  var scene = Scene(name: "main")
+  scene.add textbox
+  textbox.mesh.transform = scale(0.01, 0.01)
   var engine = initEngine("Test fonts")
   engine.initRenderer()
+  scene.addShaderGlobal("perspective", Unit4F32)
   engine.addScene(scene)
-  scene.addShaderGlobal("perspective", Unit4F32)
 
   while engine.updateInputs() == Running and not engine.keyIsDown(Escape):
     if engine.windowWasResized():
       var winSize = engine.getWindow().size
       scene.setShaderGlobal("perspective", orthoWindowAspect(winSize[1] / winSize[0]))
+      textbox.mesh.transform = scale(0.01 * (winSize[1] / winSize[0]), 0.01)
     for c in [Key.A, Key.B, Key.C, Key.D, Key.E, Key.F, Key.G, Key.H, Key.I, Key.J, Key.K, Key.L, Key.M, Key.N, Key.O, Key.P, Key.Q, Key.R, Key.S, Key.T, Key.U, Key.V, Key.W, Key.X, Key.Y, Key.Z]:
       if engine.keyWasPressed(c):
         if engine.keyIsDown(ShiftL) or engine.keyIsDown(ShiftR):
--- a/tests/test_vulkan_wrapper.nim	Sat Sep 02 23:51:02 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Sun Sep 03 17:34:29 2023 +0700
@@ -168,7 +168,6 @@
         attr[Vec4f]("outcolor"),
       ],
       outputs=[attr[Vec4f]("color")],
-      uniforms=[attr[float32]("time")],
       samplers=[
         attr[Sampler2DType]("my_little_texture")
       ],
@@ -202,7 +201,6 @@
   ]
 
   for scene in scenes.mitems:
-    scene.addShaderGlobal("time", 0.0'f32)
     engine.addScene(scene)
 
   # MAINLOOP
@@ -214,7 +212,6 @@
         if engine.updateInputs() != Running or engine.keyIsDown(Escape):
           engine.destroy()
           return
-        setShaderGlobal(scene, "time", getShaderGlobal[float32](scene, "time") + 0.0005'f)
         engine.renderScene(scene)
   echo "Rendered ", engine.framesRendered, " frames"
   echo "Processed ", engine.eventsProcessed, " events"