changeset 60:c57285d292b6

did: deep refactoring of handling vertrex attribute and buffer updates, don't ask ;(
author Sam <sam@basx.dev>
date Sun, 22 Jan 2023 22:46:53 +0700
parents d7d9420ba675
children 0f04ba283558
files README.md examples/alotof_triangles.nim examples/hello_cube.nim examples/hello_triangle.nim examples/input.nim examples/squares.nim src/semicongine.nim src/semicongine/buffer.nim src/semicongine/descriptor.nim src/semicongine/engine.nim src/semicongine/math/matrix.nim src/semicongine/math/vector.nim src/semicongine/mesh.nim src/semicongine/platform/linux/xlib.nim src/semicongine/platform/windows/win32.nim src/semicongine/thing.nim src/semicongine/vertex.nim tests/test_vector.nim
diffstat 18 files changed, 446 insertions(+), 252 deletions(-) [+]
line wrap: on
line diff
--- a/README.md	Fri Jan 20 16:53:37 2023 +0700
+++ b/README.md	Sun Jan 22 22:46:53 2023 +0700
@@ -41,8 +41,10 @@
 - [ ] Depth buffering
 - [ ] Mipmaps 
 - [ ] Multisampling 
-- [~] Instanced drawing (using it currently but number of instances is hardcoded to 1
+- [~] Instanced drawing (currently can use instance attributes, but we only support a single instance per mesh)
 - [ ] Fullscreen mode + switch between modes
+- [ ] Fixed framerate
+- [ ] Allow multipel Uniform blocks
 
 Build-system:
 - [x] move all of Makefile to config.nims
@@ -71,3 +73,4 @@
 - [ ] Animation system
 - [ ] Sprite system
 - [ ] Particle system
+- [ ] Query and display rendering information from Vulkan
--- a/examples/alotof_triangles.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/examples/alotof_triangles.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -2,7 +2,6 @@
 import std/strutils
 import std/math
 import std/random
-import std/enumerate
 
 import semicongine
 
@@ -19,7 +18,8 @@
 proc randomtransform(): Mat33 =
   let randomscale = scale2d(float32(rand(1.0) + 0.5), float32(rand(1.0) + 0.5))
   let randomrotate = rotate2d(float32(rand(2 * PI)))
-  let randomtranslate = translate2d(float32(rand(1.6) - 0.8), float32(rand(1.6) - 0.8))
+  let randomtranslate = translate2d(float32(rand(1.6) - 0.8), float32(rand(
+      1.6) - 0.8))
   result = randomtranslate * randomrotate * randomscale
 
 when isMainModule:
@@ -27,11 +27,11 @@
   var myengine = igniteEngine("A lot of triangles")
   const baseTriangle = [
     Vec3([-0.1'f32, -0.1'f32, 1'f32]),
-    Vec3([ 0.1'f32,  0.1'f32, 1'f32]),
-    Vec3([-0.1'f32,  0.1'f32, 1'f32]),
+    Vec3([0.1'f32, 0.1'f32, 1'f32]),
+    Vec3([-0.1'f32, 0.1'f32, 1'f32]),
   ]
 
-  var scene = new Thing
+  var scene = newThing("scene")
 
   for i in 1 .. 300:
     var randommesh = new Mesh[VertexDataA]
@@ -43,8 +43,8 @@
           Vec2(transform1 * baseTriangle[0]),
           Vec2(transform1 * baseTriangle[1]),
           Vec2(transform1 * baseTriangle[2]),
-        ]
-      ),
+      ]
+    ),
       color22: ColorAttribute[Vec3](
         data: @[randomcolor1, randomcolor1, randomcolor1]
       )
@@ -59,17 +59,14 @@
           Vec2(transform2 * baseTriangle[0]),
           Vec2(transform2 * baseTriangle[1]),
           Vec2(transform2 * baseTriangle[2]),
-        ]
-      ),
+      ]
+    ),
       color22: ColorAttribute[Vec3](
         data: @[randomcolor2, randomcolor2, randomcolor2]
       )
     )
     randomindexedmesh.indices = @[[0'u16, 1'u16, 2'u16]]
-    var childthing = new Thing
-    childthing.parts.add randommesh
-    childthing.parts.add randomindexedmesh
-    scene.children.add childthing
+    scene.add newThing("randommesh", randommesh, randomindexedmesh)
 
   const vertexShader = generateVertexShaderCode[VertexDataA, Uniforms]()
   const fragmentShader = generateFragmentShaderCode[VertexDataA]()
--- a/examples/hello_cube.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/examples/hello_cube.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -6,11 +6,10 @@
 #
 #
 #
-#
-#
+
+
 import std/times
 import std/strutils
-import std/enumerate
 
 import semicongine
 
@@ -26,35 +25,38 @@
 
 var
   pipeline: RenderPipeline[VertexDataA, Uniforms]
-  uniforms:Uniforms
+  uniforms: Uniforms
   t: float32
 
 
 proc globalUpdate(engine: var Engine, dt: float32) =
-  let ratio = float32(engine.vulkan.frameDimension.height) / float32(engine.vulkan.frameDimension.width)
+  let ratio = float32(engine.vulkan.frameDimension.height) / float32(
+      engine.vulkan.frameDimension.width)
   t += dt
-  uniforms.model.value = translate3d(0'f32, 0'f32, 10'f32) * rotate3d(t, Yf32) #  * rotate3d(float32(PI), Yf32)
+  uniforms.model.value = translate3d(0'f32, 0'f32, 10'f32) * rotate3d(t,
+      Yf32) #  * rotate3d(float32(PI), Yf32)
 
   uniforms.view.value = Unit44f32
-  uniforms.projection.value = Mat44(data:[
+  uniforms.projection.value = Mat44(data: [
     ratio, 0'f32, 0'f32, 0'f32,
     0'f32, 1'f32, 0'f32, 0'f32,
     0'f32, 0'f32, 1'f32, 0'f32,
     0'f32, 0'f32, 0'f32, 1'f32,
   ])
-  uniforms.projection.value = perspective(float32(PI / 4), float32(engine.vulkan.frameDimension.width) / float32(engine.vulkan.frameDimension.height), 0.1'f32, 100'f32)
-  for buffer in pipeline.uniformBuffers:
-    buffer.updateData(uniforms)
+  uniforms.projection.value = perspective(float32(PI / 4), float32(
+      engine.vulkan.frameDimension.width) / float32(
+      engine.vulkan.frameDimension.height), 0.1'f32, 100'f32)
+  pipeline.updateUniformValues(uniforms)
 
 const
-  TopLeftFront =     Vec3([ -0.5'f32, -0.5'f32, -0.5'f32])
-  TopRightFront =    Vec3([  0.5'f32, -0.5'f32, -0.5'f32])
-  BottomRightFront = Vec3([  0.5'f32,  0.5'f32, -0.5'f32])
-  BottomLeftFront =  Vec3([ -0.5'f32,  0.5'f32, -0.5'f32])
-  TopLeftBack =      Vec3([  0.5'f32, -0.5'f32,  0.5'f32])
-  TopRightBack =     Vec3([ -0.5'f32, -0.5'f32,  0.5'f32])
-  BottomRightBack =  Vec3([ -0.5'f32,  0.5'f32,  0.5'f32])
-  BottomLeftBack =   Vec3([  0.5'f32,  0.5'f32,  0.5'f32])
+  TopLeftFront = Vec3([-0.5'f32, -0.5'f32, -0.5'f32])
+  TopRightFront = Vec3([0.5'f32, -0.5'f32, -0.5'f32])
+  BottomRightFront = Vec3([0.5'f32, 0.5'f32, -0.5'f32])
+  BottomLeftFront = Vec3([-0.5'f32, 0.5'f32, -0.5'f32])
+  TopLeftBack = Vec3([0.5'f32, -0.5'f32, 0.5'f32])
+  TopRightBack = Vec3([-0.5'f32, -0.5'f32, 0.5'f32])
+  BottomRightBack = Vec3([-0.5'f32, 0.5'f32, 0.5'f32])
+  BottomLeftBack = Vec3([0.5'f32, 0.5'f32, 0.5'f32])
 const
   cube_pos = @[
     TopLeftFront, TopRightFront, BottomRightFront, BottomLeftFront, # front
@@ -78,18 +80,6 @@
   let off = i * 4
   tris.add [off + 0'u16, off + 1'u16, off + 2'u16]
   tris.add [off + 2'u16, off + 3'u16, off + 0'u16]
-var off = 0'u16 * 4
-# tris.add [off + 0'u16, off + 1'u16, off + 2'u16]
-# tris.add [off + 2'u16, off + 3'u16, off + 0'u16]
-# off = 1'u16 * 4
-# tris.add [off + 0'u16, off + 1'u16, off + 2'u16]
-# tris.add [off + 2'u16, off + 3'u16, off + 0'u16]
-# off = 4'u16 * 4
-# tris.add [off + 0'u16, off + 1'u16, off + 2'u16]
-# tris.add [off + 2'u16, off + 3'u16, off + 0'u16]
-# off = 3'u16 * 4
-# tris.add [off + 0'u16, off + 1'u16, off + 2'u16]
-# tris.add [off + 2'u16, off + 3'u16, off + 0'u16]
 
 when isMainModule:
   var myengine = igniteEngine("Hello cube")
@@ -101,10 +91,7 @@
     color: ColorAttribute[Vec3](data: cube_color),
   )
   trianglemesh.indices = tris
-  # build a single-object scene graph
-  var triangle = new Thing
-  # add the triangle mesh to the object
-  triangle.parts.add trianglemesh
+  var cube = newThing("cube", trianglemesh)
 
   # upload data, prepare shaders, etc
   const vertexShader = generateVertexShaderCode[VertexDataA, Uniforms]("""
@@ -113,7 +100,7 @@
   const fragmentShader = generateFragmentShaderCode[VertexDataA]()
   pipeline = setupPipeline[VertexDataA, Uniforms, uint16](
     myengine,
-    triangle,
+    cube,
     vertexShader,
     fragmentShader
   )
--- a/examples/hello_triangle.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/examples/hello_triangle.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -9,7 +9,6 @@
   VertexDataA = object
     position: PositionAttribute[Vec2]
     color: ColorAttribute[Vec3]
-    id: InstanceAttribute[Vec3]
 
 var pipeline: RenderPipeline[VertexDataA, void]
 
@@ -19,9 +18,9 @@
 # vertex data (types must match the above VertexAttributes)
 const
   triangle_pos = @[
-    Vec2([ 0.0'f32, -0.5'f32]),
-    Vec2([ 0.5'f32,  0.5'f32]),
-    Vec2([-0.5'f32,  0.5'f32]),
+    Vec2([0.0'f32, -0.5'f32]),
+    Vec2([0.5'f32, 0.5'f32]),
+    Vec2([-0.5'f32, 0.5'f32]),
   ]
   triangle_color = @[
     Vec3([1.0'f32, 0.0'f32, 0.0'f32]),
@@ -37,12 +36,9 @@
   trianglemesh.vertexData = VertexDataA(
     position: PositionAttribute[Vec2](data: triangle_pos),
     color: ColorAttribute[Vec3](data: triangle_color),
-    id: InstanceAttribute[Vec3](data: @[Vec3([0.5'f32, 0.5'f32, 0.5'f32])]),
   )
   # build a single-object scene graph
-  var triangle = new Thing
-  # add the triangle mesh to the object
-  triangle.parts.add trianglemesh
+  var triangle = newThing("triangle", trianglemesh)
 
   # upload data, prepare shaders, etc
   const vertexShader = generateVertexShaderCode[VertexDataA, void]()
--- a/examples/input.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/examples/input.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -1,6 +1,5 @@
+import std/strutils
 import std/times
-import std/strutils
-import std/enumerate
 
 import semicongine
 
@@ -9,7 +8,12 @@
   VertexDataA = object
     position: PositionAttribute[Vec2]
     color: ColorAttribute[Vec3]
-    iscursor: GenericAttribute[int32]
+    # transform: ModelTransformAttribute
+    # TODO: make this somehow a single vertex attribute
+    m1: GenericInstanceAttribute[Vec4]
+    m2: GenericInstanceAttribute[Vec4]
+    m3: GenericInstanceAttribute[Vec4]
+    m4: GenericInstanceAttribute[Vec4]
   Uniforms = object
     projection: Descriptor[Mat44]
     cursor: Descriptor[Vec2]
@@ -17,30 +21,46 @@
 var
   pipeline: RenderPipeline[VertexDataA, Uniforms]
   uniforms: Uniforms
+  scene: Thing
+  time: float
 
 
 proc globalUpdate(engine: var Engine, dt: float32) =
-  uniforms.cursor.value[0] = float32(engine.input.mouseX)
-  uniforms.cursor.value[1] = float32(engine.input.mouseY)
+  time += dt
+  uniforms.cursor.value = engine.input.mousePos
   uniforms.projection.value = ortho[float32](
-    0'f32, float32(engine.vulkan.frameDimension.width),
-    0'f32, float32(engine.vulkan.frameDimension.height),
+    0'f32, float32(engine.vulkan.frameSize.x),
+    0'f32, float32(engine.vulkan.frameSize.y),
     0'f32, 1'f32,
   )
-  echo uniforms.projection.value
-  # echo uniforms.projection
-  for buffer in pipeline.uniformBuffers:
-    buffer.updateData(uniforms)
+  engine.vulkan.device.updateUniformData(pipeline, uniforms)
 
-# vertex data (types must match the above VertexAttributes)
+  let cursor = firstPartWithName[Mesh[VertexDataA]](scene, "cursor")
+  if cursor != nil:
+    for c in cursor.vertexData.color.data.mitems:
+      c[1] = (sin(time * 8) * 0.5 + 0.5) * 0.2
+      c[2] = (sin(time * 8) * 0.5 + 0.5) * 0.2
+    engine.vulkan.device.updateVertexData(cursor.vertexData.color)
+    var trans = Unit44 * translate3d(engine.input.mousePos.x,
+        engine.input.mousePos.y, 0'f32)
+    cursor.vertexData.m1.data = @[trans.col(0)]
+    cursor.vertexData.m2.data = @[trans.col(1)]
+    cursor.vertexData.m3.data = @[trans.col(2)]
+    cursor.vertexData.m4.data = @[trans.col(3)]
+    engine.vulkan.device.updateVertexData(cursor.vertexData.m1)
+    engine.vulkan.device.updateVertexData(cursor.vertexData.m2)
+    engine.vulkan.device.updateVertexData(cursor.vertexData.m3)
+    engine.vulkan.device.updateVertexData(cursor.vertexData.m4)
+
+
 const
   shape = @[
-    Vec2([-  1'f32, -  1'f32]),
-    Vec2([   1'f32, -  1'f32]),
+    Vec2([ - 1'f32, - 1'f32]),
+    Vec2([1'f32, - 1'f32]),
     Vec2([-0.3'f32, -0.3'f32]),
     Vec2([-0.3'f32, -0.3'f32]),
-    Vec2([-  1'f32,    1'f32]),
-    Vec2([-  1'f32, -  1'f32]),
+    Vec2([ - 1'f32, 1'f32]),
+    Vec2([ - 1'f32, - 1'f32]),
   ]
   colors = @[
     Vec3([1'f32, 0'f32, 0'f32]),
@@ -54,13 +74,15 @@
 when isMainModule:
   var myengine = igniteEngine("Input")
 
-  # build a single-object scene graph
-  var cursor = new Thing
   var cursormesh = new Mesh[VertexDataA]
   cursormesh.vertexData = VertexDataA(
-    position: PositionAttribute[Vec2](data: shape),
+    position: PositionAttribute[Vec2](data: shape, useOnDeviceMemory: true),
     color: ColorAttribute[Vec3](data: colors),
-    iscursor: GenericAttribute[int32](data: @[1'i32, 1'i32, 1'i32, 1'i32, 1'i32, 1'i32]),
+    # transform: ModelTransformAttribute(data: @[Unit44]),
+    m1: GenericInstanceAttribute[Vec4](data: @[Unit44.row(0)]),
+    m2: GenericInstanceAttribute[Vec4](data: @[Unit44.row(1)]),
+    m3: GenericInstanceAttribute[Vec4](data: @[Unit44.row(2)]),
+    m4: GenericInstanceAttribute[Vec4](data: @[Unit44.row(3)]),
   )
   # transform the cursor a bit to make it look nice
   for i in 0 ..< cursormesh.vertexData.position.data.len:
@@ -71,28 +93,40 @@
       scale2d(0.5'f32, 1'f32) *
       rotate2d(float32(PI) / 4'f32)
     )
-    let pos = Vec3([cursormesh.vertexData.position.data[i][0], cursormesh.vertexData.position.data[i][1], 1'f32])
+    let pos = Vec3([cursormesh.vertexData.position.data[i][0],
+        cursormesh.vertexData.position.data[i][1], 1'f32])
     cursormesh.vertexData.position.data[i] = (cursorscale * pos).xy
-  cursor.parts.add cursormesh
 
-  var box = new Thing
   var boxmesh = new Mesh[VertexDataA]
   boxmesh.vertexData = VertexDataA(
     position: PositionAttribute[Vec2](data: shape),
     color: ColorAttribute[Vec3](data: colors),
-    iscursor: GenericAttribute[int32](data: @[1'i32, 1'i32, 1'i32, 1'i32, 1'i32, 1'i32]),
+    # transform: ModelTransformAttribute(data: @[Unit44]),
+    m1: GenericInstanceAttribute[Vec4](data: @[Unit44.row(0)]),
+    m2: GenericInstanceAttribute[Vec4](data: @[Unit44.row(1)]),
+    m3: GenericInstanceAttribute[Vec4](data: @[Unit44.row(2)]),
+    m4: GenericInstanceAttribute[Vec4](data: @[Unit44.row(3)]),
   )
+  for i in 0 ..< boxmesh.vertexData.position.data.len:
+    let boxscale = translate2d(100'f32, 100'f32) * scale2d(100'f32, 100'f32)
+    let pos = Vec3([boxmesh.vertexData.position.data[i][0],
+        boxmesh.vertexData.position.data[i][1], 1'f32])
+    boxmesh.vertexData.position.data[i] = (boxscale * pos).xy
+  echo boxmesh.vertexData.position.data
 
-  var scene = new Thing
-  scene.children.add cursor
+  scene = newThing("scene")
+  scene.add newThing("cursor", cursormesh)
+  scene.add newThing("a box", boxmesh, newTransform(Unit44), newTransform(
+      translate3d(1'f32, 0'f32, 0'f32)))
+  scene.add newTransform(scale3d(1.5'f32, 1.5'f32, 1.5'f32))
 
   # upload data, prepare shaders, etc
   const vertexShader = generateVertexShaderCode[VertexDataA, Uniforms]("""
-    out_position = uniforms.projection * vec4(in_position + (uniforms.cursor * iscursor), 0, 1);
+    mat4 mat = mat4(m1, m2, m3, m4);
+    out_position = uniforms.projection * mat * vec4(position, 0, 1);
   """)
+  echo vertexShader
   const fragmentShader = generateFragmentShaderCode[VertexDataA]()
-  echo vertexShader
-  echo fragmentShader
   pipeline = setupPipeline[VertexDataA, Uniforms, uint16](
     myengine,
     scene,
--- a/examples/squares.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/examples/squares.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -2,7 +2,6 @@
 import std/strutils
 import std/math
 import std/random
-import std/enumerate
 
 import semicongine
 
@@ -20,8 +19,7 @@
 
 proc globalUpdate(engine: var Engine, dt: float32) =
   uniformdata.t.value += dt
-  for buffer in pipeline.uniformBuffers:
-    buffer.updateData(uniformdata)
+  pipeline.updateUniformValues(uniformdata)
 
 when isMainModule:
   randomize()
@@ -57,11 +55,14 @@
       iValues[vertIndex + 1] = uint32(squareIndex)
       iValues[vertIndex + 2] = uint32(squareIndex)
       iValues[vertIndex + 3] = uint32(squareIndex)
-      indices[squareIndex * 2 + 0] = [uint16(vertIndex + 0), uint16(vertIndex + 1), uint16(vertIndex + 2)]
-      indices[squareIndex * 2 + 1] = [uint16(vertIndex + 2), uint16(vertIndex + 3), uint16(vertIndex + 0)]
+      indices[squareIndex * 2 + 0] = [uint16(vertIndex + 0), uint16(vertIndex +
+          1), uint16(vertIndex + 2)]
+      indices[squareIndex * 2 + 1] = [uint16(vertIndex + 2), uint16(vertIndex +
+          3), uint16(vertIndex + 0)]
 
 
-  type PIndexedMesh = ref IndexedMesh[VertexDataA, uint16] # required so we can use ctor with ref/on heap
+  type PIndexedMesh = ref IndexedMesh[VertexDataA,
+      uint16] # required so we can use ctor with ref/on heap
   var squaremesh = PIndexedMesh(
     vertexData: VertexDataA(
       position11: PositionAttribute[Vec2](data: @vertices),
@@ -70,10 +71,7 @@
     ),
     indices: @indices
   )
-  var scene = new Thing
-  var childthing = new Thing
-  childthing.parts.add squaremesh
-  scene.children.add childthing
+  var scene = newThing("scene", newThing("squares", squaremesh))
 
   const vertexShader = generateVertexShaderCode[VertexDataA, Uniforms](
     """
--- a/src/semicongine.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -8,14 +8,16 @@
 import semicongine/shader
 import semicongine/buffer
 import semicongine/events
+import semicongine/window
 
 export vector
 export matrix
 export engine
 export vertex
 export descriptor
+export thing
 export mesh
-export thing
 export shader
 export buffer
 export events
+export window
--- a/src/semicongine/buffer.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/buffer.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -1,3 +1,5 @@
+import std/typetraits
+
 import ./vulkan
 import ./vulkan_helpers
 
@@ -9,17 +11,24 @@
     UniformBuffer = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
     IndexBuffer = VK_BUFFER_USAGE_INDEX_BUFFER_BIT
     VertexBuffer = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
+  MemoryProperty* = enum
+    DeviceLocal = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+    HostVisible = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+    HostCoherent = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+  MemoryProperties* = set[MemoryProperty]
   Buffer* = object
     device*: VkDevice
     vkBuffer*: VkBuffer
     size*: uint64
     memoryRequirements*: VkMemoryRequirements
+    memoryProperties*: MemoryProperties
     memory*: VkDeviceMemory
     bufferTypes*: set[BufferType]
-    persistentMapping: bool
-    mapped: pointer
+    data*: pointer
 
 proc trash*(buffer: var Buffer) =
+  if int64(buffer.vkBuffer) != 0 and int64(buffer.memory) != 0:
+    vkUnmapMemory(buffer.device, buffer.memory)
   if int64(buffer.vkBuffer) != 0:
     vkDestroyBuffer(buffer.device, buffer.vkBuffer, nil)
     buffer.vkBuffer = VkBuffer(0)
@@ -27,12 +36,13 @@
     vkFreeMemory(buffer.device, buffer.memory, nil)
     buffer.memory = VkDeviceMemory(0)
 
-proc findMemoryType(buffer: Buffer, physicalDevice: VkPhysicalDevice, properties: VkMemoryPropertyFlags): uint32 =
+proc findMemoryType(buffer: Buffer, physicalDevice: VkPhysicalDevice, properties: MemoryProperties): uint32 =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
   vkGetPhysicalDeviceMemoryProperties(physicalDevice, addr(physicalProperties))
 
   for i in 0'u32 ..< physicalProperties.memoryTypeCount:
-    if bool(buffer.memoryRequirements.memoryTypeBits and (1'u32 shl i)) and (uint32(physicalProperties.memoryTypes[i].propertyFlags) and uint32(properties)) == uint32(properties):
+    if bool(buffer.memoryRequirements.memoryTypeBits and (1'u32 shl i)):
+      if (uint32(physicalProperties.memoryTypes[i].propertyFlags) and cast[uint32](properties)) == cast[uint32](properties):
         return i
 
 proc InitBuffer*(
@@ -40,10 +50,9 @@
   physicalDevice: VkPhysicalDevice,
   size: uint64,
   bufferTypes: set[BufferType],
-  properties: set[VkMemoryPropertyFlagBits],
-  persistentMapping: bool = false
+  properties: MemoryProperties,
 ): Buffer =
-  result = Buffer(device: device, size: size, bufferTypes: bufferTypes, persistentMapping: persistentMapping)
+  result = Buffer(device: device, size: size, bufferTypes: bufferTypes, memoryProperties: properties)
   var usageFlags = 0
   for usage in bufferTypes:
     usageFlags = ord(usageFlags) or ord(usage)
@@ -56,46 +65,28 @@
   checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr(result.vkBuffer))
   vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr(result.memoryRequirements))
 
-  var memoryProperties = 0'u32
-  for prop in properties:
-    memoryProperties = memoryProperties or uint32(prop)
-
   var allocInfo = VkMemoryAllocateInfo(
     sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
     allocationSize: result.memoryRequirements.size,
-    memoryTypeIndex: result.findMemoryType(physicalDevice, VkMemoryPropertyFlags(memoryProperties))
+    memoryTypeIndex: result.findMemoryType(physicalDevice, properties)
   )
   if result.size > 0:
     checkVkResult result.device.vkAllocateMemory(addr(allocInfo), nil, addr(result.memory))
   checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory, VkDeviceSize(0))
-  if persistentMapping:
-    checkVkResult vkMapMemory(
-      result.device,
-      result.memory,
-      offset=VkDeviceSize(0),
-      VkDeviceSize(result.size),
-      VkMemoryMapFlags(0),
-      addr(result.mapped)
-    )
+  checkVkResult vkMapMemory(
+    result.device,
+    result.memory,
+    offset=VkDeviceSize(0),
+    VkDeviceSize(result.size),
+    VkMemoryMapFlags(0),
+    addr(result.data)
+  )
 
 
-template withMapping*(buffer: Buffer, data: pointer, body: untyped): untyped =
-  assert not buffer.persistentMapping
-  checkVkResult vkMapMemory(buffer.device, buffer.memory, offset=VkDeviceSize(0), VkDeviceSize(buffer.size), VkMemoryMapFlags(0), addr(data))
-  body
-  vkUnmapMemory(buffer.device, buffer.memory)
-
-# note: does not work with seq, because of sizeof
-proc updateData*[T](buffer: Buffer, data: var T) =
-  if buffer.persistentMapping:
-    copyMem(buffer.mapped, addr(data), sizeof(T))
-  else:
-    var p: pointer
-    buffer.withMapping(p):
-      copyMem(p, addr(data), sizeof(T))
-
-proc copyBuffer*(commandPool: VkCommandPool, queue: VkQueue, src, dst: Buffer, size: uint64) =
+proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src, dst: Buffer, size: uint64) =
   assert uint64(src.device) == uint64(dst.device)
+  assert TransferSrc in src.bufferTypes
+  assert TransferDst in dst.bufferTypes
   var
     allocInfo = VkCommandBufferAllocateInfo(
       sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
@@ -124,3 +115,4 @@
   checkVkResult vkQueueSubmit(queue, 1, addr(submitInfo), VkFence(0))
   checkVkResult vkQueueWaitIdle(queue)
   vkFreeCommandBuffers(src.device, commandPool, 1, addr(commandBuffer))
+
--- a/src/semicongine/descriptor.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/descriptor.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -41,8 +41,7 @@
       physicalDevice,
       uint64(size),
       {UniformBuffer},
-      {VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT},
-      persistentMapping=true,
+      {HostVisible, HostCoherent},
     )
     result[i] = buffer
 
--- a/src/semicongine/engine.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/engine.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -1,3 +1,4 @@
+import std/options
 import std/times
 import std/typetraits
 import std/strformat
@@ -5,6 +6,7 @@
 import std/logging
 
 
+import ./math/vector
 import ./vulkan
 import ./vulkan_helpers
 import ./window
@@ -31,12 +33,14 @@
 
 type
   Device = object
-    device: VkDevice
-    physicalDevice: PhysicalDevice
-    graphicsQueueFamily: uint32
-    presentationQueueFamily: uint32
-    graphicsQueue: VkQueue
-    presentationQueue: VkQueue
+    device*: VkDevice
+    physicalDevice*: PhysicalDevice
+    graphicsQueueFamily*: uint32
+    presentationQueueFamily*: uint32
+    graphicsQueue*: VkQueue
+    presentationQueue*: VkQueue
+    commandPool*: VkCommandPool
+    commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]
   Swapchain = object
     swapchain: VkSwapchainKHR
     images: seq[VkImage]
@@ -70,12 +74,10 @@
     device*: Device
     surface*: VkSurfaceKHR
     surfaceFormat*: VkSurfaceFormatKHR
-    frameDimension*: VkExtent2D
+    frameSize*: TVec2[uint32]
     swapchain*: Swapchain
     framebuffers*: seq[VkFramebuffer]
     renderPass*: VkRenderPass
-    commandPool*: VkCommandPool
-    commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]
     imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
     renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
     inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence]
@@ -86,12 +88,11 @@
     mouseDown*: set[MouseButton]
     mousePressed*: set[MouseButton]
     mouseReleased*: set[MouseButton]
-    mouseX*: int
-    mouseY*: int
+    mousePos*: Vec2
   Engine* = object
     vulkan*: Vulkan
     window*: NativeWindow
-    currentscenedata*: ref Thing
+    currentscenedata*: Thing
     input*: Input
 
 proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[PhysicalDevice] =
@@ -129,16 +130,16 @@
     debug(&"Viable device: {cleanString(device.properties.deviceName)} (graphics queue family {graphicsQueueFamily}, presentation queue family {presentationQueueFamily})")
 
 
-proc getFrameDimension(window: NativeWindow, device: VkPhysicalDevice, surface: VkSurfaceKHR): VkExtent2D =
+proc getFrameDimension(window: NativeWindow, device: VkPhysicalDevice, surface: VkSurfaceKHR): TVec2[uint32] =
   let capabilities = device.getSurfaceCapabilities(surface)
   if capabilities.currentExtent.width != high(uint32):
-    return capabilities.currentExtent
+    return TVec2[uint32]([capabilities.currentExtent.width, capabilities.currentExtent.height])
   else:
     let (width, height) = window.size()
-    return VkExtent2D(
-      width: min(max(uint32(width), capabilities.minImageExtent.width), capabilities.maxImageExtent.width),
-      height: min(max(uint32(height), capabilities.minImageExtent.height), capabilities.maxImageExtent.height),
-    )
+    return TVec2[uint32]([
+      min(max(uint32(width), capabilities.minImageExtent.width), capabilities.maxImageExtent.width),
+      min(max(uint32(height), capabilities.minImageExtent.height), capabilities.maxImageExtent.height),
+    ])
 
 when DEBUG_LOG:
   proc setupDebugLog(instance: VkInstance): VkDebugUtilsMessengerEXT =
@@ -176,12 +177,13 @@
     result.presentationQueueFamily,
   )
 
-proc setupSwapChain(device: VkDevice, physicalDevice: PhysicalDevice, surface: VkSurfaceKHR, dimension: VkExtent2D, surfaceFormat: VkSurfaceFormatKHR): Swapchain =
+proc setupSwapChain(device: VkDevice, physicalDevice: PhysicalDevice, surface: VkSurfaceKHR, dimension: TVec2[uint32], surfaceFormat: VkSurfaceFormatKHR): Swapchain =
 
   let capabilities = physicalDevice.device.getSurfaceCapabilities(surface)
   var selectedPresentationMode = getPresentMode(physicalDevice.presentModes)
   var imageCount = capabilities.minImageCount + 1
-  imageCount = max(min(capabilities.maxImageCount, imageCount), 1)
+  if capabilities.maxImageCount > 0:
+    imageCount = min(capabilities.maxImageCount, imageCount)
   # setup swapchain
   var swapchainCreateInfo = VkSwapchainCreateInfoKHR(
     sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
@@ -189,7 +191,7 @@
     minImageCount: imageCount,
     imageFormat: surfaceFormat.format,
     imageColorSpace: surfaceFormat.colorSpace,
-    imageExtent: dimension,
+    imageExtent: VkExtent2D(width: dimension[0], height: dimension[1]),
     imageArrayLayers: 1,
     imageUsage: VkImageUsageFlags(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
     # VK_SHARING_MODE_CONCURRENT no supported (i.e cannot use different queue families for  drawing to swap surface?)
@@ -268,7 +270,7 @@
     )
   checkVkResult device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result))
 
-proc initRenderPipeline[VertexType, Uniforms](device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, Uniforms] =
+proc initRenderPipeline[VertexType, Uniforms](device: VkDevice, frameSize: TVec2[uint32], renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, Uniforms] =
   # load shaders
   result.device = device
   result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader))
@@ -396,7 +398,7 @@
     addr(result.pipeline)
   )
 
-proc setupFramebuffers(device: VkDevice, swapchain: var Swapchain, renderPass: VkRenderPass, dimension: VkExtent2D): seq[VkFramebuffer] =
+proc setupFramebuffers(device: VkDevice, swapchain: var Swapchain, renderPass: VkRenderPass, dimension: TVec2[uint32]): seq[VkFramebuffer] =
   result = newSeq[VkFramebuffer](swapchain.images.len)
   for i, imageview in enumerate(swapchain.imageviews):
     var framebufferInfo = VkFramebufferCreateInfo(
@@ -404,8 +406,8 @@
       renderPass: renderPass,
       attachmentCount: 1,
       pAttachments: addr(swapchain.imageviews[i]),
-      width: dimension.width,
-      height: dimension.height,
+      width: dimension[0],
+      height: dimension[1],
       layers: 1,
     )
     checkVkResult device.vkCreateFramebuffer(addr(framebufferInfo), nil, addr(result[i]))
@@ -418,9 +420,9 @@
   device.vkDestroySwapchainKHR(swapchain.swapchain, nil)
 
 proc recreateSwapchain(vulkan: Vulkan): (Swapchain, seq[VkFramebuffer]) =
-  if vulkan.frameDimension.width == 0 or vulkan.frameDimension.height == 0:
+  if vulkan.frameSize.x == 0 or vulkan.frameSize.y == 0:
     return (vulkan.swapchain, vulkan.framebuffers)
-  debug(&"Recreate swapchain with dimension {vulkan.frameDimension}")
+  debug(&"Recreate swapchain with dimension {vulkan.frameSize}")
   checkVkResult vulkan.device.device.vkDeviceWaitIdle()
 
   vulkan.device.device.trash(vulkan.swapchain, vulkan.framebuffers)
@@ -428,13 +430,13 @@
   result[0] = vulkan.device.device.setupSwapChain(
     vulkan.device.physicalDevice,
     vulkan.surface,
-    vulkan.frameDimension,
+    vulkan.frameSize,
     vulkan.surfaceFormat
   )
   result[1] = vulkan.device.device.setupFramebuffers(
     result[0],
     vulkan.renderPass,
-    vulkan.frameDimension
+    vulkan.frameSize
   )
 
 
@@ -473,6 +475,9 @@
 proc igniteEngine*(windowTitle: string): Engine =
 
   result.window = createWindow(windowTitle)
+  let mousepos = result.window.getMousePosition()
+  if mousepos.isSome():
+    result.input.mousePos = mousePos.get()
 
   # setup vulkan functions
   vkLoad1_0()
@@ -488,24 +493,24 @@
 
   # get basic frame information
   result.vulkan.surfaceFormat = result.vulkan.device.physicalDevice.formats.getSuitableSurfaceFormat()
-  result.vulkan.frameDimension = result.window.getFrameDimension(result.vulkan.device.physicalDevice.device, result.vulkan.surface)
+  result.vulkan.frameSize = result.window.getFrameDimension(result.vulkan.device.physicalDevice.device, result.vulkan.surface)
 
   # setup swapchain and render pipeline
   result.vulkan.swapchain = result.vulkan.device.device.setupSwapChain(
     result.vulkan.device.physicalDevice,
     result.vulkan.surface,
-    result.vulkan.frameDimension,
+    result.vulkan.frameSize,
     result.vulkan.surfaceFormat
   )
   result.vulkan.renderPass = result.vulkan.device.device.setupRenderPass(result.vulkan.surfaceFormat.format)
   result.vulkan.framebuffers = result.vulkan.device.device.setupFramebuffers(
     result.vulkan.swapchain,
     result.vulkan.renderPass,
-    result.vulkan.frameDimension
+    result.vulkan.frameSize
   )
   (
-    result.vulkan.commandPool,
-    result.vulkan.commandBuffers,
+    result.vulkan.device.commandPool,
+    result.vulkan.device.commandBuffers,
   ) = result.vulkan.device.device.setupCommandBuffers(result.vulkan.device.graphicsQueueFamily)
 
   (
@@ -515,31 +520,24 @@
   ) = result.vulkan.device.device.setupSyncPrimitives()
 
 
-proc setupPipeline*[VertexType; UniformType; IndexType: uint16|uint32](engine: var Engine, scenedata: ref Thing, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, UniformType] =
+proc setupPipeline*[VertexType; UniformType; IndexType: uint16|uint32](engine: var Engine, scenedata: Thing, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, UniformType] =
   engine.currentscenedata = scenedata
   result = initRenderPipeline[VertexType, UniformType](
     engine.vulkan.device.device,
-    engine.vulkan.frameDimension,
+    engine.vulkan.frameSize,
     engine.vulkan.renderPass,
     vertexShader,
     fragmentShader,
   )
+
   # vertex buffers
-  var allmeshes: seq[Mesh[VertexType]]
-  for mesh in partsOfType[ref Mesh[VertexType]](engine.currentscenedata):
-    allmeshes.add(mesh[])
-  if allmeshes.len > 0:
-    var ubermesh = createUberMesh(allmeshes)
-    result.vertexBuffers.add createVertexBuffers(ubermesh, result.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue)
+  for mesh in allPartsOfType[Mesh[VertexType]](engine.currentscenedata):
+    result.vertexBuffers.add createVertexBuffers(mesh, result.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.device.commandPool, engine.vulkan.device.graphicsQueue)
 
+  # vertex buffers with indexes
   when not (IndexType is void):
-    # vertex buffers with indexes
-    var allindexedmeshes: seq[IndexedMesh[VertexType, IndexType]]
-    for mesh in partsOfType[ref IndexedMesh[VertexType, IndexType]](engine.currentscenedata):
-      allindexedmeshes.add(mesh[])
-    if allindexedmeshes.len > 0:
-      var indexedubermesh = createUberMesh(allindexedmeshes)
-      result.indexedVertexBuffers.add createIndexedVertexBuffers(indexedubermesh, result.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue)
+    for mesh in allPartsOfType[IndexedMesh[VertexType, IndexType]](engine.currentscenedata):
+      result.indexedVertexBuffers.add createIndexedVertexBuffers(mesh, result.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.device.commandPool, engine.vulkan.device.graphicsQueue)
 
   # uniform buffers
   when not (UniformType is void):
@@ -592,6 +590,31 @@
         )
       vkUpdateDescriptorSets(result.device, 1, addr(descriptorWrite), 0, nil)
 
+proc updateBufferData*[T](device: Device, buffer: Buffer, data: var T) =
+  when stripGenericParams(T) is seq: # seq needs special treatment for automated data uploading
+    assert data.len > 0
+    let size = data.len * sizeof(get(genericParams(typeof(data)), 0))
+    let dataptr = addr(data[0])
+  else:
+    let size = sizeof(data)
+    let dataptr = addr(data)
+  if not (HostVisible in buffer.memoryProperties):
+    if not (TransferDst in buffer.bufferTypes):
+      raise newException(Exception, "Buffer cannot be updated")
+    var stagingBuffer = device.device.InitBuffer(device.physicalDevice.device, uint64(size), {TransferSrc}, {HostVisible, HostCoherent})
+    copyMem(stagingBuffer.data, dataptr, size)
+    transferBuffer(device.commandPool, device.graphicsQueue, stagingBuffer, buffer, uint64(size))
+    stagingBuffer.trash()
+  else:
+    copyMem(buffer.data, dataptr, size)
+
+proc updateVertexData*[T: VertexAttribute](device: Device, vertexAttribute: var T) =
+  device.updateBufferData(vertexAttribute.buffer, vertexAttribute.data)
+
+proc updateUniformData*[VertexType, Uniforms](device: Device, pipeline: RenderPipeline[VertexType, Uniforms], data: var Uniforms) =
+  for buffer in pipeline.uniformBuffers:
+    device.updateBufferData(buffer, data)
+
 
 proc runPipeline[VertexType; Uniforms](commandBuffer: VkCommandBuffer, pipeline: var RenderPipeline[VertexType, Uniforms], currentFrame: int) =
   vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline)
@@ -620,7 +643,7 @@
     vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType)
     vkCmdDrawIndexed(commandBuffer, indicesCount, 1, 0, 0, 0)
 
-proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D, currentFrame: int) =
+proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameSize: TVec2[uint32], currentFrame: int) =
   var
     beginInfo = VkCommandBufferBeginInfo(
       sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@@ -633,7 +656,7 @@
       framebuffer: framebuffer,
       renderArea: VkRect2D(
         offset: VkOffset2D(x: 0, y: 0),
-        extent: frameDimension,
+        extent: VkExtent2D(width: frameSize.x, height: frameSize.y),
       ),
       clearValueCount: 1,
       pClearValues: addr(clearColor),
@@ -641,14 +664,14 @@
     viewport = VkViewport(
       x: 0.0,
       y: 0.0,
-      width: (float) frameDimension.width,
-      height: (float) frameDimension.height,
+      width: (float) frameSize.x,
+      height: (float) frameSize.y,
       minDepth: 0.0,
       maxDepth: 1.0,
     )
     scissor = VkRect2D(
       offset: VkOffset2D(x: 0, y: 0),
-      extent: frameDimension
+      extent: VkExtent2D(width: frameSize.x, height: frameSize.y)
     )
   checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo))
   block:
@@ -671,14 +694,14 @@
     addr(bufferImageIndex)
   )
   if nextImageResult == VK_ERROR_OUT_OF_DATE_KHR:
-    vulkan.frameDimension = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
+    vulkan.frameSize = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
     (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
   elif not (nextImageResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]):
     raise newException(Exception, "Vulkan error: vkAcquireNextImageKHR returned " & $nextImageResult)
   checkVkResult vkResetFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]))
 
-  checkVkResult vkResetCommandBuffer(vulkan.commandBuffers[currentFrame], VkCommandBufferResetFlags(0))
-  vulkan.renderPass.recordCommandBuffer(pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension, currentFrame)
+  checkVkResult vkResetCommandBuffer(vulkan.device.commandBuffers[currentFrame], VkCommandBufferResetFlags(0))
+  vulkan.renderPass.recordCommandBuffer(pipeline, vulkan.device.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameSize, currentFrame)
   var
     waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]]
     waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
@@ -689,7 +712,7 @@
       pWaitSemaphores: addr(waitSemaphores[0]),
       pWaitDstStageMask: addr(waitStages[0]),
       commandBufferCount: 1,
-      pCommandBuffers: addr(vulkan.commandBuffers[currentFrame]),
+      pCommandBuffers: addr(vulkan.device.commandBuffers[currentFrame]),
       signalSemaphoreCount: 1,
       pSignalSemaphores: addr(signalSemaphores[0]),
     )
@@ -707,7 +730,7 @@
   let presentResult = vkQueuePresentKHR(vulkan.device.presentationQueue, addr(presentInfo))
 
   if presentResult == VK_ERROR_OUT_OF_DATE_KHR or presentResult == VK_SUBOPTIMAL_KHR or resized:
-    vulkan.frameDimension = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
+    vulkan.frameSize = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
     (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
 
 
@@ -743,10 +766,7 @@
           engine.input.mouseReleased.incl event.button
           engine.input.mouseDown.excl event.button
         of MouseMoved:
-          engine.input.mouseX = event.x
-          engine.input.mouseY = event.y
-        else:
-          discard
+          engine.input.mousePos = Vec2([float32(event.x), float32(event.y)])
     if killed: # at least on windows we should return immediately as swapchain recreation will fail after kill
       break
 
@@ -756,8 +776,10 @@
       dt = float32(float64((now - lastUpdate).inNanoseconds) / 1_000_000_000'f64)
     lastUpdate = now
     engine.globalUpdate(dt)
-    for entity in allEntities(engine.currentscenedata):
-      entity.update(dt)
+    for thing in allThings(engine.currentscenedata):
+      for part in thing.parts:
+        part.update(dt)
+      thing.update(dt)
 
     # submit frame for drawing
     engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline)
@@ -793,7 +815,7 @@
     engine.vulkan.device.device.vkDestroyFence(engine.vulkan.inFlightFences[i], nil)
 
   engine.vulkan.device.device.vkDestroyRenderPass(engine.vulkan.renderPass, nil)
-  engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.commandPool, nil)
+  engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.device.commandPool, nil)
 
   engine.vulkan.instance.vkDestroySurfaceKHR(engine.vulkan.surface, nil)
   engine.vulkan.device.device.vkDestroyDevice(nil)
--- a/src/semicongine/math/matrix.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/math/matrix.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -81,9 +81,9 @@
 
 generateAllConsts()
 
-const Unit22* = unit22[float]()
-const Unit33* = unit33[float]()
-const Unit44* = unit44[float]()
+const Unit22* = unit22[float32]()
+const Unit33* = unit33[float32]()
+const Unit44* = unit44[float32]()
 
 template rowCount*(m: typedesc): int =
   when m is TMat22: 2
--- a/src/semicongine/math/vector.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/math/vector.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -189,7 +189,7 @@
   if accessorvalue.len == 0:
     raise newException(Exception, "empty attribute")
   elif accessorvalue.len == 1:
-    ret = nnkBracket.newTree(ident("value"), newLit(ACCESSOR_INDICES[accessorvalue[0]]))
+    ret = nnkBracketExpr.newTree(ident("value"), newLit(ACCESSOR_INDICES[accessorvalue[0]]))
   if accessorvalue.len > 1:
     var attrs = nnkBracket.newTree()
     for attrname in accessorvalue:
--- a/src/semicongine/mesh.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/mesh.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -1,3 +1,4 @@
+import std/options
 import std/typetraits
 
 import ./vulkan
@@ -7,9 +8,9 @@
 import ./math/vector
 
 type
-  Mesh*[T] = object of Part
+  Mesh*[T] = ref object of Part
     vertexData*: T
-  IndexedMesh*[T: object, U: uint16|uint32] = object of Part
+  IndexedMesh*[T: object, U: uint16|uint32] = ref object of Part
     vertexData*: T
     indices*: seq[array[3, U]]
 
@@ -40,33 +41,32 @@
   elif U is uint32: VK_INDEX_TYPE_UINT32
       
 proc createVertexBuffers*[M: Mesh|IndexedMesh](
-  mesh: var M,
+  mesh: M,
   device: VkDevice,
   physicalDevice: VkPhysicalDevice,
   commandPool: VkCommandPool,
   queue: VkQueue,
-  useDeviceLocalBuffer: bool = true # decides if data is transfered to the fast device-local memory or not
 ): (seq[Buffer], uint32) =
   result[1] = mesh.vertexData.VertexCount
   for name, value in mesh.vertexData.fieldPairs:
     when typeof(value) is VertexAttribute:
       assert value.data.len > 0
-      var flags = if useDeviceLocalBuffer: {TransferSrc} else: {VertexBuffer}
-      var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize, flags, {VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT})
-      var d: pointer
-      stagingBuffer.withMapping(d):
-        copyMem(d, addr(value.data[0]), value.datasize)
+      var flags = if value.useOnDeviceMemory: {TransferSrc} else: {VertexBuffer}
+      var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize, flags, {HostVisible, HostCoherent})
+      copyMem(stagingBuffer.data, addr(value.data[0]), value.datasize)
 
-      if useDeviceLocalBuffer:
-        var finalBuffer = device.InitBuffer(physicalDevice, value.datasize, {TransferDst, VertexBuffer}, {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT})
-        copyBuffer(commandPool, queue, stagingBuffer, finalBuffer, value.datasize)
+      if value.useOnDeviceMemory:
+        var finalBuffer = device.InitBuffer(physicalDevice, value.datasize, {TransferDst, VertexBuffer}, {DeviceLocal})
+        transferBuffer(commandPool, queue, stagingBuffer, finalBuffer, value.datasize)
         stagingBuffer.trash()
         result[0].add(finalBuffer)
+        value.buffer = finalBuffer
       else:
         result[0].add(stagingBuffer)
+        value.buffer = stagingBuffer
 
 proc createIndexBuffer*(
-  mesh: var IndexedMesh,
+  mesh: IndexedMesh,
   device: VkDevice,
   physicalDevice: VkPhysicalDevice,
   commandPool: VkCommandPool,
@@ -76,29 +76,27 @@
   let bufferSize = uint64(mesh.indices.len * sizeof(get(genericParams(typeof(mesh.indices)), 0)))
   let flags = if useDeviceLocalBuffer: {TransferSrc} else: {IndexBuffer}
 
-  var stagingBuffer = device.InitBuffer(physicalDevice, bufferSize, flags, {VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT})
-  var d: pointer
-  stagingBuffer.withMapping(d):
-    copyMem(d, addr(mesh.indices[0]), bufferSize)
+  var stagingBuffer = device.InitBuffer(physicalDevice, bufferSize, flags, {HostVisible, HostCoherent})
+  copyMem(stagingBuffer.data, addr(mesh.indices[0]), bufferSize)
 
   if useDeviceLocalBuffer:
-    var finalBuffer = device.InitBuffer(physicalDevice, bufferSize, {TransferDst, IndexBuffer}, {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT})
-    copyBuffer(commandPool, queue, stagingBuffer, finalBuffer, bufferSize)
+    var finalBuffer = device.InitBuffer(physicalDevice, bufferSize, {TransferDst, IndexBuffer}, {DeviceLocal})
+    transferBuffer(commandPool, queue, stagingBuffer, finalBuffer, bufferSize)
     stagingBuffer.trash()
     return finalBuffer
   else:
     return stagingBuffer
 
 proc createIndexedVertexBuffers*(
-  mesh: var IndexedMesh,
+  mesh: IndexedMesh,
   device: VkDevice,
   physicalDevice: VkPhysicalDevice,
   commandPool: VkCommandPool,
   queue: VkQueue,
-  useDeviceLocalBuffer: bool = true # decides if data is transfered to the fast device-local memory or not
+  useDeviceLocalBufferForIndices: bool = true # decides if data is transfered to the fast device-local memory or not
 ): (seq[Buffer], Buffer, uint32, VkIndexType) =
-  result[0] = createVertexBuffers(mesh, device, physicalDevice, commandPool, queue, useDeviceLocalBuffer)[0]
-  result[1] = createIndexBuffer(mesh, device, physicalDevice, commandPool, queue, useDeviceLocalBuffer)
+  result[0] = createVertexBuffers(mesh, device, physicalDevice, commandPool, queue)[0]
+  result[1] = createIndexBuffer(mesh, device, physicalDevice, commandPool, queue, useDeviceLocalBufferForIndices)
   result[2] = uint32(mesh.indices.len * mesh.indices[0].len)
 
   result[3] = getVkIndexType(mesh)
--- a/src/semicongine/platform/linux/xlib.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/platform/linux/xlib.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -1,3 +1,4 @@
+import std/options
 import
   x11/xlib,
   x11/xutil,
@@ -7,6 +8,7 @@
 import x11/x
 
 import ../../events
+import ../../math/vector
 
 import ./symkey_map
 
@@ -92,3 +94,28 @@
       result.add Event(eventType: ResizedWindow)
     else:
       discard
+
+proc getMousePosition*(window: NativeWindow): Option[Vec2] =
+  var
+    root: Window
+    win: Window
+    rootX: cint
+    rootY: cint
+    winX: cint
+    winY: cint
+    mask: cuint
+    onscreen = XQueryPointer(
+      window.display,
+      window.window,
+      addr(root),
+      addr(win),
+      addr(rootX),
+      addr(rootX),
+      addr(winX),
+      addr(winY),
+      addr(mask),
+    )
+  if onscreen == 0:
+    return none(Vec2)
+  return some(Vec2([float32(winX), float32(winY)]))
+
--- a/src/semicongine/platform/windows/win32.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/platform/windows/win32.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -1,7 +1,9 @@
+import std/options
 import winim
 
 import ./virtualkey_map
 import ../../events
+import ../../math/vector
 
 type
   NativeWindow* = object
@@ -103,3 +105,10 @@
     TranslateMessage(addr(msg))
     DispatchMessage(addr(msg))
   return currentEvents
+
+proc getMousePosition*(window: NativeWindow): Option[Vec2] =
+  var p: POINT
+  let res = GetCursorPos(addr(p))
+  if res:
+    return some(Vec2([float32(p.x), float32(p.y)]))
+  return none(Vec2)
--- a/src/semicongine/thing.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/thing.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -1,17 +1,80 @@
-{.experimental: "codeReordering".}
+import std/strformat
+import std/typetraits
+
+import ./vertex
+import ./math/matrix
 
 type
-  Part* = object of RootObj
-    thing: ref Thing
+  Part* = ref object of RootObj
+    thing: Thing
+  Transform* = ref object of Part
+    mat: Mat44
+
+  Thing* = ref object of RootObj
+    name*: string
+    parent*: Thing
+    children*: seq[Thing]
+    parts*: seq[Part]
+
+
+func `$`*(thing: Thing): string = thing.name
+method `$`*(part: Part): string {.base.} = &"{part.thing} -> Part"
+method `$`*(part: Transform): string = &"{part.thing} -> Transform"
+
+func newTransform*(mat: Mat44): Transform =
+  result = new Transform
+  result.mat = mat
+
+method update*(thing: Thing, dt: float32) {.base.} = discard
+method update*(part: Part, dt: float32) {.base.} = discard
 
-  Thing* = object of RootObj
-    parent*: ref Thing
-    children*: seq[ref Thing]
-    parts*: seq[ref Part]
+proc add*(thing: Thing, child: Thing) =
+  child.parent = thing
+  thing.children.add child
+proc add*(thing: Thing, part: Part) =
+  part.thing = thing
+  thing.parts.add part
+proc add*(thing: Thing, children: seq[Thing]) =
+  for child in children:
+    child.parent = thing
+    thing.children.add child
+proc add*(thing: Thing, parts: seq[Part]) =
+  for part in parts:
+    part.thing = thing
+    thing.parts.add part
 
-method update*(thing: ref Thing, dt: float32) {.base.} = discard
+func newThing*(name: string=""): Thing =
+  result = new Thing
+  result.name = name
+  if result.name == "":
+    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
+func newThing*(name: string, firstChild: Thing, children: varargs[Thing]): Thing =
+  result = new Thing
+  result.add firstChild
+  for child in children:
+    result.add child
+  result.name = name
+  if result.name == "":
+    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
+proc newThing*(name: string, firstPart: Part, parts: varargs[Part]): Thing =
+  result = new Thing
+  result.name = name
+  result.add firstPart
+  for part in parts:
+    result.add part
+  if result.name == "":
+    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
 
-iterator partsOfType*[T: ref Part](root: ref Thing): T =
+func getModelTransform*(thing: Thing): Mat44 =
+  result = Unit44
+  var currentThing = thing
+  while currentThing != nil:
+    for part in currentThing.parts:
+      if part of Transform:
+        result = Transform(part).mat * result
+    currentThing = currentThing.parent
+    
+iterator allPartsOfType*[T: Part](root: Thing): T =
   var queue = @[root]
   while queue.len > 0:
     let thing = queue.pop
@@ -21,7 +84,47 @@
     for child in thing.children:
       queue.insert(child, 0)
 
-iterator allEntities*(root: ref Thing): ref Thing =
+func firstWithName*(root: Thing, name: string): Thing =
+  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 firstPartWithName*[T: Part](root: Thing, name: string): T =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        for part in child.parts:
+          if part of T:
+            return T(part)
+      queue.add child
+
+func allWithName*(root: Thing, name: string): seq[Thing] =
+  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 allPartsWithName*[T: Part](root: Thing, 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 part in child.parts:
+          if part of T:
+            result.add T(part)
+      queue.add child
+
+iterator allThings*(root: Thing): Thing =
   var queue = @[root]
   while queue.len > 0:
     let next = queue.pop
--- a/src/semicongine/vertex.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/src/semicongine/vertex.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -1,3 +1,4 @@
+import std/options
 import std/macros
 import std/strutils
 import std/strformat
@@ -6,20 +7,31 @@
 import ./math/vector
 import ./math/matrix
 import ./vulkan
+import ./buffer
 import ./glsl_helpers
 
 type
-  VertexAttributeType = SomeNumber|TVec
-  AttributePurpose* = enum
-    Unknown, Position Color
+  VertexAttributeType = SomeNumber|TVec|TMat
+  # useOnDeviceMemory can be used to make sure the attribute buffer memory will
+  # be on the device. Data will be faster to access but much slower to update
   GenericAttribute*[T:VertexAttributeType] = object
     data*: seq[T]
+    buffer*: Buffer
+    useOnDeviceMemory*: bool
   PositionAttribute*[T:TVec] = object
     data*: seq[T]
+    buffer*: Buffer
+    useOnDeviceMemory*: bool
   ColorAttribute*[T:TVec] = object
     data*: seq[T]
-  InstanceAttribute*[T:TVec] = object
+    buffer*: Buffer
+    useOnDeviceMemory*: bool
+  GenericInstanceAttribute*[T:VertexAttributeType] = object
     data*: seq[T]
+    buffer*: Buffer
+    useOnDeviceMemory*: bool
+  ModelTransformAttribute* = GenericInstanceAttribute[Mat44]
+  InstanceAttribute* = GenericInstanceAttribute|ModelTransformAttribute
   VertexAttribute* = GenericAttribute|PositionAttribute|ColorAttribute|InstanceAttribute
 
 template getAttributeType*(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0)
@@ -29,14 +41,12 @@
 
 # from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
 func nLocationSlots[T: VertexAttributeType](): int =
-  when (T is TMat44[float64]):
-    8
-  elif (T is TMat44[float32]):
-    4
-  elif (T is TVec3[float64] or T is TVec3[uint64] or T is TVec4[float64] or T is TVec4[float64]):
+  when (T is TVec3[float64] or T is TVec3[uint64] or T is TVec4[float64] or T is TVec4[float64]):
     2
+  elif T is SomeNumber or T is TVec:
+    1
   else:
-    1
+    {.error: "Unsupported vertex attribute type".}
 
 # numbers
 func getVkFormat[T: VertexAttributeType](): VkFormat =
@@ -80,6 +90,7 @@
   elif T is TVec4[int64]:   VK_FORMAT_R64G64B64A64_SINT
   elif T is TVec4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
   elif T is TVec4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
+  else: {.error: "Unsupported vertex attribute type".}
 
 
 
@@ -91,6 +102,12 @@
       else:
         assert result == uint32(value.data.len)
 
+func hasAttributeType*[T, AT](t: T): uint32 =
+  for name, value in t.fieldPairs:
+    when typeof(value) is AT:
+      return true
+  return false
+
 func generateGLSLVertexDeclarations*[T](): string =
   var stmtList: seq[string]
   var i = 0
--- a/tests/test_vector.nim	Fri Jan 20 16:53:37 2023 +0700
+++ b/tests/test_vector.nim	Sun Jan 22 22:46:53 2023 +0700
@@ -26,9 +26,12 @@
 proc randVec2I(): auto = newVec2(rand(1 .. 10), rand(1 .. 10))
 proc randVec2F(): auto = newVec2(rand(10'f) + 0.01, rand(10'f) + 0.01)
 proc randVec3I(): auto = newVec3(rand(1 .. 10), rand(1 .. 10), rand(1 .. 10))
-proc randVec3F(): auto = newVec3(rand(10'f) + 0.01, rand(10'f) + 0.01, rand(10'f) + 0.01)
-proc randVec4I(): auto = newVec4(rand(1 .. 10), rand(1 .. 10), rand(1 .. 10), rand(1 .. 10))
-proc randVec4F(): auto = newVec4(rand(10'f) + 0.01, rand(10'f) + 0.01, rand(10'f) + 0.01, rand(10'f) + 0.01)
+proc randVec3F(): auto = newVec3(rand(10'f) + 0.01, rand(10'f) + 0.01, rand(
+    10'f) + 0.01)
+proc randVec4I(): auto = newVec4(rand(1 .. 10), rand(1 .. 10), rand(1 .. 10),
+    rand(1 .. 10))
+proc randVec4F(): auto = newVec4(rand(10'f) + 0.01, rand(10'f) + 0.01, rand(
+    10'f) + 0.01, rand(10'f) + 0.01)
 
 
 proc testVector() =
@@ -134,6 +137,13 @@
   echo "float2int ", to[int](randVec3F())
   echo "int2float ", to[float](randVec3I())
 
+  echo "V3I.x: ", randVec3I().x
+  echo "V3I.y: ", randVec3I().y
+  echo "V3F.z: ", randVec3F().z
+  echo "V3I.r: ", randVec3I().r
+  echo "V3I.g: ", randVec3I().g
+  echo "V3F.b: ", randVec3F().b
+
   echo "V2I.xx: ", randVec2I().xx
   echo "V2I.yx: ", randVec2I().xy
   echo "V2F.xx: ", randVec2F().xx