changeset 493:680c4b8ca28a

add: working implementation of uniforms
author Sam <sam@basx.dev>
date Sat, 14 Jan 2023 23:34:50 +0700
parents d63d980fa3bb
children 0c18638c7217
files examples/alotof_triangles.nim examples/hello_triangle.nim src/zamikongine/buffer.nim src/zamikongine/descriptor.nim src/zamikongine/engine.nim src/zamikongine/glsl_helpers.nim src/zamikongine/shader.nim src/zamikongine/thing.nim src/zamikongine/vertex.nim tests/test_matrix.nim
diffstat 10 files changed, 308 insertions(+), 126 deletions(-) [+]
line wrap: on
line diff
--- a/examples/alotof_triangles.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/examples/alotof_triangles.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -1,21 +1,26 @@
 import std/times
+import std/strutils
 import std/math
 import std/random
+import std/enumerate
 
 import zamikongine/engine
 import zamikongine/math/vector
 import zamikongine/math/matrix
 import zamikongine/vertex
+import zamikongine/descriptor
 import zamikongine/mesh
 import zamikongine/thing
 import zamikongine/shader
 
 type
   VertexDataA = object
-    position11: VertexAttribute[Vec2[float32]]
-    color22: VertexAttribute[Vec3[float32]]
+    position11: PositionAttribute[Vec2[float32]]
+    color22: ColorAttribute[Vec3[float32]]
+  Uniforms = object
+    dt: Descriptor[float32]
 
-proc globalUpdate(engine: var Engine, dt: Duration) =
+proc globalUpdate(engine: var Engine, dt: float32) =
   discard
 
 proc randomtransform(): Mat33[float32] =
@@ -41,14 +46,14 @@
     let randomcolor1 = Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))])
     let transform1 = randomtransform()
     randommesh.vertexData = VertexDataA(
-      position11: VertexAttribute[Vec2[float32]](
+      position11: PositionAttribute[Vec2[float32]](
         data: @[
           Vec2[float32](transform1 * baseTriangle[0]),
           Vec2[float32](transform1 * baseTriangle[1]),
           Vec2[float32](transform1 * baseTriangle[2]),
         ]
       ),
-      color22: VertexAttribute[Vec3[float32]](
+      color22: ColorAttribute[Vec3[float32]](
         data: @[randomcolor1, randomcolor1, randomcolor1]
       )
     )
@@ -57,14 +62,14 @@
     let transform2 = randomtransform()
     var randomindexedmesh = new IndexedMesh[VertexDataA, uint16]
     randomindexedmesh.vertexData = VertexDataA(
-      position11: VertexAttribute[Vec2[float32]](
+      position11: PositionAttribute[Vec2[float32]](
         data: @[
           Vec2[float32](transform2 * baseTriangle[0]),
           Vec2[float32](transform2 * baseTriangle[1]),
           Vec2[float32](transform2 * baseTriangle[2]),
         ]
       ),
-      color22: VertexAttribute[Vec3[float32]](
+      color22: ColorAttribute[Vec3[float32]](
         data: @[randomcolor2, randomcolor2, randomcolor2]
       )
     )
@@ -74,11 +79,20 @@
     childthing.parts.add randomindexedmesh
     scene.children.add childthing
 
-  var pipeline = setupPipeline[VertexDataA, float32, float32, uint16](
+  const vertexShader = generateVertexShaderCode[VertexDataA, Uniforms]()
+  const fragmentShader = generateFragmentShaderCode[VertexDataA]()
+  static:
+    echo "--------------"
+    for (i, line) in enumerate(vertexShader.splitLines()):
+      echo $(i + 1) & " " & line
+    echo "--------------"
+    echo fragmentShader
+    echo "--------------"
+  var pipeline = setupPipeline[VertexDataA, float32, uint16](
     myengine,
     scene,
-    generateVertexShaderCode[VertexDataA]("main", "position11", "color22"),
-    generateFragmentShaderCode[VertexDataA]("main"),
+    vertexShader,
+    fragmentShader
   )
   myengine.run(pipeline, globalUpdate)
   pipeline.trash()
--- a/examples/hello_triangle.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/examples/hello_triangle.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -1,8 +1,12 @@
 import std/times
+import std/strutils
+import std/enumerate
 
 import zamikongine/engine
 import zamikongine/math/vector
+import zamikongine/math/matrix
 import zamikongine/vertex
+import zamikongine/descriptor
 import zamikongine/mesh
 import zamikongine/thing
 import zamikongine/shader
@@ -11,16 +15,27 @@
 type
   # define type of vertex
   VertexDataA = object
-    position: VertexAttribute[Vec2[float32]]
-    color: VertexAttribute[Vec3[float32]]
-  UniformType = float32
+    position: PositionAttribute[Vec2[float32]]
+    color: ColorAttribute[Vec3[float32]]
+  Uniforms = object
+    mat: Descriptor[Mat44[float32]]
+    dt: Descriptor[float32]
+
+var pipeline: RenderPipeline[VertexDataA, Uniforms]
 
-proc globalUpdate(engine: var Engine, dt: Duration) =
-  # var t = float32(dt.inNanoseconds) / 1_000_000_000'f32
-  # for buffer in engine.vulkan.uniformBuffers:
-    # buffer.updateData(t)
-
-  echo dt
+var pos = 0'f32;
+var uniforms = Uniforms(
+  mat: Descriptor[Mat44[float32]](value: Unit44f32),
+  dt: Descriptor[float32](value: 0'f32),
+)
+var scaledir = 1'f32
+proc globalUpdate(engine: var Engine, dt: float32) =
+  uniforms.mat.value = uniforms.mat.value * scale3d(1 + scaledir * dt, 1 + scaledir * dt, 0'f32)
+  if uniforms.mat.value[0, 0] > 2'f32 or uniforms.mat.value[0, 0] < 0.5'f32:
+    scaledir = - scaledir
+  for buffer in pipeline.uniformBuffers:
+    buffer.updateData(uniforms)
+  echo uniforms.mat.value
 
 # vertex data (types must match the above VertexAttributes)
 const
@@ -41,8 +56,8 @@
   # build a mesh
   var trianglemesh = new Mesh[VertexDataA]
   trianglemesh.vertexData = VertexDataA(
-    position: VertexAttribute[Vec2[float32]](data: triangle_pos),
-    color: VertexAttribute[Vec3[float32]](data: triangle_color),
+    position: PositionAttribute[Vec2[float32]](data: triangle_pos),
+    color: ColorAttribute[Vec3[float32]](data: triangle_color),
   )
   # build a single-object scene graph
   var triangle = new Thing
@@ -50,11 +65,26 @@
   triangle.parts.add trianglemesh
 
   # upload data, prepare shaders, etc
-  var pipeline = setupPipeline[VertexDataA, UniformType, float32, uint16](
+  const vertexShader = generateVertexShaderCode[VertexDataA, Uniforms](
+    # have 1 at:
+    # [2][0] [0][3]
+    # "out_position = vec4(in_position[0] + uniforms.mat[0][0], in_position[1] + uniforms.mat[0][0], 0, 1);"
+    "out_position = uniforms.mat * vec4(in_position, 0, 1);"
+    # "out_position = vec4(in_position, 0, 1);"
+  )
+  const fragmentShader = generateFragmentShaderCode[VertexDataA]()
+  static:
+    echo "--------------"
+    for (i, line) in enumerate(vertexShader.splitLines()):
+      echo $(i + 1) & " " & line
+    echo "--------------"
+    echo fragmentShader
+    echo "--------------"
+  pipeline = setupPipeline[VertexDataA, Uniforms, uint16](
     myengine,
     triangle,
-    generateVertexShaderCode[VertexDataA]("main", "position", "color"),
-    generateFragmentShaderCode[VertexDataA]("main"),
+    vertexShader,
+    fragmentShader
   )
   # show something
   myengine.run(pipeline, globalUpdate)
--- a/src/zamikongine/buffer.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/src/zamikongine/buffer.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -84,7 +84,7 @@
   body
   vkUnmapMemory(buffer.device, buffer.memory)
 
-# note: does not work with seq
+# note: does not work with seq, because of sizeof
 proc updateData*[T](buffer: Buffer, data: var T) =
   if buffer.persistentMapping:
     copyMem(buffer.mapped, addr(data), sizeof(T))
--- a/src/zamikongine/descriptor.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/src/zamikongine/descriptor.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -1,9 +1,17 @@
+import std/strutils
+import std/unicode
+import std/strformat
+import std/typetraits
+
 import ./vulkan
 import ./vulkan_helpers
 import ./math/vector
 import ./math/matrix
 import ./buffer
+import ./glsl_helpers
 
+# TODO: check for alignment in uniform blocks
+#
 type
   DescriptorType = SomeNumber|Vec|Mat
   Descriptor*[T:DescriptorType] = object
@@ -37,3 +45,20 @@
       persistentMapping=true,
     )
     result[i] = buffer
+
+template getDescriptorType*(v: Descriptor): auto = get(genericParams(typeof(v)), 0)
+
+func generateGLSLUniformDeclarations*[Uniforms](binding: int = 0): string {.compileTime.} =
+  var stmtList: seq[string]
+
+  let uniformTypeName = name(Uniforms).toUpper()
+  let uniformInstanceName = name(Uniforms).toLower()
+  stmtList.add(&"layout(binding = {binding}) uniform {uniformTypeName} {{")
+  for fieldname, value in Uniforms().fieldPairs:
+    when typeof(value) is Descriptor:
+      let glsltype = getGLSLType[getDescriptorType(value)]()
+      let n = fieldname
+      stmtList.add(&"    {glsltype} {n};")
+  stmtList.add(&"}} {uniformInstanceName};")
+
+  return stmtList.join("\n")
--- a/src/zamikongine/engine.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/src/zamikongine/engine.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -24,6 +24,10 @@
 
 
 const VULKAN_VERSION = VK_MAKE_API_VERSION(0'u32, 1'u32, 2'u32, 0'u32)
+const ENGINE_NAME = "zamkongine"
+const ENGINE_VERSION = "0.1"
+const BUILD_VERSION = ENGINE_VERSION & '-' & gorge("git log -1 --format=format:'%H'")
+echo "Engine: " & ENGINE_NAME & " " & BUILD_VERSION
 
 type
   Device = object
@@ -37,15 +41,17 @@
     swapchain: VkSwapchainKHR
     images: seq[VkImage]
     imageviews: seq[VkImageView]
-  RenderPipeline[T] = object
+  RenderPipeline*[VertexType, Uniforms] = object
     device*: VkDevice
-    shaders*: seq[ShaderProgram[T]]
+    shaders*: seq[ShaderProgram[VertexType, Uniforms]]
     layout*: VkPipelineLayout
     pipeline*: VkPipeline
-    uniformLayout*: VkDescriptorSetLayout
     vertexBuffers*: seq[(seq[Buffer], uint32)]
     indexedVertexBuffers*: seq[(seq[Buffer], Buffer, uint32, VkIndexType)]
+    descriptorSetLayout*: VkDescriptorSetLayout
     uniformBuffers*: array[MAX_FRAMES_IN_FLIGHT, Buffer]
+    descriptorPool*: VkDescriptorPool
+    descriptors: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSet]
   QueueFamily = object
     properties*: VkQueueFamilyProperties
     hasSurfaceSupport*: bool
@@ -250,11 +256,11 @@
     )
   checkVkResult device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result))
 
-proc initRenderPipeline[VertextType, T](device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[T] =
+proc initRenderPipeline[VertexType, Uniforms](device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, Uniforms] =
   # load shaders
   result.device = device
-  result.shaders.add(initShaderProgram[T](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader))
-  result.shaders.add(initShaderProgram[T](device, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader))
+  result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader))
+  result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader))
 
   var
     # define which parts can be dynamic (pipeline is fixed after setup)
@@ -264,8 +270,8 @@
       dynamicStateCount: uint32(dynamicStates.len),
       pDynamicStates: addr(dynamicStates[0]),
     )
-    vertexbindings = generateInputVertexBinding[VertextType]()
-    attributebindings = generateInputAttributeBinding[VertextType]()
+    vertexbindings = generateInputVertexBinding[VertexType]()
+    attributebindings = generateInputAttributeBinding[VertexType]()
 
     # define input data format
     vertexInputInfo = VkPipelineVertexInputStateCreateInfo(
@@ -336,13 +342,13 @@
       blendConstants: [0.0'f, 0.0'f, 0.0'f, 0.0'f],
     )
 
-  result.uniformLayout = device.createUniformDescriptorLayout(VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), 0)
+  result.descriptorSetLayout = device.createUniformDescriptorLayout(VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), 0)
   var 
     # "globals" that go into the shader, uniforms etc.
     pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
       sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
       setLayoutCount: 1,
-      pSetLayouts: addr(result.uniformLayout),
+      pSetLayouts: addr(result.descriptorSetLayout),
       pushConstantRangeCount: 0,
       pPushConstantRanges: nil,
     )
@@ -495,9 +501,9 @@
   ) = result.vulkan.device.device.setupSyncPrimitives()
 
 
-proc setupPipeline*[VertexType, UniformType, T: object, IndexType: uint16|uint32](engine: var Engine, scenedata: ref Thing, vertexShader, fragmentShader: static string): RenderPipeline[T] =
+proc setupPipeline*[VertexType, UniformType: object, IndexType: uint16|uint32](engine: var Engine, scenedata: ref Thing, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, UniformType] =
   engine.currentscenedata = scenedata
-  result = initRenderPipeline[VertexType, T](
+  result = initRenderPipeline[VertexType, UniformType](
     engine.vulkan.device.device,
     engine.vulkan.frameDimension,
     engine.vulkan.renderPass,
@@ -510,7 +516,7 @@
     allmeshes.add(mesh[])
   if allmeshes.len > 0:
     var ubermesh = createUberMesh(allmeshes)
-    result.vertexBuffers.add createVertexBuffers(ubermesh, engine.vulkan.device.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue)
+    result.vertexBuffers.add createVertexBuffers(ubermesh, result.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue)
 
   # vertex buffers with indexes
   var allindexedmeshes: seq[IndexedMesh[VertexType, IndexType]]
@@ -518,18 +524,62 @@
     allindexedmeshes.add(mesh[])
   if allindexedmeshes.len > 0:
     var indexedubermesh = createUberMesh(allindexedmeshes)
-    result.indexedVertexBuffers.add createIndexedVertexBuffers(indexedubermesh, engine.vulkan.device.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue)
+    result.indexedVertexBuffers.add createIndexedVertexBuffers(indexedubermesh, result.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue)
 
   # uniform buffers
   result.uniformBuffers = createUniformBuffers[MAX_FRAMES_IN_FLIGHT, UniformType](
-    engine.vulkan.device.device,
+    result.device,
     engine.vulkan.device.physicalDevice.device
   )
 
+  var
+    poolSize = VkDescriptorPoolSize(
+      `type`: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+      descriptorCount: uint32(MAX_FRAMES_IN_FLIGHT),
+    )
+    poolInfo = VkDescriptorPoolCreateInfo(
+      sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+      poolSizeCount: 1,
+      pPoolSizes: addr(poolSize),
+      maxSets: uint32(MAX_FRAMES_IN_FLIGHT),
+    )
+  checkVkResult vkCreateDescriptorPool(result.device, addr(poolInfo), nil, addr(result.descriptorPool))
 
-proc runPipeline(commandBuffer: VkCommandBuffer, pipeline: RenderPipeline) =
+  var layouts: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSetLayout]
+  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
+    layouts[i] = result.descriptorSetLayout
+  var allocInfo = VkDescriptorSetAllocateInfo(
+    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+    descriptorPool: result.descriptorPool,
+    descriptorSetCount: uint32(MAX_FRAMES_IN_FLIGHT),
+    pSetLayouts: addr(layouts[0]),
+  )
+
+  checkVkResult vkAllocateDescriptorSets(result.device, addr(allocInfo), addr(result.descriptors[0]))
+
+  var bufferInfos: array[MAX_FRAMES_IN_FLIGHT, array[1, VkDescriptorBufferInfo]] # because we use only one Uniform atm
+  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
+    bufferInfos[i][0] = VkDescriptorBufferInfo(
+      buffer: result.uniformBuffers[i].vkBuffer,
+      offset: VkDeviceSize(0),
+      range: VkDeviceSize(sizeof(UniformType)),
+    )
+    var descriptorWrite = VkWriteDescriptorSet(
+        sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+        dstSet: result.descriptors[i],
+        dstBinding: 0,
+        dstArrayElement: 0,
+        descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+        descriptorCount: 1,
+        pBufferInfo: cast[ptr ptr VkDescriptorBufferInfo](addr(bufferInfos[i][0])),
+      )
+    vkUpdateDescriptorSets(result.device, 1, addr(descriptorWrite), 0, nil)
+
+
+proc runPipeline(commandBuffer: VkCommandBuffer, pipeline: var RenderPipeline, currentFrame: int) =
   vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline)
 
+  vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(pipeline.descriptors[currentFrame]), 0, nil)
   for (vertexBufferSet, vertexCount) in pipeline.vertexBuffers:
     var
       vertexBuffers: seq[VkBuffer]
@@ -550,10 +600,10 @@
       offsets.add VkDeviceSize(0)
 
     vkCmdBindVertexBuffers(commandBuffer, firstBinding=0'u32, bindingCount=2'u32, pBuffers=addr(vertexBuffers[0]), pOffsets=addr(offsets[0]))
-    vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType);
-    vkCmdDrawIndexed(commandBuffer, indicesCount, 1, 0, 0, 0);
+    vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType)
+    vkCmdDrawIndexed(commandBuffer, indicesCount, 1, 0, 0, 0)
 
-proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: RenderPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D) =
+proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D, currentFrame: int) =
   var
     beginInfo = VkCommandBufferBeginInfo(
       sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@@ -588,11 +638,11 @@
     vkCmdBeginRenderPass(commandBuffer, addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE)
     vkCmdSetViewport(commandBuffer, firstViewport=0, viewportCount=1, addr(viewport))
     vkCmdSetScissor(commandBuffer, firstScissor=0, scissorCount=1, addr(scissor))
-    runPipeline(commandBuffer, pipeline)
+    runPipeline(commandBuffer, pipeline, currentFrame)
     vkCmdEndRenderPass(commandBuffer)
   checkVkResult vkEndCommandBuffer(commandBuffer)
 
-proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: RenderPipeline) =
+proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: var RenderPipeline) =
   checkVkResult vkWaitForFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64))
   var bufferImageIndex: uint32
   let nextImageResult = vkAcquireNextImageKHR(
@@ -611,7 +661,7 @@
   checkVkResult vkResetFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]))
 
   checkVkResult vkResetCommandBuffer(vulkan.commandBuffers[currentFrame], VkCommandBufferResetFlags(0))
-  vulkan.renderPass.recordCommandBuffer(pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension)
+  vulkan.renderPass.recordCommandBuffer(pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension, currentFrame)
   var
     waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]]
     waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
@@ -644,7 +694,7 @@
     (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
 
 
-proc run*(engine: var Engine, pipeline: RenderPipeline, globalUpdate: proc(engine: var Engine, dt: Duration)) =
+proc run*(engine: var Engine, pipeline: var RenderPipeline, globalUpdate: proc(engine: var Engine, dt: float32)) =
   var
     killed = false
     currentFrame = 0
@@ -670,7 +720,7 @@
     # game logic update
     let
       now = getTime()
-      dt = now - lastUpdate
+      dt = float32(float64((now - lastUpdate).inNanoseconds) / 1_000_000_000'f64)
     lastUpdate = now
     engine.globalUpdate(dt)
     for entity in allEntities(engine.currentscenedata):
@@ -679,11 +729,12 @@
     # submit frame for drawing
     engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline)
     resized = false
-    currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT;
+    currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT
   checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device)
 
 proc trash*(pipeline: var RenderPipeline) =
-  vkDestroyDescriptorSetLayout(pipeline.device, pipeline.uniformLayout, nil);
+  vkDestroyDescriptorPool(pipeline.device, pipeline.descriptorPool, nil)
+  vkDestroyDescriptorSetLayout(pipeline.device, pipeline.descriptorSetLayout, nil)
   vkDestroyPipeline(pipeline.device, pipeline.pipeline, nil)
   vkDestroyPipelineLayout(pipeline.device, pipeline.layout, nil)
   for shader in pipeline.shaders:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/zamikongine/glsl_helpers.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -0,0 +1,67 @@
+import ./math/vector
+import ./math/matrix
+
+func getGLSLType*[T](): 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"
+
+  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"
+
+  elif T is Mat22[float32]: "mat2"
+  elif T is Mat23[float32]: "mat32"
+  elif T is Mat32[float32]: "mat23"
+  elif T is Mat33[float32]: "mat3"
+  elif T is Mat34[float32]: "mat43"
+  elif T is Mat43[float32]: "mat34"
+  elif T is Mat44[float32]: "mat4"
+
+  elif T is Mat22[float64]: "dmat2"
+  elif T is Mat23[float64]: "dmat32"
+  elif T is Mat32[float64]: "dmat23"
+  elif T is Mat33[float64]: "dmat3"
+  elif T is Mat34[float64]: "dmat43"
+  elif T is Mat43[float64]: "dmat34"
+  elif T is Mat44[float64]: "dmat4"
--- a/src/zamikongine/shader.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/src/zamikongine/shader.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -5,14 +5,16 @@
 
 
 import ./vulkan_helpers
+import ./glsl_helpers
 import ./vulkan
 import ./vertex
+import ./descriptor
 import ./math/vector
 
 type
   AllowedUniformType = SomeNumber|Vec
   UniformSlot *[T:AllowedUniformType] = object
-  ShaderProgram*[Uniforms] = object
+  ShaderProgram*[VertexType, Uniforms] = object
     entryPoint*: string
     programType*: VkShaderStageFlagBits
     shader*: VkPipelineShaderStageCreateInfo
@@ -58,7 +60,7 @@
     )
     i += 4
 
-proc initShaderProgram*[T](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string="main"): ShaderProgram[T] =
+proc initShaderProgram*[VertexType, Uniforms](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string="main"): ShaderProgram[VertexType, Uniforms] =
   result.entryPoint = entryPoint
   result.programType = programType
 
@@ -79,31 +81,63 @@
     pName: cstring(result.entryPoint), # entry point for shader
   )
 
-func generateVertexShaderCode*[VertexType](entryPoint, positionAttrName, colorAttrName: static string): string {.compileTime.} =
+func generateVertexShaderCode*[VertexType, Uniforms](
+  shaderBody: static string = "",
+  entryPoint: static string = "main",
+  glslVersion: static string = "450"
+): string {.compileTime.} =
   var lines: seq[string]
-  lines.add "#version 450"
-  # lines.add "layout(binding = 0) uniform UniformBufferObject { float dt; } ubo;"
-  lines.add generateGLSLDeclarations[VertexType]()
+  lines.add "#version " & glslVersion
+  lines.add "layout(row_major) uniform;"
+  lines.add generateGLSLUniformDeclarations[Uniforms]()
+  lines.add generateGLSLVertexDeclarations[VertexType]()
   lines.add "layout(location = 0) out vec3 fragColor;"
   lines.add "void " & entryPoint & "() {"
 
+  var hasPosition = 0
+  var hasColor = 0
   for name, value in VertexType().fieldPairs:
-    when typeof(value) is VertexAttribute and name == positionAttrName:
-      # lines.add "    vec2 tmp = " & name & " * ubo.dt;"
-      lines.add "    vec2 tmp = " & name & ";"
-      lines.add "    gl_Position = vec4(tmp, 0.0, 1.0);"
-    when typeof(value) is VertexAttribute and name == colorAttrName:
-      lines.add "    fragColor = " & name & ";"
+    when typeof(value) is PositionAttribute:
+      let glsltype = getGLSLType[getAttributeType(value)]()
+      lines.add &"    {glsltype} in_position = " & name & ";"
+      if getAttributeType(value) is Vec2:
+        lines.add "    vec4 out_position = vec4(in_position, 0.0, 1.0);"
+      elif getAttributeType(value) is Vec3:
+        lines.add "    vec4 out_position = vec4(in_position, 1.0);"
+      elif getAttributeType(value) is Vec4:
+        lines.add "    vec4 out_position = in_position;"
+      hasPosition += 1
+    when typeof(value) is ColorAttribute:
+      let glsltype = getGLSLType[getAttributeType(value)]()
+      lines.add &"    {glsltype} in_color = " & name & ";"
+      lines.add &"    {glsltype} out_color = in_color;";
+      hasColor += 1
+
+  lines.add shaderBody
+  lines.add "    gl_Position = out_position;"
+  lines.add "    fragColor = out_color;"
   lines.add "}"
+  if hasPosition != 1:
+    raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type PositionAttribute (has {hasPosition})")
+  if hasColor != 1:
+    raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type ColorAttribute (has {hasColor})")
   return lines.join("\n")
 
-func generateFragmentShaderCode*[VertexType](entryPoint: static string): string {.compileTime.} =
+func generateFragmentShaderCode*[VertexType](
+  shaderBody: static string = "",
+  entryPoint: static string = "main",
+  glslVersion: static string = "450"
+): string {.compileTime.} =
   var lines: seq[string]
-  lines.add "#version 450"
+  lines.add "#version " & glslVersion
+  lines.add "layout(row_major) uniform;"
   lines.add "layout(location = 0) in vec3 fragColor;"
   lines.add "layout(location = 0) out vec4 outColor;"
   lines.add "void " & entryPoint & "() {"
-  lines.add "    outColor = vec4(fragColor, 1.0);"
+  lines.add "    vec3 in_color = fragColor;"
+  lines.add "    vec3 out_color = in_color;"
+  lines.add shaderBody
+  lines.add "    outColor = vec4(out_color, 1.0);"
   lines.add "}"
 
   return lines.join("\n")
--- a/src/zamikongine/thing.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/src/zamikongine/thing.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -10,7 +10,7 @@
     children*: seq[ref Thing]
     parts*: seq[ref Part]
 
-method update*(thing: ref Thing, dt: Duration) {.base.} = discard
+method update*(thing: ref Thing, dt: float32) {.base.} = discard
 
 iterator partsOfType*[T: ref Part](root: ref Thing): T =
   var queue = @[root]
--- a/src/zamikongine/vertex.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/src/zamikongine/vertex.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -4,17 +4,26 @@
 import std/typetraits
 
 import ./math/vector
+import ./math/matrix
 import ./vulkan
+import ./glsl_helpers
 
 type
   VertexAttributeType = SomeNumber|Vec
-  VertexAttribute*[T:VertexAttributeType] = object
+  AttributePurpose* = enum
+    Unknown, Position Color
+  GenericAttribute*[T:VertexAttributeType] = object
+    data*: seq[T]
+  PositionAttribute*[T:VertexAttributeType] = object
     data*: seq[T]
+  ColorAttribute*[T:VertexAttributeType] = object
+    data*: seq[T]
+  VertexAttribute* = GenericAttribute|PositionAttribute|ColorAttribute
 
-template rawAttributeType(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0)
+template getAttributeType*(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0)
 
 func datasize*(attribute: VertexAttribute): uint64 =
-  uint64(sizeof(rawAttributeType(attribute))) * uint64(attribute.data.len)
+  uint64(sizeof(getAttributeType(attribute))) * uint64(attribute.data.len)
 
 # from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
 func nLocationSlots[T: VertexAttributeType](): int =
@@ -66,54 +75,6 @@
   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"
-
-  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"
 
 
 func VertexCount*[T](t: T): uint32 =
@@ -124,15 +85,15 @@
       else:
         assert result == uint32(value.data.len)
 
-func generateGLSLDeclarations*[T](): string =
+func generateGLSLVertexDeclarations*[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 glsltype = getGLSLType[getAttributeType(value)]()
       let n = name
       stmtList.add(&"layout(location = {i}) in {glsltype} {n};")
-      i += nLocationSlots[rawAttributeType(value)]()
+      i += nLocationSlots[getAttributeType(value)]()
 
   return stmtList.join("\n")
 
@@ -144,7 +105,7 @@
       result.add(
         VkVertexInputBindingDescription(
           binding: uint32(binding),
-          stride: uint32(sizeof(rawAttributeType(value))),
+          stride: uint32(sizeof(getAttributeType(value))),
           inputRate: VK_VERTEX_INPUT_RATE_VERTEX, # VK_VERTEX_INPUT_RATE_INSTANCE for instances
         )
       )
@@ -160,9 +121,9 @@
         VkVertexInputAttributeDescription(
           binding: uint32(binding),
           location: uint32(location),
-          format: getVkFormat[rawAttributeType(value)](),
+          format: getVkFormat[getAttributeType(value)](),
           offset: 0,
         )
       )
-      location += nLocationSlots[rawAttributeType(value)]()
+      location += nLocationSlots[getAttributeType(value)]()
       binding += 1
--- a/tests/test_matrix.nim	Sat Jan 14 14:15:50 2023 +0700
+++ b/tests/test_matrix.nim	Sat Jan 14 23:34:50 2023 +0700
@@ -1,8 +1,8 @@
 import random
 import math
 
-import vector
-import matrix
+import zamikongine/math/vector
+import zamikongine/math/matrix
 
 
 proc echoInfo(v: Vec) =