changeset 524:37e42b3b120d

fix: scene graph, input
author Sam <sam@basx.dev>
date Sat, 04 Feb 2023 02:24:15 +0700
parents 311ee4e58032
children 0285ff2281b1
files src/semicongine/buffer.nim src/semicongine/engine.nim src/semicongine/mesh.nim src/semicongine/platform/linux/xlib.nim src/semicongine/shader.nim src/semicongine/thing.nim
diffstat 6 files changed, 136 insertions(+), 125 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/buffer.nim	Wed Jan 25 23:56:59 2023 +0700
+++ b/src/semicongine/buffer.nim	Sat Feb 04 02:24:15 2023 +0700
@@ -36,13 +36,15 @@
     vkFreeMemory(buffer.device, buffer.memory, nil)
     buffer.memory = VkDeviceMemory(0)
 
-proc findMemoryType(buffer: Buffer, physicalDevice: VkPhysicalDevice, properties: MemoryProperties): uint32 =
+proc findMemoryType(buffer: Buffer, physicalDevice: VkPhysicalDevice,
+    properties: MemoryProperties): uint32 =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
   vkGetPhysicalDeviceMemoryProperties(physicalDevice, addr(physicalProperties))
 
   for i in 0'u32 ..< physicalProperties.memoryTypeCount:
     if bool(buffer.memoryRequirements.memoryTypeBits and (1'u32 shl i)):
-      if (uint32(physicalProperties.memoryTypes[i].propertyFlags) and cast[uint32](properties)) == cast[uint32](properties):
+      if (uint32(physicalProperties.memoryTypes[i].propertyFlags) and cast[
+          uint32](properties)) == cast[uint32](properties):
         return i
 
 proc InitBuffer*(
@@ -52,7 +54,8 @@
   bufferTypes: set[BufferType],
   properties: MemoryProperties,
 ): Buffer =
-  result = Buffer(device: device, size: size, bufferTypes: bufferTypes, memoryProperties: properties)
+  result = Buffer(device: device, size: size, bufferTypes: bufferTypes,
+      memoryProperties: properties)
   var usageFlags = 0
   for usage in bufferTypes:
     usageFlags = ord(usageFlags) or ord(usage)
@@ -62,8 +65,10 @@
     usage: VkBufferUsageFlags(usageFlags),
     sharingMode: VK_SHARING_MODE_EXCLUSIVE,
   )
-  checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr(result.vkBuffer))
-  vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr(result.memoryRequirements))
+  checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr(
+      result.vkBuffer))
+  vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr(
+      result.memoryRequirements))
 
   var allocInfo = VkMemoryAllocateInfo(
     sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
@@ -72,18 +77,20 @@
   )
   if result.size > 0:
     checkVkResult result.device.vkAllocateMemory(addr(allocInfo), nil, addr(result.memory))
-  checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory, VkDeviceSize(0))
+  checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory,
+      VkDeviceSize(0))
   checkVkResult vkMapMemory(
     result.device,
     result.memory,
-    offset=VkDeviceSize(0),
+    offset = VkDeviceSize(0),
     VkDeviceSize(result.size),
     VkMemoryMapFlags(0),
     addr(result.data)
   )
 
 
-proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src, dst: Buffer, size: uint64) =
+proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src,
+    dst: Buffer, size: uint64) =
   assert uint64(src.device) == uint64(dst.device)
   assert TransferSrc in src.bufferTypes
   assert TransferDst in dst.bufferTypes
--- a/src/semicongine/engine.nim	Wed Jan 25 23:56:59 2023 +0700
+++ b/src/semicongine/engine.nim	Sat Feb 04 02:24:15 2023 +0700
@@ -15,8 +15,8 @@
 import ./vertex
 import ./buffer
 import ./thing
+import ./descriptor
 import ./mesh
-import ./descriptor
 
 const MAX_FRAMES_IN_FLIGHT = 2
 const DEBUG_LOG = not defined(release)
@@ -50,8 +50,7 @@
     shaders*: seq[ShaderProgram[VertexType, Uniforms]]
     layout*: VkPipelineLayout
     pipeline*: VkPipeline
-    vertexBuffers*: seq[(seq[Buffer], uint32)]
-    indexedVertexBuffers*: seq[(seq[Buffer], Buffer, uint32, VkIndexType)]
+    vertexBuffers*: seq[(seq[Buffer], bool, Buffer, uint32, VkIndexType)]
     descriptorSetLayout*: VkDescriptorSetLayout
     uniformBuffers*: array[MAX_FRAMES_IN_FLIGHT, Buffer]
     descriptorPool*: VkDescriptorPool
@@ -95,6 +94,17 @@
     currentscenedata*: Thing
     input*: Input
 
+
+method update*(thing: Thing, engine: Engine, dt: float32) {.base.} = discard
+method update*(part: Part, engine: Engine, dt: float32) {.base.} = discard
+
+method update*[T, U](mesh: Mesh[T, U], engine: Engine, dt: float32) =
+  let transform = @[mesh.thing.getModelTransform().transposed()]
+  for name, value in mesh.vertexData.fieldPairs:
+    when value is ModelTransformAttribute:
+      value.data = transform
+      engine.vulkan.device.updateVertexData(value)
+
 proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[
     PhysicalDevice] =
   for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance):
@@ -199,7 +209,7 @@
   var imageCount = capabilities.minImageCount + 1
   if capabilities.maxImageCount > 0:
     imageCount = min(capabilities.maxImageCount, imageCount)
-  # TODO: fix when dimension is zero
+  # TODO: something not working on window..., likely the extent
   var extent = VkExtent2D(
     width: if dimension[0] > 0: dimension[0] else: 1,
     height: if dimension[1] > 0: dimension[1] else: 1,
@@ -573,20 +583,12 @@
     fragmentShader,
   )
 
-  # vertex buffers
-  for mesh in allPartsOfType[Mesh[VertexType]](engine.currentscenedata):
-    result.vertexBuffers.add createVertexBuffers(mesh, result.device,
-        engine.vulkan.device.physicalDevice.device,
+  for mesh in allPartsOfType[Mesh[VertexType, IndexType]](
+      engine.currentscenedata):
+    result.vertexBuffers.add createIndexedVertexBuffers(mesh,
+        result.device, engine.vulkan.device.physicalDevice.device,
         engine.vulkan.device.commandPool, engine.vulkan.device.graphicsQueue)
 
-  # vertex buffers with indexes
-  when not (IndexType is void):
-    for mesh in allPartsOfType[IndexedMesh[VertexType, IndexType]](
-        engine.currentscenedata):
-      result.indexedVertexBuffers.add createIndexedVertexBuffers(mesh,
-          result.device, engine.vulkan.device.physicalDevice.device,
-          engine.vulkan.device.commandPool, engine.vulkan.device.graphicsQueue)
-
   # uniform buffers
   when not (UniformType is void):
     result.uniformBuffers = createUniformBuffers[MAX_FRAMES_IN_FLIGHT,
@@ -680,7 +682,9 @@
 
   vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
       pipeline.layout, 0, 1, addr(pipeline.descriptors[currentFrame]), 0, nil)
-  for (vertexBufferSet, vertexCount) in pipeline.vertexBuffers:
+
+  for (vertexBufferSet, indexed, indexBuffer, count, indexType) in
+    pipeline.vertexBuffers:
     var
       vertexBuffers: seq[VkBuffer]
       offsets: seq[VkDeviceSize]
@@ -690,24 +694,13 @@
 
     vkCmdBindVertexBuffers(commandBuffer, firstBinding = 0'u32,
         bindingCount = uint32(vertexBuffers.len), 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 = uint32(vertexBuffers.len), pBuffers = addr(vertexBuffers[
-        0]), pOffsets = addr(offsets[0]))
-    vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType)
-    vkCmdDrawIndexed(commandBuffer, indicesCount, 1, 0, 0, 0)
+            0]), pOffsets = addr(offsets[0]))
+    if indexed:
+      vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType)
+      vkCmdDrawIndexed(commandBuffer, count, 1, 0, 0, 0)
+    else:
+      vkCmdDraw(commandBuffer, vertexCount = count, instanceCount = 1,
+          firstVertex = 0, firstInstance = 0)
 
 proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline,
     commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer,
@@ -858,8 +851,8 @@
     engine.globalUpdate(dt)
     for thing in allThings(engine.currentscenedata):
       for part in thing.parts:
-        part.update(dt)
-      thing.update(dt)
+        update(part, engine, dt)
+      update(thing, engine, dt)
 
     # submit frame for drawing
     engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline)
@@ -875,11 +868,10 @@
   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 pipeline.indexedVertexBuffers.mitems:
-    indexbuffer.trash()
+  for (bufferset, indexed, indexbuffer, cnt, t) in
+    pipeline.vertexBuffers.mitems:
+    if indexed:
+      indexbuffer.trash()
     for buffer in bufferset.mitems:
       buffer.trash()
   for buffer in pipeline.uniformBuffers.mitems:
--- a/src/semicongine/mesh.nim	Wed Jan 25 23:56:59 2023 +0700
+++ b/src/semicongine/mesh.nim	Sat Feb 04 02:24:15 2023 +0700
@@ -8,22 +8,17 @@
 import ./math/vector
 
 type
-  Mesh*[T] = ref object of Part
+  # TODO: make single mesh type, with case-of attribute for indices
+  Mesh*[T: object, U: uint16|uint32] = ref object of Part
     vertexData*: T
-  IndexedMesh*[T: object, U: uint16|uint32] = ref object of Part
-    vertexData*: T
-    indices*: seq[array[3, U]]
+    case indexed*: bool
+    of true:
+      indices*: seq[array[3, U]]
+    of false:
+      discard
 
-func createUberMesh*[T](meshes: openArray[Mesh[T]]): Mesh[T] =
-  for mesh in meshes:
-    for srcname, srcvalue in mesh.vertexData.fieldPairs:
-      when typeof(srcvalue) is VertexAttribute:
-        for dstname, dstvalue in result.vertexData.fieldPairs:
-          when srcname == dstname:
-            dstvalue.data.add srcvalue.data
-
-func createUberMesh*[T: object, U: uint16|uint32](meshes: openArray[IndexedMesh[
-    T, U]]): IndexedMesh[T, U] =
+func createUberMesh*[T: object, U: uint16|uint32](meshes: openArray[Mesh[
+    T, U]]): Mesh[T, U] =
   var indexoffset = U(0)
   for mesh in meshes:
     for srcname, srcvalue in mesh.vertexData.fieldPairs:
@@ -37,12 +32,12 @@
       result.indices.add indexdata
     indexoffset += U(mesh.vertexData.VertexCount)
 
-func getVkIndexType[T: object, U: uint16|uint32](m: IndexedMesh[T,
+func getVkIndexType[T: object, U: uint16|uint32](m: Mesh[T,
     U]): VkIndexType =
   when U is uint16: VK_INDEX_TYPE_UINT16
   elif U is uint32: VK_INDEX_TYPE_UINT32
 
-proc createVertexBuffers*[M: Mesh|IndexedMesh](
+proc createVertexBuffers*[M: Mesh](
   mesh: M,
   device: VkDevice,
   physicalDevice: VkPhysicalDevice,
@@ -71,7 +66,7 @@
         value.buffer = stagingBuffer
 
 proc createIndexBuffer*(
-  mesh: IndexedMesh,
+  mesh: Mesh,
   device: VkDevice,
   physicalDevice: VkPhysicalDevice,
   commandPool: VkCommandPool,
@@ -96,20 +91,23 @@
     return stagingBuffer
 
 proc createIndexedVertexBuffers*(
-  mesh: IndexedMesh,
+  mesh: Mesh,
   device: VkDevice,
   physicalDevice: VkPhysicalDevice,
   commandPool: VkCommandPool,
   queue: VkQueue,
   useDeviceLocalBufferForIndices: bool = true # decides if data is transfered to the fast device-local memory or not
-): (seq[Buffer], Buffer, uint32, VkIndexType) =
+): (seq[Buffer], bool, Buffer, uint32, VkIndexType) =
   result[0] = createVertexBuffers(mesh, device, physicalDevice, commandPool,
       queue)[0]
-  result[1] = createIndexBuffer(mesh, device, physicalDevice, commandPool,
-      queue, useDeviceLocalBufferForIndices)
-  result[2] = uint32(mesh.indices.len * mesh.indices[0].len)
-
-  result[3] = getVkIndexType(mesh)
+  result[1] = mesh.indexed
+  if mesh.indexed:
+    result[2] = createIndexBuffer(mesh, device, physicalDevice, commandPool,
+        queue, useDeviceLocalBufferForIndices)
+    result[3] = uint32(mesh.indices.len * mesh.indices[0].len)
+    result[4] = getVkIndexType(mesh)
+  else:
+    result[3] = uint32(mesh.vertexData.VertexCount)
 
 func squareData*[T: SomeFloat](): auto = PositionAttribute[TVec2[T]](
   data: @[TVec2[T]([T(0), T(0)]), TVec2[T]([T(0), T(1)]), TVec2[T]([T(1), T(
@@ -118,12 +116,3 @@
 func squareIndices*[T: uint16|uint32](): auto = seq[array[3, T]](
   @[[T(0), T(1), T(3)], [T(2), T(1), T(3)]]
 )
-
-method update*(mesh: Mesh, dt: float32) =
-  echo "The update"
-  echo mesh.thing.getModelTransform()
-  echo mesh.vertexData
-method update*(mesh: IndexedMesh, dt: float32) =
-  echo "The update"
-  echo mesh.thing.getModelTransform()
-  echo mesh.vertexData
--- a/src/semicongine/platform/linux/xlib.nim	Wed Jan 25 23:56:59 2023 +0700
+++ b/src/semicongine/platform/linux/xlib.nim	Sat Feb 04 02:24:15 2023 +0700
@@ -1,4 +1,5 @@
 import std/options
+import std/tables
 import
   x11/xlib,
   x11/xutil,
@@ -25,7 +26,8 @@
 template checkXlibResult*(call: untyped) =
   let value = call
   if value == 0:
-    raise newException(Exception, "Xlib error: " & astToStr(call) & " returned " & $value)
+    raise newException(Exception, "Xlib error: " & astToStr(call) &
+        " returned " & $value)
 
 proc createWindow*(title: string): NativeWindow =
   checkXlibResult XInitThreads()
@@ -39,9 +41,12 @@
     foregroundColor = XBlackPixel(display, screen)
     backgroundColor = XWhitePixel(display, screen)
 
-  let window = XCreateSimpleWindow(display, rootWindow, -1, -1, 800, 600, 0, foregroundColor, backgroundColor)
-  checkXlibResult XSetStandardProperties(display, window, title, "window", 0, nil, 0, nil)
-  checkXlibResult XSelectInput(display, window, PointerMotionMask or ButtonPressMask or ButtonReleaseMask or KeyPressMask or KeyReleaseMask or ExposureMask)
+  let window = XCreateSimpleWindow(display, rootWindow, -1, -1, 800, 600, 0,
+      foregroundColor, backgroundColor)
+  checkXlibResult XSetStandardProperties(display, window, title, "window", 0,
+      nil, 0, nil)
+  checkXlibResult XSelectInput(display, window, PointerMotionMask or
+      ButtonPressMask or ButtonReleaseMask or KeyPressMask or KeyReleaseMask or ExposureMask)
   checkXlibResult XMapWindow(display, window)
 
   deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false))
@@ -51,11 +56,13 @@
   var data = "\0".cstring
   var pixmap = XCreateBitmapFromData(display, window, data, 1, 1)
   var color: XColor
-  var empty_cursor = XCreatePixmapCursor(display, pixmap, pixmap, addr(color), addr(color), 0, 0)
+  var empty_cursor = XCreatePixmapCursor(display, pixmap, pixmap, addr(color),
+      addr(color), 0, 0)
   checkXlibResult XFreePixmap(display, pixmap)
   checkXlibResult XDefineCursor(display, window, empty_cursor)
 
-  return NativeWindow(display: display, window: window, emptyCursor: empty_cursor)
+  return NativeWindow(display: display, window: window,
+      emptyCursor: empty_cursor)
 
 proc trash*(window: NativeWindow) =
   checkXlibResult window.display.XFreeCursor(window.emptyCursor)
@@ -68,7 +75,9 @@
   return (int(attribs.width), int(attribs.height))
 
 proc pendingEvents*(window: NativeWindow): seq[Event] =
-  var event: XEvent
+  var
+    event: XEvent
+    serials: Table[culong, seq[Event]]
   while window.display.XPending() > 0:
     discard window.display.XNextEvent(addr(event))
     case event.theType
@@ -76,17 +85,27 @@
       if cast[Atom](event.xclient.data.l[0]) == deleteMessage:
         result.add(Event(eventType: Quit))
     of KeyPress:
-      let xkey = int(cast[PXKeyEvent](addr(event)).keycode)
-      result.add Event(eventType: KeyPressed, key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN))
+      let keyevent = cast[PXKeyEvent](addr(event))
+      let xkey = int(keyevent.keycode)
+      if not (keyevent.serial in serials):
+        serials[keyevent.serial] = newSeq[Event]()
+      serials[keyevent.serial].add(Event(eventType: KeyPressed,
+          key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN)))
     of KeyRelease:
-      let xkey = int(cast[PXKeyEvent](addr(event)).keycode)
-      result.add Event(eventType: KeyReleased, key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN))
+      let keyevent = cast[PXKeyEvent](addr(event))
+      let xkey = int(keyevent.keycode)
+      if not (keyevent.serial in serials):
+        serials[keyevent.serial] = newSeq[Event]()
+      serials[keyevent.serial].add Event(eventType: KeyReleased,
+          key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN))
     of ButtonPress:
       let button = int(cast[PXButtonEvent](addr(event)).button)
-      result.add Event(eventType: MousePressed, button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN))
+      result.add Event(eventType: MousePressed,
+          button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN))
     of ButtonRelease:
       let button = int(cast[PXButtonEvent](addr(event)).button)
-      result.add Event(eventType: MouseReleased, button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN))
+      result.add Event(eventType: MouseReleased,
+          button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN))
     of MotionNotify:
       let motion = cast[PXMotionEvent](addr(event))
       result.add Event(eventType: MouseMoved, x: motion.x, y: motion.y)
@@ -94,6 +113,10 @@
       result.add Event(eventType: ResizedWindow)
     else:
       discard
+  for (serial, events) in serials.pairs:
+    if events.len == 1:
+      result.add events[0]
+
 
 proc getMousePosition*(window: NativeWindow): Option[Vec2] =
   var
--- a/src/semicongine/shader.nim	Wed Jan 25 23:56:59 2023 +0700
+++ b/src/semicongine/shader.nim	Sat Feb 04 02:24:15 2023 +0700
@@ -14,7 +14,7 @@
 
 type
   AllowedUniformType = SomeNumber|TVec
-  UniformSlot *[T:AllowedUniformType] = object
+  UniformSlot *[T: AllowedUniformType] = object
   ShaderProgram*[VertexType, Uniforms] = object
     entryPoint*: string
     programType*: VkShaderStageFlagBits
@@ -32,7 +32,9 @@
   of VK_SHADER_STAGE_COMPUTE_BIT: "comp"
   of VK_SHADER_STAGE_ALL: ""
 
-proc compileGLSLToSPIRV(stage: static VkShaderStageFlagBits, shaderSource: static string, entrypoint: string): seq[uint32] {.compileTime.} =
+proc compileGLSLToSPIRV(stage: static VkShaderStageFlagBits,
+    shaderSource: static string, entrypoint: string): seq[
+    uint32] {.compileTime.} =
   when defined(nimcheck): # will not run if nimcheck is running
     return result
   const
@@ -42,7 +44,9 @@
     shaderfile = getTempDir() / fmt"shader_{shaderHash}.{stagename}"
     projectPath = querySetting(projectPath)
 
-  let (output, exitCode_glsl) = gorgeEx(command=fmt"{projectPath}/glslangValidator --entry-point {entrypoint} -V --stdin -S {stagename} -o {shaderfile}", input=shaderSource)
+  let (output, exitCode_glsl) = gorgeEx(
+      command = fmt"{projectPath}/glslangValidator --entry-point {entrypoint} -V --stdin -S {stagename} -o {shaderfile}",
+      input = shaderSource)
   if exitCode_glsl != 0:
     raise newException(Exception, output)
 
@@ -55,14 +59,16 @@
   var i = 0
   while i < shaderbinary.len:
     result.add(
-      (uint32(shaderbinary[i + 0]) shl  0) or
-      (uint32(shaderbinary[i + 1]) shl  8) or
+      (uint32(shaderbinary[i + 0]) shl 0) or
+      (uint32(shaderbinary[i + 1]) shl 8) or
       (uint32(shaderbinary[i + 2]) shl 16) or
       (uint32(shaderbinary[i + 3]) shl 24)
     )
     i += 4
 
-proc initShaderProgram*[VertexType, Uniforms](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string="main"): ShaderProgram[VertexType, Uniforms] =
+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
 
@@ -93,7 +99,7 @@
   lines.add "layout(row_major) uniform;"
   lines.add generateGLSLUniformDeclarations[Uniforms]()
   lines.add generateGLSLVertexDeclarations[VertexType]()
-  lines.add "layout(location = 0) out vec3 fragColor;"
+  lines.add "layout(location = 0) out vec4 fragColor;"
   lines.add "void " & entryPoint & "() {"
 
   var hasPosition = 0
@@ -112,7 +118,10 @@
     when typeof(value) is ColorAttribute:
       let glsltype = getGLSLType[getAttributeType(value)]()
       lines.add &"    {glsltype} in_color = " & name & ";"
-      lines.add &"    {glsltype} out_color = in_color;";
+      if getAttributeType(value) is TVec3:
+        lines.add &"    vec4 out_color = vec4(in_color, 1);";
+      elif getAttributeType(value) is TVec4:
+        lines.add &"    vec4 out_color = in_color;";
       hasColor += 1
 
   lines.add shaderBody
@@ -133,13 +142,13 @@
   var lines: seq[string]
   lines.add "#version " & glslVersion
   lines.add "layout(row_major) uniform;"
-  lines.add "layout(location = 0) in vec3 fragColor;"
+  lines.add "layout(location = 0) in vec4 fragColor;"
   lines.add "layout(location = 0) out vec4 outColor;"
   lines.add "void " & entryPoint & "() {"
-  lines.add "    vec3 in_color = fragColor;"
-  lines.add "    vec3 out_color = in_color;"
+  lines.add "    vec4 in_color = fragColor;"
+  lines.add "    vec4 out_color = in_color;"
   lines.add shaderBody
-  lines.add "    outColor = vec4(out_color, 1.0);"
+  lines.add "    outColor = out_color;"
   lines.add "}"
 
   return lines.join("\n")
--- a/src/semicongine/thing.nim	Wed Jan 25 23:56:59 2023 +0700
+++ b/src/semicongine/thing.nim	Sat Feb 04 02:24:15 2023 +0700
@@ -1,17 +1,15 @@
 import std/strformat
 import std/typetraits
 
-import ./vertex
 import ./math/matrix
 
 type
   Part* = ref object of RootObj
-    thing: Thing
-  Transform* = ref object of Part
-    mat: Mat44
+    thing*: Thing
 
   Thing* = ref object of RootObj
     name*: string
+    transform*: Mat44 # todo: cache transform + only update VBO when transform changed
     parent*: Thing
     children*: seq[Thing]
     parts*: seq[Part]
@@ -19,11 +17,6 @@
 
 func `$`*(thing: Thing): string = thing.name
 method `$`*(part: Part): string {.base.} = &"{part.thing} -> Part"
-method `$`*(part: Transform): string = &"{part.thing} -> Transform"
-
-func newTransform*(mat: Mat44): Transform =
-  result = new Transform
-  result.mat = mat
 
 proc add*(thing: Thing, child: Thing) =
   child.parent = thing
@@ -43,6 +36,7 @@
 func newThing*(name: string = ""): Thing =
   result = new Thing
   result.name = name
+  result.transform = Unit44
   if result.name == "":
     result.name = &"Thing[{$(cast[ByteAddress](result))}]"
 func newThing*(name: string, firstChild: Thing, children: varargs[
@@ -52,6 +46,7 @@
   for child in children:
     result.add child
   result.name = name
+  result.transform = Unit44
   if result.name == "":
     result.name = &"Thing[{$(cast[ByteAddress](result))}]"
 proc newThing*(name: string, firstPart: Part, parts: varargs[Part]): Thing =
@@ -62,14 +57,13 @@
     result.add part
   if result.name == "":
     result.name = &"Thing[{$(cast[ByteAddress](result))}]"
+  result.transform = Unit44
 
 func getModelTransform*(thing: Thing): Mat44 =
   result = Unit44
   var currentThing = thing
   while currentThing != nil:
-    for part in currentThing.parts:
-      if part of Transform:
-        result = Transform(part).mat * result
+    result = currentThing.transform * result
     currentThing = currentThing.parent
 
 iterator allPartsOfType*[T: Part](root: Thing): T =
@@ -79,8 +73,8 @@
     for part in thing.parts:
       if part of T:
         yield T(part)
-    for child in thing.children:
-      queue.insert(child, 0)
+    for i in countdown(thing.children.len - 1, 0):
+      queue.add thing.children[i]
 
 func firstWithName*(root: Thing, name: string): Thing =
   var queue = @[root]
@@ -129,6 +123,3 @@
     for child in next.children:
       queue.add child
     yield next
-
-method update*(thing: Thing, dt: float32) {.base.} = discard
-method update*(part: Part, dt: float32) {.base.} = discard