changeset 478:871ee602bf95

add: vertex basics, some refactoring
author Sam <sam@basx.dev>
date Sun, 01 Jan 2023 01:00:50 +0700
parents f226c99b5043
children 16842d15319a
files notes src/engine.nim src/math/matrix.nim src/math/vector.nim src/matrix.nim src/notes src/shader.nim src/vector.nim src/vertex.nim src/vulkan_helpers.nim
diffstat 10 files changed, 877 insertions(+), 640 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/notes	Sun Jan 01 01:00:50 2023 +0700
@@ -0,0 +1,9 @@
+For implementation of font rendering:
+https://developer.apple.com/fonts/TrueType-Reference-Manual/
+
+ideas:
+- mining-game with structure simulation, crashing mineshafts, etc.
+- top-down 2d shooter (wild west?) with one room per scene, fixed camera
+- Top-down 2d shooter with autoshoot (-> what is the challenge? position? cover? effects?)
+- Clean up something
+- Defend house (embassy?), against burglar, enemies, receive guests
--- a/src/engine.nim	Fri Dec 30 15:56:17 2022 +0700
+++ b/src/engine.nim	Sun Jan 01 01:00:50 2023 +0700
@@ -8,6 +8,9 @@
 import ./vulkan_helpers
 import ./window
 import ./events
+import ./math/vector
+import ./shader
+import ./vertex
 
 import ./glslang/glslang
 
@@ -18,24 +21,56 @@
 addHandler(logger)
 
 
-var vertexShaderCode: string = """#version 450
+type
+  MyVertex = object
+    position: VertexAttribute[Vec2[float32]]
+    color: VertexAttribute[Vec3[float32]]
+
+const vertices = [
+    (Vec2([ 0.0'f32, -0.5'f32]), Vec3([1.0'f32, 0.0'f32, 0.0'f32])),
+    (Vec2([ 0.5'f32,  0.5'f32]), Vec3([0.0'f32, 1.0'f32, 0.0'f32])),
+    (Vec2([-0.5'f32,  0.5'f32]), Vec3([0.0'f32, 0.0'f32, 1.0'f32]))
+]
+
+
+proc getBindingDescription(binding: int): auto =
+  VkVertexInputBindingDescription(
+    binding: uint32(binding),
+    stride: uint32(sizeof(vertices[0])),
+    inputRate: VK_VERTEX_INPUT_RATE_VERTEX, # VK_VERTEX_INPUT_RATE_INSTANCE for instances
+  )
+
+proc getAttributeDescriptions(binding: int): auto =
+  [
+    VkVertexInputAttributeDescription(
+      binding: 0'u32,
+      location: 0,
+      format: VK_FORMAT_R32G32_SFLOAT,
+      offset: 0,
+    ),
+    VkVertexInputAttributeDescription(
+      binding: 0'u32,
+      location: 1,
+      format: VK_FORMAT_R32G32B32_SFLOAT,
+      offset: uint32(sizeof(Vec2)), # use offsetOf?
+    ),
+  ]
+
+var vertexShaderCode = """
+#version 450
+
+layout(location = 0) in vec2 inPosition;
+layout(location = 1) in vec3 inColor;
+
 layout(location = 0) out vec3 fragColor;
-vec3 colors[3] = vec3[](
-    vec3(1.0, 0.0, 0.0),
-    vec3(0.0, 1.0, 0.0),
-    vec3(0.0, 0.0, 1.0)
-);
-vec2 positions[3] = vec2[](
-  vec2(0.0, -0.5),
-  vec2(0.5, 0.5),
-  vec2(-0.5, 0.5)
-);
+
 void main() {
-  gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
-  fragColor = colors[gl_VertexIndex];
-}"""
+    gl_Position = vec4(inPosition, 0.0, 1.0);
+    fragColor = inColor;
+}
+"""
 
-var fragmentShaderCode: string = """#version 450
+var fragmentShaderCode = """#version 450
 layout(location = 0) out vec4 outColor;
 layout(location = 0) in vec3 fragColor;
 void main() {
@@ -57,7 +92,7 @@
     images: seq[VkImage]
     imageviews: seq[VkImageView]
   RenderPipeline = object
-    shaderStages*: seq[VkPipelineShaderStageCreateInfo]
+    shaders*: seq[ShaderProgram]
     layout*: VkPipelineLayout
     pipeline*: VkPipeline
   QueueFamily = object
@@ -265,11 +300,9 @@
   checkVkResult device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result))
 
 proc setupRenderPipeline(device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass): RenderPipeline =
-  # (seq[VkPipelineShaderStageCreateInfo], VkViewport, VkRect2D, VkPipelineLayout, VkPipeline) =
-
   # load shaders
-  result.shaderStages.add(device.createShaderStage(VK_SHADER_STAGE_VERTEX_BIT, vertexShaderCode))
-  result.shaderStages.add(device.createShaderStage(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShaderCode))
+  result.shaders.add(device.initShaderProgram(VK_SHADER_STAGE_VERTEX_BIT, vertexShaderCode))
+  result.shaders.add(device.initShaderProgram(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShaderCode))
 
   var
     # define which parts can be dynamic (pipeline is fixed after setup)
@@ -279,13 +312,16 @@
       dynamicStateCount: uint32(dynamicStates.len),
       pDynamicStates: addr(dynamicStates[0]),
     )
+    vertexbindings = generateInputVertexBinding[MyVertex]()
+    attributebindings = generateInputAttributeBinding[MyVertex]()
+
     # define input data format
     vertexInputInfo = VkPipelineVertexInputStateCreateInfo(
       sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
-      vertexBindingDescriptionCount: 0,
-      pVertexBindingDescriptions: nil,
-      vertexAttributeDescriptionCount: 0,
-      pVertexAttributeDescriptions: nil,
+      vertexBindingDescriptionCount: uint32(vertexbindings.len),
+      pVertexBindingDescriptions: addr(vertexbindings[0]),
+      vertexAttributeDescriptionCount: uint32(attributebindings.len),
+      pVertexAttributeDescriptions: addr(attributebindings[0]),
     )
     inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
       sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
@@ -358,10 +394,13 @@
     )
   checkVkResult device.vkCreatePipelineLayout(addr(pipelineLayoutInfo), nil, addr(result.layout))
 
+  var stages: seq[VkPipelineShaderStageCreateInfo]
+  for shader in result.shaders:
+    stages.add(shader.shader)
   var pipelineInfo = VkGraphicsPipelineCreateInfo(
     sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
-    stageCount: 2,
-    pStages: addr(result.shaderStages[0]),
+    stageCount: uint32(stages.len),
+    pStages: addr(stages[0]),
     pVertexInputState: addr(vertexInputInfo),
     pInputAssemblyState: addr(inputAssembly),
     pViewportState: addr(viewportState),
@@ -633,8 +672,8 @@
   engine.vulkan.device.device.vkDestroyPipelineLayout(engine.vulkan.pipeline.layout, nil)
   engine.vulkan.device.device.vkDestroyRenderPass(engine.vulkan.renderPass, nil)
 
-  for shaderStage in engine.vulkan.pipeline.shaderStages:
-    engine.vulkan.device.device.vkDestroyShaderModule(shaderStage.module, nil)
+  for shader in engine.vulkan.pipeline.shaders:
+    engine.vulkan.device.device.vkDestroyShaderModule(shader.shader.module, nil)
 
   engine.vulkan.instance.vkDestroySurfaceKHR(engine.vulkan.surface, nil)
   engine.vulkan.device.device.vkDestroyDevice(nil)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/math/matrix.nim	Sun Jan 01 01:00:50 2023 +0700
@@ -0,0 +1,357 @@
+import std/math
+import std/macros
+import std/random
+import std/strutils
+import std/typetraits
+
+import ./vector
+
+type
+  # layout is row-first
+  # having an object instead of directly aliasing the array seems a bit ugly at
+  # first, but is necessary to be able to work correctly with distinguished
+  # types (i.e. Mat23 and Mat32 would be an alias for the same type array[6, T]
+  # which prevents the type system from identifying the correct type at times)
+  #
+  # Though, great news is that objects have zero overhead!
+  Mat22*[T: SomeNumber] = object
+    data: array[4, T]
+  Mat23*[T: SomeNumber] = object
+    data: array[6, T]
+  Mat32*[T: SomeNumber] = object
+    data: array[6, T]
+  Mat33*[T: SomeNumber] = object
+    data: array[9, T]
+  Mat34*[T: SomeNumber] = object
+    data: array[12, T]
+  Mat43*[T: SomeNumber] = object
+    data: array[12, T]
+  Mat44*[T: SomeNumber] = object
+    data: array[16, T]
+  MatMM* = Mat22|Mat33|Mat44
+  MatMN* = Mat23|Mat32|Mat34|Mat43
+  Mat* = MatMM|MatMN
+  IntegerMat = Mat22[SomeInteger]|Mat33[SomeInteger]|Mat44[SomeInteger]|Mat23[SomeInteger]|Mat32[SomeInteger]|Mat34[SomeInteger]|Mat43[SomeInteger]
+  FloatMat = Mat22[SomeFloat]|Mat33[SomeFloat]|Mat44[SomeFloat]|Mat23[SomeFloat]|Mat32[SomeFloat]|Mat34[SomeFloat]|Mat43[SomeFloat]
+
+func unit22[T: SomeNumber](): auto {.compiletime.} = Mat22[T](data:[
+  T(1), T(0),
+  T(0), T(1),
+])
+func unit33[T: SomeNumber](): auto {.compiletime.} = Mat33[T](data:[
+  T(1), T(0), T(0),
+  T(0), T(1), T(0),
+  T(0), T(0), T(1),
+])
+func unit44[T: SomeNumber](): auto {.compiletime.} = Mat44[T](data: [
+  T(1), T(0), T(0), T(0),
+  T(0), T(1), T(0), T(0),
+  T(0), T(0), T(1), T(0),
+  T(0), T(0), T(0), T(1),
+])
+
+# generates constants: Unit
+# Also for Y, Z, R, G, B
+# not sure if this is necessary or even a good idea...
+macro generateAllConsts() =
+  result = newStmtList()
+  for theType in ["int", "int8", "int16", "int32", "int64", "float", "float32", "float64"]:
+    var typename = theType[0 .. 0]
+    if theType[^2].isDigit:
+      typename = typename & theType[^2]
+    if theType[^1].isDigit:
+      typename = typename & theType[^1]
+    result.add(newConstStmt(
+      postfix(ident("Unit22" & typename), "*"),
+      newCall(nnkBracketExpr.newTree(ident("unit22"), ident(theType)))
+    ))
+    result.add(newConstStmt(
+      postfix(ident("Unit33" & typename), "*"),
+      newCall(nnkBracketExpr.newTree(ident("unit33"), ident(theType)))
+    ))
+    result.add(newConstStmt(
+      postfix(ident("Unit44" & typename), "*"),
+      newCall(nnkBracketExpr.newTree(ident("unit44"), ident(theType)))
+    ))
+
+generateAllConsts()
+
+const Unit22* = unit22[float]()
+const Unit33* = unit33[float]()
+const Unit44* = unit44[float]()
+
+template rowCount*(m: typedesc): int =
+  when m is Mat22: 2
+  elif m is Mat23: 2
+  elif m is Mat32: 3
+  elif m is Mat33: 3
+  elif m is Mat34: 3
+  elif m is Mat43: 4
+  elif m is Mat44: 4
+template columnCount*(m: typedesc): int =
+  when m is Mat22: 2
+  elif m is Mat23: 3
+  elif m is Mat32: 2
+  elif m is Mat33: 3
+  elif m is Mat34: 4
+  elif m is Mat43: 3
+  elif m is Mat44: 4
+
+
+func toString[T](value: T): string =
+  var
+    strvalues: seq[string]
+    maxwidth = 0
+
+  for n in value.data:
+    let strval = $n
+    strvalues.add(strval)
+    if strval.len > maxwidth:
+      maxwidth = strval.len
+
+  for i in 0 ..< strvalues.len:
+    let filler = " ".repeat(maxwidth - strvalues[i].len)
+    if i mod T.columnCount == T.columnCount - 1:
+      result &= filler & strvalues[i] & "\n"
+    else:
+      if i mod T.columnCount == 0:
+        result &= "  "
+      result &= filler & strvalues[i] & "  "
+  result = $T & "\n" & result
+
+func `$`*(v: Mat22[SomeNumber]): string = toString[Mat22[SomeNumber]](v)
+func `$`*(v: Mat23[SomeNumber]): string = toString[Mat23[SomeNumber]](v)
+func `$`*(v: Mat32[SomeNumber]): string = toString[Mat32[SomeNumber]](v)
+func `$`*(v: Mat33[SomeNumber]): string = toString[Mat33[SomeNumber]](v)
+func `$`*(v: Mat34[SomeNumber]): string = toString[Mat34[SomeNumber]](v)
+func `$`*(v: Mat43[SomeNumber]): string = toString[Mat43[SomeNumber]](v)
+func `$`*(v: Mat44[SomeNumber]): string = toString[Mat44[SomeNumber]](v)
+
+func `[]`*[T: Mat](m: T, row, col: int): auto = m.data[col + row * T.columnCount]
+proc `[]=`*[T: Mat, U](m: var T, row, col: int, value: U) = m.data[col + row * T.columnCount] = value
+
+func row*[T: Mat22](m: T, i: 0..1): auto = Vec2([m[i, 0], m[i, 1]])
+func row*[T: Mat32](m: T, i: 0..2): auto = Vec2([m[i, 0], m[i, 1]])
+func row*[T: Mat23](m: T, i: 0..1): auto = Vec3([m[i, 0], m[i, 1], m[i, 2]])
+func row*[T: Mat33](m: T, i: 0..2): auto = Vec3([m[i, 0], m[i, 1], m[i, 2]])
+func row*[T: Mat43](m: T, i: 0..3): auto = Vec3([m[i, 0], m[i, 1], m[i, 2]])
+func row*[T: Mat34](m: T, i: 0..2): auto = Vec4([m[i, 0], m[i, 1], m[i, 2], m[i, 3]])
+func row*[T: Mat44](m: T, i: 0..3): auto = Vec4([m[i, 0], m[i, 1], m[i, 2], m[i, 3]])
+
+func col*[T: Mat22](m: T, i: 0..1): auto = Vec2([m[0, i], m[1, i]])
+func col*[T: Mat23](m: T, i: 0..2): auto = Vec2([m[0, i], m[1, i]])
+func col*[T: Mat32](m: T, i: 0..1): auto = Vec3([m[0, i], m[1, i], m[2, i]])
+func col*[T: Mat33](m: T, i: 0..2): auto = Vec3([m[0, i], m[1, i], m[2, i]])
+func col*[T: Mat34](m: T, i: 0..3): auto = Vec3([m[0, i], m[1, i], m[2, i]])
+func col*[T: Mat43](m: T, i: 0..2): auto = Vec4([m[0, i], m[1, i], m[2, i], m[3, i]])
+func col*[T: Mat44](m: T, i: 0..3): auto = Vec4([m[0, i], m[1, i], m[2, i], m[3, i]])
+
+proc createMatMatMultiplicationOperator(leftType: typedesc, rightType: typedesc, outType: typedesc): NimNode =
+  var data = nnkBracket.newTree()
+  for i in 0 ..< rowCount(leftType):
+    for j in 0 ..< rightType.columnCount:
+      data.add(newCall(
+        ident("sum"),
+        infix(
+          newCall(newDotExpr(ident("a"), ident("row")), newLit(i)),
+          "*",
+          newCall(newDotExpr(ident("b"), ident("col")), newLit(j))
+        )
+      ))
+
+  return newProc(
+    postfix(nnkAccQuoted.newTree(ident("*")), "*"),
+    params=[
+      ident("auto"),
+      newIdentDefs(ident("a"), ident(leftType.name)),
+      newIdentDefs(ident("b"), ident(rightType.name))
+    ],
+    body=nnkObjConstr.newTree(ident(outType.name), nnkExprColonExpr.newTree(ident("data"), data)),
+    procType=nnkFuncDef,
+  )
+
+proc createVecMatMultiplicationOperator(matType: typedesc, vecType: typedesc): NimNode =
+  var data = nnkBracket.newTree()
+  for i in 0 ..< matType.rowCount:
+    data.add(newCall(
+      ident("sum"),
+      infix(
+        ident("v"),
+        "*",
+        newCall(newDotExpr(ident("m"), ident("row")), newLit(i))
+      )
+    ))
+
+  let resultVec = newCall(
+    nnkBracketExpr.newTree(ident(vecType.name), ident("T")),
+    data,
+  )
+  let name = postfix(nnkAccQuoted.newTree(ident("*")), "*")
+  let genericParams = nnkGenericParams.newTree(nnkIdentDefs.newTree(ident("T"), ident("SomeNumber"), newEmptyNode()))
+  let formalParams = nnkFormalParams.newTree(
+    ident("auto"),
+    newIdentDefs(ident("m"), nnkBracketExpr.newTree(ident(matType.name), ident("T"))),
+    newIdentDefs(ident("v"), nnkBracketExpr.newTree(ident(vecType.name), ident("T"))),
+  )
+
+  return nnkFuncDef.newTree(
+    name,
+    newEmptyNode(),
+    genericParams,
+    formalParams,
+    newEmptyNode(),
+    newEmptyNode(),
+    resultVec
+  )
+   
+proc createVecMatMultiplicationOperator1(vecType: typedesc, matType: typedesc): NimNode =
+  var data = nnkBracket.newTree()
+  for i in 0 ..< matType.columnCount:
+    data.add(newCall(
+      ident("sum"),
+      infix(
+        ident("v"),
+        "*",
+        newCall(newDotExpr(ident("m"), ident("col")), newLit(i))
+      )
+    ))
+  let resultVec = nnkObjConstr.newTree(
+    nnkBracketExpr.newTree(ident(vecType.name), ident("float")),
+    nnkExprColonExpr.newTree(ident("data"), data)
+  )
+
+  return nnkFuncDef.newTree(
+    ident("test"),
+    newEmptyNode(),
+    newEmptyNode(),
+    newEmptyNode(),
+    newEmptyNode(),
+    newEmptyNode(),
+    resultVec,
+  )
+
+proc createMatScalarOperator(matType: typedesc, op: string): NimNode =
+  result = newStmtList()
+
+  var data = nnkBracket.newTree()
+  for i in 0 ..< matType.rowCount * matType.columnCount:
+    data.add(infix(nnkBracketExpr.newTree(newDotExpr(ident("a"), ident("data")), newLit(i)), op, ident("b")))
+  result.add(newProc(
+    postfix(nnkAccQuoted.newTree(ident(op)), "*"),
+    params=[
+      ident("auto"),
+      newIdentDefs(ident("a"), ident(matType.name)),
+      newIdentDefs(ident("b"), ident("SomeNumber")),
+    ],
+    body=nnkObjConstr.newTree(ident(matType.name), nnkExprColonExpr.newTree(ident("data"), data)),
+    procType=nnkFuncDef,
+  ))
+  result.add(newProc(
+    postfix(nnkAccQuoted.newTree(ident(op)), "*"),
+    params=[
+      ident("auto"),
+      newIdentDefs(ident("b"), ident("SomeNumber")),
+      newIdentDefs(ident("a"), ident(matType.name)),
+    ],
+    body=nnkObjConstr.newTree(ident(matType.name), nnkExprColonExpr.newTree(ident("data"), data)),
+    procType=nnkFuncDef,
+  ))
+  if op == "-":
+    var data2 = nnkBracket.newTree()
+    for i in 0 ..< matType.rowCount * matType.columnCount:
+      data2.add(prefix(nnkBracketExpr.newTree(newDotExpr(ident("a"), ident("data")), newLit(i)), op))
+    result.add(newProc(
+      postfix(nnkAccQuoted.newTree(ident(op)), "*"),
+      params=[
+        ident("auto"),
+        newIdentDefs(ident("a"), ident(matType.name)),
+      ],
+      body=nnkObjConstr.newTree(ident(matType.name), nnkExprColonExpr.newTree(ident("data"), data2)),
+      procType=nnkFuncDef,
+    ))
+
+macro createAllMultiplicationOperators() =
+  result = newStmtList()
+
+  for op in ["+", "-", "*", "/"]:
+    result.add(createMatScalarOperator(Mat22, op))
+    result.add(createMatScalarOperator(Mat23, op))
+    result.add(createMatScalarOperator(Mat32, op))
+    result.add(createMatScalarOperator(Mat33, op))
+    result.add(createMatScalarOperator(Mat34, op))
+    result.add(createMatScalarOperator(Mat43, op))
+    result.add(createMatScalarOperator(Mat44, op))
+
+  result.add(createMatMatMultiplicationOperator(Mat22, Mat22, Mat22))
+  result.add(createMatMatMultiplicationOperator(Mat22, Mat23, Mat23))
+  result.add(createMatMatMultiplicationOperator(Mat23, Mat32, Mat22))
+  result.add(createMatMatMultiplicationOperator(Mat23, Mat33, Mat23))
+  result.add(createMatMatMultiplicationOperator(Mat32, Mat22, Mat32))
+  result.add(createMatMatMultiplicationOperator(Mat32, Mat23, Mat33))
+  result.add(createMatMatMultiplicationOperator(Mat33, Mat32, Mat32))
+  result.add(createMatMatMultiplicationOperator(Mat33, Mat33, Mat33))
+  result.add(createMatMatMultiplicationOperator(Mat33, Mat34, Mat34))
+  result.add(createMatMatMultiplicationOperator(Mat43, Mat33, Mat43))
+  result.add(createMatMatMultiplicationOperator(Mat43, Mat34, Mat44))
+  result.add(createMatMatMultiplicationOperator(Mat44, Mat43, Mat43))
+  result.add(createMatMatMultiplicationOperator(Mat44, Mat44, Mat44))
+
+  result.add(createVecMatMultiplicationOperator(Mat22, Vec2))
+  result.add(createVecMatMultiplicationOperator(Mat33, Vec3))
+  result.add(createVecMatMultiplicationOperator(Mat44, Vec4))
+
+createAllMultiplicationOperators()
+
+
+func transposed*[T](m: Mat22[T]): Mat22[T] = Mat22[T](data: [
+  m[0, 0], m[1, 0],
+  m[0, 1], m[1, 1],
+])
+func transposed*[T](m: Mat23[T]): Mat32[T] = Mat32[T](data: [
+  m[0, 0], m[1, 0],
+  m[0, 1], m[1, 1],
+  m[0, 2], m[1, 2],
+])
+func transposed*[T](m: Mat32[T]): Mat23[T] = Mat23[T](data: [
+  m[0, 0], m[1, 0], m[2, 0],
+  m[0, 1], m[1, 1], m[2, 1],
+])
+func transposed*[T](m: Mat33[T]): Mat33[T] = Mat33[T](data: [
+  m[0, 0], m[1, 0], m[2, 0],
+  m[0, 1], m[1, 1], m[2, 1],
+  m[0, 2], m[1, 2], m[2, 2],
+])
+func transposed*[T](m: Mat43[T]): Mat34[T] = Mat34[T](data: [
+  m[0, 0], m[1, 0], m[2, 0], m[3, 0],
+  m[0, 1], m[1, 1], m[2, 1], m[3, 1],
+  m[0, 2], m[1, 2], m[2, 2], m[3, 2],
+])
+func transposed*[T](m: Mat34[T]): Mat43[T] = Mat43[T](data: [
+  m[0, 0], m[1, 0], m[2, 0],
+  m[0, 1], m[1, 1], m[2, 1],
+  m[0, 2], m[1, 2], m[2, 2],
+  m[0, 3], m[1, 3], m[2, 3],
+])
+func transposed*[T](m: Mat44[T]): Mat44[T] = Mat44[T](data: [
+  m[0, 0], m[1, 0], m[2, 0], m[3, 0],
+  m[0, 1], m[1, 1], m[2, 1], m[3, 1],
+  m[0, 2], m[1, 2], m[2, 2], m[3, 2],
+  m[0, 3], m[1, 3], m[2, 3], m[3, 3],
+])
+
+# call e.g. Mat32[int]().randomized() to get a random matrix
+template makeRandomInit(mattype: typedesc) =
+    proc randomized*[T: SomeInteger](m: mattype[T]): mattype[T] =
+      for i in 0 ..< result.data.len:
+        result.data[i] = rand(low(typeof(m.data[0])) .. high(typeof(m.data[0])))
+    proc randomized*[T: SomeFloat](m: mattype[T]): mattype[T] =
+      for i in 0 ..< result.data.len:
+        result.data[i] = rand(1.0)
+
+makeRandomInit(Mat22)
+makeRandomInit(Mat23)
+makeRandomInit(Mat32)
+makeRandomInit(Mat33)
+makeRandomInit(Mat34)
+makeRandomInit(Mat43)
+makeRandomInit(Mat44)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/math/vector.nim	Sun Jan 01 01:00:50 2023 +0700
@@ -0,0 +1,223 @@
+import std/random
+import std/math
+import std/strutils
+import std/macros
+import std/typetraits
+import std/tables
+
+
+type
+  Vec2*[T: SomeNumber] = array[2, T]
+  Vec3*[T: SomeNumber] = array[3, T]
+  Vec4*[T: SomeNumber] = array[4, T]
+  Vec* = Vec2|Vec3|Vec4
+
+# define some often used constants
+func ConstOne2[T: SomeNumber](): auto {.compiletime.} = Vec2[T]([T(1), T(1)])
+func ConstOne3[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(1), T(1), T(1)])
+func ConstOne4[T: SomeNumber](): auto {.compiletime.} = Vec4[T]([T(1), T(1), T(1), T(1)])
+func ConstX[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(1), T(0), T(0)])
+func ConstY[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(0), T(1), T(0)])
+func ConstZ[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(0), T(0), T(1)])
+func ConstR[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(1), T(0), T(0)])
+func ConstG[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(0), T(1), T(0)])
+func ConstB[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(0), T(0), T(1)])
+
+# generates constants: Xf, Xf32, Xf64, Xi, Xi8, Xi16, Xi32, Xi64
+# Also for Y, Z, R, G, B and One
+# not sure if this is necessary or even a good idea...
+macro generateAllConsts() =
+  result = newStmtList()
+  for component in ["X", "Y", "Z", "R", "G", "B", "One2", "One3", "One4"]:
+    for theType in ["int", "int8", "int16", "int32", "int64", "float", "float32", "float64"]:
+      var typename = theType[0 .. 0]
+      if theType[^2].isDigit:
+        typename = typename & theType[^2]
+      if theType[^1].isDigit:
+        typename = typename & theType[^1]
+      result.add(
+        newConstStmt(
+          postfix(ident(component & typename), "*"),
+          newCall(nnkBracketExpr.newTree(ident("Const" & component), ident(theType)))
+        )
+      )
+
+generateAllConsts()
+
+const X* = ConstX[float]()
+const Y* = ConstY[float]()
+const Z* = ConstZ[float]()
+const One2* = ConstOne2[float]()
+const One3* = ConstOne3[float]()
+const One4* = ConstOne4[float]()
+
+func newVec2*[T](x, y: T): auto = Vec2([x, y])
+func newVec3*[T](x, y, z: T): auto = Vec3([x, y, z])
+func newVec4*[T](x, y, z, w: T): auto = Vec4([x, y, z, w])
+
+func to*[T](v: Vec2): auto = Vec2([T(v[0]), T(v[1])])
+func to*[T](v: Vec3): auto = Vec3([T(v[0]), T(v[1]), T(v[2])])
+func to*[T](v: Vec4): auto = Vec4([T(v[0]), T(v[1]), T(v[2]), T(v[3])])
+
+func toString[T](value: T): string =
+  var items: seq[string]
+  for item in value:
+    items.add($item)
+  $T & "(" & join(items, "  ") & ")"
+
+func `$`*(v: Vec2[SomeNumber]): string = toString[Vec2[SomeNumber]](v)
+func `$`*(v: Vec3[SomeNumber]): string = toString[Vec3[SomeNumber]](v)
+func `$`*(v: Vec4[SomeNumber]): string = toString[Vec4[SomeNumber]](v)
+
+func length*(vec: Vec2[SomeFloat]): auto = sqrt(vec[0] * vec[0] + vec[1] * vec[1])
+func length*(vec: Vec2[SomeInteger]): auto = sqrt(float(vec[0] * vec[0] + vec[1] * vec[1]))
+func length*(vec: Vec3[SomeFloat]): auto = sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2])
+func length*(vec: Vec3[SomeInteger]): auto = sqrt(float(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]))
+func length*(vec: Vec4[SomeFloat]): auto = sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2] + vec[3] * vec[3])
+func length*(vec: Vec4[SomeInteger]): auto = sqrt(float(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2] + vec[3] * vec[3]))
+
+func normalized*[T](vec: Vec2[T]): auto =
+  let l = vec.length
+  when T is SomeFloat:
+    Vec2[T]([vec[0] / l, vec[1] / l])
+  else:
+    Vec2[float]([float(vec[0]) / l, float(vec[1]) / l])
+func normalized*[T](vec: Vec3[T]): auto =
+  let l = vec.length
+  when T is SomeFloat:
+    Vec3[T]([vec[0] / l, vec[1] / l, vec[2] / l])
+  else:
+    Vec3[float]([float(vec[0]) / l, float(vec[1]) / l, float(vec[2]) / l])
+func normalized*[T](vec: Vec4[T]): auto =
+  let l = vec.length
+  when T is SomeFloat:
+    Vec4[T]([vec[0] / l, vec[1] / l, vec[2] / l, vec[3] / l])
+  else:
+    Vec4[float]([float(vec[0]) / l, float(vec[1]) / l, float(vec[2]) / l, float(vec[3]) / l])
+
+# scalar operations
+func `+`*(a: Vec2, b: SomeNumber): auto = Vec2([a[0] + b, a[1] + b])
+func `+`*(a: Vec3, b: SomeNumber): auto = Vec3([a[0] + b, a[1] + b, a[2] + b])
+func `+`*(a: Vec4, b: SomeNumber): auto = Vec4([a[0] + b, a[1] + b, a[2] + b, a[3] + b])
+func `-`*(a: Vec2, b: SomeNumber): auto = Vec2([a[0] - b, a[1] - b])
+func `-`*(a: Vec3, b: SomeNumber): auto = Vec3([a[0] - b, a[1] - b, a[2] - b])
+func `-`*(a: Vec4, b: SomeNumber): auto = Vec4([a[0] - b, a[1] - b, a[2] - b, a[3] - b])
+func `*`*(a: Vec2, b: SomeNumber): auto = Vec2([a[0] * b, a[1] * b])
+func `*`*(a: Vec3, b: SomeNumber): auto = Vec3([a[0] * b, a[1] * b, a[2] * b])
+func `*`*(a: Vec4, b: SomeNumber): auto = Vec4([a[0] * b, a[1] * b, a[2] * b, a[3] * b])
+func `/`*[T: SomeInteger](a: Vec2[T], b: SomeInteger): auto = Vec2([a[0] div b, a[1] div b])
+func `/`*[T: SomeFloat](a: Vec2[T], b: SomeFloat): auto = Vec2([a[0] / b, a[1] / b])
+func `/`*[T: SomeInteger](a: Vec3[T], b: SomeInteger): auto = Vec3([a[0] div b, a[1] div b, a[2] div b])
+func `/`*[T: SomeFloat](a: Vec3[T], b: SomeFloat): auto = Vec3([a[0] / b, a[1] / b, a[2] / b])
+func `/`*[T: SomeInteger](a: Vec4[T], b: SomeInteger): auto = Vec4([a[0] div b, a[1] div b, a[2] div b, a[3] div b])
+func `/`*[T: SomeFloat](a: Vec4[T], b: SomeFloat): auto = Vec4([a[0] / b, a[1] / b, a[2] / b, a[3] / b])
+
+func `+`*(a: SomeNumber, b: Vec2): auto = Vec2([a + b[0], a + b[1]])
+func `+`*(a: SomeNumber, b: Vec3): auto = Vec3([a + b[0], a + b[1], a + b[2]])
+func `+`*(a: SomeNumber, b: Vec4): auto = Vec4([a + b[0], a + b[1], a + b[2], a + b[3]])
+func `-`*(a: SomeNumber, b: Vec2): auto = Vec2([a - b[0], a - b[1]])
+func `-`*(a: SomeNumber, b: Vec3): auto = Vec3([a - b[0], a - b[1], a - b[2]])
+func `-`*(a: SomeNumber, b: Vec4): auto = Vec4([a - b[0], a - b[1], a - b[2], a - b[3]])
+func `*`*(a: SomeNumber, b: Vec2): auto = Vec2([a * b[0], a * b[1]])
+func `*`*(a: SomeNumber, b: Vec3): auto = Vec3([a * b[0], a * b[1], a * b[2]])
+func `*`*(a: SomeNumber, b: Vec4): auto = Vec4([a * b[0], a * b[1], a * b[2], a * b[3]])
+func `/`*[T: SomeInteger](a: SomeInteger, b: Vec2[T]): auto = Vec2([a div b[0], a div b[1]])
+func `/`*[T: SomeFloat](a: SomeFloat, b: Vec2[T]): auto = Vec2([a / b[0], a / b[1]])
+func `/`*[T: SomeInteger](a: SomeInteger, b: Vec3[T]): auto = Vec3([a div b[0], a div b[1], a div b[2]])
+func `/`*[T: SomeFloat](a: SomeFloat, b: Vec3[T]): auto = Vec3([a / b[0], a / b[1], a / b[2]])
+func `/`*[T: SomeInteger](a: SomeInteger, b: Vec4[T]): auto = Vec4([a div b[0], a div b[1], a div b[2], a div b[3]])
+func `/`*[T: SomeFloat](a: SomeFloat, b: Vec4[T]): auto = Vec4([a / b[0], a / b[1], a / b[2], a / b[3]])
+
+# compontent-wise operations
+func `+`*(a, b: Vec2): auto = Vec2([a[0] + b[0], a[1] + b[1]])
+func `+`*(a, b: Vec3): auto = Vec3([a[0] + b[0], a[1] + b[1], a[2] + b[2]])
+func `+`*(a, b: Vec4): auto = Vec4([a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]])
+func `-`*(a: Vec2): auto = Vec2([-a[0], -a[1]])
+func `-`*(a: Vec3): auto = Vec3([-a[0], -a[1], -a[2]])
+func `-`*(a: Vec4): auto = Vec4([-a[0], -a[1], -a[2], -a[3]])
+func `-`*(a, b: Vec2): auto = Vec2([a[0] - b[0], a[1] - b[1]])
+func `-`*(a, b: Vec3): auto = Vec3([a[0] - b[0], a[1] - b[1], a[2] - b[2]])
+func `-`*(a, b: Vec4): auto = Vec4([a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]])
+func `*`*(a, b: Vec2): auto = Vec2([a[0] * b[0], a[1] * b[1]])
+func `*`*(a, b: Vec3): auto = Vec3([a[0] * b[0], a[1] * b[1], a[2] * b[2]])
+func `*`*(a, b: Vec4): auto = Vec4([a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]])
+func `/`*[T: SomeInteger](a, b: Vec2[T]): auto = Vec2([a[0] div b[0], a[1] div b[1]])
+func `/`*[T: SomeFloat](a, b: Vec2[T]): auto = Vec2([a[0] / b[0], a[1] / b[1]])
+func `/`*[T: SomeInteger](a, b: Vec3[T]): auto = Vec3([a[0] div b[0], a[1] div b[1], a[2] div b[2]])
+func `/`*[T: SomeFloat](a, b: Vec3[T]): auto = Vec3([a[0] / b[0], a[1] / b[1], a[2] / b[2]])
+func `/`*[T: SomeInteger](a, b: Vec4[T]): auto = Vec4([a[0] div b[0], a[1] div b[1], a[2] div b[2], a[3] div b[3]])
+func `/`*[T: SomeFloat](a, b: Vec4[T]): auto = Vec4([a[0] / b[0], a[1] / b[1], a[2] / b[2], a[3] / b[3]])
+
+# special operations
+func dot*(a, b: Vec2): auto = a[0] * b[0] + a[1] * b[1]
+func dot*(a, b: Vec3): auto = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
+func dot*(a, b: Vec4): auto = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
+func cross*(a, b: Vec3): auto = Vec3([
+  a[1] * b[2] - a[2] * b[1],
+  a[2] * b[0] - a[0] * b[2],
+  a[0] * b[1] - a[1] * b[0],
+])
+
+
+# macro to allow creation of new vectors by specifying vector components as attributes
+# e.g. myVec.xxy will return a new Vec3 that contains the components x, x an y of the original vector
+# (instead of x, y, z for a simple copy)
+proc vectorAttributeAccessor(accessor: string): NimNode =
+  const ACCESSOR_INDICES = {
+    'x': 0,
+    'y': 1,
+    'z': 2,
+    'w': 3,
+    'r': 0,
+    'g': 1,
+    'b': 2,
+    'a': 3,
+  }.toTable
+  var ret: NimNode
+  let accessorvalue = accessor
+
+  if accessorvalue.len == 0:
+    raise newException(Exception, "empty attribute")
+  elif accessorvalue.len == 1:
+    ret = nnkBracket.newTree(ident("value"), newLit(ACCESSOR_INDICES[accessorvalue[0]]))
+  if accessorvalue.len > 1:
+    var attrs = nnkBracket.newTree()
+    for attrname in accessorvalue:
+      attrs.add(nnkBracketExpr.newTree(ident("value"), newLit(ACCESSOR_INDICES[attrname])))
+    ret = nnkCall.newTree(ident("Vec" & $accessorvalue.len), attrs)
+
+  newProc(
+    name=nnkPostfix.newTree(ident("*"), ident(accessor)),
+    params=[ident("auto"), nnkIdentDefs.newTree(ident("value"), ident("Vec"), newEmptyNode())],
+    body=newStmtList(ret),
+    procType = nnkFuncDef,
+  )
+
+macro createVectorAttribAccessorFuncs() =
+  const COORD_ATTRS = ["x", "y", "z", "w"]
+  const COLOR_ATTRS = ["r", "g", "b", "a"]
+  result = nnkStmtList.newTree()
+  for attlist in [COORD_ATTRS, COLOR_ATTRS]:
+    for i in attlist:
+      result.add(vectorAttributeAccessor(i))
+      for j in attlist:
+        result.add(vectorAttributeAccessor(i & j))
+        for k in attlist:
+          result.add(vectorAttributeAccessor(i & j & k))
+          for l in attlist:
+            result.add(vectorAttributeAccessor(i & j & k & l))
+
+createVectorAttribAccessorFuncs()
+
+# call e.g. Vec2[int]().randomized() to get a random matrix
+template makeRandomInit(mattype: typedesc) =
+    proc randomized*[T: SomeInteger](m: mattype[T]): mattype[T] =
+      for i in 0 ..< result.len:
+        result[i] = rand(low(typeof(m[0])) .. high(typeof(m[0])))
+    proc randomized*[T: SomeFloat](m: mattype[T]): mattype[T] =
+      for i in 0 ..< result.len:
+        result[i] = rand(1.0)
+
+makeRandomInit(Vec2)
+makeRandomInit(Vec3)
+makeRandomInit(Vec4)
--- a/src/matrix.nim	Fri Dec 30 15:56:17 2022 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,357 +0,0 @@
-import std/math
-import std/macros
-import std/random
-import std/strutils
-import std/typetraits
-
-import ./vector
-
-type
-  # layout is row-first
-  # having an object instead of directly aliasing the array seems a bit ugly at
-  # first, but is necessary to be able to work correctly with distinguished
-  # types (i.e. Mat23 and Mat32 would be an alias for the same type array[6, T]
-  # which prevents the type system from identifying the correct type at times)
-  #
-  # Though, great news is that objects have zero overhead!
-  Mat22*[T: SomeNumber] = object
-    data: array[4, T]
-  Mat23*[T: SomeNumber] = object
-    data: array[6, T]
-  Mat32*[T: SomeNumber] = object
-    data: array[6, T]
-  Mat33*[T: SomeNumber] = object
-    data: array[9, T]
-  Mat34*[T: SomeNumber] = object
-    data: array[12, T]
-  Mat43*[T: SomeNumber] = object
-    data: array[12, T]
-  Mat44*[T: SomeNumber] = object
-    data: array[16, T]
-  MatMM* = Mat22|Mat33|Mat44
-  MatMN* = Mat23|Mat32|Mat34|Mat43
-  Mat* = MatMM|MatMN
-  IntegerMat = Mat22[SomeInteger]|Mat33[SomeInteger]|Mat44[SomeInteger]|Mat23[SomeInteger]|Mat32[SomeInteger]|Mat34[SomeInteger]|Mat43[SomeInteger]
-  FloatMat = Mat22[SomeFloat]|Mat33[SomeFloat]|Mat44[SomeFloat]|Mat23[SomeFloat]|Mat32[SomeFloat]|Mat34[SomeFloat]|Mat43[SomeFloat]
-
-func unit22[T: SomeNumber](): auto {.compiletime.} = Mat22[T](data:[
-  T(1), T(0),
-  T(0), T(1),
-])
-func unit33[T: SomeNumber](): auto {.compiletime.} = Mat33[T](data:[
-  T(1), T(0), T(0),
-  T(0), T(1), T(0),
-  T(0), T(0), T(1),
-])
-func unit44[T: SomeNumber](): auto {.compiletime.} = Mat44[T](data: [
-  T(1), T(0), T(0), T(0),
-  T(0), T(1), T(0), T(0),
-  T(0), T(0), T(1), T(0),
-  T(0), T(0), T(0), T(1),
-])
-
-# generates constants: Unit
-# Also for Y, Z, R, G, B
-# not sure if this is necessary or even a good idea...
-macro generateAllConsts() =
-  result = newStmtList()
-  for theType in ["int", "int8", "int16", "int32", "int64", "float", "float32", "float64"]:
-    var typename = theType[0 .. 0]
-    if theType[^2].isDigit:
-      typename = typename & theType[^2]
-    if theType[^1].isDigit:
-      typename = typename & theType[^1]
-    result.add(newConstStmt(
-      postfix(ident("Unit22" & typename), "*"),
-      newCall(nnkBracketExpr.newTree(ident("unit22"), ident(theType)))
-    ))
-    result.add(newConstStmt(
-      postfix(ident("Unit33" & typename), "*"),
-      newCall(nnkBracketExpr.newTree(ident("unit33"), ident(theType)))
-    ))
-    result.add(newConstStmt(
-      postfix(ident("Unit44" & typename), "*"),
-      newCall(nnkBracketExpr.newTree(ident("unit44"), ident(theType)))
-    ))
-
-generateAllConsts()
-
-const Unit22* = unit22[float]()
-const Unit33* = unit33[float]()
-const Unit44* = unit44[float]()
-
-template rowCount*(m: typedesc): int =
-  when m is Mat22: 2
-  elif m is Mat23: 2
-  elif m is Mat32: 3
-  elif m is Mat33: 3
-  elif m is Mat34: 3
-  elif m is Mat43: 4
-  elif m is Mat44: 4
-template columnCount*(m: typedesc): int =
-  when m is Mat22: 2
-  elif m is Mat23: 3
-  elif m is Mat32: 2
-  elif m is Mat33: 3
-  elif m is Mat34: 4
-  elif m is Mat43: 3
-  elif m is Mat44: 4
-
-
-func toString[T](value: T): string =
-  var
-    strvalues: seq[string]
-    maxwidth = 0
-
-  for n in value.data:
-    let strval = $n
-    strvalues.add(strval)
-    if strval.len > maxwidth:
-      maxwidth = strval.len
-
-  for i in 0 ..< strvalues.len:
-    let filler = " ".repeat(maxwidth - strvalues[i].len)
-    if i mod T.columnCount == T.columnCount - 1:
-      result &= filler & strvalues[i] & "\n"
-    else:
-      if i mod T.columnCount == 0:
-        result &= "  "
-      result &= filler & strvalues[i] & "  "
-  result = $T & "\n" & result
-
-func `$`*(v: Mat22[SomeNumber]): string = toString[Mat22[SomeNumber]](v)
-func `$`*(v: Mat23[SomeNumber]): string = toString[Mat23[SomeNumber]](v)
-func `$`*(v: Mat32[SomeNumber]): string = toString[Mat32[SomeNumber]](v)
-func `$`*(v: Mat33[SomeNumber]): string = toString[Mat33[SomeNumber]](v)
-func `$`*(v: Mat34[SomeNumber]): string = toString[Mat34[SomeNumber]](v)
-func `$`*(v: Mat43[SomeNumber]): string = toString[Mat43[SomeNumber]](v)
-func `$`*(v: Mat44[SomeNumber]): string = toString[Mat44[SomeNumber]](v)
-
-func `[]`*[T: Mat](m: T, row, col: int): auto = m.data[col + row * T.columnCount]
-proc `[]=`*[T: Mat, U](m: var T, row, col: int, value: U) = m.data[col + row * T.columnCount] = value
-
-func row*[T: Mat22](m: T, i: 0..1): auto = Vec2([m[i, 0], m[i, 1]])
-func row*[T: Mat32](m: T, i: 0..2): auto = Vec2([m[i, 0], m[i, 1]])
-func row*[T: Mat23](m: T, i: 0..1): auto = Vec3([m[i, 0], m[i, 1], m[i, 2]])
-func row*[T: Mat33](m: T, i: 0..2): auto = Vec3([m[i, 0], m[i, 1], m[i, 2]])
-func row*[T: Mat43](m: T, i: 0..3): auto = Vec3([m[i, 0], m[i, 1], m[i, 2]])
-func row*[T: Mat34](m: T, i: 0..2): auto = Vec4([m[i, 0], m[i, 1], m[i, 2], m[i, 3]])
-func row*[T: Mat44](m: T, i: 0..3): auto = Vec4([m[i, 0], m[i, 1], m[i, 2], m[i, 3]])
-
-func col*[T: Mat22](m: T, i: 0..1): auto = Vec2([m[0, i], m[1, i]])
-func col*[T: Mat23](m: T, i: 0..2): auto = Vec2([m[0, i], m[1, i]])
-func col*[T: Mat32](m: T, i: 0..1): auto = Vec3([m[0, i], m[1, i], m[2, i]])
-func col*[T: Mat33](m: T, i: 0..2): auto = Vec3([m[0, i], m[1, i], m[2, i]])
-func col*[T: Mat34](m: T, i: 0..3): auto = Vec3([m[0, i], m[1, i], m[2, i]])
-func col*[T: Mat43](m: T, i: 0..2): auto = Vec4([m[0, i], m[1, i], m[2, i], m[3, i]])
-func col*[T: Mat44](m: T, i: 0..3): auto = Vec4([m[0, i], m[1, i], m[2, i], m[3, i]])
-
-proc createMatMatMultiplicationOperator(leftType: typedesc, rightType: typedesc, outType: typedesc): NimNode =
-  var data = nnkBracket.newTree()
-  for i in 0 ..< rowCount(leftType):
-    for j in 0 ..< rightType.columnCount:
-      data.add(newCall(
-        ident("sum"),
-        infix(
-          newCall(newDotExpr(ident("a"), ident("row")), newLit(i)),
-          "*",
-          newCall(newDotExpr(ident("b"), ident("col")), newLit(j))
-        )
-      ))
-
-  return newProc(
-    postfix(nnkAccQuoted.newTree(ident("*")), "*"),
-    params=[
-      ident("auto"),
-      newIdentDefs(ident("a"), ident(leftType.name)),
-      newIdentDefs(ident("b"), ident(rightType.name))
-    ],
-    body=nnkObjConstr.newTree(ident(outType.name), nnkExprColonExpr.newTree(ident("data"), data)),
-    procType=nnkFuncDef,
-  )
-
-proc createVecMatMultiplicationOperator(matType: typedesc, vecType: typedesc): NimNode =
-  var data = nnkBracket.newTree()
-  for i in 0 ..< matType.rowCount:
-    data.add(newCall(
-      ident("sum"),
-      infix(
-        ident("v"),
-        "*",
-        newCall(newDotExpr(ident("m"), ident("row")), newLit(i))
-      )
-    ))
-
-  let resultVec = newCall(
-    nnkBracketExpr.newTree(ident(vecType.name), ident("T")),
-    data,
-  )
-  let name = postfix(nnkAccQuoted.newTree(ident("*")), "*")
-  let genericParams = nnkGenericParams.newTree(nnkIdentDefs.newTree(ident("T"), ident("SomeNumber"), newEmptyNode()))
-  let formalParams = nnkFormalParams.newTree(
-    ident("auto"),
-    newIdentDefs(ident("m"), nnkBracketExpr.newTree(ident(matType.name), ident("T"))),
-    newIdentDefs(ident("v"), nnkBracketExpr.newTree(ident(vecType.name), ident("T"))),
-  )
-
-  return nnkFuncDef.newTree(
-    name,
-    newEmptyNode(),
-    genericParams,
-    formalParams,
-    newEmptyNode(),
-    newEmptyNode(),
-    resultVec
-  )
-   
-proc createVecMatMultiplicationOperator1(vecType: typedesc, matType: typedesc): NimNode =
-  var data = nnkBracket.newTree()
-  for i in 0 ..< matType.columnCount:
-    data.add(newCall(
-      ident("sum"),
-      infix(
-        ident("v"),
-        "*",
-        newCall(newDotExpr(ident("m"), ident("col")), newLit(i))
-      )
-    ))
-  let resultVec = nnkObjConstr.newTree(
-    nnkBracketExpr.newTree(ident(vecType.name), ident("float")),
-    nnkExprColonExpr.newTree(ident("data"), data)
-  )
-
-  return nnkFuncDef.newTree(
-    ident("test"),
-    newEmptyNode(),
-    newEmptyNode(),
-    newEmptyNode(),
-    newEmptyNode(),
-    newEmptyNode(),
-    resultVec,
-  )
-
-proc createMatScalarOperator(matType: typedesc, op: string): NimNode =
-  result = newStmtList()
-
-  var data = nnkBracket.newTree()
-  for i in 0 ..< matType.rowCount * matType.columnCount:
-    data.add(infix(nnkBracketExpr.newTree(newDotExpr(ident("a"), ident("data")), newLit(i)), op, ident("b")))
-  result.add(newProc(
-    postfix(nnkAccQuoted.newTree(ident(op)), "*"),
-    params=[
-      ident("auto"),
-      newIdentDefs(ident("a"), ident(matType.name)),
-      newIdentDefs(ident("b"), ident("SomeNumber")),
-    ],
-    body=nnkObjConstr.newTree(ident(matType.name), nnkExprColonExpr.newTree(ident("data"), data)),
-    procType=nnkFuncDef,
-  ))
-  result.add(newProc(
-    postfix(nnkAccQuoted.newTree(ident(op)), "*"),
-    params=[
-      ident("auto"),
-      newIdentDefs(ident("b"), ident("SomeNumber")),
-      newIdentDefs(ident("a"), ident(matType.name)),
-    ],
-    body=nnkObjConstr.newTree(ident(matType.name), nnkExprColonExpr.newTree(ident("data"), data)),
-    procType=nnkFuncDef,
-  ))
-  if op == "-":
-    var data2 = nnkBracket.newTree()
-    for i in 0 ..< matType.rowCount * matType.columnCount:
-      data2.add(prefix(nnkBracketExpr.newTree(newDotExpr(ident("a"), ident("data")), newLit(i)), op))
-    result.add(newProc(
-      postfix(nnkAccQuoted.newTree(ident(op)), "*"),
-      params=[
-        ident("auto"),
-        newIdentDefs(ident("a"), ident(matType.name)),
-      ],
-      body=nnkObjConstr.newTree(ident(matType.name), nnkExprColonExpr.newTree(ident("data"), data2)),
-      procType=nnkFuncDef,
-    ))
-
-macro createAllMultiplicationOperators() =
-  result = newStmtList()
-
-  for op in ["+", "-", "*", "/"]:
-    result.add(createMatScalarOperator(Mat22, op))
-    result.add(createMatScalarOperator(Mat23, op))
-    result.add(createMatScalarOperator(Mat32, op))
-    result.add(createMatScalarOperator(Mat33, op))
-    result.add(createMatScalarOperator(Mat34, op))
-    result.add(createMatScalarOperator(Mat43, op))
-    result.add(createMatScalarOperator(Mat44, op))
-
-  result.add(createMatMatMultiplicationOperator(Mat22, Mat22, Mat22))
-  result.add(createMatMatMultiplicationOperator(Mat22, Mat23, Mat23))
-  result.add(createMatMatMultiplicationOperator(Mat23, Mat32, Mat22))
-  result.add(createMatMatMultiplicationOperator(Mat23, Mat33, Mat23))
-  result.add(createMatMatMultiplicationOperator(Mat32, Mat22, Mat32))
-  result.add(createMatMatMultiplicationOperator(Mat32, Mat23, Mat33))
-  result.add(createMatMatMultiplicationOperator(Mat33, Mat32, Mat32))
-  result.add(createMatMatMultiplicationOperator(Mat33, Mat33, Mat33))
-  result.add(createMatMatMultiplicationOperator(Mat33, Mat34, Mat34))
-  result.add(createMatMatMultiplicationOperator(Mat43, Mat33, Mat43))
-  result.add(createMatMatMultiplicationOperator(Mat43, Mat34, Mat44))
-  result.add(createMatMatMultiplicationOperator(Mat44, Mat43, Mat43))
-  result.add(createMatMatMultiplicationOperator(Mat44, Mat44, Mat44))
-
-  result.add(createVecMatMultiplicationOperator(Mat22, Vec2))
-  result.add(createVecMatMultiplicationOperator(Mat33, Vec3))
-  result.add(createVecMatMultiplicationOperator(Mat44, Vec4))
-
-createAllMultiplicationOperators()
-
-
-func transposed*[T](m: Mat22[T]): Mat22[T] = Mat22[T](data: [
-  m[0, 0], m[1, 0],
-  m[0, 1], m[1, 1],
-])
-func transposed*[T](m: Mat23[T]): Mat32[T] = Mat32[T](data: [
-  m[0, 0], m[1, 0],
-  m[0, 1], m[1, 1],
-  m[0, 2], m[1, 2],
-])
-func transposed*[T](m: Mat32[T]): Mat23[T] = Mat23[T](data: [
-  m[0, 0], m[1, 0], m[2, 0],
-  m[0, 1], m[1, 1], m[2, 1],
-])
-func transposed*[T](m: Mat33[T]): Mat33[T] = Mat33[T](data: [
-  m[0, 0], m[1, 0], m[2, 0],
-  m[0, 1], m[1, 1], m[2, 1],
-  m[0, 2], m[1, 2], m[2, 2],
-])
-func transposed*[T](m: Mat43[T]): Mat34[T] = Mat34[T](data: [
-  m[0, 0], m[1, 0], m[2, 0], m[3, 0],
-  m[0, 1], m[1, 1], m[2, 1], m[3, 1],
-  m[0, 2], m[1, 2], m[2, 2], m[3, 2],
-])
-func transposed*[T](m: Mat34[T]): Mat43[T] = Mat43[T](data: [
-  m[0, 0], m[1, 0], m[2, 0],
-  m[0, 1], m[1, 1], m[2, 1],
-  m[0, 2], m[1, 2], m[2, 2],
-  m[0, 3], m[1, 3], m[2, 3],
-])
-func transposed*[T](m: Mat44[T]): Mat44[T] = Mat44[T](data: [
-  m[0, 0], m[1, 0], m[2, 0], m[3, 0],
-  m[0, 1], m[1, 1], m[2, 1], m[3, 1],
-  m[0, 2], m[1, 2], m[2, 2], m[3, 2],
-  m[0, 3], m[1, 3], m[2, 3], m[3, 3],
-])
-
-# call e.g. Mat32[int]().randomized() to get a random matrix
-template makeRandomInit(mattype: typedesc) =
-    proc randomized*[T: SomeInteger](m: mattype[T]): mattype[T] =
-      for i in 0 ..< result.data.len:
-        result.data[i] = rand(low(typeof(m.data[0])) .. high(typeof(m.data[0])))
-    proc randomized*[T: SomeFloat](m: mattype[T]): mattype[T] =
-      for i in 0 ..< result.data.len:
-        result.data[i] = rand(1.0)
-
-makeRandomInit(Mat22)
-makeRandomInit(Mat23)
-makeRandomInit(Mat32)
-makeRandomInit(Mat33)
-makeRandomInit(Mat34)
-makeRandomInit(Mat43)
-makeRandomInit(Mat44)
--- a/src/notes	Fri Dec 30 15:56:17 2022 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-For implementation of font rendering:
-https://developer.apple.com/fonts/TrueType-Reference-Manual/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/shader.nim	Sun Jan 01 01:00:50 2023 +0700
@@ -0,0 +1,35 @@
+import std/tables
+
+import ./vulkan_helpers
+import ./vulkan
+import ./glslang/glslang
+
+type
+  ShaderProgram* = object
+    entryPoint*: string
+    programType*: VkShaderStageFlagBits
+    shader*: VkPipelineShaderStageCreateInfo
+
+proc initShaderProgram*(device: VkDevice, programType: VkShaderStageFlagBits, shader: string, entryPoint: string="main"): ShaderProgram =
+  result.entryPoint = entryPoint
+  result.programType = programType
+
+  const VK_GLSL_MAP = {
+    VK_SHADER_STAGE_VERTEX_BIT: GLSLANG_STAGE_VERTEX,
+    VK_SHADER_STAGE_FRAGMENT_BIT: GLSLANG_STAGE_FRAGMENT,
+  }.toTable()
+  var code = compileGLSLToSPIRV(VK_GLSL_MAP[result.programType], shader, "<memory-shader>")
+  var createInfo = VkShaderModuleCreateInfo(
+    sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+    codeSize: uint(code.len * sizeof(uint32)),
+    pCode: if code.len > 0: addr(code[0]) else: nil,
+  )
+  var shaderModule: VkShaderModule
+  checkVkResult vkCreateShaderModule(device, addr(createInfo), nil, addr(shaderModule))
+
+  result.shader = VkPipelineShaderStageCreateInfo(
+    sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+    stage: programType,
+    module: shaderModule,
+    pName: cstring(result.entryPoint), # entry point for shader
+  )
--- a/src/vector.nim	Fri Dec 30 15:56:17 2022 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-import std/random
-import std/math
-import std/strutils
-import std/macros
-import std/typetraits
-import std/tables
-
-
-type
-  Vec2*[T: SomeNumber] = array[2, T]
-  Vec3*[T: SomeNumber] = array[3, T]
-  Vec4*[T: SomeNumber] = array[4, T]
-  Vec* = Vec2|Vec3|Vec4
-
-# define some often used constants
-func ConstOne2[T: SomeNumber](): auto {.compiletime.} = Vec2[T]([T(1), T(1)])
-func ConstOne3[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(1), T(1), T(1)])
-func ConstOne4[T: SomeNumber](): auto {.compiletime.} = Vec4[T]([T(1), T(1), T(1), T(1)])
-func ConstX[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(1), T(0), T(0)])
-func ConstY[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(0), T(1), T(0)])
-func ConstZ[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(0), T(0), T(1)])
-func ConstR[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(1), T(0), T(0)])
-func ConstG[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(0), T(1), T(0)])
-func ConstB[T: SomeNumber](): auto {.compiletime.} = Vec3[T]([T(0), T(0), T(1)])
-
-# generates constants: Xf, Xf32, Xf64, Xi, Xi8, Xi16, Xi32, Xi64
-# Also for Y, Z, R, G, B and One
-# not sure if this is necessary or even a good idea...
-macro generateAllConsts() =
-  result = newStmtList()
-  for component in ["X", "Y", "Z", "R", "G", "B", "One2", "One3", "One4"]:
-    for theType in ["int", "int8", "int16", "int32", "int64", "float", "float32", "float64"]:
-      var typename = theType[0 .. 0]
-      if theType[^2].isDigit:
-        typename = typename & theType[^2]
-      if theType[^1].isDigit:
-        typename = typename & theType[^1]
-      result.add(
-        newConstStmt(
-          postfix(ident(component & typename), "*"),
-          newCall(nnkBracketExpr.newTree(ident("Const" & component), ident(theType)))
-        )
-      )
-
-generateAllConsts()
-
-const X* = ConstX[float]()
-const Y* = ConstY[float]()
-const Z* = ConstZ[float]()
-const One2* = ConstOne2[float]()
-const One3* = ConstOne3[float]()
-const One4* = ConstOne4[float]()
-
-func newVec2*[T](x, y: T): auto = Vec2([x, y])
-func newVec3*[T](x, y, z: T): auto = Vec3([x, y, z])
-func newVec4*[T](x, y, z, w: T): auto = Vec4([x, y, z, w])
-
-func to*[T](v: Vec2): auto = Vec2([T(v[0]), T(v[1])])
-func to*[T](v: Vec3): auto = Vec3([T(v[0]), T(v[1]), T(v[2])])
-func to*[T](v: Vec4): auto = Vec4([T(v[0]), T(v[1]), T(v[2]), T(v[3])])
-
-func toString[T](value: T): string =
-  var items: seq[string]
-  for item in value:
-    items.add($item)
-  $T & "(" & join(items, "  ") & ")"
-
-func `$`*(v: Vec2[SomeNumber]): string = toString[Vec2[SomeNumber]](v)
-func `$`*(v: Vec3[SomeNumber]): string = toString[Vec3[SomeNumber]](v)
-func `$`*(v: Vec4[SomeNumber]): string = toString[Vec4[SomeNumber]](v)
-
-func length*(vec: Vec2[SomeFloat]): auto = sqrt(vec[0] * vec[0] + vec[1] * vec[1])
-func length*(vec: Vec2[SomeInteger]): auto = sqrt(float(vec[0] * vec[0] + vec[1] * vec[1]))
-func length*(vec: Vec3[SomeFloat]): auto = sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2])
-func length*(vec: Vec3[SomeInteger]): auto = sqrt(float(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]))
-func length*(vec: Vec4[SomeFloat]): auto = sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2] + vec[3] * vec[3])
-func length*(vec: Vec4[SomeInteger]): auto = sqrt(float(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2] + vec[3] * vec[3]))
-
-func normalized*[T](vec: Vec2[T]): auto =
-  let l = vec.length
-  when T is SomeFloat:
-    Vec2[T]([vec[0] / l, vec[1] / l])
-  else:
-    Vec2[float]([float(vec[0]) / l, float(vec[1]) / l])
-func normalized*[T](vec: Vec3[T]): auto =
-  let l = vec.length
-  when T is SomeFloat:
-    Vec3[T]([vec[0] / l, vec[1] / l, vec[2] / l])
-  else:
-    Vec3[float]([float(vec[0]) / l, float(vec[1]) / l, float(vec[2]) / l])
-func normalized*[T](vec: Vec4[T]): auto =
-  let l = vec.length
-  when T is SomeFloat:
-    Vec4[T]([vec[0] / l, vec[1] / l, vec[2] / l, vec[3] / l])
-  else:
-    Vec4[float]([float(vec[0]) / l, float(vec[1]) / l, float(vec[2]) / l, float(vec[3]) / l])
-
-# scalar operations
-func `+`*(a: Vec2, b: SomeNumber): auto = Vec2([a[0] + b, a[1] + b])
-func `+`*(a: Vec3, b: SomeNumber): auto = Vec3([a[0] + b, a[1] + b, a[2] + b])
-func `+`*(a: Vec4, b: SomeNumber): auto = Vec4([a[0] + b, a[1] + b, a[2] + b, a[3] + b])
-func `-`*(a: Vec2, b: SomeNumber): auto = Vec2([a[0] - b, a[1] - b])
-func `-`*(a: Vec3, b: SomeNumber): auto = Vec3([a[0] - b, a[1] - b, a[2] - b])
-func `-`*(a: Vec4, b: SomeNumber): auto = Vec4([a[0] - b, a[1] - b, a[2] - b, a[3] - b])
-func `*`*(a: Vec2, b: SomeNumber): auto = Vec2([a[0] * b, a[1] * b])
-func `*`*(a: Vec3, b: SomeNumber): auto = Vec3([a[0] * b, a[1] * b, a[2] * b])
-func `*`*(a: Vec4, b: SomeNumber): auto = Vec4([a[0] * b, a[1] * b, a[2] * b, a[3] * b])
-func `/`*[T: SomeInteger](a: Vec2[T], b: SomeInteger): auto = Vec2([a[0] div b, a[1] div b])
-func `/`*[T: SomeFloat](a: Vec2[T], b: SomeFloat): auto = Vec2([a[0] / b, a[1] / b])
-func `/`*[T: SomeInteger](a: Vec3[T], b: SomeInteger): auto = Vec3([a[0] div b, a[1] div b, a[2] div b])
-func `/`*[T: SomeFloat](a: Vec3[T], b: SomeFloat): auto = Vec3([a[0] / b, a[1] / b, a[2] / b])
-func `/`*[T: SomeInteger](a: Vec4[T], b: SomeInteger): auto = Vec4([a[0] div b, a[1] div b, a[2] div b, a[3] div b])
-func `/`*[T: SomeFloat](a: Vec4[T], b: SomeFloat): auto = Vec4([a[0] / b, a[1] / b, a[2] / b, a[3] / b])
-
-func `+`*(a: SomeNumber, b: Vec2): auto = Vec2([a + b[0], a + b[1]])
-func `+`*(a: SomeNumber, b: Vec3): auto = Vec3([a + b[0], a + b[1], a + b[2]])
-func `+`*(a: SomeNumber, b: Vec4): auto = Vec4([a + b[0], a + b[1], a + b[2], a + b[3]])
-func `-`*(a: SomeNumber, b: Vec2): auto = Vec2([a - b[0], a - b[1]])
-func `-`*(a: SomeNumber, b: Vec3): auto = Vec3([a - b[0], a - b[1], a - b[2]])
-func `-`*(a: SomeNumber, b: Vec4): auto = Vec4([a - b[0], a - b[1], a - b[2], a - b[3]])
-func `*`*(a: SomeNumber, b: Vec2): auto = Vec2([a * b[0], a * b[1]])
-func `*`*(a: SomeNumber, b: Vec3): auto = Vec3([a * b[0], a * b[1], a * b[2]])
-func `*`*(a: SomeNumber, b: Vec4): auto = Vec4([a * b[0], a * b[1], a * b[2], a * b[3]])
-func `/`*[T: SomeInteger](a: SomeInteger, b: Vec2[T]): auto = Vec2([a div b[0], a div b[1]])
-func `/`*[T: SomeFloat](a: SomeFloat, b: Vec2[T]): auto = Vec2([a / b[0], a / b[1]])
-func `/`*[T: SomeInteger](a: SomeInteger, b: Vec3[T]): auto = Vec3([a div b[0], a div b[1], a div b[2]])
-func `/`*[T: SomeFloat](a: SomeFloat, b: Vec3[T]): auto = Vec3([a / b[0], a / b[1], a / b[2]])
-func `/`*[T: SomeInteger](a: SomeInteger, b: Vec4[T]): auto = Vec4([a div b[0], a div b[1], a div b[2], a div b[3]])
-func `/`*[T: SomeFloat](a: SomeFloat, b: Vec4[T]): auto = Vec4([a / b[0], a / b[1], a / b[2], a / b[3]])
-
-# compontent-wise operations
-func `+`*(a, b: Vec2): auto = Vec2([a[0] + b[0], a[1] + b[1]])
-func `+`*(a, b: Vec3): auto = Vec3([a[0] + b[0], a[1] + b[1], a[2] + b[2]])
-func `+`*(a, b: Vec4): auto = Vec4([a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]])
-func `-`*(a: Vec2): auto = Vec2([-a[0], -a[1]])
-func `-`*(a: Vec3): auto = Vec3([-a[0], -a[1], -a[2]])
-func `-`*(a: Vec4): auto = Vec4([-a[0], -a[1], -a[2], -a[3]])
-func `-`*(a, b: Vec2): auto = Vec2([a[0] - b[0], a[1] - b[1]])
-func `-`*(a, b: Vec3): auto = Vec3([a[0] - b[0], a[1] - b[1], a[2] - b[2]])
-func `-`*(a, b: Vec4): auto = Vec4([a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]])
-func `*`*(a, b: Vec2): auto = Vec2([a[0] * b[0], a[1] * b[1]])
-func `*`*(a, b: Vec3): auto = Vec3([a[0] * b[0], a[1] * b[1], a[2] * b[2]])
-func `*`*(a, b: Vec4): auto = Vec4([a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]])
-func `/`*[T: SomeInteger](a, b: Vec2[T]): auto = Vec2([a[0] div b[0], a[1] div b[1]])
-func `/`*[T: SomeFloat](a, b: Vec2[T]): auto = Vec2([a[0] / b[0], a[1] / b[1]])
-func `/`*[T: SomeInteger](a, b: Vec3[T]): auto = Vec3([a[0] div b[0], a[1] div b[1], a[2] div b[2]])
-func `/`*[T: SomeFloat](a, b: Vec3[T]): auto = Vec3([a[0] / b[0], a[1] / b[1], a[2] / b[2]])
-func `/`*[T: SomeInteger](a, b: Vec4[T]): auto = Vec4([a[0] div b[0], a[1] div b[1], a[2] div b[2], a[3] div b[3]])
-func `/`*[T: SomeFloat](a, b: Vec4[T]): auto = Vec4([a[0] / b[0], a[1] / b[1], a[2] / b[2], a[3] / b[3]])
-
-# special operations
-func dot*(a, b: Vec2): auto = a[0] * b[0] + a[1] * b[1]
-func dot*(a, b: Vec3): auto = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
-func dot*(a, b: Vec4): auto = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
-func cross*(a, b: Vec3): auto = Vec3([
-  a[1] * b[2] - a[2] * b[1],
-  a[2] * b[0] - a[0] * b[2],
-  a[0] * b[1] - a[1] * b[0],
-])
-
-
-# macro to allow creation of new vectors by specifying vector components as attributes
-# e.g. myVec.xxy will return a new Vec3 that contains the components x, x an y of the original vector
-# (instead of x, y, z for a simple copy)
-proc vectorAttributeAccessor(accessor: string): NimNode =
-  const ACCESSOR_INDICES = {
-    'x': 0,
-    'y': 1,
-    'z': 2,
-    'w': 3,
-    'r': 0,
-    'g': 1,
-    'b': 2,
-    'a': 3,
-  }.toTable
-  var ret: NimNode
-  let accessorvalue = accessor
-
-  if accessorvalue.len == 0:
-    raise newException(Exception, "empty attribute")
-  elif accessorvalue.len == 1:
-    ret = nnkBracket.newTree(ident("value"), newLit(ACCESSOR_INDICES[accessorvalue[0]]))
-  if accessorvalue.len > 1:
-    var attrs = nnkBracket.newTree()
-    for attrname in accessorvalue:
-      attrs.add(nnkBracketExpr.newTree(ident("value"), newLit(ACCESSOR_INDICES[attrname])))
-    ret = nnkCall.newTree(ident("Vec" & $accessorvalue.len), attrs)
-
-  newProc(
-    name=nnkPostfix.newTree(ident("*"), ident(accessor)),
-    params=[ident("auto"), nnkIdentDefs.newTree(ident("value"), ident("Vec"), newEmptyNode())],
-    body=newStmtList(ret),
-    procType = nnkFuncDef,
-  )
-
-macro createVectorAttribAccessorFuncs() =
-  const COORD_ATTRS = ["x", "y", "z", "w"]
-  const COLOR_ATTRS = ["r", "g", "b", "a"]
-  result = nnkStmtList.newTree()
-  for attlist in [COORD_ATTRS, COLOR_ATTRS]:
-    for i in attlist:
-      result.add(vectorAttributeAccessor(i))
-      for j in attlist:
-        result.add(vectorAttributeAccessor(i & j))
-        for k in attlist:
-          result.add(vectorAttributeAccessor(i & j & k))
-          for l in attlist:
-            result.add(vectorAttributeAccessor(i & j & k & l))
-
-createVectorAttribAccessorFuncs()
-
-# call e.g. Vec2[int]().randomized() to get a random matrix
-template makeRandomInit(mattype: typedesc) =
-    proc randomized*[T: SomeInteger](m: mattype[T]): mattype[T] =
-      for i in 0 ..< result.len:
-        result[i] = rand(low(typeof(m[0])) .. high(typeof(m[0])))
-    proc randomized*[T: SomeFloat](m: mattype[T]): mattype[T] =
-      for i in 0 ..< result.len:
-        result[i] = rand(1.0)
-
-makeRandomInit(Vec2)
-makeRandomInit(Vec3)
-makeRandomInit(Vec4)
--- a/src/vertex.nim	Fri Dec 30 15:56:17 2022 +0700
+++ b/src/vertex.nim	Sun Jan 01 01:00:50 2023 +0700
@@ -1,5 +1,179 @@
+import std/macros
+import std/strutils
+import std/strformat
+import std/typetraits
+
+import ./math/vector
+import ./vulkan
+
 type
-  VertexAttribute = object
+  VertexAttributeType = SomeNumber|Vec
+  VertexAttribute*[T:VertexAttributeType] = object
+
+
+# from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
+func nLocationSlots[T: VertexAttributeType](): int =
+  when (T is Vec3[float64] or T is Vec3[uint64] or T is Vec4[float64] or T is Vec4[float64]):
+    2
+  else:
+    1
+
+# numbers
+func getVkFormat[T: VertexAttributeType](): VkFormat =
+  when T is uint8:         VK_FORMAT_R8_UINT
+  elif T is int8:          VK_FORMAT_R8_SINT
+  elif T is uint16:        VK_FORMAT_R16_UINT
+  elif T is int16:         VK_FORMAT_R16_SINT
+  elif T is uint32:        VK_FORMAT_R32_UINT
+  elif T is int32:         VK_FORMAT_R32_SINT
+  elif T is uint64:        VK_FORMAT_R64_UINT
+  elif T is int64:         VK_FORMAT_R64_SINT
+  elif T is float32:       VK_FORMAT_R32_SFLOAT
+  elif T is float64:       VK_FORMAT_R64_SFLOAT
+  elif T is Vec2[uint8]:   VK_FORMAT_R8G8_UINT
+  elif T is Vec2[int8]:    VK_FORMAT_R8G8_SINT
+  elif T is Vec2[uint16]:  VK_FORMAT_R16G16_UINT
+  elif T is Vec2[int16]:   VK_FORMAT_R16G16_SINT
+  elif T is Vec2[uint32]:  VK_FORMAT_R32G32_UINT
+  elif T is Vec2[int32]:   VK_FORMAT_R32G32_SINT
+  elif T is Vec2[uint64]:  VK_FORMAT_R64G64_UINT
+  elif T is Vec2[int64]:   VK_FORMAT_R64G64_SINT
+  elif T is Vec2[float32]: VK_FORMAT_R32G32_SFLOAT
+  elif T is Vec2[float64]: VK_FORMAT_R64G64_SFLOAT
+  elif T is Vec3[uint8]:   VK_FORMAT_R8G8B8_UINT
+  elif T is Vec3[int8]:    VK_FORMAT_R8G8B8_SINT
+  elif T is Vec3[uint16]:  VK_FORMAT_R16G16B16_UINT
+  elif T is Vec3[int16]:   VK_FORMAT_R16G16B16_SINT
+  elif T is Vec3[uint32]:  VK_FORMAT_R32G32B32_UINT
+  elif T is Vec3[int32]:   VK_FORMAT_R32G32B32_SINT
+  elif T is Vec3[uint64]:  VK_FORMAT_R64G64B64_UINT
+  elif T is Vec3[int64]:   VK_FORMAT_R64G64B64_SINT
+  elif T is Vec3[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is Vec3[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is Vec4[uint8]:   VK_FORMAT_R8G8B8A8_UINT
+  elif T is Vec4[int8]:    VK_FORMAT_R8G8B8A8_SINT
+  elif T is Vec4[uint16]:  VK_FORMAT_R16G16B16A16_UINT
+  elif T is Vec4[int16]:   VK_FORMAT_R16G16B16A16_SINT
+  elif T is Vec4[uint32]:  VK_FORMAT_R32G32B32A32_UINT
+  elif T is Vec4[int32]:   VK_FORMAT_R32G32B32A32_SINT
+  elif T is Vec4[uint64]:  VK_FORMAT_R64G64B64A64_UINT
+  elif T is Vec4[int64]:   VK_FORMAT_R64G64B64A64_SINT
+  elif T is Vec4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
+  elif T is Vec4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
+
+func getGLSLType[T: VertexAttributeType](): string =
+  # todo: likely not correct as we would need to enable some 
+  # extensions somewhere (Vulkan/GLSL compiler?) to have 
+  # everything work as intended. Or maybe the GPU driver does
+  # some automagic conversion stuf..
+  when T is uint8:         "uint"
+  elif T is int8:          "int"
+  elif T is uint16:        "uint"
+  elif T is int16:         "int"
+  elif T is uint32:        "uint"
+  elif T is int32:         "int"
+  elif T is uint64:        "uint"
+  elif T is int64:         "int"
+  elif T is float32:       "float"
+  elif T is float64:       "double"
+
+  elif T is Vec2[uint8]:   "uvec2"
+  elif T is Vec2[int8]:    "ivec2"
+  elif T is Vec2[uint16]:  "uvec2"
+  elif T is Vec2[int16]:   "ivec2"
+  elif T is Vec2[uint32]:  "uvec2"
+  elif T is Vec2[int32]:   "ivec2"
+  elif T is Vec2[uint64]:  "uvec2"
+  elif T is Vec2[int64]:   "ivec2"
+  elif T is Vec2[float32]: "vec2"
+  elif T is Vec2[float64]: "dvec2"
 
-  VertexType = object
+  elif T is Vec3[uint8]:   "uvec3"
+  elif T is Vec3[int8]:    "ivec3"
+  elif T is Vec3[uint16]:  "uvec3"
+  elif T is Vec3[int16]:   "ivec3"
+  elif T is Vec3[uint32]:  "uvec3"
+  elif T is Vec3[int32]:   "ivec3"
+  elif T is Vec3[uint64]:  "uvec3"
+  elif T is Vec3[int64]:   "ivec3"
+  elif T is Vec3[float32]: "vec3"
+  elif T is Vec3[float64]: "dvec3"
+
+  elif T is Vec4[uint8]:   "uvec4"
+  elif T is Vec4[int8]:    "ivec4"
+  elif T is Vec4[uint16]:  "uvec4"
+  elif T is Vec4[int16]:   "ivec4"
+  elif T is Vec4[uint32]:  "uvec4"
+  elif T is Vec4[int32]:   "ivec4"
+  elif T is Vec4[uint64]:  "uvec4"
+  elif T is Vec4[int64]:   "ivec4"
+  elif T is Vec4[float32]: "vec4"
+  elif T is Vec4[float64]: "dvec4"
+
+template rawAttributeType(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0)
+
+func generateGLSL[T](): string =
+  var stmtList: seq[string]
+  var i = 0
+  for name, value in T().fieldPairs:
+    when typeof(value) is VertexAttribute:
+      let glsltype = getGLSLType[rawAttributeType(value)]()
+      let n = name
+      stmtList.add(&"layout(location = {i}) in {glsltype} {n};")
+      i += nLocationSlots[rawAttributeType(value)]()
+
+  return stmtList.join("\n")
 
+func generateInputVertexBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputBindingDescription] =
+  # packed attribute data, not interleaved (aks "struct of arrays")
+  var binding = bindingoffset
+  for name, value in T().fieldPairs:
+    when typeof(value) is VertexAttribute:
+      result.add(
+        VkVertexInputBindingDescription(
+          binding: uint32(binding),
+          stride: uint32(sizeof(rawAttributeType(value))),
+          inputRate: VK_VERTEX_INPUT_RATE_VERTEX, # VK_VERTEX_INPUT_RATE_INSTANCE for instances
+        )
+      )
+      binding += 1
+
+func generateInputAttributeBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputAttributeDescription] =
+  # packed attribute data, not interleaved (aks "struct of arrays")
+  var location = 0
+  var binding = bindingoffset
+  for name, value in T().fieldPairs:
+    when typeof(value) is VertexAttribute:
+      result.add(
+        VkVertexInputAttributeDescription(
+          binding: uint32(binding),
+          location: uint32(location),
+          format: getVkFormat[rawAttributeType(value)](),
+          offset: 0,
+        )
+      )
+      location += nLocationSlots[rawAttributeType(value)]()
+      binding += 1
+
+func getBindingDescription(binding: int): auto =
+  VkVertexInputBindingDescription(
+    binding: uint32(binding),
+    stride: 0, # either sizeof of vertex (array of structs) or of attribute (struct of arrays)
+    inputRate: VK_VERTEX_INPUT_RATE_VERTEX, # VK_VERTEX_INPUT_RATE_INSTANCE for instances
+  )
+
+func getAttributeDescriptions(binding: int): auto =
+  [
+    VkVertexInputAttributeDescription(
+      binding: 0'u32,
+      location: 0,
+      format: VK_FORMAT_R32G32_SFLOAT,
+      offset: 0,
+    ),
+    VkVertexInputAttributeDescription(
+      binding: 0'u32,
+      location: 1,
+      format: VK_FORMAT_R32G32B32_SFLOAT,
+      offset: uint32(sizeof(Vec2)), # use offsetOf?
+    ),
+  ]
--- a/src/vulkan_helpers.nim	Fri Dec 30 15:56:17 2022 +0700
+++ b/src/vulkan_helpers.nim	Sun Jan 01 01:00:50 2023 +0700
@@ -4,7 +4,6 @@
 import std/logging
 import std/macros
 
-import ./glslang/glslang
 import ./vulkan
 import ./window
 
@@ -16,7 +15,11 @@
   when defined(release):
     discard call
   else:
-    debug "CALLING vulkan: ", astToStr(call)
+    # yes, a bit cheap, but this is only for nice debug output
+    var callstr = astToStr(call).replace("\n", "")
+    while callstr.find("  ") >= 0:
+      callstr = callstr.replace("  ", " ")
+    debug "CALLING vulkan: ", callstr
     let value = call
     if value != VK_SUCCESS:
       error "Vulkan error: ",  astToStr(call),  " returned ", $value
@@ -25,23 +28,23 @@
 func addrOrNil[T](obj: var openArray[T]): ptr T =
   if obj.len > 0: addr(obj[0]) else: nil
 
-proc VK_MAKE_API_VERSION*(variant: uint32, major: uint32, minor: uint32, patch: uint32): uint32 {.compileTime.} =
+func VK_MAKE_API_VERSION*(variant: uint32, major: uint32, minor: uint32, patch: uint32): uint32 {.compileTime.} =
   (variant shl 29) or (major shl 22) or (minor shl 12) or patch
 
 
-proc filterForSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] =
+func filterForSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] =
   for format in formats:
     if format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
       result.add(format)
 
-proc getSuitableSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR =
+func getSuitableSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR =
   let usableSurfaceFormats = filterForSurfaceFormat(formats)
   if len(usableSurfaceFormats) == 0:
     raise newException(Exception, "No suitable surface formats found")
   return usableSurfaceFormats[0]
 
 
-proc cleanString*(str: openArray[char]): string =
+func cleanString*(str: openArray[char]): string =
   for i in 0 ..< len(str):
     if str[i] == char(0):
       result = join(str[0 ..< i])
@@ -112,7 +115,7 @@
   checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), addrOrNil(result));
 
 
-proc getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR =
+func getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR =
   let preferredModes = [
     VK_PRESENT_MODE_MAILBOX_KHR, # triple buffering
     VK_PRESENT_MODE_FIFO_RELAXED_KHR, # double duffering
@@ -212,27 +215,6 @@
   vkGetDeviceQueue(result[0], graphicsQueueFamily, 0'u32, addr(result[1]));
   vkGetDeviceQueue(result[0], presentationQueueFamily, 0'u32, addr(result[2]));
 
-proc createShaderStage*(device: VkDevice, stage: VkShaderStageFlagBits, shader: string): VkPipelineShaderStageCreateInfo =
-  const VK_GLSL_MAP = {
-    VK_SHADER_STAGE_VERTEX_BIT: GLSLANG_STAGE_VERTEX,
-    VK_SHADER_STAGE_FRAGMENT_BIT: GLSLANG_STAGE_FRAGMENT,
-  }.toTable()
-  var code = compileGLSLToSPIRV(VK_GLSL_MAP[stage], shader, "<memory-shader>")
-  var createInfo = VkShaderModuleCreateInfo(
-    sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
-    codeSize: uint(code.len * sizeof(uint32)),
-    pCode: addrOrNil(code),
-  )
-  var shaderModule: VkShaderModule
-  checkVkResult vkCreateShaderModule(device, addr(createInfo), nil, addr(shaderModule))
-
-  return VkPipelineShaderStageCreateInfo(
-    sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
-    stage: stage,
-    module: shaderModule,
-    pName: "main", # entry point for shader
-  )
-
 proc debugCallback*(
   messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT,
   messageTypes: VkDebugUtilsMessageTypeFlagsEXT,