changeset 1203:6360c8d17ce0 compiletime-tests

did: preprations to add rendering tests
author sam <sam@basx.dev>
date Mon, 15 Jul 2024 20:06:42 +0700
parents a8864fe6fe6e
children e2901100a596
files old_tests/config.nims old_tests/megatest.nim old_tests/resources/default/DejaVuSans.ttf old_tests/resources/default/a old_tests/resources/default/b old_tests/resources/default/c old_tests/resources/default/donut.glb old_tests/resources/default/flag.png old_tests/resources/default/key.ogg old_tests/resources/default/test1.glb old_tests/resources/default/toccata_et_fugue.ogg old_tests/resources/mod1/aSubdir/moreSubdir/superSubdir/empty.stuff old_tests/resources/mod1/aSubdir/moreSubdir/superSubdir/secret.txt old_tests/resources/mod1/d old_tests/test_audio.nim old_tests/test_collision.nim old_tests/test_font.nim old_tests/test_materials.nim old_tests/test_matrix.nim old_tests/test_mesh.nim old_tests/test_noise.nim old_tests/test_panel.nim old_tests/test_resources.nim old_tests/test_storage.nim old_tests/test_vector.nim old_tests/test_vulkan_wrapper.nim semicongine/rendering.nim semicongine/rendering/renderer.nim tests/config.nims tests/megatest.nim tests/resources/default/DejaVuSans.ttf tests/resources/default/a tests/resources/default/b tests/resources/default/c tests/resources/default/donut.glb tests/resources/default/flag.png tests/resources/default/key.ogg tests/resources/default/test1.glb tests/resources/default/toccata_et_fugue.ogg tests/resources/mod1/aSubdir/moreSubdir/superSubdir/empty.stuff tests/resources/mod1/aSubdir/moreSubdir/superSubdir/secret.txt tests/resources/mod1/d tests/test_audio.nim tests/test_collision.nim tests/test_font.nim tests/test_materials.nim tests/test_matrix.nim tests/test_mesh.nim tests/test_noise.nim tests/test_panel.nim tests/test_rendering tests/test_rendering.nim tests/test_resources.nim tests/test_storage.nim tests/test_vector.nim tests/test_vulkan_wrapper.nim
diffstat 52 files changed, 1278 insertions(+), 1207 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/config.nims	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,1 @@
+switch("path", "$projectDir/..")
Binary file old_tests/resources/default/DejaVuSans.ttf has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/resources/default/a	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,1 @@
+A
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/resources/default/b	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,1 @@
+B
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/resources/default/c	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,1 @@
+C
Binary file old_tests/resources/default/donut.glb has changed
Binary file old_tests/resources/default/flag.png has changed
Binary file old_tests/resources/default/key.ogg has changed
Binary file old_tests/resources/default/test1.glb has changed
Binary file old_tests/resources/default/toccata_et_fugue.ogg has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/resources/mod1/aSubdir/moreSubdir/superSubdir/secret.txt	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,1 @@
+hello world!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/resources/mod1/d	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,1 @@
+D
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_audio.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,88 @@
+import std/os
+import std/sequtils
+import std/times
+
+import semicongine
+
+
+proc test1() =
+  mixer[].AddSound("test1", NewSound(SineSoundData(1000, 2, 44100)))
+  mixer[].AddSound("test2", NewSound(SineSoundData(500, 2, 44100)))
+
+
+  let s1 = mixer[].Play("test1", loop = true)
+  let s2 = mixer[].Play("test2", loop = true)
+
+  let t0 = now()
+  mixer[].SetLevel(0.5)
+  while true:
+    let runtime = (now() - t0).inMilliseconds()
+    if runtime > 1500:
+      mixer[].SetLevel(0.2)
+    if runtime > 3000:
+      mixer[].Stop(s2)
+    if runtime > 6000:
+      mixer[].Stop("")
+    if runtime > 8000:
+      break
+
+proc test2() =
+  let
+    # notes
+    c = SineSoundData(261.6256, 0.5, 44100)
+    d = SineSoundData(293.6648, 0.5, 44100)
+    e = SineSoundData(329.6276, 0.5, 44100)
+    f = SineSoundData(349.2282, 0.5, 44100)
+    g = SineSoundData(391.9954, 0.5, 44100)
+    a = SineSoundData(440.0000, 0.5, 44100)
+    b = SineSoundData(493.8833, 0.5, 44100)
+    bb = SineSoundData(466.1638, 0.5, 44100)
+    c2 = SineSoundData(523.2511, 0.5, 44100)
+    d2 = SineSoundData(587.3295, 0.5, 44100)
+    bbShort = SineSoundData(466.1638, 0.25, 44100)
+    c2Short = SineSoundData(523.2511, 0.25, 44100)
+    d2Short = SineSoundData(587.3295, 0.25, 44100)
+
+    # song
+    frerejaquesData = concat(
+      f, g, a, f,
+      f, g, a, f,
+      a, bb, c2, c2,
+      a, bb, c2, c2,
+      c2Short, d2Short, c2Short, bbShort, a, f,
+      c2Short, d2Short, c2Short, bbShort, a, f,
+      f, c, f, f,
+      f, c, f, f,
+    )
+
+  mixer[].AddSound("frerejaques", NewSound(frerejaquesData))
+  discard mixer[].Play("frerejaques")
+
+  while mixer[].IsPlaying():
+    sleep(1)
+
+proc test3() =
+  mixer[].AddSound("toccata et fugue", LoadAudio("toccata_et_fugue.ogg"))
+  mixer[].AddSound("ping", NewSound(SineSoundData(500, 0.05, 44100)))
+  mixer[].AddTrack("effects")
+  discard mixer[].Play("toccata et fugue")
+
+
+when isMainModule:
+  StartMixerThread()
+  test1()
+  mixer[].Stop()
+  test2()
+  mixer[].Stop()
+  test3()
+
+  while mixer[].IsPlaying():
+    discard mixer[].Play("ping", track = "effects", stopOtherSounds = true, level = 0.5)
+    # on windows we re-open stdin and this will not work
+    when defined(linux):
+      echo "Press q and enter to exit"
+      if stdin.readLine() == "q":
+        mixer[].Stop()
+    elif defined(windows):
+      sleep(5)
+      mixer[].Stop()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_collision.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,59 @@
+import semicongine
+
+proc main() =
+  var scene = Scene(name: "main")
+
+  scene.Add Rect(color = "f00f")
+  scene.Add Rect()
+  scene.Add Circle(color = "0f0f")
+  scene.meshes[0].material = VERTEX_COLORED_MATERIAL.InitMaterialData()
+  scene.meshes[1].material = VERTEX_COLORED_MATERIAL.InitMaterialData()
+  scene.meshes[2].material = VERTEX_COLORED_MATERIAL.InitMaterialData()
+  scene.meshes[1].transform = Scale(0.8, 0.8)
+  scene.meshes[2].transform = Scale(0.1, 0.1)
+  scene.AddShaderGlobal("perspective", Unit4F32)
+
+  const
+    shaderConfiguration = CreateShaderConfiguration(
+      name = "default shader",
+      inputs = [
+        Attr[Mat4]("transform", memoryPerformanceHint = PreferFastRead, perInstance = true),
+        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
+        Attr[Vec4f]("color", memoryPerformanceHint = PreferFastRead),
+      ],
+      intermediates = [Attr[Vec4f]("colorout")],
+      uniforms = [Attr[Mat4]("perspective")],
+      outputs = [Attr[Vec4f]("fragcolor")],
+      vertexCode = """gl_Position = vec4(position, 1.0) * (transform * Uniforms.perspective); colorout = color;""",
+      fragmentCode = """fragcolor = colorout;""",
+    )
+
+  var engine = InitEngine("Test collisions")
+
+  engine.InitRenderer({VERTEX_COLORED_MATERIAL: shaderConfiguration})
+  engine.LoadScene(scene)
+
+  while engine.UpdateInputs() and not KeyIsDown(Escape):
+    if WindowWasResized():
+      var winSize = engine.GetWindow().Size
+      scene.SetShaderGlobal("perspective", OrthoWindowAspect(winSize[0] / winSize[1]))
+    if KeyIsDown(A): scene.meshes[0].transform = scene.meshes[0].transform * Translate(-0.001, 0, 0)
+    if KeyIsDown(D): scene.meshes[0].transform = scene.meshes[0].transform * Translate(0.001, 0, 0)
+    if KeyIsDown(W): scene.meshes[0].transform = scene.meshes[0].transform * Translate(0, -0.001, 0)
+    if KeyIsDown(S): scene.meshes[0].transform = scene.meshes[0].transform * Translate(0, 0.001, 0)
+    if KeyIsDown(Q): scene.meshes[0].transform = scene.meshes[0].transform * Rotate(-0.001, Z)
+    if KeyIsDown(Key.E): scene.meshes[0].transform = scene.meshes[0].transform * Rotate(0.001, Z)
+
+    if KeyIsDown(Key.Z): scene.meshes[1].transform = scene.meshes[1].transform * Rotate(-0.001, Z)
+    if KeyIsDown(Key.X): scene.meshes[1].transform = scene.meshes[1].transform * Rotate(0.001, Z)
+    if KeyIsDown(Key.C): scene.meshes[1].transform = scene.meshes[1].transform * Translate(0, -0.001, 0)
+    if KeyIsDown(Key.V): scene.meshes[1].transform = scene.meshes[1].transform * Translate(0, 0.001, 0)
+    let hitbox = Collider(theType: Box, transform: scene.meshes[0].transform * Translate(-0.5, -0.5))
+    let hitsphere = Collider(theType: Sphere, transform: scene.meshes[2].transform, radius: 0.5)
+    echo Intersects(hitbox, hitsphere)
+    engine.RenderScene(scene)
+  engine.Destroy()
+
+
+when isMainModule:
+  main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_font.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,79 @@
+import std/times
+import std/unicode
+
+import semicongine
+
+
+proc main() =
+  # setup engine
+  var engine = InitEngine("Test fonts")
+  engine.InitRenderer([])
+
+  # build scene
+  var scene = Scene(name: "main")
+  var font = LoadFont("DejaVuSans.ttf", lineHeightPixels = 210'f32)
+  var origin = InitPanel(transform = Scale(0.01, 0.01))
+  var main_text = font.InitText("".toRunes, maxLen = 255, color = NewVec4f(1, 0.15, 0.15, 1), maxWidth = 1.0, transform = Scale(0.0005, 0.0005))
+  var help_text = font.InitText("""Controls
+
+Horizontal alignment:
+  F1: Left
+  F2: Center
+  F3: Right
+Vertical alignment:
+  F4: Top
+  F5: Center
+  F6: Bottom""".toRunes, horizontalAlignment = Left, verticalAlignment = Top, transform = Translate(-0.9, -0.9) * Scale(0.0002, 0.0002))
+  scene.Add origin
+  scene.Add main_text
+  scene.Add help_text
+  engine.LoadScene(scene)
+  mixer[].LoadSound("key", "key.ogg")
+  mixer[].SetLevel(0.5)
+
+  while engine.UpdateInputs() and not KeyIsDown(Escape):
+    var t = cpuTime()
+    main_text.Color = NewVec4f(sin(t) * 0.5 + 0.5, 0.15, 0.15, 1)
+
+    # add character
+    if main_text.text.len < main_text.maxLen - 1:
+      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 KeyWasPressed(c):
+          discard mixer[].Play("key")
+          if KeyIsDown(ShiftL) or KeyIsDown(ShiftR):
+            main_text.text = main_text.text & ($c).toRunes
+          else:
+            main_text.text = main_text.text & ($c).toRunes[0].toLower()
+      if KeyWasPressed(Enter):
+        discard mixer[].Play("key")
+        main_text.text = main_text.text & Rune('\n')
+      if KeyWasPressed(Space):
+        discard mixer[].Play("key")
+        main_text.text = main_text.text & Rune(' ')
+
+    # remove character
+    if KeyWasPressed(Backspace) and main_text.text.len > 0:
+      discard mixer[].Play("key")
+      main_text.text = main_text.text[0 ..< ^1]
+
+    # alignemtn with F-keys
+    if KeyWasPressed(F1): main_text.horizontalAlignment = Left
+    elif KeyWasPressed(F2): main_text.horizontalAlignment = Center
+    elif KeyWasPressed(F3): main_text.horizontalAlignment = Right
+    elif KeyWasPressed(F4): main_text.verticalAlignment = Top
+    elif KeyWasPressed(F5): main_text.verticalAlignment = Center
+    elif KeyWasPressed(F6): main_text.verticalAlignment = Bottom
+
+    origin.Refresh()
+    main_text.text = main_text.text & Rune('_')
+    main_text.Refresh()
+    main_text.text = main_text.text[0 ..< ^1]
+    help_text.Refresh()
+    engine.RenderScene(scene)
+  engine.Destroy()
+
+
+when isMainModule:
+  main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_materials.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,79 @@
+import std/times
+import std/tables
+
+import semicongine
+
+let
+  sampler = Sampler(magnification: VK_FILTER_NEAREST, minification: VK_FILTER_NEAREST)
+  (RT, WT, PT) = (ToRGBA("A51931").AsPixel, ToRGBA("F4F5F8").AsPixel, ToRGBA("2D2A4A").AsPixel)
+  thai = Image[RGBAPixel](width: 7, height: 5, imagedata: @[
+    RT, RT, RT, RT, RT, RT, RT,
+    WT, WT, WT, WT, WT, WT, WT,
+    PT, PT, PT, PT, PT, PT, PT,
+    WT, WT, WT, WT, WT, WT, WT,
+    RT, RT, RT, RT, RT, RT, RT,
+  ])
+  swiss = LoadImage[RGBAPixel]("flag.png")
+  doubleTextureMaterial = MaterialType(
+    name: "Double texture",
+    vertexAttributes: {
+      "position": Vec3F32,
+      "uv": Vec2F32,
+    }.toTable,
+    attributes: {"tex1": TextureType, "tex2": TextureType}.toTable
+  )
+  material = InitMaterialData(
+    theType = doubleTextureMaterial,
+    name = "swiss-thai",
+    attributes = {
+      "tex1": InitDataList(@[Texture(colorImage: thai, sampler: sampler, isGrayscale: false)]),
+      "tex2": InitDataList(@[Texture(colorImage: swiss, sampler: sampler, isGrayscale: false)]),
+    }
+  )
+
+proc main() =
+  var flag = Rect()
+  flag.material = material
+  var scene = Scene(name: "main", meshes: @[flag])
+  scene.AddShaderGlobalArray("test2", @[NewVec4f(), NewVec4f()])
+
+  var engine = InitEngine("Test materials")
+
+  const
+    shaderConfiguration1 = CreateShaderConfiguration(
+      name = "shader 1",
+      inputs = [
+        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
+        Attr[Vec2f]("uv", memoryPerformanceHint = PreferFastRead),
+      ],
+      intermediates = [
+        Attr[Vec2f]("uvout"),
+      ],
+      uniforms = [Attr[Vec4f]("test2", arrayCount = 2)],
+      samplers = @[
+        Attr[Texture]("tex1"),
+        Attr[Texture]("tex2"),
+      ],
+      outputs = [Attr[Vec4f]("color")],
+      vertexCode = """
+      gl_Position = vec4(position.x, position.y + sin(Uniforms.test2[1].x) / Uniforms.test2[1].x * 0.5, position.z, 1.0);
+      uvout = uv;""",
+      fragmentCode = """
+      float d = sin(Uniforms.test2[0].x) * 0.5 + 0.5;
+      color = texture(tex1, uvout) * (1 - d) + texture(tex2, uvout) * d;
+      """,
+    )
+  engine.InitRenderer({
+    doubleTextureMaterial: shaderConfiguration1,
+  })
+  engine.LoadScene(scene)
+  var t = cpuTime()
+  while engine.UpdateInputs() and not KeyIsDown(Escape):
+    var d = float32(cpuTime() - t)
+    SetShaderGlobalArray(scene, "test2", @[NewVec4f(d), NewVec4f(d * 2)])
+    engine.RenderScene(scene)
+  engine.Destroy()
+
+
+when isMainModule:
+  main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_matrix.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,158 @@
+import random
+import math
+
+import semicongine
+
+
+proc echoInfo(v: TVec) =
+  echo v
+  echo "  Length: ", v.length
+  echo "  Normlized: ", v.normalized
+  echo "  negated: ", -v
+
+proc echoAdd[T, U](v1: T, v2: U) =
+  echo v1, " + ", v2, " = ", v1 + v2
+proc echoSub[T, U](v1: T, v2: U) =
+  echo v1, " - ", v2, " = ", v1 - v2
+proc echoMul[T, U](v1: T, v2: U) =
+  echo v1, " * ", v2, " = ", v1 * v2
+proc echoDiv[T, U](v1: T, v2: U) =
+  echo v1, " / ", v2, " = ", v1 / v2
+proc echoDot[T, U](v1: T, v2: U) =
+  echo v1, " o ", v2, " = ", v1.dot(v2)
+proc echoCross[T, U](v1: T, v2: U) =
+  echo v1, " x ", v2, " = ", v1.cross(v2)
+
+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)
+
+
+template withAllIntegerMats(stuff: untyped) =
+  stuff(TMat2[int32])
+  stuff(TMat23[int32])
+  stuff(TMat32[int32])
+  stuff(TMat3[int32])
+  stuff(TMat34[int32])
+  stuff(TMat43[int32])
+  stuff(TMat4[int32])
+  stuff(TMat2[int64])
+  stuff(TMat23[int64])
+  stuff(TMat32[int64])
+  stuff(TMat3[int64])
+  stuff(TMat34[int64])
+  stuff(TMat43[int64])
+  stuff(TMat4[int64])
+
+template withAllFloatMats(stuff: untyped) =
+  stuff(TMat2[float32])
+  stuff(TMat23[float32])
+  stuff(TMat32[float32])
+  stuff(TMat3[float32])
+  stuff(TMat34[float32])
+  stuff(TMat43[float32])
+  stuff(TMat4[float32])
+  stuff(TMat2[float64])
+  stuff(TMat23[float64])
+  stuff(TMat32[float64])
+  stuff(TMat3[float64])
+  stuff(TMat34[float64])
+  stuff(TMat43[float64])
+  stuff(TMat4[float64])
+
+template withAllMats(stuff: untyped) =
+  stuff(TMat2[int])
+  stuff(TMat23[int])
+  stuff(TMat32[int])
+  stuff(TMat3[int])
+  stuff(TMat34[int])
+  stuff(TMat43[int])
+  stuff(TMat4[int])
+  stuff(TMat2[float])
+  stuff(TMat23[float])
+  stuff(TMat32[float])
+  stuff(TMat3[float])
+  stuff(TMat34[float])
+  stuff(TMat43[float])
+  stuff(TMat4[float])
+
+template testTranspose(t: typedesc) =
+  echo "testTranspose: ", t
+  let m = t().Randomized()
+  assert m == m.Transposed().Transposed()
+
+template testInversed(t: typedesc) =
+  echo "testTranspose: ", t
+  let m = t().Randomized()
+  var unit = t()
+  for i in unit.RowCount:
+    unit[i][i] = 1
+  assert m.Transposed() * m == unit
+
+template testAssignI(t: typedesc) =
+  echo "testAssignI: ", t
+  var m = t()
+  for i in 0 ..< t.data.len:
+    m[rand(0 ..< t.RowCount), rand(0 ..< t.ColumnCount)] = rand(0'i32 .. 100'i32)
+
+template testAssignF(t: typedesc) =
+  echo "testAssignF: ", t
+  var m = t()
+  for i in 0 ..< t.data.len:
+    m[rand(0 ..< t.RowCount), rand(0 ..< t.ColumnCount)] = rand(100'f)
+
+template testRowCols(t: typedesc) =
+  echo "testRowCols: ", t
+  var m = t().Randomized()
+  for i in 0 ..< t.RowCount:
+    echo m.Row(i)
+  for i in 0 ..< t.ColumnCount:
+    echo m.Col(i)
+
+
+proc testMatrix() =
+  withAllMats(testTranspose)
+  withAllIntegerMats(testAssignI)
+  withAllFloatMats(testAssignF)
+  withAllMats(testRowCols)
+
+  echo Unit2
+  echo Unit2i
+  echo Unit2i8
+  echo Unit2i16
+  echo Unit2i32
+  echo Unit2i64
+
+  echo Unit3
+  echo Unit3i
+  echo Unit3i8
+  echo Unit3i16
+  echo Unit3i32
+  echo Unit3i64
+
+  echo Unit4
+  echo Unit4i
+  echo Unit4i8
+  echo Unit4i16
+  echo Unit4i32
+  echo Unit4i64
+
+  echo TMat2[float32]().Randomized() * One2.Randomized()
+  echo TMat3[float32]().Randomized() * One3.Randomized()
+  echo TMat4[float32]().Randomized() * One4.Randomized()
+
+  echo float32(rand(1'f32)) * TMat2[float32]().Randomized()
+  echo TMat2[float]().Randomized() * rand(1'f)
+  echo TMat2[float]().Randomized() * rand(1'f)
+  echo TMat23[float]().Randomized() * rand(1'f)
+  echo TMat23[float]().Randomized() * rand(1'f)
+  echo TMat3[float]().Randomized() * rand(1'f)
+  echo TMat34[float]().Randomized() * rand(1'f)
+  echo TMat43[float]().Randomized() * rand(1'f)
+  echo TMat4[float]().Randomized() * rand(1'f)
+
+randomize()
+testMatrix()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_mesh.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,94 @@
+import std/strformat
+import semicongine
+
+const
+  MeshMaterial* = MaterialType(
+    name: "colored single texture material",
+    vertexAttributes: {
+      "position": Vec3F32,
+      "texcoord_0": Vec2F32,
+    }.toTable,
+    attributes: {"baseTexture": TextureType, "color": Vec4F32}.toTable
+  )
+
+proc main() =
+  var scenes = [
+    Scene(name: "Donut", meshes: LoadMeshes("donut.glb", MeshMaterial)[0].toSeq),
+  ]
+
+  var engine = InitEngine("Test meshes")
+  const
+    shaderConfiguration = CreateShaderConfiguration(
+      name = "default shader",
+      inputs = [
+        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
+        Attr[uint16](MATERIALINDEX_ATTRIBUTE, memoryPerformanceHint = PreferFastRead, perInstance = true),
+        Attr[Vec2f]("texcoord_0", memoryPerformanceHint = PreferFastRead),
+        Attr[Mat4]("transform", memoryPerformanceHint = PreferFastWrite, perInstance = true),
+      ],
+      intermediates = [
+        Attr[Vec4f]("vertexColor"),
+        Attr[Vec2f]("colorTexCoord"),
+        Attr[uint16]("materialIndexOut", noInterpolation = true)
+      ],
+      outputs = [Attr[Vec4f]("color")],
+      uniforms = [
+        Attr[Mat4]("projection"),
+        Attr[Mat4]("view"),
+        Attr[Vec4f]("color", arrayCount = 4),
+      ],
+      samplers = [Attr[Texture]("baseTexture", arrayCount = 4)],
+      vertexCode = &"""
+  gl_Position =  vec4(position, 1.0) * (transform * (Uniforms.view * Uniforms.projection));
+  vertexColor = Uniforms.color[{MATERIALINDEX_ATTRIBUTE}];
+  colorTexCoord = texcoord_0;
+  materialIndexOut = {MATERIALINDEX_ATTRIBUTE};
+  """,
+      fragmentCode = "color = texture(baseTexture[materialIndexOut], colorTexCoord) * vertexColor;"
+    )
+  engine.InitRenderer({MeshMaterial: shaderConfiguration})
+
+  for scene in scenes.mitems:
+    scene.AddShaderGlobal("projection", Unit4F32)
+    scene.AddShaderGlobal("view", Unit4F32)
+    engine.LoadScene(scene)
+
+  var
+    size = 1'f32
+    elevation = 0'f32
+    azimut = 0'f32
+    currentScene = 0
+
+  while engine.UpdateInputs() and not KeyIsDown(Escape):
+    if KeyWasPressed(`1`):
+      currentScene = 0
+    elif KeyWasPressed(`2`):
+      currentScene = 1
+    elif KeyWasPressed(`3`):
+      currentScene = 2
+    elif KeyWasPressed(`4`):
+      currentScene = 3
+    elif KeyWasPressed(`5`):
+      currentScene = 4
+    elif KeyWasPressed(`6`):
+      currentScene = 5
+
+    if KeyWasPressed(NumberRowExtra3):
+      size = 0.3'f32
+      elevation = 0'f32
+      azimut = 0'f32
+
+    let ratio = engine.GetWindow().Size[0] / engine.GetWindow().Size[1]
+    size *= 1'f32 + MouseWheel() * 0.05
+    azimut += MouseMove().x / 180'f32
+    elevation -= MouseMove().y / 180'f32
+    scenes[currentScene].SetShaderGlobal("projection", Perspective(PI / 2, ratio, -0.5, 1))
+    scenes[currentScene].SetShaderGlobal(
+      "view",
+       Scale(size, size, size) * Rotate(elevation, NewVec3f(1, 0, 0)) * Rotate(azimut, Yf32)
+    )
+    engine.RenderScene(scenes[currentScene])
+  engine.Destroy()
+
+when isMainModule:
+  main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_noise.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,17 @@
+import semicongine
+
+const w = 500
+const h = 500
+
+var o = "P2\n" & $ w & " " & $ h & "\n255\n"
+
+for y in 0 ..< h:
+  for x in 0 ..< w:
+    let v = (
+      Perlin(NewVec2f(float(x) * 0.01, float(y) * 0.01)) * 0.7 +
+      Perlin(NewVec2f(float(x) * 0.05, float(y) * 0.05)) * 0.25 +
+      Perlin(NewVec2f(float(x) * 0.2, float(y) * 0.2)) * 0.05
+    )
+    o = o & $(int((v * 0.5 + 0.5) * 255)) & " "
+  o = o & "\n"
+echo o
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_panel.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,99 @@
+import std/unicode
+
+import semicongine
+
+var counter = 0
+var counterText: Text
+
+proc click(panel: var Panel, buttons: set[MouseButton]) =
+  if buttons.contains(Mouse1):
+    counter.inc
+  if buttons.contains(Mouse3):
+    counter.dec
+  counterText.text = $counter
+proc enter(panel: var Panel) =
+  panel.mesh.transform = panel.mesh.transform * Scale(1.05, 1.05)
+  panel.color = NewVec4f(1, 0, 0, 0.3)
+proc leave(panel: var Panel) =
+  panel.mesh.transform = panel.mesh.transform * Scale(1 / 1.05, 1 / 1.05)
+  panel.color = NewVec4f(1, 0, 0, 0.5)
+
+proc main() =
+  # setup engine
+  var engine = InitEngine("Test panels")
+  engine.InitRenderer([])
+
+  const B = [0'u8, 0'u8, 0'u8, 255'u8]
+  const T = [0'u8, 0'u8, 0'u8, 0'u8]
+  # build scene
+  #
+  var
+    font = LoadFont("DejaVuSans.ttf", lineHeightPixels = 210'f32)
+    scene = Scene(name: "main")
+    origin = InitPanel(
+      transform = Scale(0.005, 0.005),
+      color = NewVec4f(1, 1, 1, 1),
+      texture = Texture(isGrayscale: false, colorImage: NewImage[RGBAPixel](3, 3, [T, B, T, B, B, B, T, B, T]), sampler: NEAREST_SAMPLER),
+    )
+    button = InitPanel(
+      transform = Translate(0.2, 0.1) * Scale(0.3, 0.1),
+      color = NewVec4f(1, 0, 0, 0.5),
+      onMouseDown = click,
+      onMouseEnter = enter,
+      onMouseLeave = leave
+    )
+    help_text = font.InitText("""Controls
+
+Horizontal alignment:
+  F1: Left
+  F2: Center
+  F3: Right
+Vertical alignment:
+  F4: Top
+  F5: Center
+  F6: Bottom
+Mouse:
+  Left click: Increase counter
+  Right click: Decrease counter""".toRunes, horizontalAlignment = Left, verticalAlignment = Top, transform = Translate(-0.9, -0.9) * Scale(0.0002, 0.0002))
+
+  counterText = font.InitText(($counter).toRunes, maxLen = 99, transform = Translate(0.2, 0.1) * Scale(0.0004, 0.0004))
+
+  scene.Add counterText
+  scene.Add button
+  scene.Add help_text
+  scene.Add origin
+  engine.LoadScene(scene)
+
+  while engine.UpdateInputs() and not KeyIsDown(Escape):
+    if KeyWasPressed(F1):
+      button.horizontalAlignment = Left
+      counterText.horizontalAlignment = Left
+    elif KeyWasPressed(F2):
+      button.horizontalAlignment = Center
+      counterText.horizontalAlignment = Center
+    elif KeyWasPressed(F3):
+      button.horizontalAlignment = Right
+      counterText.horizontalAlignment = Right
+    elif KeyWasPressed(F4):
+      button.verticalAlignment = Top
+      counterText.verticalAlignment = Top
+    elif KeyWasPressed(F5):
+      button.verticalAlignment = Center
+      counterText.verticalAlignment = Center
+    elif KeyWasPressed(F6):
+      button.verticalAlignment = Bottom
+      counterText.verticalAlignment = Bottom
+
+    engine.ProcessEvents(button)
+
+    button.Refresh()
+    counterText.Refresh()
+    origin.Refresh()
+    help_text.Refresh()
+
+    engine.RenderScene(scene)
+  engine.Destroy()
+
+
+when isMainModule:
+  main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_resources.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,34 @@
+import std/os
+import std/streams
+import std/strformat
+import std/strutils
+
+import semicongine
+
+proc list_all_mods_all_files() =
+  for package in Packages():
+    echo &"Files in package {package}:"
+    for i in WalkResources(package = package):
+      echo "  ", i, ": ", i.LoadResource(package = package).readAll().len
+
+proc print_ls(dir, package: string, indent = 2) =
+  for i in dir.List(package = package):
+    if i.kind == pcDir:
+      echo "".align(indent), i.path, "/"
+      print_ls(dir.joinPath(i.path), package = package, indent = indent + 2)
+    else:
+      echo "".align(indent), i.path, ": ", dir.joinPath(i.path).LoadResource(package = package).readAll().len
+
+proc list_files() =
+  for package in Packages():
+    echo &"Recursive walk of package {package}: "
+    print_ls("", package = package)
+
+
+proc main() =
+  echo "Packages available: ", Packages()
+  list_all_mods_all_files()
+  list_files()
+
+when isMainModule:
+  main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_storage.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,39 @@
+import std/strformat
+
+import semicongine
+
+proc testSimple(storage: StorageType) =
+  const TEST_VALUE = 42
+  const KEY = "test"
+
+  # get default
+  assert storage.Load(KEY, 0) == default(type(TEST_VALUE))
+
+  # save and load custom
+  Store(storage, KEY, TEST_VALUE)
+  assert storage.Load(KEY, 0) == TEST_VALUE
+
+proc stressTest(storage: StorageType) =
+  for i in 1 .. 10000:
+    let key = &"key-{i}"
+    Store(storage, key, i)
+    assert storage.Load(key, 0) == i
+
+proc main() =
+  SystemStorage.Purge()
+  echo "SystemStorage: Testing simple store/load"
+  SystemStorage.testSimple()
+
+  UserStorage.Purge()
+  echo "UserStorage: Testing simple store/load"
+  UserStorage.testSimple()
+
+  echo "Stress test with 10'000 saves/loads"
+  SystemStorage.stressTest()
+
+  SystemStorage.Purge()
+  UserStorage.Purge()
+
+
+when isMainModule:
+  main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_vector.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,199 @@
+import random
+
+import semicongine
+
+
+proc echoInfo[T](v: TVec2[T] or TVec3[T] or TVec4[T]) =
+  echo v
+  echo "  Length: ", v.Length
+  when T is SomeFloat:
+    echo "  Normlized: ", v.Normalized
+  echo "  negated: ", -v
+
+proc echoAdd[T, U](v1: T, v2: U) =
+  echo v1, " + ", v2, " = ", v1 + v2
+proc echoSub[T, U](v1: T, v2: U) =
+  echo v1, " - ", v2, " = ", v1 - v2
+proc echoMul[T, U](v1: T, v2: U) =
+  echo v1, " * ", v2, " = ", v1 * v2
+proc echoDiv[T, U](v1: T, v2: U) =
+  echo v1, " / ", v2, " = ", v1 / v2
+proc echoDot[T, U](v1: T, v2: U) =
+  echo v1, " o ", v2, " = ", v1.Dot(v2)
+proc echoCross[T, U](v1: T, v2: U) =
+  echo v1, " x ", v2, " = ", v1.Cross(v2)
+
+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 testVector() =
+  echoInfo(randVec2I())
+  echoInfo(randVec2F())
+  echoInfo(randVec3I())
+  echoInfo(randVec3F())
+  echoInfo(randVec4I())
+  echoInfo(randVec4F())
+
+  # test math operations vector-vector
+  echoAdd(randVec2I(), randVec2I())
+  echoAdd(randVec2F(), randVec2F())
+  echoAdd(randVec3I(), randVec3I())
+  echoAdd(randVec3F(), randVec3F())
+  echoAdd(randVec4I(), randVec4I())
+  echoAdd(randVec4F(), randVec4F())
+  echoSub(randVec2I(), randVec2I())
+  echoSub(randVec2F(), randVec2F())
+  echoSub(randVec3I(), randVec3I())
+  echoSub(randVec3F(), randVec3F())
+  echoSub(randVec4I(), randVec4I())
+  echoSub(randVec4F(), randVec4F())
+  echoMul(randVec2I(), randVec2I())
+  echoMul(randVec2F(), randVec2F())
+  echoMul(randVec3I(), randVec3I())
+  echoMul(randVec3F(), randVec3F())
+  echoMul(randVec4I(), randVec4I())
+  echoMul(randVec4F(), randVec4F())
+  echoDiv(randVec2I(), randVec2I())
+  echoDiv(randVec2F(), randVec2F())
+  echoDiv(randVec3I(), randVec3I())
+  echoDiv(randVec3F(), randVec3F())
+  echoDiv(randVec4I(), randVec4I())
+  echoDiv(randVec4F(), randVec4F())
+  echoDot(randVec2I(), randVec2I())
+  echoDot(randVec2F(), randVec2F())
+  echoDot(randVec3I(), randVec3I())
+  echoDot(randVec3F(), randVec3F())
+  echoDot(randVec4I(), randVec4I())
+  echoDot(randVec4F(), randVec4F())
+  echoCross(randVec3I(), randVec3I())
+  echoCross(randVec3F(), randVec3F())
+
+
+  # test math operations vector-scalar
+  echoAdd(randVec2I(), rand(1 .. 10))
+  echoAdd(randVec2F(), rand(10'f))
+  echoAdd(randVec3I(), rand(1 .. 10))
+  echoAdd(randVec3F(), rand(10'f))
+  echoAdd(randVec4I(), rand(1 .. 10))
+  echoAdd(randVec4F(), rand(10'f))
+  echoSub(randVec2I(), rand(1 .. 10))
+  echoSub(randVec2F(), rand(10'f))
+  echoSub(randVec3I(), rand(1 .. 10))
+  echoSub(randVec3F(), rand(10'f))
+  echoSub(randVec4I(), rand(1 .. 10))
+  echoSub(randVec4F(), rand(10'f))
+  echoMul(randVec2I(), rand(1 .. 10))
+  echoMul(randVec2F(), rand(10'f))
+  echoMul(randVec3I(), rand(1 .. 10))
+  echoMul(randVec3F(), rand(10'f))
+  echoMul(randVec4I(), rand(1 .. 10))
+  echoMul(randVec4F(), rand(10'f))
+  echoDiv(randVec2I(), rand(1 .. 10))
+  echoDiv(randVec2F(), rand(10'f))
+  echoDiv(randVec3I(), rand(1 .. 10))
+  echoDiv(randVec3F(), rand(10'f))
+  echoDiv(randVec4I(), rand(1 .. 10))
+  echoDiv(randVec4F(), rand(10'f))
+
+  # test math operations scalar-vector
+  echoAdd(rand(1 .. 10), randVec2I())
+  echoAdd(rand(10'f), randVec2F())
+  echoAdd(rand(1 .. 10), randVec3I())
+  echoAdd(rand(10'f), randVec3F())
+  echoAdd(rand(1 .. 10), randVec4I())
+  echoAdd(rand(10'f), randVec4F())
+  echoSub(rand(1 .. 10), randVec2I())
+  echoSub(rand(10'f), randVec2F())
+  echoSub(rand(1 .. 10), randVec3I())
+  echoSub(rand(10'f), randVec3F())
+  echoSub(rand(1 .. 10), randVec4I())
+  echoSub(rand(10'f), randVec4F())
+  echoMul(rand(1 .. 10), randVec2I())
+  echoMul(rand(10'f), randVec2F())
+  echoMul(rand(1 .. 10), randVec3I())
+  echoMul(rand(10'f), randVec3F())
+  echoMul(rand(1 .. 10), randVec4I())
+  echoMul(rand(10'f), randVec4F())
+  echoDiv(rand(1 .. 10), randVec2I())
+  echoDiv(rand(10'f), randVec2F())
+  echoDiv(rand(1 .. 10), randVec3I())
+  echoDiv(rand(10'f), randVec3F())
+  echoDiv(rand(1 .. 10), randVec4I())
+  echoDiv(rand(10'f), randVec4F())
+
+  # test attribute syntax sugar
+  echo "float2int ", To[int](randVec2F())
+  echo "int2float ", To[float](randVec2I())
+  echo "float2int ", To[int](randVec3F())
+  echo "int2float ", To[float](randVec3I())
+  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
+
+  # test setters
+  var v1 = randVec2I(); v1.x = 1; v1.y = 2; v1.r = 3; v1.g = 4
+  v1.xy = randVec2I(); v1.yx = randVec2I(); v1.rg = randVec2I(); v1.gr = randVec2I()
+  var v2 = randVec2F(); v2.x = 1.0; v2.y = 2.0; v2.r = 3.0; v2.g = 4.0
+  v2.xy = randVec2F(); v2.yx = randVec2F(); v2.rg = randVec2F(); v2.gr = randVec2F()
+
+  var v3 = randVec3I(); v3.x = 1; v3.y = 2; v3.z = 3; v3.r = 4; v3.g = 5; v3.b = 6
+  v3.xyz = randVec3I(); v3.rgb = randVec3I()
+  var v4 = randVec3F(); v4.x = 1.0; v4.y = 2.0; v4.z = 3.0; v4.r = 4.0; v4.g = 5.0; v4.b = 6.0
+  v4.xyz = randVec3F(); v4.rgb = randVec3F()
+
+  var v5 = randVec4I(); v5.x = 1; v5.y = 2; v5.z = 3; v5.w = 4; v5.r = 5; v5.g = 6; v5.b = 7; v5.a = 8
+  v5.xyzw = randVec4I(); v5.rgba = randVec4I()
+  var v6 = randVec4F(); v6.x = 1.0; v6.y = 2.0; v6.z = 3.0; v6.w = 4.0; v6.r = 5.0; v6.g = 6.0; v6.b = 7.0; v6.a = 8.0
+  v6.xyzw = randVec4F(); v6.rgba = randVec4F()
+
+  echo "V2I.xx: ", randVec2I().xx
+  echo "V2I.yx: ", randVec2I().xy
+  echo "V2F.xx: ", randVec2F().xx
+  echo "V2F.yx: ", randVec2F().yx
+  echo "V2I.rr: ", randVec2I().rr
+  echo "V2I.gr: ", randVec2I().gr
+  echo "V2F.rr: ", randVec2F().rr
+  echo "V2F.gr: ", randVec2F().gr
+
+  echo "V3I.yyy: ", randVec3I().yyy
+  echo "V3I.yxz: ", randVec3I().xyz
+  echo "V3F.yyy: ", randVec3F().yyy
+  echo "V3F.yxz: ", randVec3F().yxz
+  echo "V3I.ggg: ", randVec3I().ggg
+  echo "V3I.grb: ", randVec3I().grb
+  echo "V3F.ggg: ", randVec3F().ggg
+  echo "V3F.grb: ", randVec3F().grb
+
+  echo "V4I.zzzz: ", randVec4I().zzzz
+  echo "V4I.yxzw: ", randVec4I().xyzw
+  echo "V4F.zzzz: ", randVec4F().zzzz
+  echo "V4F.yxzw: ", randVec4F().yxzw
+  echo "V4I.bbbb: ", randVec4I().bbbb
+  echo "V4I.grba: ", randVec4I().grba
+  echo "V4F.bbbb: ", randVec4F().bbbb
+  echo "V4F.grba: ", randVec4F().grba
+
+  echo "X: ", X
+  echo "Y: ", Y
+  echo "Z: ", Z
+  echo "X: ", Xi
+  echo "Y: ", Yi
+  echo "Z: ", Zi
+
+
+randomize()
+testVector()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/old_tests/test_vulkan_wrapper.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,252 @@
+import std/tables
+
+import semicongine
+
+
+let
+  sampler = Sampler(
+    magnification: VK_FILTER_NEAREST,
+    minification: VK_FILTER_NEAREST,
+    wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT,
+    wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT,
+  )
+  (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8])
+  Mat1Type = MaterialType(
+    name: "single texture material 1",
+    vertexAttributes: {
+      "color": Vec4F32,
+      "position": Vec3F32,
+      "uv": Vec2F32,
+    }.toTable,
+    attributes: {"baseTexture": TextureType}.toTable
+  )
+  mat = Mat1Type.InitMaterialData(
+    name = "mat",
+    attributes = {
+      "baseTexture": InitDataList(@[Texture(isGrayscale: false, colorImage: Image[RGBAPixel](width: 5, height: 5, imagedata: @[
+      R, R, R, R, R,
+      R, R, W, R, R,
+      R, W, W, W, R,
+      R, R, W, R, R,
+      R, R, R, R, R,
+    ]), sampler: sampler)])
+  }.toTable
+  )
+  Mat2Type = MaterialType(
+    name: "single texture material 2",
+    vertexAttributes: {
+      "color": Vec4F32,
+      "position": Vec3F32,
+      "uv": Vec2F32,
+    }.toTable,
+    attributes: {"baseTexture": TextureType}.toTable
+  )
+  mat2 = Mat2Type.InitMaterialData(
+    name = "mat2",
+    attributes = {
+      "baseTexture": InitDataList(@[Texture(isGrayscale: false, colorImage: Image[RGBAPixel](width: 5, height: 5, imagedata: @[
+      R, W, R, W, R,
+      W, R, W, R, W,
+      R, W, R, W, R,
+      W, R, W, R, W,
+      R, W, R, W, R,
+    ]), sampler: sampler)])
+  }.toTable
+  )
+  mat3 = SINGLE_COLOR_MATERIAL.InitMaterialData(
+    name = "mat3",
+    attributes = {
+      "color": InitDataList(@[NewVec4f(0, 1, 0, 1)])
+    }.toTable
+  )
+
+proc scene_different_mesh_types(): seq[Mesh] =
+  @[
+    NewMesh(
+      positions = [NewVec3f(0.0, -0.5), NewVec3f(0.5, 0.5), NewVec3f(-0.5, 0.5)],
+      uvs = [NewVec2f(0.0, -0.5), NewVec2f(0.5, 0.5), NewVec2f(-0.5, 0.5)],
+      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
+      material = mat,
+      transform = Translate(-0.7, -0.5),
+    ),
+    NewMesh(
+      positions = [NewVec3f(0.0, -0.4), NewVec3f(0.4, 0.4), NewVec3f(-0.4, 0.5)],
+      uvs = [NewVec2f(0.0, -0.4), NewVec2f(0.4, 0.4), NewVec2f(-0.4, 0.5)],
+      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
+      material = mat,
+      transform = Translate(0, -0.5),
+    ),
+    NewMesh(
+      positions = [NewVec3f(0.0, 0.5), NewVec3f(0.5, -0.5), NewVec3f(-0.5, -0.5)],
+      uvs = [NewVec2f(0.0, 0.5), NewVec2f(0.5, -0.5), NewVec2f(-0.5, -0.5)],
+      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
+      indices = [[0'u16, 2'u16, 1'u16]],
+      material = mat2,
+      transform = Translate(0.7, -0.5),
+    ),
+    NewMesh(
+      positions = [NewVec3f(0.0, 0.4), NewVec3f(0.4, -0.4), NewVec3f(-0.4, -0.4)],
+      uvs = [NewVec2f(0.0, 0.4), NewVec2f(0.4, -0.4), NewVec2f(-0.4, -0.4)],
+      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
+      indices = [[0'u16, 2'u16, 1'u16]],
+      material = mat2,
+      transform = Translate(-0.7, 0.5),
+    ),
+    NewMesh(
+      positions = [NewVec3f(0.4, 0.5), NewVec3f(0.9, -0.3), NewVec3f(0.0, -0.3)],
+      uvs = [NewVec2f(0.4, 0.5), NewVec2f(0.9, -0.3), NewVec2f(0.0, -0.3)],
+      colors = [NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1)],
+      indices = [[0'u32, 2'u32, 1'u32]],
+      autoResize = false,
+      material = mat2,
+      transform = Translate(0, 0.5),
+    ),
+    NewMesh(
+      positions = [NewVec3f(0.4, 0.5), NewVec3f(0.9, -0.3), NewVec3f(0.0, -0.3)],
+      uvs = [NewVec2f(0.4, 0.5), NewVec2f(0.9, -0.3), NewVec2f(0.0, -0.3)],
+      colors = [NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1)],
+      indices = [[0'u32, 2'u32, 1'u32]],
+      autoResize = false,
+      material = mat2,
+      transform = Translate(0.7, 0.5),
+    ),
+  ]
+
+proc scene_simple(): seq[Mesh] =
+  @[
+    NewMesh(
+      positions = [NewVec3f(0.0, -0.3), NewVec3f(0.3, 0.3), NewVec3f(-0.3, 0.3)],
+      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
+      uvs = [NewVec2f(0.0, -0.3), NewVec2f(0.3, 0.3), NewVec2f(-0.3, 0.3)],
+      material = mat,
+      transform = Translate(0.4, 0.4),
+    ),
+    NewMesh(
+      positions = [NewVec3f(0.0, -0.5), NewVec3f(0.5, 0.5), NewVec3f(-0.5, 0.5)],
+      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
+      uvs = [NewVec2f(0.0, -0.5), NewVec2f(0.5, 0.5), NewVec2f(-0.5, 0.5)],
+      material = mat,
+      transform = Translate(0.4, -0.4),
+    ),
+    NewMesh(
+      positions = [NewVec3f(0.0, -0.6), NewVec3f(0.6, 0.6), NewVec3f(-0.6, 0.6)],
+      colors = [NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1)],
+      uvs = [NewVec2f(0.0, -0.6), NewVec2f(0.6, 0.6), NewVec2f(-0.6, 0.6)],
+      indices = [[0'u32, 1'u32, 2'u32]],
+      autoResize = false,
+      material = mat,
+      transform = Translate(-0.4, 0.4),
+    ),
+    NewMesh(
+      positions = [NewVec3f(0.0, -0.8), NewVec3f(0.8, 0.8), NewVec3f(-0.8, 0.8)],
+      colors = [NewVec4f(0.0, 0.0, 1.0, 1), NewVec4f(0.0, 0.0, 1.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
+      uvs = [NewVec2f(0.0, -0.8), NewVec2f(0.8, 0.8), NewVec2f(-0.8, 0.8)],
+      indices = [[0'u16, 1'u16, 2'u16]],
+      instanceTransforms = [Unit4F32, Unit4F32],
+      material = mat,
+      transform = Translate(-0.4, -0.4),
+    )
+  ]
+
+proc scene_primitives(): seq[Mesh] =
+  var r = Rect(color = "ff0000")
+  var t = Tri(color = "0000ff")
+  var c = Circle(color = "00ff00")
+  r.material = mat
+  t.material = mat
+  c.material = mat
+  r.transform = Translate(NewVec3f(0.5, -0.3))
+  t.transform = Translate(NewVec3f(0.3, 0.3))
+  c.transform = Translate(NewVec3f(-0.3, 0.1))
+  result = @[r, c, t]
+
+proc scene_flag(): seq[Mesh] =
+  @[
+    NewMesh(
+      positions = [NewVec3f(-1.0, -1.0), NewVec3f(1.0, -1.0), NewVec3f(1.0, 1.0), NewVec3f(-1.0, 1.0)],
+      uvs = [NewVec2f(-1.0, -1.0), NewVec2f(1.0, -1.0), NewVec2f(1.0, 1.0), NewVec2f(-1.0, 1.0)],
+      colors = [NewVec4f(-1, -1, 1, 1), NewVec4f(1, -1, 1, 1), NewVec4f(1, 1, 1, 1), NewVec4f(-1, 1, 1, 1)],
+      indices = [[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]],
+      material = mat,
+      transform = Scale(0.5, 0.5)
+    )
+  ]
+
+proc scene_multi_material(): seq[Mesh] =
+  var
+    r1 = Rect(color = "ffffff")
+    r2 = Rect(color = "000000")
+  r1.material = mat
+  r2.material = mat3
+  r1.transform = Translate(NewVec3f(-0.5))
+  r2.transform = Translate(NewVec3f(+0.5))
+  result = @[r1, r2]
+
+proc main() =
+  var engine = InitEngine("Test")
+
+  # INIT RENDERER:
+  const
+    shaderConfiguration1 = CreateShaderConfiguration(
+      name = "shader1",
+      inputs = [
+        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
+        Attr[Vec4f]("color", memoryPerformanceHint = PreferFastWrite),
+        Attr[Mat4]("transform", perInstance = true),
+      ],
+      intermediates = [
+        Attr[Vec4f]("outcolor"),
+      ],
+      outputs = [Attr[Vec4f]("color")],
+      samplers = [Attr[Texture]("baseTexture")],
+      vertexCode = """gl_Position = vec4(position, 1.0) * transform; outcolor = color;""",
+      fragmentCode = "color = texture(baseTexture, outcolor.xy) * 0.5 + outcolor * 0.5;",
+    )
+    shaderConfiguration2 = CreateShaderConfiguration(
+      name = "shader2",
+      inputs = [
+        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
+        Attr[Mat4]("transform", perInstance = true),
+      ],
+      intermediates = [Attr[Vec4f]("outcolor")],
+      outputs = [Attr[Vec4f]("color")],
+      uniforms = [Attr[Vec4f]("color", arrayCount = 1)],
+      vertexCode = """gl_Position = vec4(position, 1.0) * transform; outcolor = Uniforms.color[0];""",
+      fragmentCode = "color = outcolor;",
+    )
+  engine.InitRenderer({
+    Mat1Type: shaderConfiguration1,
+    Mat1Type: shaderConfiguration1,
+    Mat2Type: shaderConfiguration1,
+    SINGLE_COLOR_MATERIAL: shaderConfiguration2,
+  })
+
+  # INIT SCENES
+  var scenes = [
+    Scene(name: "simple", meshes: scene_simple()),
+    Scene(name: "different mesh types", meshes: scene_different_mesh_types()),
+    Scene(name: "primitives", meshes: scene_primitives()),
+    Scene(name: "flag", meshes: scene_flag()),
+    Scene(name: "multimaterial", meshes: scene_multi_material()),
+  ]
+
+  for scene in scenes.mitems:
+    engine.LoadScene(scene)
+
+  # MAINLOOP
+  echo "Setup successfull, start rendering"
+  for i in 0 ..< 3:
+    for scene in scenes.mitems:
+      echo "rendering scene ", scene.name
+      for i in 0 ..< 1000:
+        if not engine.UpdateInputs() or KeyIsDown(Escape):
+          engine.Destroy()
+          return
+        engine.RenderScene(scene)
+
+  # cleanup
+  echo "Start cleanup"
+  engine.Destroy()
+
+when isMainModule:
+  main()
--- a/semicongine/rendering.nim	Mon Jul 15 00:04:33 2024 +0700
+++ b/semicongine/rendering.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -38,7 +38,7 @@
     debugMessenger: VkDebugUtilsMessengerEXT
     # unclear as of yet
     anisotropy*: float32 = 0 # needs to be enable during device creation
-  Swapchain = object
+  Swapchain* = object
     # parameters to InitSwapchain, required for swapchain recreation
     renderPass: VkRenderPass
     vSync: bool
@@ -72,9 +72,6 @@
   SupportedGPUType = float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] | TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] | TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] | TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] | TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] | TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[float32] | TMat43[float64] | TMat4[float32] | TMat4[float64]
   TextureType = TVec1[uint8] | TVec2[uint8] | TVec3[uint8] | TVec4[uint8]
 
-  IndexType = enum
-    None, UInt8, UInt16, UInt32
-
   # shader related types
   DescriptorSetType* = enum
     GlobalSet
--- a/semicongine/rendering/renderer.nim	Mon Jul 15 00:04:33 2024 +0700
+++ b/semicongine/rendering/renderer.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -639,3 +639,14 @@
   mesh: TMesh,
 ) =
   Render(commandBuffer, pipeline, globalSet, materialSet, mesh, EMPTY())
+
+#[
+proc `@`*[T: SupportedGPUType, BT: static BufferType](
+  bufferType: BT,
+  data: openArray[T]
+): GPUArray[T, BT] =
+  GPUArray[T, BT](data: @data)
+]#
+
+proc asGPUArray*[T](data: openArray[T], bufferType: static BufferType): auto =
+  GPUArray[T, bufferType](data: @data)
--- a/tests/config.nims	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-switch("path", "$projectDir/..")
Binary file tests/resources/default/DejaVuSans.ttf has changed
--- a/tests/resources/default/a	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-A
--- a/tests/resources/default/b	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-B
--- a/tests/resources/default/c	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-C
Binary file tests/resources/default/donut.glb has changed
Binary file tests/resources/default/flag.png has changed
Binary file tests/resources/default/key.ogg has changed
Binary file tests/resources/default/test1.glb has changed
Binary file tests/resources/default/toccata_et_fugue.ogg has changed
--- a/tests/resources/mod1/aSubdir/moreSubdir/superSubdir/secret.txt	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-hello world!
--- a/tests/resources/mod1/d	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-D
--- a/tests/test_audio.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-import std/os
-import std/sequtils
-import std/times
-
-import semicongine
-
-
-proc test1() =
-  mixer[].AddSound("test1", NewSound(SineSoundData(1000, 2, 44100)))
-  mixer[].AddSound("test2", NewSound(SineSoundData(500, 2, 44100)))
-
-
-  let s1 = mixer[].Play("test1", loop = true)
-  let s2 = mixer[].Play("test2", loop = true)
-
-  let t0 = now()
-  mixer[].SetLevel(0.5)
-  while true:
-    let runtime = (now() - t0).inMilliseconds()
-    if runtime > 1500:
-      mixer[].SetLevel(0.2)
-    if runtime > 3000:
-      mixer[].Stop(s2)
-    if runtime > 6000:
-      mixer[].Stop("")
-    if runtime > 8000:
-      break
-
-proc test2() =
-  let
-    # notes
-    c = SineSoundData(261.6256, 0.5, 44100)
-    d = SineSoundData(293.6648, 0.5, 44100)
-    e = SineSoundData(329.6276, 0.5, 44100)
-    f = SineSoundData(349.2282, 0.5, 44100)
-    g = SineSoundData(391.9954, 0.5, 44100)
-    a = SineSoundData(440.0000, 0.5, 44100)
-    b = SineSoundData(493.8833, 0.5, 44100)
-    bb = SineSoundData(466.1638, 0.5, 44100)
-    c2 = SineSoundData(523.2511, 0.5, 44100)
-    d2 = SineSoundData(587.3295, 0.5, 44100)
-    bbShort = SineSoundData(466.1638, 0.25, 44100)
-    c2Short = SineSoundData(523.2511, 0.25, 44100)
-    d2Short = SineSoundData(587.3295, 0.25, 44100)
-
-    # song
-    frerejaquesData = concat(
-      f, g, a, f,
-      f, g, a, f,
-      a, bb, c2, c2,
-      a, bb, c2, c2,
-      c2Short, d2Short, c2Short, bbShort, a, f,
-      c2Short, d2Short, c2Short, bbShort, a, f,
-      f, c, f, f,
-      f, c, f, f,
-    )
-
-  mixer[].AddSound("frerejaques", NewSound(frerejaquesData))
-  discard mixer[].Play("frerejaques")
-
-  while mixer[].IsPlaying():
-    sleep(1)
-
-proc test3() =
-  mixer[].AddSound("toccata et fugue", LoadAudio("toccata_et_fugue.ogg"))
-  mixer[].AddSound("ping", NewSound(SineSoundData(500, 0.05, 44100)))
-  mixer[].AddTrack("effects")
-  discard mixer[].Play("toccata et fugue")
-
-
-when isMainModule:
-  StartMixerThread()
-  test1()
-  mixer[].Stop()
-  test2()
-  mixer[].Stop()
-  test3()
-
-  while mixer[].IsPlaying():
-    discard mixer[].Play("ping", track = "effects", stopOtherSounds = true, level = 0.5)
-    # on windows we re-open stdin and this will not work
-    when defined(linux):
-      echo "Press q and enter to exit"
-      if stdin.readLine() == "q":
-        mixer[].Stop()
-    elif defined(windows):
-      sleep(5)
-      mixer[].Stop()
--- a/tests/test_collision.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-import semicongine
-
-proc main() =
-  var scene = Scene(name: "main")
-
-  scene.Add Rect(color = "f00f")
-  scene.Add Rect()
-  scene.Add Circle(color = "0f0f")
-  scene.meshes[0].material = VERTEX_COLORED_MATERIAL.InitMaterialData()
-  scene.meshes[1].material = VERTEX_COLORED_MATERIAL.InitMaterialData()
-  scene.meshes[2].material = VERTEX_COLORED_MATERIAL.InitMaterialData()
-  scene.meshes[1].transform = Scale(0.8, 0.8)
-  scene.meshes[2].transform = Scale(0.1, 0.1)
-  scene.AddShaderGlobal("perspective", Unit4F32)
-
-  const
-    shaderConfiguration = CreateShaderConfiguration(
-      name = "default shader",
-      inputs = [
-        Attr[Mat4]("transform", memoryPerformanceHint = PreferFastRead, perInstance = true),
-        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
-        Attr[Vec4f]("color", memoryPerformanceHint = PreferFastRead),
-      ],
-      intermediates = [Attr[Vec4f]("colorout")],
-      uniforms = [Attr[Mat4]("perspective")],
-      outputs = [Attr[Vec4f]("fragcolor")],
-      vertexCode = """gl_Position = vec4(position, 1.0) * (transform * Uniforms.perspective); colorout = color;""",
-      fragmentCode = """fragcolor = colorout;""",
-    )
-
-  var engine = InitEngine("Test collisions")
-
-  engine.InitRenderer({VERTEX_COLORED_MATERIAL: shaderConfiguration})
-  engine.LoadScene(scene)
-
-  while engine.UpdateInputs() and not KeyIsDown(Escape):
-    if WindowWasResized():
-      var winSize = engine.GetWindow().Size
-      scene.SetShaderGlobal("perspective", OrthoWindowAspect(winSize[0] / winSize[1]))
-    if KeyIsDown(A): scene.meshes[0].transform = scene.meshes[0].transform * Translate(-0.001, 0, 0)
-    if KeyIsDown(D): scene.meshes[0].transform = scene.meshes[0].transform * Translate(0.001, 0, 0)
-    if KeyIsDown(W): scene.meshes[0].transform = scene.meshes[0].transform * Translate(0, -0.001, 0)
-    if KeyIsDown(S): scene.meshes[0].transform = scene.meshes[0].transform * Translate(0, 0.001, 0)
-    if KeyIsDown(Q): scene.meshes[0].transform = scene.meshes[0].transform * Rotate(-0.001, Z)
-    if KeyIsDown(Key.E): scene.meshes[0].transform = scene.meshes[0].transform * Rotate(0.001, Z)
-
-    if KeyIsDown(Key.Z): scene.meshes[1].transform = scene.meshes[1].transform * Rotate(-0.001, Z)
-    if KeyIsDown(Key.X): scene.meshes[1].transform = scene.meshes[1].transform * Rotate(0.001, Z)
-    if KeyIsDown(Key.C): scene.meshes[1].transform = scene.meshes[1].transform * Translate(0, -0.001, 0)
-    if KeyIsDown(Key.V): scene.meshes[1].transform = scene.meshes[1].transform * Translate(0, 0.001, 0)
-    let hitbox = Collider(theType: Box, transform: scene.meshes[0].transform * Translate(-0.5, -0.5))
-    let hitsphere = Collider(theType: Sphere, transform: scene.meshes[2].transform, radius: 0.5)
-    echo Intersects(hitbox, hitsphere)
-    engine.RenderScene(scene)
-  engine.Destroy()
-
-
-when isMainModule:
-  main()
--- a/tests/test_font.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-import std/times
-import std/unicode
-
-import semicongine
-
-
-proc main() =
-  # setup engine
-  var engine = InitEngine("Test fonts")
-  engine.InitRenderer([])
-
-  # build scene
-  var scene = Scene(name: "main")
-  var font = LoadFont("DejaVuSans.ttf", lineHeightPixels = 210'f32)
-  var origin = InitPanel(transform = Scale(0.01, 0.01))
-  var main_text = font.InitText("".toRunes, maxLen = 255, color = NewVec4f(1, 0.15, 0.15, 1), maxWidth = 1.0, transform = Scale(0.0005, 0.0005))
-  var help_text = font.InitText("""Controls
-
-Horizontal alignment:
-  F1: Left
-  F2: Center
-  F3: Right
-Vertical alignment:
-  F4: Top
-  F5: Center
-  F6: Bottom""".toRunes, horizontalAlignment = Left, verticalAlignment = Top, transform = Translate(-0.9, -0.9) * Scale(0.0002, 0.0002))
-  scene.Add origin
-  scene.Add main_text
-  scene.Add help_text
-  engine.LoadScene(scene)
-  mixer[].LoadSound("key", "key.ogg")
-  mixer[].SetLevel(0.5)
-
-  while engine.UpdateInputs() and not KeyIsDown(Escape):
-    var t = cpuTime()
-    main_text.Color = NewVec4f(sin(t) * 0.5 + 0.5, 0.15, 0.15, 1)
-
-    # add character
-    if main_text.text.len < main_text.maxLen - 1:
-      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 KeyWasPressed(c):
-          discard mixer[].Play("key")
-          if KeyIsDown(ShiftL) or KeyIsDown(ShiftR):
-            main_text.text = main_text.text & ($c).toRunes
-          else:
-            main_text.text = main_text.text & ($c).toRunes[0].toLower()
-      if KeyWasPressed(Enter):
-        discard mixer[].Play("key")
-        main_text.text = main_text.text & Rune('\n')
-      if KeyWasPressed(Space):
-        discard mixer[].Play("key")
-        main_text.text = main_text.text & Rune(' ')
-
-    # remove character
-    if KeyWasPressed(Backspace) and main_text.text.len > 0:
-      discard mixer[].Play("key")
-      main_text.text = main_text.text[0 ..< ^1]
-
-    # alignemtn with F-keys
-    if KeyWasPressed(F1): main_text.horizontalAlignment = Left
-    elif KeyWasPressed(F2): main_text.horizontalAlignment = Center
-    elif KeyWasPressed(F3): main_text.horizontalAlignment = Right
-    elif KeyWasPressed(F4): main_text.verticalAlignment = Top
-    elif KeyWasPressed(F5): main_text.verticalAlignment = Center
-    elif KeyWasPressed(F6): main_text.verticalAlignment = Bottom
-
-    origin.Refresh()
-    main_text.text = main_text.text & Rune('_')
-    main_text.Refresh()
-    main_text.text = main_text.text[0 ..< ^1]
-    help_text.Refresh()
-    engine.RenderScene(scene)
-  engine.Destroy()
-
-
-when isMainModule:
-  main()
--- a/tests/test_materials.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-import std/times
-import std/tables
-
-import semicongine
-
-let
-  sampler = Sampler(magnification: VK_FILTER_NEAREST, minification: VK_FILTER_NEAREST)
-  (RT, WT, PT) = (ToRGBA("A51931").AsPixel, ToRGBA("F4F5F8").AsPixel, ToRGBA("2D2A4A").AsPixel)
-  thai = Image[RGBAPixel](width: 7, height: 5, imagedata: @[
-    RT, RT, RT, RT, RT, RT, RT,
-    WT, WT, WT, WT, WT, WT, WT,
-    PT, PT, PT, PT, PT, PT, PT,
-    WT, WT, WT, WT, WT, WT, WT,
-    RT, RT, RT, RT, RT, RT, RT,
-  ])
-  swiss = LoadImage[RGBAPixel]("flag.png")
-  doubleTextureMaterial = MaterialType(
-    name: "Double texture",
-    vertexAttributes: {
-      "position": Vec3F32,
-      "uv": Vec2F32,
-    }.toTable,
-    attributes: {"tex1": TextureType, "tex2": TextureType}.toTable
-  )
-  material = InitMaterialData(
-    theType = doubleTextureMaterial,
-    name = "swiss-thai",
-    attributes = {
-      "tex1": InitDataList(@[Texture(colorImage: thai, sampler: sampler, isGrayscale: false)]),
-      "tex2": InitDataList(@[Texture(colorImage: swiss, sampler: sampler, isGrayscale: false)]),
-    }
-  )
-
-proc main() =
-  var flag = Rect()
-  flag.material = material
-  var scene = Scene(name: "main", meshes: @[flag])
-  scene.AddShaderGlobalArray("test2", @[NewVec4f(), NewVec4f()])
-
-  var engine = InitEngine("Test materials")
-
-  const
-    shaderConfiguration1 = CreateShaderConfiguration(
-      name = "shader 1",
-      inputs = [
-        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
-        Attr[Vec2f]("uv", memoryPerformanceHint = PreferFastRead),
-      ],
-      intermediates = [
-        Attr[Vec2f]("uvout"),
-      ],
-      uniforms = [Attr[Vec4f]("test2", arrayCount = 2)],
-      samplers = @[
-        Attr[Texture]("tex1"),
-        Attr[Texture]("tex2"),
-      ],
-      outputs = [Attr[Vec4f]("color")],
-      vertexCode = """
-      gl_Position = vec4(position.x, position.y + sin(Uniforms.test2[1].x) / Uniforms.test2[1].x * 0.5, position.z, 1.0);
-      uvout = uv;""",
-      fragmentCode = """
-      float d = sin(Uniforms.test2[0].x) * 0.5 + 0.5;
-      color = texture(tex1, uvout) * (1 - d) + texture(tex2, uvout) * d;
-      """,
-    )
-  engine.InitRenderer({
-    doubleTextureMaterial: shaderConfiguration1,
-  })
-  engine.LoadScene(scene)
-  var t = cpuTime()
-  while engine.UpdateInputs() and not KeyIsDown(Escape):
-    var d = float32(cpuTime() - t)
-    SetShaderGlobalArray(scene, "test2", @[NewVec4f(d), NewVec4f(d * 2)])
-    engine.RenderScene(scene)
-  engine.Destroy()
-
-
-when isMainModule:
-  main()
--- a/tests/test_matrix.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,158 +0,0 @@
-import random
-import math
-
-import semicongine
-
-
-proc echoInfo(v: TVec) =
-  echo v
-  echo "  Length: ", v.length
-  echo "  Normlized: ", v.normalized
-  echo "  negated: ", -v
-
-proc echoAdd[T, U](v1: T, v2: U) =
-  echo v1, " + ", v2, " = ", v1 + v2
-proc echoSub[T, U](v1: T, v2: U) =
-  echo v1, " - ", v2, " = ", v1 - v2
-proc echoMul[T, U](v1: T, v2: U) =
-  echo v1, " * ", v2, " = ", v1 * v2
-proc echoDiv[T, U](v1: T, v2: U) =
-  echo v1, " / ", v2, " = ", v1 / v2
-proc echoDot[T, U](v1: T, v2: U) =
-  echo v1, " o ", v2, " = ", v1.dot(v2)
-proc echoCross[T, U](v1: T, v2: U) =
-  echo v1, " x ", v2, " = ", v1.cross(v2)
-
-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)
-
-
-template withAllIntegerMats(stuff: untyped) =
-  stuff(TMat2[int32])
-  stuff(TMat23[int32])
-  stuff(TMat32[int32])
-  stuff(TMat3[int32])
-  stuff(TMat34[int32])
-  stuff(TMat43[int32])
-  stuff(TMat4[int32])
-  stuff(TMat2[int64])
-  stuff(TMat23[int64])
-  stuff(TMat32[int64])
-  stuff(TMat3[int64])
-  stuff(TMat34[int64])
-  stuff(TMat43[int64])
-  stuff(TMat4[int64])
-
-template withAllFloatMats(stuff: untyped) =
-  stuff(TMat2[float32])
-  stuff(TMat23[float32])
-  stuff(TMat32[float32])
-  stuff(TMat3[float32])
-  stuff(TMat34[float32])
-  stuff(TMat43[float32])
-  stuff(TMat4[float32])
-  stuff(TMat2[float64])
-  stuff(TMat23[float64])
-  stuff(TMat32[float64])
-  stuff(TMat3[float64])
-  stuff(TMat34[float64])
-  stuff(TMat43[float64])
-  stuff(TMat4[float64])
-
-template withAllMats(stuff: untyped) =
-  stuff(TMat2[int])
-  stuff(TMat23[int])
-  stuff(TMat32[int])
-  stuff(TMat3[int])
-  stuff(TMat34[int])
-  stuff(TMat43[int])
-  stuff(TMat4[int])
-  stuff(TMat2[float])
-  stuff(TMat23[float])
-  stuff(TMat32[float])
-  stuff(TMat3[float])
-  stuff(TMat34[float])
-  stuff(TMat43[float])
-  stuff(TMat4[float])
-
-template testTranspose(t: typedesc) =
-  echo "testTranspose: ", t
-  let m = t().Randomized()
-  assert m == m.Transposed().Transposed()
-
-template testInversed(t: typedesc) =
-  echo "testTranspose: ", t
-  let m = t().Randomized()
-  var unit = t()
-  for i in unit.RowCount:
-    unit[i][i] = 1
-  assert m.Transposed() * m == unit
-
-template testAssignI(t: typedesc) =
-  echo "testAssignI: ", t
-  var m = t()
-  for i in 0 ..< t.data.len:
-    m[rand(0 ..< t.RowCount), rand(0 ..< t.ColumnCount)] = rand(0'i32 .. 100'i32)
-
-template testAssignF(t: typedesc) =
-  echo "testAssignF: ", t
-  var m = t()
-  for i in 0 ..< t.data.len:
-    m[rand(0 ..< t.RowCount), rand(0 ..< t.ColumnCount)] = rand(100'f)
-
-template testRowCols(t: typedesc) =
-  echo "testRowCols: ", t
-  var m = t().Randomized()
-  for i in 0 ..< t.RowCount:
-    echo m.Row(i)
-  for i in 0 ..< t.ColumnCount:
-    echo m.Col(i)
-
-
-proc testMatrix() =
-  withAllMats(testTranspose)
-  withAllIntegerMats(testAssignI)
-  withAllFloatMats(testAssignF)
-  withAllMats(testRowCols)
-
-  echo Unit2
-  echo Unit2i
-  echo Unit2i8
-  echo Unit2i16
-  echo Unit2i32
-  echo Unit2i64
-
-  echo Unit3
-  echo Unit3i
-  echo Unit3i8
-  echo Unit3i16
-  echo Unit3i32
-  echo Unit3i64
-
-  echo Unit4
-  echo Unit4i
-  echo Unit4i8
-  echo Unit4i16
-  echo Unit4i32
-  echo Unit4i64
-
-  echo TMat2[float32]().Randomized() * One2.Randomized()
-  echo TMat3[float32]().Randomized() * One3.Randomized()
-  echo TMat4[float32]().Randomized() * One4.Randomized()
-
-  echo float32(rand(1'f32)) * TMat2[float32]().Randomized()
-  echo TMat2[float]().Randomized() * rand(1'f)
-  echo TMat2[float]().Randomized() * rand(1'f)
-  echo TMat23[float]().Randomized() * rand(1'f)
-  echo TMat23[float]().Randomized() * rand(1'f)
-  echo TMat3[float]().Randomized() * rand(1'f)
-  echo TMat34[float]().Randomized() * rand(1'f)
-  echo TMat43[float]().Randomized() * rand(1'f)
-  echo TMat4[float]().Randomized() * rand(1'f)
-
-randomize()
-testMatrix()
--- a/tests/test_mesh.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,94 +0,0 @@
-import std/strformat
-import semicongine
-
-const
-  MeshMaterial* = MaterialType(
-    name: "colored single texture material",
-    vertexAttributes: {
-      "position": Vec3F32,
-      "texcoord_0": Vec2F32,
-    }.toTable,
-    attributes: {"baseTexture": TextureType, "color": Vec4F32}.toTable
-  )
-
-proc main() =
-  var scenes = [
-    Scene(name: "Donut", meshes: LoadMeshes("donut.glb", MeshMaterial)[0].toSeq),
-  ]
-
-  var engine = InitEngine("Test meshes")
-  const
-    shaderConfiguration = CreateShaderConfiguration(
-      name = "default shader",
-      inputs = [
-        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
-        Attr[uint16](MATERIALINDEX_ATTRIBUTE, memoryPerformanceHint = PreferFastRead, perInstance = true),
-        Attr[Vec2f]("texcoord_0", memoryPerformanceHint = PreferFastRead),
-        Attr[Mat4]("transform", memoryPerformanceHint = PreferFastWrite, perInstance = true),
-      ],
-      intermediates = [
-        Attr[Vec4f]("vertexColor"),
-        Attr[Vec2f]("colorTexCoord"),
-        Attr[uint16]("materialIndexOut", noInterpolation = true)
-      ],
-      outputs = [Attr[Vec4f]("color")],
-      uniforms = [
-        Attr[Mat4]("projection"),
-        Attr[Mat4]("view"),
-        Attr[Vec4f]("color", arrayCount = 4),
-      ],
-      samplers = [Attr[Texture]("baseTexture", arrayCount = 4)],
-      vertexCode = &"""
-  gl_Position =  vec4(position, 1.0) * (transform * (Uniforms.view * Uniforms.projection));
-  vertexColor = Uniforms.color[{MATERIALINDEX_ATTRIBUTE}];
-  colorTexCoord = texcoord_0;
-  materialIndexOut = {MATERIALINDEX_ATTRIBUTE};
-  """,
-      fragmentCode = "color = texture(baseTexture[materialIndexOut], colorTexCoord) * vertexColor;"
-    )
-  engine.InitRenderer({MeshMaterial: shaderConfiguration})
-
-  for scene in scenes.mitems:
-    scene.AddShaderGlobal("projection", Unit4F32)
-    scene.AddShaderGlobal("view", Unit4F32)
-    engine.LoadScene(scene)
-
-  var
-    size = 1'f32
-    elevation = 0'f32
-    azimut = 0'f32
-    currentScene = 0
-
-  while engine.UpdateInputs() and not KeyIsDown(Escape):
-    if KeyWasPressed(`1`):
-      currentScene = 0
-    elif KeyWasPressed(`2`):
-      currentScene = 1
-    elif KeyWasPressed(`3`):
-      currentScene = 2
-    elif KeyWasPressed(`4`):
-      currentScene = 3
-    elif KeyWasPressed(`5`):
-      currentScene = 4
-    elif KeyWasPressed(`6`):
-      currentScene = 5
-
-    if KeyWasPressed(NumberRowExtra3):
-      size = 0.3'f32
-      elevation = 0'f32
-      azimut = 0'f32
-
-    let ratio = engine.GetWindow().Size[0] / engine.GetWindow().Size[1]
-    size *= 1'f32 + MouseWheel() * 0.05
-    azimut += MouseMove().x / 180'f32
-    elevation -= MouseMove().y / 180'f32
-    scenes[currentScene].SetShaderGlobal("projection", Perspective(PI / 2, ratio, -0.5, 1))
-    scenes[currentScene].SetShaderGlobal(
-      "view",
-       Scale(size, size, size) * Rotate(elevation, NewVec3f(1, 0, 0)) * Rotate(azimut, Yf32)
-    )
-    engine.RenderScene(scenes[currentScene])
-  engine.Destroy()
-
-when isMainModule:
-  main()
--- a/tests/test_noise.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-import semicongine
-
-const w = 500
-const h = 500
-
-var o = "P2\n" & $ w & " " & $ h & "\n255\n"
-
-for y in 0 ..< h:
-  for x in 0 ..< w:
-    let v = (
-      Perlin(NewVec2f(float(x) * 0.01, float(y) * 0.01)) * 0.7 +
-      Perlin(NewVec2f(float(x) * 0.05, float(y) * 0.05)) * 0.25 +
-      Perlin(NewVec2f(float(x) * 0.2, float(y) * 0.2)) * 0.05
-    )
-    o = o & $(int((v * 0.5 + 0.5) * 255)) & " "
-  o = o & "\n"
-echo o
--- a/tests/test_panel.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-import std/unicode
-
-import semicongine
-
-var counter = 0
-var counterText: Text
-
-proc click(panel: var Panel, buttons: set[MouseButton]) =
-  if buttons.contains(Mouse1):
-    counter.inc
-  if buttons.contains(Mouse3):
-    counter.dec
-  counterText.text = $counter
-proc enter(panel: var Panel) =
-  panel.mesh.transform = panel.mesh.transform * Scale(1.05, 1.05)
-  panel.color = NewVec4f(1, 0, 0, 0.3)
-proc leave(panel: var Panel) =
-  panel.mesh.transform = panel.mesh.transform * Scale(1 / 1.05, 1 / 1.05)
-  panel.color = NewVec4f(1, 0, 0, 0.5)
-
-proc main() =
-  # setup engine
-  var engine = InitEngine("Test panels")
-  engine.InitRenderer([])
-
-  const B = [0'u8, 0'u8, 0'u8, 255'u8]
-  const T = [0'u8, 0'u8, 0'u8, 0'u8]
-  # build scene
-  #
-  var
-    font = LoadFont("DejaVuSans.ttf", lineHeightPixels = 210'f32)
-    scene = Scene(name: "main")
-    origin = InitPanel(
-      transform = Scale(0.005, 0.005),
-      color = NewVec4f(1, 1, 1, 1),
-      texture = Texture(isGrayscale: false, colorImage: NewImage[RGBAPixel](3, 3, [T, B, T, B, B, B, T, B, T]), sampler: NEAREST_SAMPLER),
-    )
-    button = InitPanel(
-      transform = Translate(0.2, 0.1) * Scale(0.3, 0.1),
-      color = NewVec4f(1, 0, 0, 0.5),
-      onMouseDown = click,
-      onMouseEnter = enter,
-      onMouseLeave = leave
-    )
-    help_text = font.InitText("""Controls
-
-Horizontal alignment:
-  F1: Left
-  F2: Center
-  F3: Right
-Vertical alignment:
-  F4: Top
-  F5: Center
-  F6: Bottom
-Mouse:
-  Left click: Increase counter
-  Right click: Decrease counter""".toRunes, horizontalAlignment = Left, verticalAlignment = Top, transform = Translate(-0.9, -0.9) * Scale(0.0002, 0.0002))
-
-  counterText = font.InitText(($counter).toRunes, maxLen = 99, transform = Translate(0.2, 0.1) * Scale(0.0004, 0.0004))
-
-  scene.Add counterText
-  scene.Add button
-  scene.Add help_text
-  scene.Add origin
-  engine.LoadScene(scene)
-
-  while engine.UpdateInputs() and not KeyIsDown(Escape):
-    if KeyWasPressed(F1):
-      button.horizontalAlignment = Left
-      counterText.horizontalAlignment = Left
-    elif KeyWasPressed(F2):
-      button.horizontalAlignment = Center
-      counterText.horizontalAlignment = Center
-    elif KeyWasPressed(F3):
-      button.horizontalAlignment = Right
-      counterText.horizontalAlignment = Right
-    elif KeyWasPressed(F4):
-      button.verticalAlignment = Top
-      counterText.verticalAlignment = Top
-    elif KeyWasPressed(F5):
-      button.verticalAlignment = Center
-      counterText.verticalAlignment = Center
-    elif KeyWasPressed(F6):
-      button.verticalAlignment = Bottom
-      counterText.verticalAlignment = Bottom
-
-    engine.ProcessEvents(button)
-
-    button.Refresh()
-    counterText.Refresh()
-    origin.Refresh()
-    help_text.Refresh()
-
-    engine.RenderScene(scene)
-  engine.Destroy()
-
-
-when isMainModule:
-  main()
Binary file tests/test_rendering has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_rendering.nim	Mon Jul 15 20:06:42 2024 +0700
@@ -0,0 +1,63 @@
+import std/options
+import ../semicongine
+
+var
+  mainRenderpass: VkRenderPass
+  swapchain: Swapchain
+
+proc test_01_gl_triangle() =
+  var renderdata = InitRenderData()
+
+  type
+    TrianglShader = object
+      position {.VertexAttribute.}: Vec3f
+      color {.VertexAttribute.}: Vec3f
+      fragmentColor {.Pass.}: Vec3f
+      outColor {.ShaderOutput.}: Vec4f
+      # code
+      vertexCode: string = """void main() {
+      fragmentColor = color;
+      gl_Position = vec4(position, 1);}"""
+      fragmentCode: string = """void main() {
+      outColor = vec4(fragmentColor, 1);}"""
+    TriangleMesh = object
+      position: GPUArray[Vec3f, VertexBuffer]
+      color: GPUArray[Vec3f, VertexBuffer]
+    Empty = object
+  var mesh = TriangleMesh(
+    position: asGPUArray([NewVec3f(-0.5, -0.5), NewVec3f(-0.5, 0.5), NewVec3f(0.5, -0.5)], VertexBuffer),
+    color: asGPUArray([NewVec3f(0, 0, 1), NewVec3f(0, 1, 0), NewVec3f(1, 0, 0)], VertexBuffer),
+  )
+
+  var
+    pipeline = CreatePipeline[TrianglShader](renderPass = mainRenderpass)
+    a, b: Empty
+
+  while UpdateInputs():
+    WithNextFrame(swapchain, framebuffer, commandbuffer):
+      WithRenderPass(mainRenderpass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
+        WithPipeline(commandbuffer, pipeline):
+          # WithBind(commandBuffer, a, b, pipeline, swapchain.currentFiF.int):
+            Render(
+              commandbuffer = commandbuffer,
+              pipeline = pipeline,
+              globalSet = a,
+              materialSet = b,
+              mesh = mesh,
+            )
+
+  # cleanup
+  DestroyPipeline(pipeline)
+  DestroyRenderData(renderdata)
+
+
+when isMainModule:
+  mainRenderpass = CreatePresentationRenderPass()
+  swapchain = InitSwapchain(renderpass = mainRenderpass).get()
+
+  test_01_gl_triangle()
+
+  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  vkDestroyRenderPass(vulkan.device, mainRenderpass, nil)
+  DestroySwapchain(swapchain)
+  DestroyVulkan()
--- a/tests/test_resources.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-import std/os
-import std/streams
-import std/strformat
-import std/strutils
-
-import semicongine
-
-proc list_all_mods_all_files() =
-  for package in Packages():
-    echo &"Files in package {package}:"
-    for i in WalkResources(package = package):
-      echo "  ", i, ": ", i.LoadResource(package = package).readAll().len
-
-proc print_ls(dir, package: string, indent = 2) =
-  for i in dir.List(package = package):
-    if i.kind == pcDir:
-      echo "".align(indent), i.path, "/"
-      print_ls(dir.joinPath(i.path), package = package, indent = indent + 2)
-    else:
-      echo "".align(indent), i.path, ": ", dir.joinPath(i.path).LoadResource(package = package).readAll().len
-
-proc list_files() =
-  for package in Packages():
-    echo &"Recursive walk of package {package}: "
-    print_ls("", package = package)
-
-
-proc main() =
-  echo "Packages available: ", Packages()
-  list_all_mods_all_files()
-  list_files()
-
-when isMainModule:
-  main()
--- a/tests/test_storage.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-import std/strformat
-
-import semicongine
-
-proc testSimple(storage: StorageType) =
-  const TEST_VALUE = 42
-  const KEY = "test"
-
-  # get default
-  assert storage.Load(KEY, 0) == default(type(TEST_VALUE))
-
-  # save and load custom
-  Store(storage, KEY, TEST_VALUE)
-  assert storage.Load(KEY, 0) == TEST_VALUE
-
-proc stressTest(storage: StorageType) =
-  for i in 1 .. 10000:
-    let key = &"key-{i}"
-    Store(storage, key, i)
-    assert storage.Load(key, 0) == i
-
-proc main() =
-  SystemStorage.Purge()
-  echo "SystemStorage: Testing simple store/load"
-  SystemStorage.testSimple()
-
-  UserStorage.Purge()
-  echo "UserStorage: Testing simple store/load"
-  UserStorage.testSimple()
-
-  echo "Stress test with 10'000 saves/loads"
-  SystemStorage.stressTest()
-
-  SystemStorage.Purge()
-  UserStorage.Purge()
-
-
-when isMainModule:
-  main()
--- a/tests/test_vector.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-import random
-
-import semicongine
-
-
-proc echoInfo[T](v: TVec2[T] or TVec3[T] or TVec4[T]) =
-  echo v
-  echo "  Length: ", v.Length
-  when T is SomeFloat:
-    echo "  Normlized: ", v.Normalized
-  echo "  negated: ", -v
-
-proc echoAdd[T, U](v1: T, v2: U) =
-  echo v1, " + ", v2, " = ", v1 + v2
-proc echoSub[T, U](v1: T, v2: U) =
-  echo v1, " - ", v2, " = ", v1 - v2
-proc echoMul[T, U](v1: T, v2: U) =
-  echo v1, " * ", v2, " = ", v1 * v2
-proc echoDiv[T, U](v1: T, v2: U) =
-  echo v1, " / ", v2, " = ", v1 / v2
-proc echoDot[T, U](v1: T, v2: U) =
-  echo v1, " o ", v2, " = ", v1.Dot(v2)
-proc echoCross[T, U](v1: T, v2: U) =
-  echo v1, " x ", v2, " = ", v1.Cross(v2)
-
-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 testVector() =
-  echoInfo(randVec2I())
-  echoInfo(randVec2F())
-  echoInfo(randVec3I())
-  echoInfo(randVec3F())
-  echoInfo(randVec4I())
-  echoInfo(randVec4F())
-
-  # test math operations vector-vector
-  echoAdd(randVec2I(), randVec2I())
-  echoAdd(randVec2F(), randVec2F())
-  echoAdd(randVec3I(), randVec3I())
-  echoAdd(randVec3F(), randVec3F())
-  echoAdd(randVec4I(), randVec4I())
-  echoAdd(randVec4F(), randVec4F())
-  echoSub(randVec2I(), randVec2I())
-  echoSub(randVec2F(), randVec2F())
-  echoSub(randVec3I(), randVec3I())
-  echoSub(randVec3F(), randVec3F())
-  echoSub(randVec4I(), randVec4I())
-  echoSub(randVec4F(), randVec4F())
-  echoMul(randVec2I(), randVec2I())
-  echoMul(randVec2F(), randVec2F())
-  echoMul(randVec3I(), randVec3I())
-  echoMul(randVec3F(), randVec3F())
-  echoMul(randVec4I(), randVec4I())
-  echoMul(randVec4F(), randVec4F())
-  echoDiv(randVec2I(), randVec2I())
-  echoDiv(randVec2F(), randVec2F())
-  echoDiv(randVec3I(), randVec3I())
-  echoDiv(randVec3F(), randVec3F())
-  echoDiv(randVec4I(), randVec4I())
-  echoDiv(randVec4F(), randVec4F())
-  echoDot(randVec2I(), randVec2I())
-  echoDot(randVec2F(), randVec2F())
-  echoDot(randVec3I(), randVec3I())
-  echoDot(randVec3F(), randVec3F())
-  echoDot(randVec4I(), randVec4I())
-  echoDot(randVec4F(), randVec4F())
-  echoCross(randVec3I(), randVec3I())
-  echoCross(randVec3F(), randVec3F())
-
-
-  # test math operations vector-scalar
-  echoAdd(randVec2I(), rand(1 .. 10))
-  echoAdd(randVec2F(), rand(10'f))
-  echoAdd(randVec3I(), rand(1 .. 10))
-  echoAdd(randVec3F(), rand(10'f))
-  echoAdd(randVec4I(), rand(1 .. 10))
-  echoAdd(randVec4F(), rand(10'f))
-  echoSub(randVec2I(), rand(1 .. 10))
-  echoSub(randVec2F(), rand(10'f))
-  echoSub(randVec3I(), rand(1 .. 10))
-  echoSub(randVec3F(), rand(10'f))
-  echoSub(randVec4I(), rand(1 .. 10))
-  echoSub(randVec4F(), rand(10'f))
-  echoMul(randVec2I(), rand(1 .. 10))
-  echoMul(randVec2F(), rand(10'f))
-  echoMul(randVec3I(), rand(1 .. 10))
-  echoMul(randVec3F(), rand(10'f))
-  echoMul(randVec4I(), rand(1 .. 10))
-  echoMul(randVec4F(), rand(10'f))
-  echoDiv(randVec2I(), rand(1 .. 10))
-  echoDiv(randVec2F(), rand(10'f))
-  echoDiv(randVec3I(), rand(1 .. 10))
-  echoDiv(randVec3F(), rand(10'f))
-  echoDiv(randVec4I(), rand(1 .. 10))
-  echoDiv(randVec4F(), rand(10'f))
-
-  # test math operations scalar-vector
-  echoAdd(rand(1 .. 10), randVec2I())
-  echoAdd(rand(10'f), randVec2F())
-  echoAdd(rand(1 .. 10), randVec3I())
-  echoAdd(rand(10'f), randVec3F())
-  echoAdd(rand(1 .. 10), randVec4I())
-  echoAdd(rand(10'f), randVec4F())
-  echoSub(rand(1 .. 10), randVec2I())
-  echoSub(rand(10'f), randVec2F())
-  echoSub(rand(1 .. 10), randVec3I())
-  echoSub(rand(10'f), randVec3F())
-  echoSub(rand(1 .. 10), randVec4I())
-  echoSub(rand(10'f), randVec4F())
-  echoMul(rand(1 .. 10), randVec2I())
-  echoMul(rand(10'f), randVec2F())
-  echoMul(rand(1 .. 10), randVec3I())
-  echoMul(rand(10'f), randVec3F())
-  echoMul(rand(1 .. 10), randVec4I())
-  echoMul(rand(10'f), randVec4F())
-  echoDiv(rand(1 .. 10), randVec2I())
-  echoDiv(rand(10'f), randVec2F())
-  echoDiv(rand(1 .. 10), randVec3I())
-  echoDiv(rand(10'f), randVec3F())
-  echoDiv(rand(1 .. 10), randVec4I())
-  echoDiv(rand(10'f), randVec4F())
-
-  # test attribute syntax sugar
-  echo "float2int ", To[int](randVec2F())
-  echo "int2float ", To[float](randVec2I())
-  echo "float2int ", To[int](randVec3F())
-  echo "int2float ", To[float](randVec3I())
-  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
-
-  # test setters
-  var v1 = randVec2I(); v1.x = 1; v1.y = 2; v1.r = 3; v1.g = 4
-  v1.xy = randVec2I(); v1.yx = randVec2I(); v1.rg = randVec2I(); v1.gr = randVec2I()
-  var v2 = randVec2F(); v2.x = 1.0; v2.y = 2.0; v2.r = 3.0; v2.g = 4.0
-  v2.xy = randVec2F(); v2.yx = randVec2F(); v2.rg = randVec2F(); v2.gr = randVec2F()
-
-  var v3 = randVec3I(); v3.x = 1; v3.y = 2; v3.z = 3; v3.r = 4; v3.g = 5; v3.b = 6
-  v3.xyz = randVec3I(); v3.rgb = randVec3I()
-  var v4 = randVec3F(); v4.x = 1.0; v4.y = 2.0; v4.z = 3.0; v4.r = 4.0; v4.g = 5.0; v4.b = 6.0
-  v4.xyz = randVec3F(); v4.rgb = randVec3F()
-
-  var v5 = randVec4I(); v5.x = 1; v5.y = 2; v5.z = 3; v5.w = 4; v5.r = 5; v5.g = 6; v5.b = 7; v5.a = 8
-  v5.xyzw = randVec4I(); v5.rgba = randVec4I()
-  var v6 = randVec4F(); v6.x = 1.0; v6.y = 2.0; v6.z = 3.0; v6.w = 4.0; v6.r = 5.0; v6.g = 6.0; v6.b = 7.0; v6.a = 8.0
-  v6.xyzw = randVec4F(); v6.rgba = randVec4F()
-
-  echo "V2I.xx: ", randVec2I().xx
-  echo "V2I.yx: ", randVec2I().xy
-  echo "V2F.xx: ", randVec2F().xx
-  echo "V2F.yx: ", randVec2F().yx
-  echo "V2I.rr: ", randVec2I().rr
-  echo "V2I.gr: ", randVec2I().gr
-  echo "V2F.rr: ", randVec2F().rr
-  echo "V2F.gr: ", randVec2F().gr
-
-  echo "V3I.yyy: ", randVec3I().yyy
-  echo "V3I.yxz: ", randVec3I().xyz
-  echo "V3F.yyy: ", randVec3F().yyy
-  echo "V3F.yxz: ", randVec3F().yxz
-  echo "V3I.ggg: ", randVec3I().ggg
-  echo "V3I.grb: ", randVec3I().grb
-  echo "V3F.ggg: ", randVec3F().ggg
-  echo "V3F.grb: ", randVec3F().grb
-
-  echo "V4I.zzzz: ", randVec4I().zzzz
-  echo "V4I.yxzw: ", randVec4I().xyzw
-  echo "V4F.zzzz: ", randVec4F().zzzz
-  echo "V4F.yxzw: ", randVec4F().yxzw
-  echo "V4I.bbbb: ", randVec4I().bbbb
-  echo "V4I.grba: ", randVec4I().grba
-  echo "V4F.bbbb: ", randVec4F().bbbb
-  echo "V4F.grba: ", randVec4F().grba
-
-  echo "X: ", X
-  echo "Y: ", Y
-  echo "Z: ", Z
-  echo "X: ", Xi
-  echo "Y: ", Yi
-  echo "Z: ", Zi
-
-
-randomize()
-testVector()
--- a/tests/test_vulkan_wrapper.nim	Mon Jul 15 00:04:33 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,252 +0,0 @@
-import std/tables
-
-import semicongine
-
-
-let
-  sampler = Sampler(
-    magnification: VK_FILTER_NEAREST,
-    minification: VK_FILTER_NEAREST,
-    wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT,
-    wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT,
-  )
-  (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8])
-  Mat1Type = MaterialType(
-    name: "single texture material 1",
-    vertexAttributes: {
-      "color": Vec4F32,
-      "position": Vec3F32,
-      "uv": Vec2F32,
-    }.toTable,
-    attributes: {"baseTexture": TextureType}.toTable
-  )
-  mat = Mat1Type.InitMaterialData(
-    name = "mat",
-    attributes = {
-      "baseTexture": InitDataList(@[Texture(isGrayscale: false, colorImage: Image[RGBAPixel](width: 5, height: 5, imagedata: @[
-      R, R, R, R, R,
-      R, R, W, R, R,
-      R, W, W, W, R,
-      R, R, W, R, R,
-      R, R, R, R, R,
-    ]), sampler: sampler)])
-  }.toTable
-  )
-  Mat2Type = MaterialType(
-    name: "single texture material 2",
-    vertexAttributes: {
-      "color": Vec4F32,
-      "position": Vec3F32,
-      "uv": Vec2F32,
-    }.toTable,
-    attributes: {"baseTexture": TextureType}.toTable
-  )
-  mat2 = Mat2Type.InitMaterialData(
-    name = "mat2",
-    attributes = {
-      "baseTexture": InitDataList(@[Texture(isGrayscale: false, colorImage: Image[RGBAPixel](width: 5, height: 5, imagedata: @[
-      R, W, R, W, R,
-      W, R, W, R, W,
-      R, W, R, W, R,
-      W, R, W, R, W,
-      R, W, R, W, R,
-    ]), sampler: sampler)])
-  }.toTable
-  )
-  mat3 = SINGLE_COLOR_MATERIAL.InitMaterialData(
-    name = "mat3",
-    attributes = {
-      "color": InitDataList(@[NewVec4f(0, 1, 0, 1)])
-    }.toTable
-  )
-
-proc scene_different_mesh_types(): seq[Mesh] =
-  @[
-    NewMesh(
-      positions = [NewVec3f(0.0, -0.5), NewVec3f(0.5, 0.5), NewVec3f(-0.5, 0.5)],
-      uvs = [NewVec2f(0.0, -0.5), NewVec2f(0.5, 0.5), NewVec2f(-0.5, 0.5)],
-      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
-      material = mat,
-      transform = Translate(-0.7, -0.5),
-    ),
-    NewMesh(
-      positions = [NewVec3f(0.0, -0.4), NewVec3f(0.4, 0.4), NewVec3f(-0.4, 0.5)],
-      uvs = [NewVec2f(0.0, -0.4), NewVec2f(0.4, 0.4), NewVec2f(-0.4, 0.5)],
-      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
-      material = mat,
-      transform = Translate(0, -0.5),
-    ),
-    NewMesh(
-      positions = [NewVec3f(0.0, 0.5), NewVec3f(0.5, -0.5), NewVec3f(-0.5, -0.5)],
-      uvs = [NewVec2f(0.0, 0.5), NewVec2f(0.5, -0.5), NewVec2f(-0.5, -0.5)],
-      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
-      indices = [[0'u16, 2'u16, 1'u16]],
-      material = mat2,
-      transform = Translate(0.7, -0.5),
-    ),
-    NewMesh(
-      positions = [NewVec3f(0.0, 0.4), NewVec3f(0.4, -0.4), NewVec3f(-0.4, -0.4)],
-      uvs = [NewVec2f(0.0, 0.4), NewVec2f(0.4, -0.4), NewVec2f(-0.4, -0.4)],
-      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
-      indices = [[0'u16, 2'u16, 1'u16]],
-      material = mat2,
-      transform = Translate(-0.7, 0.5),
-    ),
-    NewMesh(
-      positions = [NewVec3f(0.4, 0.5), NewVec3f(0.9, -0.3), NewVec3f(0.0, -0.3)],
-      uvs = [NewVec2f(0.4, 0.5), NewVec2f(0.9, -0.3), NewVec2f(0.0, -0.3)],
-      colors = [NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1)],
-      indices = [[0'u32, 2'u32, 1'u32]],
-      autoResize = false,
-      material = mat2,
-      transform = Translate(0, 0.5),
-    ),
-    NewMesh(
-      positions = [NewVec3f(0.4, 0.5), NewVec3f(0.9, -0.3), NewVec3f(0.0, -0.3)],
-      uvs = [NewVec2f(0.4, 0.5), NewVec2f(0.9, -0.3), NewVec2f(0.0, -0.3)],
-      colors = [NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1)],
-      indices = [[0'u32, 2'u32, 1'u32]],
-      autoResize = false,
-      material = mat2,
-      transform = Translate(0.7, 0.5),
-    ),
-  ]
-
-proc scene_simple(): seq[Mesh] =
-  @[
-    NewMesh(
-      positions = [NewVec3f(0.0, -0.3), NewVec3f(0.3, 0.3), NewVec3f(-0.3, 0.3)],
-      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
-      uvs = [NewVec2f(0.0, -0.3), NewVec2f(0.3, 0.3), NewVec2f(-0.3, 0.3)],
-      material = mat,
-      transform = Translate(0.4, 0.4),
-    ),
-    NewMesh(
-      positions = [NewVec3f(0.0, -0.5), NewVec3f(0.5, 0.5), NewVec3f(-0.5, 0.5)],
-      colors = [NewVec4f(1.0, 0.0, 0.0, 1), NewVec4f(0.0, 1.0, 0.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
-      uvs = [NewVec2f(0.0, -0.5), NewVec2f(0.5, 0.5), NewVec2f(-0.5, 0.5)],
-      material = mat,
-      transform = Translate(0.4, -0.4),
-    ),
-    NewMesh(
-      positions = [NewVec3f(0.0, -0.6), NewVec3f(0.6, 0.6), NewVec3f(-0.6, 0.6)],
-      colors = [NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1), NewVec4f(1.0, 1.0, 0.0, 1)],
-      uvs = [NewVec2f(0.0, -0.6), NewVec2f(0.6, 0.6), NewVec2f(-0.6, 0.6)],
-      indices = [[0'u32, 1'u32, 2'u32]],
-      autoResize = false,
-      material = mat,
-      transform = Translate(-0.4, 0.4),
-    ),
-    NewMesh(
-      positions = [NewVec3f(0.0, -0.8), NewVec3f(0.8, 0.8), NewVec3f(-0.8, 0.8)],
-      colors = [NewVec4f(0.0, 0.0, 1.0, 1), NewVec4f(0.0, 0.0, 1.0, 1), NewVec4f(0.0, 0.0, 1.0, 1)],
-      uvs = [NewVec2f(0.0, -0.8), NewVec2f(0.8, 0.8), NewVec2f(-0.8, 0.8)],
-      indices = [[0'u16, 1'u16, 2'u16]],
-      instanceTransforms = [Unit4F32, Unit4F32],
-      material = mat,
-      transform = Translate(-0.4, -0.4),
-    )
-  ]
-
-proc scene_primitives(): seq[Mesh] =
-  var r = Rect(color = "ff0000")
-  var t = Tri(color = "0000ff")
-  var c = Circle(color = "00ff00")
-  r.material = mat
-  t.material = mat
-  c.material = mat
-  r.transform = Translate(NewVec3f(0.5, -0.3))
-  t.transform = Translate(NewVec3f(0.3, 0.3))
-  c.transform = Translate(NewVec3f(-0.3, 0.1))
-  result = @[r, c, t]
-
-proc scene_flag(): seq[Mesh] =
-  @[
-    NewMesh(
-      positions = [NewVec3f(-1.0, -1.0), NewVec3f(1.0, -1.0), NewVec3f(1.0, 1.0), NewVec3f(-1.0, 1.0)],
-      uvs = [NewVec2f(-1.0, -1.0), NewVec2f(1.0, -1.0), NewVec2f(1.0, 1.0), NewVec2f(-1.0, 1.0)],
-      colors = [NewVec4f(-1, -1, 1, 1), NewVec4f(1, -1, 1, 1), NewVec4f(1, 1, 1, 1), NewVec4f(-1, 1, 1, 1)],
-      indices = [[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]],
-      material = mat,
-      transform = Scale(0.5, 0.5)
-    )
-  ]
-
-proc scene_multi_material(): seq[Mesh] =
-  var
-    r1 = Rect(color = "ffffff")
-    r2 = Rect(color = "000000")
-  r1.material = mat
-  r2.material = mat3
-  r1.transform = Translate(NewVec3f(-0.5))
-  r2.transform = Translate(NewVec3f(+0.5))
-  result = @[r1, r2]
-
-proc main() =
-  var engine = InitEngine("Test")
-
-  # INIT RENDERER:
-  const
-    shaderConfiguration1 = CreateShaderConfiguration(
-      name = "shader1",
-      inputs = [
-        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
-        Attr[Vec4f]("color", memoryPerformanceHint = PreferFastWrite),
-        Attr[Mat4]("transform", perInstance = true),
-      ],
-      intermediates = [
-        Attr[Vec4f]("outcolor"),
-      ],
-      outputs = [Attr[Vec4f]("color")],
-      samplers = [Attr[Texture]("baseTexture")],
-      vertexCode = """gl_Position = vec4(position, 1.0) * transform; outcolor = color;""",
-      fragmentCode = "color = texture(baseTexture, outcolor.xy) * 0.5 + outcolor * 0.5;",
-    )
-    shaderConfiguration2 = CreateShaderConfiguration(
-      name = "shader2",
-      inputs = [
-        Attr[Vec3f]("position", memoryPerformanceHint = PreferFastRead),
-        Attr[Mat4]("transform", perInstance = true),
-      ],
-      intermediates = [Attr[Vec4f]("outcolor")],
-      outputs = [Attr[Vec4f]("color")],
-      uniforms = [Attr[Vec4f]("color", arrayCount = 1)],
-      vertexCode = """gl_Position = vec4(position, 1.0) * transform; outcolor = Uniforms.color[0];""",
-      fragmentCode = "color = outcolor;",
-    )
-  engine.InitRenderer({
-    Mat1Type: shaderConfiguration1,
-    Mat1Type: shaderConfiguration1,
-    Mat2Type: shaderConfiguration1,
-    SINGLE_COLOR_MATERIAL: shaderConfiguration2,
-  })
-
-  # INIT SCENES
-  var scenes = [
-    Scene(name: "simple", meshes: scene_simple()),
-    Scene(name: "different mesh types", meshes: scene_different_mesh_types()),
-    Scene(name: "primitives", meshes: scene_primitives()),
-    Scene(name: "flag", meshes: scene_flag()),
-    Scene(name: "multimaterial", meshes: scene_multi_material()),
-  ]
-
-  for scene in scenes.mitems:
-    engine.LoadScene(scene)
-
-  # MAINLOOP
-  echo "Setup successfull, start rendering"
-  for i in 0 ..< 3:
-    for scene in scenes.mitems:
-      echo "rendering scene ", scene.name
-      for i in 0 ..< 1000:
-        if not engine.UpdateInputs() or KeyIsDown(Escape):
-          engine.Destroy()
-          return
-        engine.RenderScene(scene)
-
-  # cleanup
-  echo "Start cleanup"
-  engine.Destroy()
-
-when isMainModule:
-  main()