changeset 1254:b0f4c8ccd49a

did: stuff to test gltf importer
author sam <sam@basx.dev>
date Sat, 27 Jul 2024 20:47:54 +0700
parents c4f98eb4bb05
children 2b5ca798f6d6
files semiconginev2/core/matrix.nim semiconginev2/core/vector.nim semiconginev2/gltf.nim semiconginev2/input.nim semiconginev2/rendering/platform/linux.nim semiconginev2/rendering/platform/windows.nim semiconginev2/rendering/renderer.nim tests/test_gltf.nim
diffstat 8 files changed, 236 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- a/semiconginev2/core/matrix.nim	Fri Jul 26 23:39:24 2024 +0700
+++ b/semiconginev2/core/matrix.nim	Sat Jul 27 20:47:54 2024 +0700
@@ -23,6 +23,7 @@
   TMat4*[T: SomeNumber] = object
     data*: array[16, T]
   TMat* = TMat2|TMat3|TMat4|TMat23|TMat32|TMat34|TMat43
+  TSquareMat = TMat2|TMat3|TMat4
   Mat2* = TMat2[float32]
   Mat23* = TMat23[float32]
   Mat32* = TMat32[float32]
@@ -312,6 +313,15 @@
 
 createAllMultiplicationOperators()
 
+proc `+=`*[T1: TSquareMat, T2: TSquareMat|SomeNumber](a: var T1, b: T2) =
+  a = a + b
+
+proc `-=`*[T1: TSquareMat, T2: TSquareMat|SomeNumber](a: var T1, b: T2) =
+  a = a + b
+
+proc `*=`*[T1: TSquareMat, T2: TSquareMat|SomeNumber](a: var T1, b: T2) =
+  a = a * b
+
 func `*`*(mat: Mat4, vec: Vec3f): Vec3f =
   (mat * vec.ToVec4(1)).ToVec3
 
--- a/semiconginev2/core/vector.nim	Fri Jul 26 23:39:24 2024 +0700
+++ b/semiconginev2/core/vector.nim	Sat Jul 27 20:47:54 2024 +0700
@@ -40,9 +40,9 @@
 func ConstX[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(1), T(0), T(0)])
 func ConstY[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(0), T(1), T(0)])
 func ConstZ[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(0), T(0), T(1)])
-func ConstR[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(1), T(0), T(0)])
-func ConstG[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(0), T(1), T(0)])
-func ConstB[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(0), T(0), T(1)])
+func ConstR[T: SomeNumber](): auto {.compiletime.} = TVec4[T]([T(1), T(0), T(0), T(1)])
+func ConstG[T: SomeNumber](): auto {.compiletime.} = TVec4[T]([T(0), T(1), T(0), T(1)])
+func ConstB[T: SomeNumber](): auto {.compiletime.} = TVec4[T]([T(0), T(0), T(1), T(1)])
 
 func NewVec2f*(x = 0'f32, y = 0'f32): auto =
   Vec2f([x, y])
@@ -87,6 +87,9 @@
 const X* = ConstX[float32]()
 const Y* = ConstY[float32]()
 const Z* = ConstZ[float32]()
+const R* = ConstR[float32]()
+const G* = ConstG[float32]()
+const B* = ConstB[float32]()
 const One1* = ConstOne1[float32]()
 const One2* = ConstOne2[float32]()
 const One3* = ConstOne3[float32]()
--- a/semiconginev2/gltf.nim	Fri Jul 26 23:39:24 2024 +0700
+++ b/semiconginev2/gltf.nim	Sat Jul 27 20:47:54 2024 +0700
@@ -74,7 +74,8 @@
     6: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN,
   ]
 
-proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8], baseBufferOffset = 0): seq[uint8] =
+proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8],
+    baseBufferOffset = 0): seq[uint8] =
   assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported"
 
   result = newSeq[uint8](bufferView["byteLength"].getInt())
@@ -93,12 +94,14 @@
   elif t is uint32: return 5125
   elif t is float32: return 5126
 
-proc getAccessorData[T](root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): seq[T] =
+proc getAccessorData[T](root: JsonNode, accessor: JsonNode, mainBuffer: seq[
+    uint8]): seq[T] =
   let componentType = accessor["componentType"].getInt()
   let itemType = accessor["type"].getStr()
 
   when T is TVec or T is TMat:
-    assert componentTypeId(elementType(default(T))) == componentType, name(T) & " != " & $componentType
+    assert componentTypeId(elementType(default(T))) == componentType, name(T) &
+        " != " & $componentType
   else:
     assert componentTypeId(T) == componentType, name(T) & " != " & $componentType
 
@@ -121,7 +124,8 @@
   if accessor.hasKey("sparse"):
     raise newException(Exception, "Sparce accessors are currently not supported")
 
-  let accessorOffset = if accessor.hasKey("byteOffset"): accessor["byteOffset"].getInt() else: 0
+  let accessorOffset = if accessor.hasKey("byteOffset"): accessor[
+      "byteOffset"].getInt() else: 0
   let length = bufferView["byteLength"].getInt()
   let bufferOffset = bufferView["byteOffset"].getInt() + accessorOffset
   var dstPointer = result.ToCPointer()
@@ -130,12 +134,14 @@
     warn "Congratulations, you try to test a feature (loading buffer data with stride attributes) that we have no idea where it is used and how it can be tested (need a coresponding *.glb file)."
     # we don't support stride, have to convert stuff here... does this even work?
     for i in 0 ..< result.len:
-      copyMem(dstPointer, addr mainBuffer[bufferOffset + i * bufferView["byteStride"].getInt()], sizeof(T))
+      copyMem(dstPointer, addr mainBuffer[bufferOffset + i * bufferView[
+          "byteStride"].getInt()], sizeof(T))
       dstPointer = cast[typeof(dstPointer)](cast[uint](dstPointer) + sizeof(T).uint)
   else:
     copyMem(dstPointer, addr mainBuffer[bufferOffset], length)
 
-proc loadTexture(root: JsonNode, textureNode: JsonNode, mainBuffer: seq[uint8]): Image[BGRA] =
+proc loadTexture(root: JsonNode, textureNode: JsonNode, mainBuffer: seq[
+    uint8]): Image[BGRA] =
 
   let imageIndex = textureNode["source"].getInt()
 
@@ -144,22 +150,26 @@
   let imageType = root["images"][imageIndex]["mimeType"].getStr()
   assert imageType == "image/png", "glTF loader currently only supports PNG"
 
-  let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()]
+  let bufferView = root["bufferViews"][root["images"][imageIndex][
+      "bufferView"].getInt()]
   result = LoadImageData[BGRA](getBufferViewData(bufferView, mainBuffer))
 
   if textureNode.hasKey("sampler"):
     let sampler = root["samplers"][textureNode["sampler"].getInt()]
     if sampler.hasKey("magFilter"):
-      result.magInterpolation = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()]
+      result.magInterpolation = SAMPLER_FILTER_MODE_MAP[sampler[
+          "magFilter"].getInt()]
     if sampler.hasKey("minFilter"):
-      result.minInterpolation = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()]
+      result.minInterpolation = SAMPLER_FILTER_MODE_MAP[sampler[
+          "minFilter"].getInt()]
     if sampler.hasKey("wrapS"):
       result.wrapU = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
     if sampler.hasKey("wrapT"):
       result.wrapV = SAMPLER_WRAP_MODE_MAP[sampler["wrapT"].getInt()]
 
 proc getVec4f(node: JsonNode): Vec4f =
-  NewVec4f(node[0].getFloat(), node[1].getFloat(), node[2].getFloat(), node[3].getFloat())
+  NewVec4f(node[0].getFloat(), node[1].getFloat(), node[2].getFloat(), node[
+      3].getFloat())
 
 proc loadMaterial[TMaterial](
   root: JsonNode,
@@ -204,14 +214,17 @@
           when gltfAttribute == "indices":
             if primitive.hasKey(gltfAttribute):
               let accessor = primitive[gltfAttribute].getInt()
-              resultValue.data = getAccessorData[elementType(resultValue.data)](root, root["accessors"][accessor], mainBuffer)
+              resultValue.data = getAccessorData[elementType(resultValue.data)](
+                  root, root["accessors"][accessor], mainBuffer)
           elif gltfAttribute == "material":
             if primitive.hasKey(gltfAttribute):
-              resultValue.data = typeof(resultValue.data)(primitive[gltfAttribute].getInt())
+              resultValue.data = typeof(resultValue.data)(primitive[
+                  gltfAttribute].getInt())
           else:
             if primitive["attributes"].hasKey(gltfAttribute):
               let accessor = primitive["attributes"][gltfAttribute].getInt()
-              resultValue.data = getAccessorData[elementType(resultValue.data)](root, root["accessors"][accessor], mainBuffer)
+              resultValue.data = getAccessorData[elementType(resultValue.data)](
+                  root, root["accessors"][accessor], mainBuffer)
       else:
         var i = 0
         for mappedIndexName in mappedName:
@@ -219,8 +232,10 @@
             assert resultValue is GPUData, "Attribute " & resultFieldName & " must be of type GPUData"
             let gltfAttributeIndexed = gltfAttribute & "_" & $i
             if primitive["attributes"].hasKey(gltfAttributeIndexed):
-              let accessor = primitive["attributes"][gltfAttributeIndexed].getInt()
-              resultValue.data = getAccessorData[elementType(resultValue.data)](root, root["accessors"][accessor], mainBuffer)
+              let accessor = primitive["attributes"][
+                  gltfAttributeIndexed].getInt()
+              resultValue.data = getAccessorData[elementType(resultValue.data)](
+                  root, root["accessors"][accessor], mainBuffer)
           inc i
 
 proc loadNode(node: JsonNode): GltfNode =
@@ -282,30 +297,35 @@
   chunkLength = stream.readUint32()
   assert stream.readUint32() == BINARY_CHUNK
   data.binaryBufferData.setLen(chunkLength)
-  assert stream.readData(addr data.binaryBufferData[0], int(chunkLength)) == int(chunkLength)
+  assert stream.readData(addr data.binaryBufferData[0], int(chunkLength)) ==
+      int(chunkLength)
 
   # check that the refered buffer is the same as the binary chunk
   # external binary buffers are not supported
   assert data.structuredContent["buffers"].len == 1
   assert not data.structuredContent["buffers"][0].hasKey("uri")
-  let bufferLenDiff = int(chunkLength) - data.structuredContent["buffers"][0]["byteLength"].getInt()
+  let bufferLenDiff = int(chunkLength) - data.structuredContent["buffers"][0][
+      "byteLength"].getInt()
   assert 0 <= bufferLenDiff and bufferLenDiff <= 3 # binary buffer may be aligned to 4 bytes
 
   debug "Loading mesh: ", data.structuredContent.pretty
 
   if "materials" in data.structuredContent:
     for materialnode in items(data.structuredContent["materials"]):
-      result.materials.add loadMaterial[TMaterial](data.structuredContent, materialnode, materialAttributesMapping, data.binaryBufferData)
+      result.materials.add loadMaterial[TMaterial](data.structuredContent,
+          materialnode, materialAttributesMapping, data.binaryBufferData)
 
   if "textures" in data.structuredContent:
     for texturenode in items(data.structuredContent["textures"]):
-      result.textures.add loadTexture(data.structuredContent, texturenode, data.binaryBufferData)
+      result.textures.add loadTexture(data.structuredContent, texturenode,
+          data.binaryBufferData)
 
   if "meshes" in data.structuredContent:
     for mesh in items(data.structuredContent["meshes"]):
       var primitives: seq[(TMesh, VkPrimitiveTopology)]
       for primitive in items(mesh["primitives"]):
-        primitives.add loadPrimitive[TMesh](data.structuredContent, primitive, meshAttributesMapping, data.binaryBufferData)
+        primitives.add loadPrimitive[TMesh](data.structuredContent, primitive,
+            meshAttributesMapping, data.binaryBufferData)
       result.meshes.add primitives
 
   if "nodes" in data.structuredContent:
--- a/semiconginev2/input.nim	Fri Jul 26 23:39:24 2024 +0700
+++ b/semiconginev2/input.nim	Sat Jul 27 20:47:54 2024 +0700
@@ -11,6 +11,7 @@
     mouseWheel: float32
     windowWasResized: bool = true
     windowIsMinimized: bool = false
+    lockMouse: bool = false
 
 # warning, shit is not thread safe
 var input: Input
@@ -25,6 +26,9 @@
   input.mouseMove = NewVec2f()
   input.windowWasResized = false
 
+  if input.lockMouse:
+    SetMousePosition(vulkan.window, x=int(vulkan.swapchain.width div 2), y=int(vulkan.swapchain.height div 2))
+
   var killed = false
   for event in vulkan.window.PendingEvents():
     case event.eventType:
@@ -72,10 +76,12 @@
 proc MousePositionNormalized*(size: (int, int)): Vec2f =
   result.x = (input.mousePosition.x / float32(size[0])) * 2.0 - 1.0
   result.y = (input.mousePosition.y / float32(size[1])) * 2.0 - 1.0
-proc MouseMove*(): auto = input.mouseMove
-proc MouseWheel*(): auto = input.mouseWheel
+proc MouseMove*(): Vec2f = input.mouseMove
+proc MouseWheel*(): float32 = input.mouseWheel
 proc WindowWasResized*(): auto = input.windowWasResized
 proc WindowIsMinimized*(): auto = input.windowIsMinimized
+proc LockMouse*(value: bool) = input.lockMouse = value
+
 
 # actions as a slight abstraction over raw input
 
--- a/semiconginev2/rendering/platform/linux.nim	Fri Jul 26 23:39:24 2024 +0700
+++ b/semiconginev2/rendering/platform/linux.nim	Sat Jul 27 20:47:54 2024 +0700
@@ -204,6 +204,16 @@
   if onscreen != 0:
     result = some(Vec2f([float32(winX), float32(winY)]))
 
+proc SetMousePosition*(window: NativeWindow, x, y: int) =
+  checkXlibResult XWarpPointer(
+    window.display,
+    default(x11.Window),
+    window.window,
+    0, 0, 0, 0,
+    x.cint,
+    y.cint,
+  )
+  checkXlibResult XSync(window.display, false.XBool)
 
 proc CreateNativeSurface(instance: VkInstance, window: NativeWindow): VkSurfaceKHR =
   var surfaceCreateInfo = VkXlibSurfaceCreateInfoKHR(
--- a/semiconginev2/rendering/platform/windows.nim	Fri Jul 26 23:39:24 2024 +0700
+++ b/semiconginev2/rendering/platform/windows.nim	Sat Jul 27 20:47:54 2024 +0700
@@ -193,6 +193,8 @@
     return some(Vec2f([float32(p.x), float32(p.y)]))
   return none(Vec2f)
 
+proc SetMousePosition*(window: NativeWindow, x, y: int) =
+  CheckWin32Result SetCursorPos(x, y)
 
 proc CreateNativeSurface*(instance: VkInstance, window: NativeWindow): VkSurfaceKHR =
   assert instance.Valid
--- a/semiconginev2/rendering/renderer.nim	Fri Jul 26 23:39:24 2024 +0700
+++ b/semiconginev2/rendering/renderer.nim	Sat Jul 27 20:47:54 2024 +0700
@@ -533,51 +533,97 @@
         for image in value.mitems:
           renderdata.createVulkanImage(image)
 
-proc HasGPUValueField[T](name: static string): bool {.compileTime.} =
-  for fieldname, value in default(T).fieldPairs():
-    when typeof(value) is GPUValue and fieldname == name: return true
-  return false
+type EMPTY = object # only used for static assertions
+
+proc assertAllDescriptorsBound(A, B, C, D, TShader: typedesc) =
+  var foundDescriptorSets = false
+  for attrName, attrValue in default(TShader).fieldPairs():
+    when hasCustomPragma(attrValue, DescriptorSets):
+      assert not foundDescriptorSets, "Only one shader attribute is allowed to have the pragma 'DescriptorSets'"
+      when not (A is EMPTY): assert typeof(attrValue[0]) is A
+      when not (B is EMPTY): assert typeof(attrValue[1]) is B
+      when not (C is EMPTY): assert typeof(attrValue[2]) is C
+      when not (D is EMPTY): assert typeof(attrValue[3]) is D
+
+var hasBoundDescriptorSets {.compileTime.} = false # okay, I am not sure if this is clean, unproblematic or sane. Just trying to get some comptime-validation
+var hasDescriptorSets {.compileTime} = false
 
-template WithGPUValueField(obj: object, name: static string, fieldvalue, body: untyped): untyped =
-  # HasGPUValueField MUST be used to check if this is supported
-  for fieldname, value in obj.fieldPairs():
-    when fieldname == name:
-      block:
-        let `fieldvalue` {.inject.} = value
-        body
-
-template WithBind*[A, B, C, D](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B], DescriptorSet[C], DescriptorSet[D]), pipeline: Pipeline, body: untyped): untyped =
+template WithBind*[A, B, C, D, TShader](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B], DescriptorSet[C], DescriptorSet[D]), pipeline: Pipeline[TShader], body: untyped): untyped =
+  static: assertAllDescriptorsBound(A, B, C, D, TShader)
+  block:
+    var descriptorSets: seq[VkDescriptorSet]
+    for dSet in sets.fields:
+      assert dSet.vk[currentFiF()].Valid, "DescriptorSet not initialized, maybe forgot to call InitDescriptorSet"
+      descriptorSets.add dSet.vk[currentFiF()]
+    svkCmdBindDescriptorSets(commandBuffer, descriptorSets, pipeline.layout)
+    static:
+      assert not hasBoundDescriptorSets, "Cannot call WithBind nested"
+      hasBoundDescriptorSets = true
+    body
+    static:
+      hasBoundDescriptorSets = false
+template WithBind*[A, B, C, TShader](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B], DescriptorSet[C]), pipeline: Pipeline[TShader], body: untyped): untyped =
+  static: assertAllDescriptorsBound(A, B, C, EMPTY, TShader)
   block:
     var descriptorSets: seq[VkDescriptorSet]
     for dSet in sets.fields:
       assert dSet.vk[currentFiF()].Valid, "DescriptorSet not initialized, maybe forgot to call InitDescriptorSet"
       descriptorSets.add dSet.vk[currentFiF()]
     svkCmdBindDescriptorSets(commandBuffer, descriptorSets, pipeline.layout)
+    static:
+      assert not hasBoundDescriptorSets, "Cannot call WithBind nested"
+      hasBoundDescriptorSets = true
     body
-template WithBind*[A, B, C](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B], DescriptorSet[C]), pipeline: Pipeline, body: untyped): untyped =
+    static:
+      hasBoundDescriptorSets = false
+template WithBind*[A, B, TShader](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B]), pipeline: Pipeline[TShader], body: untyped): untyped =
+  static: assertAllDescriptorsBound(A, B, EMPTY, EMPTY, TShader)
   block:
     var descriptorSets: seq[VkDescriptorSet]
     for dSet in sets.fields:
       assert dSet.vk[currentFiF()].Valid, "DescriptorSet not initialized, maybe forgot to call InitDescriptorSet"
       descriptorSets.add dSet.vk[currentFiF()]
     svkCmdBindDescriptorSets(commandBuffer, descriptorSets, pipeline.layout)
+    static:
+      assert not hasBoundDescriptorSets, "Cannot call WithBind nested"
+      hasBoundDescriptorSets = true
     body
-template WithBind*[A, B](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B]), pipeline: Pipeline, body: untyped): untyped =
+    static:
+      hasBoundDescriptorSets = false
+template WithBind*[A, TShader](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], ), pipeline: Pipeline[TShader], body: untyped): untyped =
+  static: assertAllDescriptorsBound(A, EMPTY, EMPTY, EMPTY, TShader)
   block:
     var descriptorSets: seq[VkDescriptorSet]
     for dSet in sets.fields:
       assert dSet.vk[currentFiF()].Valid, "DescriptorSet not initialized, maybe forgot to call InitDescriptorSet"
       descriptorSets.add dSet.vk[currentFiF()]
     svkCmdBindDescriptorSets(commandBuffer, descriptorSets, pipeline.layout)
+    static:
+      assert not hasBoundDescriptorSets, "Cannot call WithBind nested"
+      hasBoundDescriptorSets = true
     body
-template WithBind*[A](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], ), pipeline: Pipeline, body: untyped): untyped =
-  block:
-    var descriptorSets: seq[VkDescriptorSet]
-    for dSet in sets.fields:
-      assert dSet.vk[currentFiF()].Valid, "DescriptorSet not initialized, maybe forgot to call InitDescriptorSet"
-      descriptorSets.add dSet.vk[currentFiF()]
-    svkCmdBindDescriptorSets(commandBuffer, descriptorSets, pipeline.layout)
-    body
+    static:
+      hasBoundDescriptorSets = false
+
+proc assertCanRenderMesh(TShader, TMesh, TInstance: typedesc) =
+  for attrName, attrValue in default(TShader).fieldPairs:
+    if hasCustomPragma(attrValue, VertexAttribute):
+      var foundAttr = false
+      for meshAttrName, meshAttrValue in default(TMesh).fieldPairs:
+        if attrName == meshAttrName:
+          assert typeof(meshAttrValue) is GPUArray, "Mesh attribute '" & attrName & "' must be a GPUArray"
+          assert typeof(attrValue) is elementType(meshAttrValue.data), "Type of shader attribute and mesh attribute '" & attrName & "' is not the same"
+          foundAttr = true
+      assert foundAttr, "Attribute '" & attrName & "' is not provided in mesh type '" & name(TMesh) & "'"
+    if hasCustomPragma(attrValue, InstanceAttribute):
+      var foundAttr = false
+      for instAttrName, instAttrValue in default(TInstance).fieldPairs:
+        if attrName == instAttrName:
+          assert typeof(instAttrValue) is GPUArray, "Instance attribute '" & attrName & "' must be a GPUArray"
+          assert foundAttr == false, "Attribute '" & attrName & "' is defined in Mesh and Instance, can only be one"
+          assert typeof(attrValue) is elementType(instAttrValue.data), "Type of shader attribute and mesh attribute '" & attrName & "' is not the same"
+          foundAttr = true
+      assert foundAttr, "Attribute '" & attrName & "' is not provided in instance type '" & name(TInstance) & "'"
 
 proc Render*[TShader, TMesh, TInstance](
   commandBuffer: VkCommandBuffer,
@@ -586,6 +632,15 @@
   instances: TInstance,
 ) =
 
+  static: assertCanRenderMesh(TShader, TMesh, TInstance)
+  static:
+    hasDescriptorSets = false
+    for attrName, attrValue in default(TShader).fieldPairs():
+      if attrValue.hasCustomPragma(DescriptorSets):
+        hasDescriptorSets = true
+    if hasDescriptorSets:
+      assert hasBoundDescriptorSets, "Shader uses descriptor sets, but none are bound"
+
   var vertexBuffers: seq[VkBuffer]
   var vertexBuffersOffsets: seq[uint64]
   var elementCount = 0'u32
@@ -661,8 +716,6 @@
       firstInstance = 0
     )
 
-type EMPTY = object
-
 proc Render*[TShader, TMesh](
   commandBuffer: VkCommandBuffer,
   pipeline: Pipeline[TShader],
--- a/tests/test_gltf.nim	Fri Jul 26 23:39:24 2024 +0700
+++ b/tests/test_gltf.nim	Sat Jul 27 20:47:54 2024 +0700
@@ -14,8 +14,7 @@
     ObjectData = object
       transform: Mat4
     Camera = object
-      view: Mat4
-      perspective: Mat4
+      viewPerspective: Mat4
     Material = object
       color: Vec4f = NewVec4f(1, 1, 1, 1)
       colorTexture: int32 = -1
@@ -33,7 +32,8 @@
     Shader = object
       objectData {.PushConstantAttribute.}: ObjectData
       position {.VertexAttribute.}: Vec3f
-      uv {.VertexAttribute.}: Vec2f
+      color {.VertexAttribute.}: Vec4f
+      # uv {.VertexAttribute.}: Vec2f
       fragmentColor {.Pass.}: Vec4f
       fragmentUv {.Pass.}: Vec2f
       outColor {.ShaderOutput.}: Vec4f
@@ -41,21 +41,26 @@
       # code
       vertexCode: string = """
 void main() {
-  fragmentColor = vec4(1, 1, 1, 1);
-  fragmentUv = uv;
-  gl_Position = vec4(position, 1) * camera.perspective * camera.view;
+  fragmentColor = color;
+  // fragmentUv = uv;
+  gl_Position = vec4(position, 1) * camera.viewPerspective;
 }"""
       fragmentCode: string = """void main() { outColor = fragmentColor;}"""
     Mesh = object
       position: GPUArray[Vec3f, VertexBuffer]
+      color: GPUArray[Vec4f, VertexBuffer]
       uv: GPUArray[Vec2f, VertexBuffer]
+    DebugMesh = object
+      position: GPUArray[Vec3f, VertexBuffer]
+      color: GPUArray[Vec4f, VertexBuffer]
 
   var gltfData = LoadMeshes[Mesh, Material](
     "town.glb",
     MeshAttributeNames(
       POSITION: "position",
-    TEXCOORD: @["uv"],
-  ),
+      COLOR: @["color"],
+      TEXCOORD: @["uv"],
+    ),
     MaterialAttributeNames(
       baseColorFactor: "color",
       baseColorTexture: "colorTexture",
@@ -71,31 +76,86 @@
   var descriptors = asDescriptorSet(
     MainDescriptors(
       camera: asGPUValue(Camera(
-        view: Unit4,
-        perspective: Unit4,
-    ), UniformBufferMapped)
-  )
+        viewPerspective: Unit4,
+      ), UniformBufferMapped)
+    )
   )
   for mesh in mitems(gltfData.meshes):
     for primitive in mitems(mesh):
+      primitive[0].color = asGPUArray(newSeqWith(primitive[0].position.data.len, NewVec4f(1, 1, 1, 1)), VertexBuffer)
       renderdata.AssignBuffers(primitive[0])
   renderdata.AssignBuffers(descriptors)
 
-  var pipeline = CreatePipeline[Shader](renderPass = vulkan.swapchain.renderPass)
+  let O = default(Vec3f)
+  let Gray = NewVec4f(0.5, 0.5, 0.5, 1)
+  var gridPos = @[O, X, O, Y, O, Z]
+  var gridColor = @[R, R, G, G, B, B]
+  for i in 0 ..< 10:
+    gridPos.add [NewVec3f(-5, -0.001, i.float32 - 5), NewVec3f(5, -0.001, i.float32 - 5)]
+    gridPos.add [NewVec3f(i.float32 - 5, -0.001, -5), NewVec3f(i.float32 - 5, -0.001, 5)]
+    gridColor.add [Gray, Gray, Gray, Gray]
+  var grid = DebugMesh(
+    position: asGPUArray(gridPos, VertexBuffer),
+    color: asGPUArray(gridColor, VertexBuffer),
+  )
+  renderdata.AssignBuffers(grid)
+
+  var pipeline = CreatePipeline[Shader](renderPass = vulkan.swapchain.renderPass, cullMode = [])
+  var debugpipeline = CreatePipeline[Shader](renderPass = vulkan.swapchain.renderPass, topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST, lineWidth=10)
   InitDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], descriptors)
+
   renderdata.FlushAllMemory()
 
-  proc drawNode(commandbuffer: VkCommandBuffer, pipeline: Pipeline, nodeId: int, transform: Mat4 = Unit4) =
+  proc drawNode(commandbuffer: VkCommandBuffer, pipeline: Pipeline, nodeId: int,
+      transform: Mat4 = Unit4) =
     let nodeTransform = gltfData.nodes[nodeId].transform * transform
     if gltfData.nodes[nodeId].mesh >= 0:
       for primitive in gltfData.meshes[gltfData.nodes[nodeId].mesh]:
-        RenderWithPushConstant(commandbuffer = commandbuffer, pipeline = pipeline, mesh = primitive[0], pushConstant = ObjectData(transform: nodeTransform))
+        RenderWithPushConstant(
+          commandbuffer = commandbuffer,
+          pipeline = pipeline,
+          mesh = primitive[0],
+          pushConstant = ObjectData(transform: nodeTransform)
+        )
     for childNode in gltfData.nodes[nodeId].children:
       drawNode(commandbuffer = commandbuffer, pipeline = pipeline, nodeId = childNode, transform = nodeTransform)
 
+  var camPos: Vec3f
+  var camYaw: float32
+  var camPitch: float32
+
+  discard UpdateInputs() # clear inputs, otherwise MouseMove will have stuff
 
   var start = getMonoTime()
-  while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
+  var lastT = getMonoTime()
+  while ((getMonoTime() - start).inMilliseconds().int / 1000) < time and UpdateInputs():
+    let dt = ((getMonoTime() - lastT).inNanoseconds().int / 1_000_000_000).float32
+    lastT = getMonoTime()
+
+    camYaw  -= MouseMove().x / 1000
+    camPitch -= MouseMove().y / 1000
+    var
+      forward = 0'f32
+      sideward = 0'f32
+    if KeyIsDown(W): forward += 2
+    if KeyIsDown(S): forward -= 2
+    if KeyIsDown(A): sideward -= 2
+    if KeyIsDown(D): sideward += 2
+
+    let camDir = (Rotate(camPitch, X) * Rotate(camYaw, Y)) * Z
+    echo camDir
+    let camDirSide = (Rotate(camPitch, X) * Rotate(camYaw, Y)) * X
+    # echo camDir
+    # echo camDirSide
+    camPos += camDir * forward * dt
+    camPos += camDirSide * sideward * dt
+
+    descriptors.data.camera.data.viewPerspective = (
+      Perspective(PI/3, aspect = GetAspectRatio(), zNear = 0.001, zFar = 100) *
+      Rotate(-camPitch, X) * Rotate(-camYaw, Y) * Translate(-camPos)
+    )
+
+    UpdateGPUBuffer(descriptors.data.camera)
 
     WithNextFrame(framebuffer, commandbuffer):
 
@@ -105,17 +165,22 @@
           WithBind(commandbuffer, (descriptors, ), pipeline):
             for nodeId in gltfData.scenes[0]:
               drawNode(commandbuffer = commandbuffer, pipeline = pipeline, nodeId = nodeId)
+        WithBind(commandbuffer, (descriptors, ), pipeline):
+          WithPipeline(commandbuffer, debugpipeline):
+            Render(commandbuffer = commandbuffer, pipeline = debugpipeline, mesh = grid)
 
   # cleanup
   checkVkResult vkDeviceWaitIdle(vulkan.device)
   DestroyPipeline(pipeline)
+  DestroyPipeline(debugpipeline)
   DestroyRenderData(renderdata)
 when isMainModule:
-  var time = 5'f32
+  var time = 1000'f32
   InitVulkan()
 
   var renderpass = CreateDirectPresentationRenderPass(depthBuffer = true, samples = VK_SAMPLE_COUNT_4_BIT)
   SetupSwapchain(renderpass = renderpass)
+  LockMouse(true)
 
   # tests a simple triangle with minimalistic shader and vertex format
   test_gltf(time)