changeset 1377:aaf8fa2c7bb2

merge
author sam <sam@basx.dev>
date Sun, 08 Dec 2024 08:21:27 +0700
parents cd2455307a6c (current diff) ca3299ea1bdf (diff)
children 31f57a969ffd
files
diffstat 10 files changed, 348 insertions(+), 107 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/contrib/algorithms/texture_packing.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/semicongine/contrib/algorithms/texture_packing.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -84,10 +84,10 @@
     for y in 0 ..< rect.h:
       for x in 0 ..< rect.w:
         when T is Gray:
-          assert result.atlas[rect.x + x, rect.y + y] == [0'u8],
+          assert result.atlas[rect.x + x, rect.y + y] == default(T),
             "Atlas texture packing encountered an overlap error"
         elif T is BGRA:
-          assert result.atlas[rect.x + x, rect.y + y] == [0'u8, 0'u8, 0'u8, 0'u8],
+          assert result.atlas[rect.x + x, rect.y + y] == default(T),
             "Atlas texture packing encountered an overlap error"
         else:
           {.error: "Unsupported type for texture packing".}
--- a/semicongine/core.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/semicongine/core.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -1,3 +1,4 @@
+import std/hashes
 import std/macros
 import std/math
 import std/monotimes
--- a/semicongine/core/vector.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/semicongine/core/vector.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -1,8 +1,8 @@
 type
-  TVec1*[T: SomeNumber] = array[1, T]
-  TVec2*[T: SomeNumber] = array[2, T]
-  TVec3*[T: SomeNumber] = array[3, T]
-  TVec4*[T: SomeNumber] = array[4, T]
+  TVec1*[T: SomeNumber] = distinct array[1, T]
+  TVec2*[T: SomeNumber] = distinct array[2, T]
+  TVec3*[T: SomeNumber] = distinct array[3, T]
+  TVec4*[T: SomeNumber] = distinct array[4, T]
   TVec* = TVec1 | TVec2 | TVec3 | TVec4
   Vec1f* = TVec1[float32]
   Vec2f* = TVec2[float32]
@@ -18,8 +18,116 @@
   Vec4u* = TVec4[uint32]
 
   # support for shorts
+  Vec1i8* = TVec1[int8]
+  Vec1u8* = TVec1[uint8]
   Vec2i8* = TVec2[int8]
+  Vec2u8* = TVec2[uint8]
   Vec3i8* = TVec3[int8]
+  Vec3u8* = TVec3[uint8]
+
+# stuff to allow working like an array, despite 'distinct'
+
+converter toArray*[T](v: TVec1[T]): array[1, T] =
+  array[1, T](v)
+
+converter toArray*[T](v: TVec2[T]): array[2, T] =
+  array[2, T](v)
+
+converter toArray*[T](v: TVec3[T]): array[3, T] =
+  array[3, T](v)
+
+converter toArray*[T](v: TVec4[T]): array[4, T] =
+  array[4, T](v)
+
+template `[]`*[T](v: TVec1[T], i: Ordinal): T =
+  (array[1, T](v))[i]
+
+template `[]`*[T](v: TVec2[T], i: Ordinal): T =
+  (array[2, T](v))[i]
+
+template `[]`*[T](v: TVec3[T], i: Ordinal): T =
+  (array[3, T](v))[i]
+
+template `[]`*[T](v: TVec4[T], i: Ordinal): T =
+  (array[4, T](v))[i]
+
+template `[]=`*[T](v: TVec1[T], i: Ordinal, a: T) =
+  (array[1, T](v))[i] = a
+
+template `[]=`*[T](v: TVec2[T], i: Ordinal, a: T) =
+  (array[2, T](v))[i] = a
+
+template `[]=`*[T](v: TVec3[T], i: Ordinal, a: T) =
+  (array[3, T](v))[i] = a
+
+template `[]=`*[T](v: TVec4[T], i: Ordinal, a: T) =
+  (array[4, T](v))[i] = a
+
+template `==`*[T](a, b: TVec1[T]): bool =
+  `==`(array[1, T](a), array[1, T](b))
+
+template `==`*[T](a, b: TVec2[T]): bool =
+  `==`(array[2, T](a), array[2, T](b))
+
+template `==`*[T](a, b: TVec3[T]): bool =
+  `==`(array[3, T](a), array[3, T](b))
+
+template `==`*[T](a, b: TVec4[T]): bool =
+  `==`(array[4, T](a), array[4, T](b))
+
+func len*(v: TVec1): int =
+  1
+func len*(v: TVec2): int =
+  2
+func len*(v: TVec3): int =
+  3
+func len*(v: TVec4): int =
+  4
+
+func `$`*[T](v: TVec1[T]): string =
+  `$`(array[1, T](v))
+func `$`*[T](v: TVec2[T]): string =
+  `$`(array[2, T](v))
+func `$`*[T](v: TVec3[T]): string =
+  `$`(array[3, T](v))
+func `$`*[T](v: TVec4[T]): string =
+  `$`(array[4, T](v))
+
+func sum*[T](v: TVec1[T]): T =
+  sum(array[1, T](v))
+func sum*[T](v: TVec2[T]): T =
+  sum(array[2, T](v))
+func sum*[T](v: TVec3[T]): T =
+  sum(array[3, T](v))
+func sum*[T](v: TVec4[T]): T =
+  sum(array[4, T](v))
+
+func hash*[T](v: TVec1[T]): Hash =
+  hash(array[1, T](v))
+func hash*[T](v: TVec2[T]): Hash =
+  hash(array[2, T](v))
+func hash*[T](v: TVec3[T]): Hash =
+  hash(array[3, T](v))
+func hash*[T](v: TVec4[T]): Hash =
+  hash(array[4, T](v))
+
+iterator items*[T](v: TVec1[T]): T =
+  yield v[0]
+
+iterator items*[T](v: TVec2[T]): T =
+  yield v[0]
+  yield v[1]
+
+iterator items*[T](v: TVec3[T]): T =
+  yield v[0]
+  yield v[1]
+  yield v[2]
+
+iterator items*[T](v: TVec4[T]): T =
+  yield v[0]
+  yield v[1]
+  yield v[2]
+  yield v[3]
 
 func toVec1*[T: SomeNumber](orig: TVec3[T] | TVec4[T]): TVec1[T] =
   TVec1[T]([orig[0]])
@@ -94,12 +202,23 @@
   vec4i(0, 0, 0, 0)
 
 # shortcuts Vec3i8
+func vec1i8*[T: SomeInteger](x: T): Vec1i8 =
+  Vec1i8([int8(x)])
+func vec1i8*(): Vec1i8 =
+  vec1i8(0)
+func vec1u8*[T: SomeInteger](x: T): Vec1u8 =
+  Vec1u8([uint8(x)])
+func vec1u8*(): Vec1u8 =
+  vec1u8(0)
+
+# missing: unsigned ones
 func vec2i8*[T, S: SomeInteger](x: T, y: S): Vec2i8 =
   Vec2i8([int8(x), int8(y)])
 func vec2i8*[T: SomeInteger](x: T): Vec2i8 =
   vec2i8(x, 0)
 func vec2i8*(): Vec2i8 =
   vec2i8(0, 0)
+
 func vec3i8*[T, S, U: SomeInteger](x: T, y: S, z: U): Vec3i8 =
   Vec3i8([int8(x), int8(y), int8(z)])
 func vec3i8*[T, S: SomeInteger](x: T, y: S): Vec3i8 =
--- a/semicongine/rendering.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/semicongine/rendering.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -131,6 +131,8 @@
     IndexBufferMapped
     UniformBuffer
     UniformBufferMapped
+    StorageBuffer
+    StorageBufferMapped
 
   MemoryBlock* = object
     vk: VkDeviceMemory
@@ -187,42 +189,58 @@
 proc `[]=`*[T, S](a: var GPUArray[T, S], i: SomeInteger, value: T) =
   a.data[i] = value
 
-template forDescriptorFields(
-    shader: typed, valuename, typename, countname, bindingNumber, body: untyped
-): untyped =
-  var `bindingNumber` {.inject.} = 0'u32
-  for theFieldname, `valuename` in fieldPairs(shader):
-    when typeof(`valuename`) is ImageObject:
-      block:
-        const `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
-        const `countname` {.inject.} = 1'u32
-        body
-        `bindingNumber`.inc
-    elif typeof(`valuename`) is GPUValue:
-      block:
-        const `typename` {.inject.} = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
-        const `countname` {.inject.} = 1'u32
-        body
-        `bindingNumber`.inc
-    elif typeof(`valuename`) is array:
-      when elementType(`valuename`) is ImageObject:
-        block:
-          const `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
-          const `countname` {.inject.} = uint32(typeof(`valuename`).len)
-          body
-          `bindingNumber`.inc
-      elif elementType(`valuename`) is GPUValue:
-        block:
-          const `typename` {.inject.} = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
-          const `countname` {.inject.} = len(`valuename`).uint32
-          body
-          `bindingNumber`.inc
+func getBufferType*[A, B](value: GPUValue[A, B]): BufferType {.compileTime.} =
+  B
+
+func getBufferType*[A, B](
+    value: openArray[GPUValue[A, B]]
+): BufferType {.compileTime.} =
+  B
+
+func getDescriptorType[T](): VkDescriptorType {.compileTIme.} =
+  when T is ImageObject:
+    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
+  elif T is GPUValue:
+    when getBufferType(default(T)) in [UniformBuffer, UniformBufferMapped]:
+      VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
+    elif getBufferType(default(T)) in [StorageBuffer, StorageBufferMapped]:
+      VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
+    else:
+      {.error: "Unsupported descriptor type: " & typetraits.name(T).}
+  elif T is array:
+    when elementType(default(T)) is ImageObject:
+      VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
+    elif elementType(default(T)) is GPUValue:
+      when getBufferType(default(elementType(default(T)))) in
+          [UniformBuffer, UniformBufferMapped]:
+        VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
+      elif getBufferType(default(elementType(default(T)))) in
+          [StorageBuffer, StorageBufferMapped]:
+        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
       else:
-        {.
-          error: "Unsupported descriptor type: " & typetraits.name(typeof(`valuename`))
-        .}
+        {.error: "Unsupported descriptor type: " & typetraits.name(T).}
     else:
-      {.error: "Unsupported descriptor type: " & typetraits.name(typeof(`valuename`)).}
+      {.error: "Unsupported descriptor type: " & typetraits.name(T).}
+  else:
+    {.error: "Unsupported descriptor type: " & typetraits.name(T).}
+
+func getDescriptorCount[T](): uint32 {.compileTIme.} =
+  when T is array:
+    len(T)
+  else:
+    1
+
+func getBindingNumber[T](field: static string): uint32 {.compileTime.} =
+  var c = 0'u32
+  var found = false
+  for name, value in fieldPairs(default(T)):
+    when name == field:
+      result = c
+      found = true
+    else:
+      inc c
+  assert found,
+    "Field '" & field & "' of descriptor '" & typetraits.name(T) & "' not found"
 
 proc currentFiF*(): int =
   assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
@@ -385,7 +403,8 @@
     initSwapchain(renderPass, vSync = vSync, tripleBuffering = tripleBuffering)
 
 proc destroyVulkan*() =
-  clearSwapchain()
+  if vulkan.swapchain != nil:
+    clearSwapchain()
   vkDestroyDevice(vulkan.device, nil)
   vkDestroySurfaceKHR(vulkan.instance, vulkan.surface, nil)
   if vulkan.debugMessenger.Valid:
--- a/semicongine/rendering/renderer.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/semicongine/rendering/renderer.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -1,20 +1,16 @@
 func pointerAddOffset[T: SomeInteger](p: pointer, offset: T): pointer =
   cast[pointer](cast[T](p) + offset)
 
-func usage(bType: BufferType): seq[VkBufferUsageFlagBits] =
-  case bType
-  of VertexBuffer:
-    @[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
-  of VertexBufferMapped:
-    @[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT]
-  of IndexBuffer:
-    @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
-  of IndexBufferMapped:
-    @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT]
-  of UniformBuffer:
-    @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
-  of UniformBufferMapped:
-    @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT]
+const BUFFER_USAGE: array[BufferType, seq[VkBufferUsageFlagBits]] = [
+  VertexBuffer: @[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
+  VertexBufferMapped: @[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT],
+  IndexBuffer: @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
+  IndexBufferMapped: @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT],
+  UniformBuffer: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
+  UniformBufferMapped: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
+  StorageBuffer: @[VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT],
+  StorageBufferMapped: @[VK_BUFFER_USAGE_STORAGE_BUFFER_BIT],
+]
 
 proc getVkFormat(grayscale: bool, usage: openArray[VkImageUsageFlagBits]): VkFormat =
   let formats =
@@ -56,7 +52,8 @@
   typeof(gpuData).TBuffer
 
 func needsMapping(bType: BufferType): bool =
-  bType in [VertexBufferMapped, IndexBufferMapped, UniformBufferMapped]
+  bType in
+    [VertexBufferMapped, IndexBufferMapped, UniformBufferMapped, StorageBufferMapped]
 template needsMapping(gpuData: GPUData): untyped =
   gpuData.bufferType.needsMapping
 
@@ -127,16 +124,17 @@
   var imageWrites = newSeqOfCap[VkDescriptorImageInfo](1024)
   var bufferWrites = newSeqOfCap[VkDescriptorBufferInfo](1024)
 
-  forDescriptorFields(
-    descriptorSet.data, fieldValue, descriptorType, descriptorCount,
-    descriptorBindingNumber,
-  ):
+  for theFieldname, fieldvalue in fieldPairs(descriptorSet.data):
+    const descriptorType = getDescriptorType[typeof(fieldvalue)]()
+    const descriptorCount = getDescriptorCount[typeof(fieldvalue)]()
+    const descriptorBindingNumber =
+      getBindingNumber[typeof(descriptorSet.data)](theFieldname)
     for i in 0 ..< descriptorSet.vk.len:
-      when typeof(fieldValue) is GPUValue:
+      when typeof(fieldvalue) is GPUValue:
         bufferWrites.add VkDescriptorBufferInfo(
-          buffer: fieldValue.buffer.vk,
-          offset: fieldValue.offset,
-          range: fieldValue.size,
+          buffer: fieldvalue.buffer.vk,
+          offset: fieldvalue.offset,
+          range: fieldvalue.size,
         )
         descriptorSetWrites.add VkWriteDescriptorSet(
           sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
@@ -148,10 +146,10 @@
           pImageInfo: nil,
           pBufferInfo: addr(bufferWrites[^1]),
         )
-      elif typeof(fieldValue) is ImageObject:
+      elif typeof(fieldvalue) is ImageObject:
         imageWrites.add VkDescriptorImageInfo(
-          sampler: fieldValue.sampler,
-          imageView: fieldValue.imageView,
+          sampler: fieldvalue.sampler,
+          imageView: fieldvalue.imageView,
           imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
         )
         descriptorSetWrites.add VkWriteDescriptorSet(
@@ -164,9 +162,9 @@
           pImageInfo: addr(imageWrites[^1]),
           pBufferInfo: nil,
         )
-      elif typeof(fieldValue) is array:
-        when elementType(fieldValue) is ImageObject:
-          for image in fieldValue.litems:
+      elif typeof(fieldvalue) is array:
+        when elementType(fieldvalue) is ImageObject:
+          for image in fieldvalue.litems:
             imageWrites.add VkDescriptorImageInfo(
               sampler: image.sampler,
               imageView: image.imageView,
@@ -182,8 +180,8 @@
             pImageInfo: addr(imageWrites[^descriptorCount.int]),
             pBufferInfo: nil,
           )
-        elif elementType(fieldValue) is GPUValue:
-          for entry in fieldValue.litems:
+        elif elementType(fieldvalue) is GPUValue:
+          for entry in fieldvalue.litems:
             bufferWrites.add VkDescriptorBufferInfo(
               buffer: entry.buffer.vk, offset: entry.offset, range: entry.size
             )
@@ -199,11 +197,11 @@
           )
         else:
           {.
-            error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldValue))
+            error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldvalue))
           .}
       else:
         {.
-          error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldValue))
+          error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldvalue))
         .}
 
   vkUpdateDescriptorSets(
@@ -259,7 +257,7 @@
     renderData: var RenderData, size: uint64, bufferType: BufferType
 ): Buffer =
   result = Buffer(
-    vk: svkCreateBuffer(size, bufferType.usage),
+    vk: svkCreateBuffer(size, BUFFER_USAGE[bufferType]),
     size: size,
     rawPointer: nil,
     offsetNextFree: 0,
@@ -394,6 +392,9 @@
     VkDescriptorPoolSize(
       thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: descriptorPoolLimit
     ),
+    VkDescriptorPoolSize(
+      thetype: VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, descriptorCount: descriptorPoolLimit
+    ),
   ]
   var poolInfo = VkDescriptorPoolCreateInfo(
     sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
@@ -504,13 +505,14 @@
 
 proc createVulkanImage(renderData: var RenderData, image: var ImageObject) =
   assert image.vk == VkImage(0), "Image has already been created"
-  var usage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT]
+  var imgUsage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT]
   if image.isRenderTarget:
-    usage.add VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
-  let format = getVkFormat(grayscale = elementType(image.data) is Gray, usage = usage)
+    imgUsage.add VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
+  let format =
+    getVkFormat(grayscale = elementType(image.data) is Gray, usage = imgUsage)
 
   image.vk = svkCreate2DImage(
-    image.width, image.height, format, usage, image.samples, image.nLayers
+    image.width, image.height, format, imgUsage, image.samples, image.nLayers
   )
   renderData.images.add image.vk
   image.sampler = createSampler(
--- a/semicongine/rendering/shaders.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/semicongine/rendering/shaders.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -79,6 +79,9 @@
     const n = typetraits.name(T)
     {.error: "Unsupported data type on GPU: " & n.}
 
+func glslType[T: SupportedGPUType](value: openArray[T]): string =
+  return glslType(default(T)) & "[]"
+
 func vkType[T: SupportedGPUType](value: T): VkFormat =
   when T is float32:
     VK_FORMAT_R32_SFLOAT
@@ -204,6 +207,12 @@
   else:
     return 1
 
+func assertGPUType[T](t: T) =
+  assert T is SupportedGPUType, "'" & $(t) & "' is not a supported GPU type"
+
+func assertGPUType[T](t: openArray[T]) =
+  assert T is SupportedGPUType, "'" & $(t) & "' is not a supported GPU type"
+
 proc generateShaderSource[TShader](shader: TShader): (string, string) {.compileTime.} =
   const GLSL_VERSION = "450"
   var vsInput: seq[string]
@@ -264,12 +273,23 @@
             ") uniform " & glslType(descriptorValue) & " " & descriptorName & ";"
           descriptorBinding.inc
         elif typeof(descriptorValue) is GPUValue:
+          let bufferType =
+            if getBufferType(descriptorValue) in [UniformBuffer, UniformBufferMapped]:
+              "uniform"
+            else:
+              "readonly buffer"
           uniforms.add "layout(set=" & $setIndex & ", binding = " & $descriptorBinding &
-            ") uniform T" & descriptorName & " {"
+            ") " & bufferType & " T" & descriptorName & " {"
           when typeof(descriptorValue.data) is object:
-            for blockFieldName, blockFieldValue in descriptorValue.data.fieldPairs():
-              assert typeof(blockFieldValue) is SupportedGPUType,
-                "uniform block field '" & blockFieldName & "' is not a SupportedGPUType"
+            for blockFieldName, blockFieldValue in fieldPairs(descriptorValue.data):
+              when typeof(blockFieldValue) is array:
+                assert elementType(blockFieldValue) is SupportedGPUType,
+                  bufferType & " block field '" & blockFieldName &
+                    "' is not a SupportedGPUType"
+              else:
+                assert typeof(blockFieldValue) is SupportedGPUType,
+                  bufferType & " block field '" & blockFieldName &
+                    "' is not a SupportedGPUType"
               uniforms.add "  " & glslType(blockFieldValue) & " " & blockFieldName & ";"
             uniforms.add "} " & descriptorName & ";"
           else:
@@ -287,13 +307,18 @@
               descriptorName & "" & arrayDecl & ";"
             descriptorBinding.inc
           elif elementType(descriptorValue) is GPUValue:
+            let bufferType =
+              if getBufferType(descriptorValue) in [UniformBuffer, UniformBufferMapped]:
+                "uniform"
+              else:
+                "readonly buffer"
             uniforms.add "layout(set=" & $setIndex & ", binding = " & $descriptorBinding &
-              ") uniform T" & descriptorName & " {"
+              ") " & bufferType & " T" & descriptorName & " {"
 
-            for blockFieldName, blockFieldValue in default(elementType(descriptorValue)).data
-            .fieldPairs():
-              assert typeof(blockFieldValue) is SupportedGPUType,
-                "uniform block field '" & blockFieldName & "' is not a SupportedGPUType"
+            for blockFieldName, blockFieldValue in fieldPairs(
+              default(elementType(descriptorValue)).data
+            ):
+              assertGPUType(blockFieldValue)
               uniforms.add "  " & glslType(blockFieldValue) & " " & blockFieldName & ";"
             uniforms.add "} " & descriptorName & "[" & $descriptorValue.len & "];"
             descriptorBinding.inc
@@ -306,7 +331,7 @@
       assert value is object, "push constants need to be objects"
       pushConstants.add "layout( push_constant ) uniform constants"
       pushConstants.add "{"
-      for constFieldName, constFieldValue in value.fieldPairs():
+      for constFieldName, constFieldValue in fieldPairs(value):
         assert typeof(constFieldValue) is SupportedGPUType,
           "push constant field '" & constFieldName & "' is not a SupportedGPUType"
         pushConstants.add "  " & glslType(constFieldValue) & " " & constFieldName & ";"
@@ -423,13 +448,11 @@
   for _, value in fieldPairs(default(TShader)):
     when hasCustomPragma(value, DescriptorSet):
       var layoutbindings: seq[VkDescriptorSetLayoutBinding]
-      forDescriptorFields(
-        value, fieldValue, descriptorType, descriptorCount, descriptorBindingNumber
-      ):
+      for theFieldname, fieldvalue in fieldPairs(value):
         layoutbindings.add VkDescriptorSetLayoutBinding(
-          binding: descriptorBindingNumber,
-          descriptorType: descriptorType,
-          descriptorCount: descriptorCount,
+          binding: getBindingNumber[typeof(value)](theFieldname),
+          descriptorType: getDescriptorType[typeof(fieldvalue)](),
+          descriptorCount: getDescriptorCount[typeof(fieldvalue)](),
           stageFlags: VkShaderStageFlags(VK_SHADER_STAGE_ALL_GRAPHICS),
           pImmutableSamplers: nil,
         )
--- a/semicongine/text.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/semicongine/text.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -69,6 +69,45 @@
     color = vec4(textbox.color.rgb, textbox.color.a * v);
 }"""
 
+  GlyphDescriptors[N: static int] = object
+    fontAtlas: Image[Gray]
+    uvs1: array[N, Vec2f]
+    uvs2: array[N, Vec2f]
+    vertexPos1: array[N, Vec2f]
+    vertexPos2: array[N, Vec2f]
+
+  GlyphShader*[N: static int] = object
+    position {.InstanceAttribute.}: Vec3f
+    color {.InstanceAttribute.}: Vec4f
+    scale {.InstanceAttribute.}: float32
+    glyphIndex {.InstanceAttribute.}: uint16
+
+    fragmentUv {.Pass.}: Vec2f
+    fragmentColor {.PassFlat.}: Vec4f
+    outColor {.ShaderOutput.}: Vec4f
+    glyphData {.DescriptorSet: 0.}: GlyphDescriptors[N]
+    vertexCode* =
+      """void main() {
+  vec2 uv1 = uvs1[glyphIndex];
+  vec2 uv2 = uvs2[glyphIndex];
+  vec2 p1 = vertexPos1[glyphIndex];
+  vec2 p2 = vertexPos2[glyphIndex];
+  uv1[gl_VertexIndex % ]
+
+  gl_Position = vec4(position * vec3(textbox.scale, 1) + textbox.position, 1.0);
+  fragmentUv = uv;
+  fragmentColor = color;
+}  """
+    fragmentCode* =
+      """void main() {
+    float v = texture(fontAtlas, fragmentUv).r;
+    // CARFULL: This can lead to rough edges at times
+    if(v == 0) {
+      discard;
+    }
+    outColor = vec4(fragmentColor.rgb, fragmentColor.a * v);
+}"""
+
 proc `=copy`(dest: var FontObj, source: FontObj) {.error.}
 
 include ./text/font
--- a/semicongine/text/font.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/semicongine/text/font.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -97,10 +97,10 @@
     if width > 0 and height > 0:
       var bitmap = newSeq[Gray](width * height)
       for i in 0 ..< width * height:
-        bitmap[i] = [data[i].uint8]
+        bitmap[i] = vec1u8(data[i].uint8)
       images.add Image[Gray](width: width.uint32, height: height.uint32, data: bitmap)
     else:
-      images.add Image[Gray](width: 1, height: 1, data: @[[0'u8]])
+      images.add Image[Gray](width: 1, height: 1, data: @[vec1u8()])
 
     nativeFree(data)
 
--- a/tests/test_rendering.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/tests/test_rendering.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -1032,24 +1032,24 @@
     setupSwapchain(renderpass = renderpass)
 
     # tests a simple triangle with minimalistic shader and vertex format
-    # test_01_triangle(time)
+    test_01_triangle(time)
 
     # tests instanced triangles and quads, mixing meshes and instances
-    # test_02_triangle_quad_instanced(time)
+    test_02_triangle_quad_instanced(time)
 
     # teste descriptor sets
-    # test_03_simple_descriptorset(time)
+    test_03_simple_descriptorset(time)
 
     # tests multiple descriptor sets and arrays
-    # test_04_multiple_descriptorsets(time)
+    test_04_multiple_descriptorsets(time)
 
     # rotating cube
-    # test_05_cube(time)
+    test_05_cube(time)
 
     # different draw modes (lines, points, and topologies)
-    # test_06_different_draw_modes(time)
+    test_06_different_draw_modes(time)
 
-    # test_07_png_texture(time)
+    test_07_png_texture(time)
 
     test_08_texture_array(time)
 
@@ -1058,7 +1058,7 @@
     clearSwapchain()
 
   # test multiple render passes
-  # for i, (depthBuffer, samples) in renderPasses:
-  # test_09_triangle_2pass(time, depthBuffer, samples)
+  for i, (depthBuffer, samples) in renderPasses:
+    test_09_triangle_2pass(time, depthBuffer, samples)
 
   destroyVulkan()
--- a/tests/test_text.nim	Sun Dec 08 08:20:36 2024 +0700
+++ b/tests/test_text.nim	Sun Dec 08 08:21:27 2024 +0700
@@ -12,6 +12,36 @@
 type FontDS = object
   fontAtlas: Image[Gray]
 
+proc test_01_static_label_new(time: float32) =
+  var font = loadFont("Overhaul.ttf", lineHeightPixels = 160)
+  var renderdata = initRenderData()
+  var pipeline =
+    createPipeline[GlyphShader[200]](renderPass = vulkan.swapchain.renderPass)
+
+  var ds = asDescriptorSetData(GlyphDescriptors[200](fontAtlas: font.fontAtlas.copy()))
+  uploadImages(renderdata, ds)
+  initDescriptorSet(renderdata, pipeline.layout(0), ds)
+
+  var start = getMonoTime()
+  while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
+    withNextFrame(framebuffer, commandbuffer):
+      bindDescriptorSet(commandbuffer, ds, 0, pipeline)
+      withRenderPass(
+        vulkan.swapchain.renderPass,
+        framebuffer,
+        commandbuffer,
+        vulkan.swapchain.width,
+        vulkan.swapchain.height,
+        vec4(0, 0, 0, 0),
+      ):
+        withPipeline(commandbuffer, pipeline):
+          render(commandbuffer, pipeline, label1, vec3(), vec4(1, 1, 1, 1))
+
+        # cleanup
+  checkVkResult vkDeviceWaitIdle(vulkan.device)
+  destroyPipeline(pipeline)
+  destroyRenderData(renderdata)
+
 proc test_01_static_label(time: float32) =
   var font = loadFont("Overhaul.ttf", lineHeightPixels = 160)
   var renderdata = initRenderData()
@@ -39,7 +69,14 @@
         vec4(0, 0, 0, 0),
       ):
         withPipeline(commandbuffer, pipeline):
-          render(commandbuffer, pipeline, label1, vec3(), vec4(1, 1, 1, 1))
+          proc render(
+            commandBuffer = commandbuffer,
+            pipeline = pipeline,
+            mesh: TMesh,
+            instances: TInstance,
+            fixedVertexCount = -1,
+            fixedInstanceCount = -1,
+          )
 
         # cleanup
   checkVkResult vkDeviceWaitIdle(vulkan.device)
@@ -250,6 +287,7 @@
     setupSwapchain(renderpass = renderpass)
 
     # tests a simple triangle with minimalistic shader and vertex format
+    test_01_static_label_new(time)
     test_01_static_label(time)
     test_02_multiple_animated(time)
     test_03_layouting(time)