changeset 28:b1b05d4efb52

big refactoring, part1
author Sam <sam@basx.dev>
date Sat, 14 Jan 2023 14:08:00 +0700
parents 8cb2d96ac28e
children da922b506570
files examples/alotof_triangles.nim examples/hello_triangle.nim src/zamikongine/buffer.nim src/zamikongine/engine.nim src/zamikongine/shader.nim src/zamikongine/thing.nim
diffstat 6 files changed, 201 insertions(+), 108 deletions(-) [+]
line wrap: on
line diff
--- a/examples/alotof_triangles.nim	Wed Jan 11 11:55:54 2023 +0700
+++ b/examples/alotof_triangles.nim	Sat Jan 14 14:08:00 2023 +0700
@@ -1,3 +1,4 @@
+import std/times
 import std/math
 import std/random
 
@@ -14,6 +15,9 @@
     position11: VertexAttribute[Vec2[float32]]
     color22: VertexAttribute[Vec3[float32]]
 
+proc globalUpdate(engine: var Engine, dt: Duration) =
+  discard
+
 proc randomtransform(): Mat33[float32] =
   let randomscale = scale2d(float32(rand(1.0) + 0.5), float32(rand(1.0) + 0.5))
   let randomrotate = rotate2d(float32(rand(2 * PI)))
@@ -22,7 +26,7 @@
 
 when isMainModule:
   randomize()
-  var myengine = igniteEngine()
+  var myengine = igniteEngine("A lot of triangles")
   const baseTriangle = [
     Vec3([-0.1'f32, -0.1'f32, 1'f32]),
     Vec3([ 0.1'f32,  0.1'f32, 1'f32]),
@@ -34,6 +38,7 @@
   for i in 1 .. 300:
     var randommesh = new Mesh[VertexDataA]
     # TODO: create randomized position11 from baseTriangle with random transformation matrix
+    let randomcolor1 = Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))])
     let transform1 = randomtransform()
     randommesh.vertexData = VertexDataA(
       position11: VertexAttribute[Vec2[float32]](
@@ -44,14 +49,11 @@
         ]
       ),
       color22: VertexAttribute[Vec3[float32]](
-        data: @[
-          Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))]),
-          Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))]),
-          Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))]),
-        ]
+        data: @[randomcolor1, randomcolor1, randomcolor1]
       )
     )
 
+    let randomcolor2 = Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))])
     let transform2 = randomtransform()
     var randomindexedmesh = new IndexedMesh[VertexDataA, uint16]
     randomindexedmesh.vertexData = VertexDataA(
@@ -63,11 +65,7 @@
         ]
       ),
       color22: VertexAttribute[Vec3[float32]](
-        data: @[
-          Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))]),
-          Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))]),
-          Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))]),
-        ]
+        data: @[randomcolor2, randomcolor2, randomcolor2]
       )
     )
     randomindexedmesh.indices = @[[0'u16, 1'u16, 2'u16]]
@@ -76,11 +74,12 @@
     childthing.parts.add randomindexedmesh
     scene.children.add childthing
 
-  setupPipeline[VertexDataA, uint16](
+  var pipeline = setupPipeline[VertexDataA, float32, float32, uint16](
     myengine,
     scene,
     generateVertexShaderCode[VertexDataA]("main", "position11", "color22"),
     generateFragmentShaderCode[VertexDataA]("main"),
   )
-  myengine.fullThrottle()
+  myengine.run(pipeline, globalUpdate)
+  pipeline.trash()
   myengine.trash()
--- a/examples/hello_triangle.nim	Wed Jan 11 11:55:54 2023 +0700
+++ b/examples/hello_triangle.nim	Sat Jan 14 14:08:00 2023 +0700
@@ -1,15 +1,26 @@
+import std/times
+
 import zamikongine/engine
 import zamikongine/math/vector
 import zamikongine/vertex
 import zamikongine/mesh
 import zamikongine/thing
 import zamikongine/shader
+import zamikongine/buffer
 
 type
   # define type of vertex
   VertexDataA = object
     position: VertexAttribute[Vec2[float32]]
     color: VertexAttribute[Vec3[float32]]
+  UniformType = float32
+
+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
 
 # vertex data (types must match the above VertexAttributes)
 const
@@ -25,7 +36,7 @@
   ]
 
 when isMainModule:
-  var myengine = igniteEngine()
+  var myengine = igniteEngine("Hello triangle")
 
   # build a mesh
   var trianglemesh = new Mesh[VertexDataA]
@@ -39,12 +50,13 @@
   triangle.parts.add trianglemesh
 
   # upload data, prepare shaders, etc
-  setupPipeline[VertexDataA, uint16](
+  var pipeline = setupPipeline[VertexDataA, UniformType, float32, uint16](
     myengine,
     triangle,
     generateVertexShaderCode[VertexDataA]("main", "position", "color"),
     generateFragmentShaderCode[VertexDataA]("main"),
   )
   # show something
-  myengine.fullThrottle()
+  myengine.run(pipeline, globalUpdate)
+  pipeline.trash()
   myengine.trash()
--- a/src/zamikongine/buffer.nim	Wed Jan 11 11:55:54 2023 +0700
+++ b/src/zamikongine/buffer.nim	Sat Jan 14 14:08:00 2023 +0700
@@ -6,6 +6,7 @@
     None = 0
     TransferSrc = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
     TransferDst = VK_BUFFER_USAGE_TRANSFER_DST_BIT
+    UniformBuffer = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
     IndexBuffer = VK_BUFFER_USAGE_INDEX_BUFFER_BIT
     VertexBuffer = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
   Buffer* = object
@@ -15,6 +16,8 @@
     memoryRequirements*: VkMemoryRequirements
     memory*: VkDeviceMemory
     bufferTypes*: set[BufferType]
+    persistentMapping: bool
+    mapped: pointer
 
 proc trash*(buffer: var Buffer) =
   assert int64(buffer.vkBuffer) != 0
@@ -37,9 +40,10 @@
   physicalDevice: VkPhysicalDevice,
   size: uint64,
   bufferTypes: set[BufferType],
-  properties: set[VkMemoryPropertyFlagBits]
+  properties: set[VkMemoryPropertyFlagBits],
+  persistentMapping: bool = false
 ): Buffer =
-  result = Buffer(device: device, size: size, bufferTypes: bufferTypes)
+  result = Buffer(device: device, size: size, bufferTypes: bufferTypes, persistentMapping: persistentMapping)
   var usageFlags = 0
   for usage in bufferTypes:
     usageFlags = ord(usageFlags) or ord(usage)
@@ -63,13 +67,31 @@
   )
   checkVkResult result.device.vkAllocateMemory(addr(allocInfo), nil, addr(result.memory))
   checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory, VkDeviceSize(0))
+  if persistentMapping:
+    checkVkResult vkMapMemory(
+      result.device,
+      result.memory,
+      offset=VkDeviceSize(0),
+      VkDeviceSize(result.size),
+      VkMemoryMapFlags(0),
+      addr(result.mapped)
+    )
 
 
 template withMapping*(buffer: Buffer, data: pointer, body: untyped): untyped =
+  assert not buffer.persistentMapping
   checkVkResult vkMapMemory(buffer.device, buffer.memory, offset=VkDeviceSize(0), VkDeviceSize(buffer.size), VkMemoryMapFlags(0), addr(data))
   body
   vkUnmapMemory(buffer.device, buffer.memory)
 
+# note: does not work with seq
+proc updateData*[T](buffer: Buffer, data: var T) =
+  if buffer.persistentMapping:
+    copyMem(buffer.mapped, addr(data), sizeof(T))
+  else:
+    var p: pointer
+    buffer.withMapping(p):
+      copyMem(p, addr(data), sizeof(T))
 
 proc copyBuffer*(commandPool: VkCommandPool, queue: VkQueue, src, dst: Buffer, size: uint64) =
   assert uint64(src.device) == uint64(dst.device)
--- a/src/zamikongine/engine.nim	Wed Jan 11 11:55:54 2023 +0700
+++ b/src/zamikongine/engine.nim	Sat Jan 14 14:08:00 2023 +0700
@@ -1,3 +1,4 @@
+import std/times
 import std/typetraits
 import std/strformat
 import std/enumerate
@@ -13,6 +14,7 @@
 import ./buffer
 import ./thing
 import ./mesh
+import ./descriptor
 
 const MAX_FRAMES_IN_FLIGHT = 2
 const DEBUG_LOG = not defined(release)
@@ -35,10 +37,15 @@
     swapchain: VkSwapchainKHR
     images: seq[VkImage]
     imageviews: seq[VkImageView]
-  RenderPipeline = object
-    shaders*: seq[ShaderProgram]
+  RenderPipeline[T] = object
+    device*: VkDevice
+    shaders*: seq[ShaderProgram[T]]
     layout*: VkPipelineLayout
     pipeline*: VkPipeline
+    uniformLayout*: VkDescriptorSetLayout
+    vertexBuffers*: seq[(seq[Buffer], uint32)]
+    indexedVertexBuffers*: seq[(seq[Buffer], Buffer, uint32, VkIndexType)]
+    uniformBuffers*: array[MAX_FRAMES_IN_FLIGHT, Buffer]
   QueueFamily = object
     properties*: VkQueueFamilyProperties
     hasSurfaceSupport*: bool
@@ -51,28 +58,25 @@
     formats: seq[VkSurfaceFormatKHR]
     presentModes: seq[VkPresentModeKHR]
   Vulkan* = object
-    debugMessenger: VkDebugUtilsMessengerEXT
+    debugMessenger*: VkDebugUtilsMessengerEXT
     instance*: VkInstance
     deviceList*: seq[PhysicalDevice]
     device*: Device
     surface*: VkSurfaceKHR
-    surfaceFormat: VkSurfaceFormatKHR
-    frameDimension: VkExtent2D
-    swapchain: Swapchain
-    framebuffers: seq[VkFramebuffer]
+    surfaceFormat*: VkSurfaceFormatKHR
+    frameDimension*: VkExtent2D
+    swapchain*: Swapchain
+    framebuffers*: seq[VkFramebuffer]
     renderPass*: VkRenderPass
-    pipeline*: RenderPipeline
     commandPool*: VkCommandPool
     commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]
     imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
     renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
     inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence]
-    vertexBuffers: seq[(seq[Buffer], uint32)]
-    indexedVertexBuffers: seq[(seq[Buffer], Buffer, uint32, VkIndexType)]
   Engine* = object
-    vulkan: Vulkan
-    window: NativeWindow
-    currentscenedata: ref Thing
+    vulkan*: Vulkan
+    window*: NativeWindow
+    currentscenedata*: ref Thing
 
 proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[PhysicalDevice] =
   for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance):
@@ -246,10 +250,11 @@
     )
   checkVkResult device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result))
 
-proc setupRenderPipeline[T](device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline =
+proc initRenderPipeline[VertextType, T](device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[T] =
   # load shaders
-  result.shaders.add(device.initShaderProgram(VK_SHADER_STAGE_VERTEX_BIT, vertexShader))
-  result.shaders.add(device.initShaderProgram(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader))
+  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))
 
   var
     # define which parts can be dynamic (pipeline is fixed after setup)
@@ -259,8 +264,8 @@
       dynamicStateCount: uint32(dynamicStates.len),
       pDynamicStates: addr(dynamicStates[0]),
     )
-    vertexbindings = generateInputVertexBinding[T]()
-    attributebindings = generateInputAttributeBinding[T]()
+    vertexbindings = generateInputVertexBinding[VertextType]()
+    attributebindings = generateInputAttributeBinding[VertextType]()
 
     # define input data format
     vertexInputInfo = VkPipelineVertexInputStateCreateInfo(
@@ -331,11 +336,13 @@
       blendConstants: [0.0'f, 0.0'f, 0.0'f, 0.0'f],
     )
 
-    # create pipeline
+  result.uniformLayout = 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: 0,
-      pSetLayouts: nil,
+      setLayoutCount: 1,
+      pSetLayouts: addr(result.uniformLayout),
       pushConstantRangeCount: 0,
       pPushConstantRanges: nil,
     )
@@ -443,9 +450,9 @@
     checkVkResult device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(result[1][i]))
     checkVkResult device.vkCreateFence(addr(fenceInfo), nil, addr(result[2][i]))
 
-proc igniteEngine*(): Engine =
+proc igniteEngine*(windowTitle: string): Engine =
 
-  result.window = createWindow("Hello triangle")
+  result.window = createWindow(windowTitle)
 
   # setup vulkan functions
   vkLoad1_0()
@@ -488,30 +495,65 @@
   ) = result.vulkan.device.device.setupSyncPrimitives()
 
 
-proc setupPipeline*[T: object, U: uint16|uint32](engine: var Engine, scenedata: ref Thing, vertexShader, fragmentShader: static string) =
+proc setupPipeline*[VertexType, UniformType, T: object, IndexType: uint16|uint32](engine: var Engine, scenedata: ref Thing, vertexShader, fragmentShader: static string): RenderPipeline[T] =
   engine.currentscenedata = scenedata
-  engine.vulkan.pipeline = setupRenderPipeline[T](
+  result = initRenderPipeline[VertexType, T](
     engine.vulkan.device.device,
     engine.vulkan.frameDimension,
     engine.vulkan.renderPass,
     vertexShader,
     fragmentShader,
   )
-  var allmeshes: seq[Mesh[T]]
-  for mesh in partsOfType[ref Mesh[T]](engine.currentscenedata):
+  # vertex buffers
+  var allmeshes: seq[Mesh[VertexType]]
+  for mesh in partsOfType[ref Mesh[VertexType]](engine.currentscenedata):
     allmeshes.add(mesh[])
   if allmeshes.len > 0:
     var ubermesh = createUberMesh(allmeshes)
-    engine.vulkan.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, engine.vulkan.device.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue)
 
-  var allindexedmeshes: seq[IndexedMesh[T, U]]
-  for mesh in partsOfType[ref IndexedMesh[T, U]](engine.currentscenedata):
+  # vertex buffers with indexes
+  var allindexedmeshes: seq[IndexedMesh[VertexType, IndexType]]
+  for mesh in partsOfType[ref IndexedMesh[VertexType, IndexType]](engine.currentscenedata):
     allindexedmeshes.add(mesh[])
   if allindexedmeshes.len > 0:
     var indexedubermesh = createUberMesh(allindexedmeshes)
-    engine.vulkan.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, engine.vulkan.device.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,
+    engine.vulkan.device.physicalDevice.device
+  )
+
+
+proc runPipeline(commandBuffer: VkCommandBuffer, pipeline: RenderPipeline) =
+  vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline)
 
-proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: VkPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D, engine: var Engine) =
+  for (vertexBufferSet, vertexCount) in pipeline.vertexBuffers:
+    var
+      vertexBuffers: seq[VkBuffer]
+      offsets: seq[VkDeviceSize]
+    for buffer in vertexBufferSet:
+      vertexBuffers.add buffer.vkBuffer
+      offsets.add VkDeviceSize(0)
+
+    vkCmdBindVertexBuffers(commandBuffer, firstBinding=0'u32, bindingCount=2'u32, pBuffers=addr(vertexBuffers[0]), pOffsets=addr(offsets[0]))
+    vkCmdDraw(commandBuffer, vertexCount=vertexCount, instanceCount=1'u32, firstVertex=0'u32, firstInstance=0'u32)
+
+  for (vertexBufferSet, indexBuffer, indicesCount, indexType) in pipeline.indexedVertexBuffers:
+    var
+      vertexBuffers: seq[VkBuffer]
+      offsets: seq[VkDeviceSize]
+    for buffer in vertexBufferSet:
+      vertexBuffers.add buffer.vkBuffer
+      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);
+
+proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: RenderPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D) =
   var
     beginInfo = VkCommandBufferBeginInfo(
       sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
@@ -541,41 +583,20 @@
       offset: VkOffset2D(x: 0, y: 0),
       extent: frameDimension
     )
-  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
-  commandBuffer.vkCmdBeginRenderPass(addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE)
-  commandBuffer.vkCmdSetViewport(firstViewport=0, viewportCount=1, addr(viewport))
-  commandBuffer.vkCmdSetScissor(firstScissor=0, scissorCount=1, addr(scissor))
-  commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline)
-
-  for (vertexBufferSet, vertexCount) in engine.vulkan.vertexBuffers:
-    var
-      vertexBuffers: seq[VkBuffer]
-      offsets: seq[VkDeviceSize]
-    for buffer in vertexBufferSet:
-      vertexBuffers.add buffer.vkBuffer
-      offsets.add VkDeviceSize(0)
-
-    commandBuffer.vkCmdBindVertexBuffers(firstBinding=0'u32, bindingCount=2'u32, pBuffers=addr(vertexBuffers[0]), pOffsets=addr(offsets[0]))
-    commandBuffer.vkCmdDraw(vertexCount=vertexCount, instanceCount=1'u32, firstVertex=0'u32, firstInstance=0'u32)
+  checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo))
+  block:
+    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)
+    vkCmdEndRenderPass(commandBuffer)
+  checkVkResult vkEndCommandBuffer(commandBuffer)
 
-  for (vertexBufferSet, indexBuffer, indicesCount, indexType) in engine.vulkan.indexedVertexBuffers:
-    var
-      vertexBuffers: seq[VkBuffer]
-      offsets: seq[VkDeviceSize]
-    for buffer in vertexBufferSet:
-      vertexBuffers.add buffer.vkBuffer
-      offsets.add VkDeviceSize(0)
-
-    commandBuffer.vkCmdBindVertexBuffers(firstBinding=0'u32, bindingCount=2'u32, pBuffers=addr(vertexBuffers[0]), pOffsets=addr(offsets[0]))
-    commandBuffer.vkCmdBindIndexBuffer(indexBuffer.vkBuffer, VkDeviceSize(0), indexType);
-    commandBuffer.vkCmdDrawIndexed(indicesCount, 1, 0, 0, 0);
-  commandBuffer.vkCmdEndRenderPass()
-  checkVkResult commandBuffer.vkEndCommandBuffer()
-
-proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, engine: var Engine) =
-  checkVkResult vulkan.device.device.vkWaitForFences(1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64))
+proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: RenderPipeline) =
+  checkVkResult vkWaitForFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64))
   var bufferImageIndex: uint32
-  let nextImageResult = vulkan.device.device.vkAcquireNextImageKHR(
+  let nextImageResult = vkAcquireNextImageKHR(
+    vulkan.device.device,
     vulkan.swapchain.swapchain,
     high(uint64),
     vulkan.imageAvailableSemaphores[currentFrame],
@@ -587,10 +608,10 @@
     (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
   elif not (nextImageResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]):
     raise newException(Exception, "Vulkan error: vkAcquireNextImageKHR returned " & $nextImageResult)
-  checkVkResult vulkan.device.device.vkResetFences(1, addr(vulkan.inFlightFences[currentFrame]))
+  checkVkResult vkResetFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]))
 
-  checkVkResult vulkan.commandBuffers[currentFrame].vkResetCommandBuffer(VkCommandBufferResetFlags(0))
-  vulkan.renderPass.recordCommandBuffer(vulkan.pipeline.pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension, engine)
+  checkVkResult vkResetCommandBuffer(vulkan.commandBuffers[currentFrame], VkCommandBufferResetFlags(0))
+  vulkan.renderPass.recordCommandBuffer(pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension)
   var
     waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]]
     waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
@@ -623,13 +644,16 @@
     (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
 
 
-proc fullThrottle*(engine: var Engine) =
+proc run*(engine: var Engine, pipeline: RenderPipeline, globalUpdate: proc(engine: var Engine, dt: Duration)) =
   var
     killed = false
     currentFrame = 0
     resized = false
+    lastUpdate = getTime()
 
   while not killed:
+
+    # process input
     for event in engine.window.pendingEvents():
       case event.eventType:
         of Quit:
@@ -642,38 +666,54 @@
             killed = true
         else:
           discard
-    engine.window.drawFrame(engine.vulkan, currentFrame, resized, engine)
+
+    # game logic update
+    let
+      now = getTime()
+      dt = now - lastUpdate
+    lastUpdate = now
+    engine.globalUpdate(dt)
+    for entity in allEntities(engine.currentscenedata):
+      entity.update(dt)
+
+    # submit frame for drawing
+    engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline)
     resized = false
     currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT;
-  checkVkResult engine.vulkan.device.device.vkDeviceWaitIdle()
+  checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device)
 
-proc trash*(engine: var Engine) =
-  for (bufferset, cnt) in engine.vulkan.vertexBuffers.mitems:
+proc trash*(pipeline: var RenderPipeline) =
+  vkDestroyDescriptorSetLayout(pipeline.device, pipeline.uniformLayout, nil);
+  vkDestroyPipeline(pipeline.device, pipeline.pipeline, nil)
+  vkDestroyPipelineLayout(pipeline.device, pipeline.layout, nil)
+  for shader in pipeline.shaders:
+    vkDestroyShaderModule(pipeline.device, shader.shader.module, nil)
+
+  for (bufferset, cnt) in pipeline.vertexBuffers.mitems:
     for buffer in bufferset.mitems:
       buffer.trash()
-  for (bufferset, indexbuffer, cnt, t) in engine.vulkan.indexedVertexBuffers.mitems:
+  for (bufferset, indexbuffer, cnt, t) in pipeline.indexedVertexBuffers.mitems:
     indexbuffer.trash()
     for buffer in bufferset.mitems:
       buffer.trash()
+  for buffer in pipeline.uniformBuffers.mitems:
+    buffer.trash()
+
+proc trash*(engine: var Engine) =
+  checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device)
   engine.vulkan.device.device.trash(engine.vulkan.swapchain, engine.vulkan.framebuffers)
-  checkVkResult engine.vulkan.device.device.vkDeviceWaitIdle()
 
   for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
     engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.imageAvailableSemaphores[i], nil)
     engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.renderFinishedSemaphores[i], nil)
     engine.vulkan.device.device.vkDestroyFence(engine.vulkan.inFlightFences[i], nil)
 
+  engine.vulkan.device.device.vkDestroyRenderPass(engine.vulkan.renderPass, nil)
   engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.commandPool, nil)
-  engine.vulkan.device.device.vkDestroyPipeline(engine.vulkan.pipeline.pipeline, nil)
-  engine.vulkan.device.device.vkDestroyPipelineLayout(engine.vulkan.pipeline.layout, nil)
-  engine.vulkan.device.device.vkDestroyRenderPass(engine.vulkan.renderPass, 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)
   when DEBUG_LOG:
     engine.vulkan.instance.vkDestroyDebugUtilsMessengerEXT(engine.vulkan.debugMessenger, nil)
   engine.window.trash()
-  engine.vulkan.instance.vkDestroyInstance(nil)
+  engine.vulkan.instance.vkDestroyInstance(nil) # needs to happen after window is trashed as the driver might have a hook registered for the window destruction
--- a/src/zamikongine/shader.nim	Wed Jan 11 11:55:54 2023 +0700
+++ b/src/zamikongine/shader.nim	Sat Jan 14 14:08:00 2023 +0700
@@ -7,12 +7,16 @@
 import ./vulkan_helpers
 import ./vulkan
 import ./vertex
+import ./math/vector
 
 type
-  ShaderProgram* = object
+  AllowedUniformType = SomeNumber|Vec
+  UniformSlot *[T:AllowedUniformType] = object
+  ShaderProgram*[Uniforms] = object
     entryPoint*: string
     programType*: VkShaderStageFlagBits
     shader*: VkPipelineShaderStageCreateInfo
+    uniforms*: Uniforms
 
 func stage2string(stage: VkShaderStageFlagBits): string {.compileTime.} =
   case stage
@@ -26,9 +30,11 @@
   of VK_SHADER_STAGE_ALL: ""
 
 proc compileGLSLToSPIRV(stage: VkShaderStageFlagBits, shaderSource: string, entrypoint: string): seq[uint32] {.compileTime.} =
-  # TODO: compiles only on linux for now (because we don't have compile-time functionality in std/tempfile)
   let stagename = stage2string(stage)
 
+  # TODO: compiles only on linux for now (because we don't have compile-time functionality in std/tempfile)
+  if not defined(linux):
+    raise newException(Exception, "Compilation is currently only supported on linux (need mktemp command), sorry!")
   let (tmpfile, exitCode) = gorgeEx(command=fmt"mktemp --tmpdir shader_XXXXXXX.{stagename}")
   if exitCode != 0:
     raise newException(Exception, tmpfile)
@@ -52,7 +58,7 @@
     )
     i += 4
 
-proc initShaderProgram*(device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string="main"): ShaderProgram =
+proc initShaderProgram*[T](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string="main"): ShaderProgram[T] =
   result.entryPoint = entryPoint
   result.programType = programType
 
@@ -73,22 +79,25 @@
     pName: cstring(result.entryPoint), # entry point for shader
   )
 
-func generateVertexShaderCode*[T](entryPoint, positionAttrName, colorAttrName: static string): string {.compileTime.} =
+func generateVertexShaderCode*[VertexType](entryPoint, positionAttrName, colorAttrName: static string): string {.compileTime.} =
   var lines: seq[string]
   lines.add "#version 450"
-  lines.add generateGLSLDeclarations[T]()
+  # lines.add "layout(binding = 0) uniform UniformBufferObject { float dt; } ubo;"
+  lines.add generateGLSLDeclarations[VertexType]()
   lines.add "layout(location = 0) out vec3 fragColor;"
   lines.add "void " & entryPoint & "() {"
 
-  for name, value in T().fieldPairs:
+  for name, value in VertexType().fieldPairs:
     when typeof(value) is VertexAttribute and name == positionAttrName:
-      lines.add "    gl_Position = vec4(" & name & ", 0.0, 1.0);"
+      # 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 & ";"
   lines.add "}"
   return lines.join("\n")
 
-func generateFragmentShaderCode*[T](entryPoint: static string): string {.compileTime.} =
+func generateFragmentShaderCode*[VertexType](entryPoint: static string): string {.compileTime.} =
   var lines: seq[string]
   lines.add "#version 450"
   lines.add "layout(location = 0) in vec3 fragColor;"
--- a/src/zamikongine/thing.nim	Wed Jan 11 11:55:54 2023 +0700
+++ b/src/zamikongine/thing.nim	Sat Jan 14 14:08:00 2023 +0700
@@ -1,4 +1,5 @@
 {.experimental: "codeReordering".}
+import std/times
 
 type
   Part* = object of RootObj
@@ -9,6 +10,8 @@
     children*: seq[ref Thing]
     parts*: seq[ref Part]
 
+method update*(thing: ref Thing, dt: Duration) {.base.} = discard
+
 iterator partsOfType*[T: ref Part](root: ref Thing): T =
   var queue = @[root]
   while queue.len > 0:
@@ -18,3 +21,11 @@
         yield T(part)
     for child in thing.children:
       queue.insert(child, 0)
+
+iterator allEntities*(root: ref Thing): ref Thing =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      queue.add child
+    yield next