changeset 1322:4a1c2b1128bc

did: improve handling of descriptor sets
author sam <sam@basx.dev>
date Thu, 15 Aug 2024 18:30:00 +0700
parents 385dbd68a947
children 3ba2c180e52c
files semicongine/rendering.nim semicongine/rendering/platform/linux.nim semicongine/rendering/renderer.nim semicongine/rendering/shaders.nim semicongine/rendering/vulkan_wrappers.nim semicongine/text.nim semicongine/text/textbox.nim tests/test_rendering.nim
diffstat 8 files changed, 143 insertions(+), 204 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/rendering.nim	Thu Aug 15 12:12:27 2024 +0700
+++ b/semicongine/rendering.nim	Thu Aug 15 18:30:00 2024 +0700
@@ -31,13 +31,14 @@
 const PUSH_CONSTANT_SIZE = 128
 
 # custom pragmas to classify shader attributes
+type DescriptorSetIndex = range[0 .. MAX_DESCRIPTORSETS - 1]
 template VertexAttribute* {.pragma.}
 template InstanceAttribute* {.pragma.}
-template PushConstantAttribute* {.pragma.}
+template PushConstant* {.pragma.}
 template Pass* {.pragma.}
 template PassFlat* {.pragma.}
 template ShaderOutput* {.pragma.}
-template DescriptorSets* {.pragma.}
+template DescriptorSet*(index: DescriptorSetIndex) {.pragma.}
 
 # there is a big, bad global vulkan object
 # believe me, this makes everything much, much easier
@@ -99,7 +100,7 @@
     oldSwapchainCounter: int # swaps until old swapchain will be destroyed
 
   # shader related types
-  DescriptorSet*[T: object] = object
+  DescriptorSetData*[T: object] = object
     data*: T
     vk: array[INFLIGHTFRAMES.int, VkDescriptorSet]
   Pipeline*[TShader] = object
@@ -156,7 +157,7 @@
 proc `=copy`[T, S](dest: var GPUArray[T, S]; source: GPUArray[T, S]) {.error.}
 proc `=copy`(dest: var MemoryBlock; source: MemoryBlock) {.error.}
 proc `=copy`[T](dest: var Pipeline[T]; source: Pipeline[T]) {.error.}
-proc `=copy`[T](dest: var DescriptorSet[T]; source: DescriptorSet[T]) {.error.}
+proc `=copy`[T](dest: var DescriptorSetData[T]; source: DescriptorSetData[T]) {.error.}
 
 template forDescriptorFields(shader: typed, valuename, typename, countname, bindingNumber, body: untyped): untyped =
   var `bindingNumber` {.inject.} = 0'u32
@@ -191,6 +192,10 @@
     else:
       {.error: "Unsupported descriptor type: " & typetraits.name(typeof(`valuename`)).}
 
+proc currentFiF*(): int =
+  assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
+  vulkan.swapchain.currentFiF
+
 include ./rendering/vulkan_wrappers
 include ./rendering/renderpasses
 include ./rendering/swapchain
@@ -357,10 +362,6 @@
   assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
   vulkan.swapchain.width.float32 / vulkan.swapchain.height.float32
 
-proc currentFiF*(): int =
-  assert vulkan.swapchain != nil, "Swapchain has not been initialized yet"
-  vulkan.swapchain.currentFiF
-
 proc maxFramebufferSampleCount*(maxSamples = VK_SAMPLE_COUNT_8_BIT): VkSampleCountFlagBits =
   let limits = svkGetPhysicalDeviceProperties().limits
   let available = VkSampleCountFlags(
--- a/semicongine/rendering/platform/linux.nim	Thu Aug 15 12:12:27 2024 +0700
+++ b/semicongine/rendering/platform/linux.nim	Thu Aug 15 18:30:00 2024 +0700
@@ -1,6 +1,4 @@
-import std/logging
 import std/options
-import std/strformat
 import std/tables
 
 import ../../thirdparty/x11/xlib
--- a/semicongine/rendering/renderer.nim	Thu Aug 15 12:12:27 2024 +0700
+++ b/semicongine/rendering/renderer.nim	Thu Aug 15 18:30:00 2024 +0700
@@ -73,7 +73,7 @@
 proc initDescriptorSet*(
   renderData: RenderData,
   layout: VkDescriptorSetLayout,
-  descriptorSet: var DescriptorSet,
+  descriptorSet: var DescriptorSetData,
 ) =
 
   # santization checks
@@ -336,7 +336,7 @@
     when typeof(value) is GPUData:
       (value.buffer, value.offset) = allocateGPUData(renderdata, value.bufferType, value.size)
 
-    elif typeof(value) is DescriptorSet:
+    elif typeof(value) is DescriptorSetData:
       assignBuffers(renderdata, value.data, uploadData = uploadData)
 
     elif typeof(value) is array:
@@ -347,7 +347,7 @@
   if uploadData:
     updateAllGPUBuffers(data, flush = true)
 
-proc assignBuffers*(renderdata: var RenderData, descriptorSet: var DescriptorSet, uploadData = true) =
+proc assignBuffers*(renderdata: var RenderData, descriptorSet: var DescriptorSetData, uploadData = true) =
   assignBuffers(renderdata, descriptorSet.data, uploadData = uploadData)
 
 proc initRenderData*(descriptorPoolLimit = 1024'u32): RenderData =
@@ -520,7 +520,7 @@
   transitionImageLayout(image.vk, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
 
 
-proc uploadImages*(renderdata: var RenderData, descriptorSet: var DescriptorSet) =
+proc uploadImages*(renderdata: var RenderData, descriptorSet: var DescriptorSetData) =
   for name, value in fieldPairs(descriptorSet.data):
     when typeof(value) is Image:
       renderdata.createVulkanImage(value)
@@ -531,75 +531,17 @@
 
 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
+proc assertCompatibleDescriptorSet(TDescriptorSet, TShader: typedesc, index: static DescriptorSetIndex) =
+  for _, fieldvalue in default(TShader).fieldPairs:
+    when fieldvalue.hasCustomPragma(DescriptorSet):
+      when fieldvalue.getCustomPragmaVal(DescriptorSet) == index:
+        assert TDescriptorSet is typeof(fieldvalue), "Incompatible descriptor set types for set number " & $index & " in shader " & name(TShader)
 
-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
-    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
-    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
-    static:
-      hasBoundDescriptorSets = false
+
+proc bindDescriptorSet*[TDescriptorSet, TShader](commandBuffer: VkCommandBuffer, descriptorSet: DescriptorSetData[TDescriptorSet], index: static DescriptorSetIndex, pipeline: Pipeline[TShader]) =
+  assert descriptorSet.vk[currentFiF()].Valid, "DescriptorSetData not initialized, maybe forgot to call initDescriptorSet"
+  static: assertCompatibleDescriptorSet(TDescriptorSet, TShader, index)
+  svkCmdBindDescriptorSet(commandBuffer, descriptorSet.vk[currentFiF()], index, pipeline.layout)
 
 proc assertCanRenderMesh(TShader, TMesh, TInstance: typedesc) =
   for attrName, attrValue in default(TShader).fieldPairs:
@@ -629,14 +571,6 @@
 ) =
 
   static: assertCanRenderMesh(TShader, TMesh, TInstance)
-  static:
-    hasDescriptorSets = false
-    for attrName, attrValue in default(TShader).fieldPairs():
-      if attrValue.hasCustomPragma(DescriptorSets):
-        hasDescriptorSets = true
-    # TODO: fix this, not working as intended, seems to depend on scope
-    # if hasDescriptorSets:
-      # assert hasBoundDescriptorSets, "Shader uses descriptor sets, but none are bound"
 
   var vertexBuffers: seq[VkBuffer]
   var vertexBuffersOffsets: seq[uint64]
@@ -722,7 +656,7 @@
   assert sizeof(TPushConstant) <= PUSH_CONSTANT_SIZE, "Push constant values must be <= 128 bytes"
   var foundPushConstant = false
   for fieldname, fieldvalue in default(TShader).fieldPairs():
-    when hasCustomPragma(fieldvalue, PushConstantAttribute):
+    when hasCustomPragma(fieldvalue, PushConstant):
       assert typeof(fieldvalue) is TPushConstant, "Provided push constant has not same type as declared in shader"
       assert foundPushConstant == false, "More than on push constant found in shader"
       foundPushConstant = true
@@ -768,5 +702,5 @@
 proc asGPUValue*[T](data: sink T, bufferType: static BufferType): auto =
   GPUValue[T, bufferType](data: data)
 
-proc asDescriptorSet*[T](data: sink T): auto =
-  DescriptorSet[T](data: data)
+proc asDescriptorSetData*[T](data: sink T): auto =
+  DescriptorSetData[T](data: data)
--- a/semicongine/rendering/shaders.nim	Thu Aug 15 12:12:27 2024 +0700
+++ b/semicongine/rendering/shaders.nim	Thu Aug 15 18:30:00 2024 +0700
@@ -137,7 +137,7 @@
   var passLocation {.hint[XDeclaredButNotUsed]: off.} = 0
   var fsOutputLocation = 0
 
-  var sawDescriptorSets = false
+  var sawDescriptorSets: array[MAX_DESCRIPTORSETS, bool]
   for fieldname, value in fieldPairs(shader):
     # vertex shader inputs
     when hasCustomPragma(value, VertexAttribute) or hasCustomPragma(value, InstanceAttribute):
@@ -160,61 +160,56 @@
 
     # descriptor sets
     # need to consider 4 cases: uniform block, texture, uniform block array, texture array
-    elif hasCustomPragma(value, DescriptorSets):
-      assert not sawDescriptorSets, "Only one field with pragma DescriptorSets allowed per shader"
-      assert typeof(value) is tuple, "Descriptor field '" & fieldname & "' must be of type tuple"
-      assert tupleLen(value) <= MAX_DESCRIPTORSETS, typetraits.name(TShader) & ": maximum " & $MAX_DESCRIPTORSETS & " allowed"
-      sawDescriptorSets = true
-      var descriptorSetIndex = 0
-      for descriptor in value.fields:
+    elif hasCustomPragma(value, DescriptorSet):
+      let setIndex = value.getCustomPragmaVal(DescriptorSet)
+      assert not sawDescriptorSets[setIndex], "Only one DescriptorSet per index is allowed per shader"
+      assert typeof(value) is object, "Descriptor field '" & fieldname & "' must be of type object"
+      assert setIndex < MAX_DESCRIPTORSETS, typetraits.name(TShader) & ": maximum " & $MAX_DESCRIPTORSETS & " descriptor sets allowed"
+      sawDescriptorSets[setIndex] = true
+
+      var descriptorBinding = 0
+      for descriptorName, descriptorValue in fieldPairs(value):
+        when typeof(descriptorValue) is Image:
+          samplers.add "layout(set=" & $setIndex & ", binding = " & $descriptorBinding & ") uniform " & glslType(descriptorValue) & " " & descriptorName & ";"
+          descriptorBinding.inc
+
+        elif typeof(descriptorValue) is GPUValue:
 
-        var descriptorBinding = 0
+          uniforms.add "layout(set=" & $setIndex & ", binding = " & $descriptorBinding & ") uniform 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"
+              uniforms.add "  " & glslType(blockFieldValue) & " " & blockFieldName & ";"
+            uniforms.add "} " & descriptorName & ";"
 
-        for descriptorName, descriptorValue in fieldPairs(descriptor):
+          else:
+            {.error: "Unsupported shader descriptor field " & descriptorName & " (must be object)".}
+          descriptorBinding.inc
 
-          when typeof(descriptorValue) is Image:
-            samplers.add "layout(set=" & $descriptorSetIndex & ", binding = " & $descriptorBinding & ") uniform " & glslType(descriptorValue) & " " & descriptorName & ";"
+        elif typeof(descriptorValue) is array:
+
+          when elementType(descriptorValue) is Image:
+
+            let arrayDecl = "[" & $typeof(descriptorValue).len & "]"
+            samplers.add "layout(set=" & $setIndex & ", binding = " & $descriptorBinding & ") uniform " & glslType(default(elementType(descriptorValue))) & " " & descriptorName & "" & arrayDecl & ";"
             descriptorBinding.inc
 
-          elif typeof(descriptorValue) is GPUValue:
+          elif elementType(descriptorValue) is GPUValue:
 
-            uniforms.add "layout(set=" & $descriptorSetIndex & ", binding = " & $descriptorBinding & ") uniform T" & descriptorName & " {"
-            when typeof(descriptorValue.data) is object:
+            uniforms.add "layout(set=" & $setIndex & ", binding = " & $descriptorBinding & ") uniform T" & descriptorName & " {"
 
-              for blockFieldName, blockFieldValue in descriptorValue.data.fieldPairs():
-                assert typeof(blockFieldValue) is SupportedGPUType, "uniform block field '" & blockFieldName & "' is not a SupportedGPUType"
-                uniforms.add "  " & glslType(blockFieldValue) & " " & blockFieldName & ";"
-              uniforms.add "} " & descriptorName & ";"
-
-            else:
-              {.error: "Unsupported shader descriptor field " & descriptorName & " (must be object)".}
+            for blockFieldName, blockFieldValue in default(elementType(descriptorValue)).data.fieldPairs():
+              assert typeof(blockFieldValue) is SupportedGPUType, "uniform block field '" & blockFieldName & "' is not a SupportedGPUType"
+              uniforms.add "  " & glslType(blockFieldValue) & " " & blockFieldName & ";"
+            uniforms.add "} " & descriptorName & "[" & $descriptorValue.len & "];"
             descriptorBinding.inc
 
-          elif typeof(descriptorValue) is array:
-
-            when elementType(descriptorValue) is Image:
-
-              let arrayDecl = "[" & $typeof(descriptorValue).len & "]"
-              samplers.add "layout(set=" & $descriptorSetIndex & ", binding = " & $descriptorBinding & ") uniform " & glslType(default(elementType(descriptorValue))) & " " & descriptorName & "" & arrayDecl & ";"
-              descriptorBinding.inc
-
-            elif elementType(descriptorValue) is GPUValue:
-
-              uniforms.add "layout(set=" & $descriptorSetIndex & ", binding = " & $descriptorBinding & ") uniform T" & descriptorName & " {"
-
-              for blockFieldName, blockFieldValue in default(elementType(descriptorValue)).data.fieldPairs():
-                assert typeof(blockFieldValue) is SupportedGPUType, "uniform block field '" & blockFieldName & "' is not a SupportedGPUType"
-                uniforms.add "  " & glslType(blockFieldValue) & " " & blockFieldName & ";"
-              uniforms.add "} " & descriptorName & "[" & $descriptorValue.len & "];"
-              descriptorBinding.inc
-
-            else:
-              {.error: "Unsupported shader descriptor field " & descriptorName.}
-
-        descriptorSetIndex.inc
+          else:
+            {.error: "Unsupported shader descriptor field " & descriptorName.}
     elif fieldname in ["vertexCode", "fragmentCode"]:
       discard
-    elif hasCustomPragma(value, PushConstantAttribute):
+    elif hasCustomPragma(value, PushConstant):
       assert pushConstants.len == 0, "Only one push constant value allowed"
       assert value is object, "push constants need to be objects"
       pushConstants.add "layout( push_constant ) uniform constants"
@@ -327,37 +322,29 @@
         const `isinstancename` {.inject.} = hasCustomPragma(value, InstanceAttribute)
         body
 
-proc getDescriptorSetCount[TShader](): uint32 =
-  for _, value in fieldPairs(default(TShader)):
-    when hasCustomPragma(value, DescriptorSets):
-      return tupleLen(typeof(value)).uint32
-
 proc createDescriptorSetLayouts[TShader](): array[MAX_DESCRIPTORSETS, VkDescriptorSetLayout] =
-  var setNumber: int
   for _, value in fieldPairs(default(TShader)):
-    when hasCustomPragma(value, DescriptorSets):
-      for descriptorSet in value.fields:
-        var layoutbindings: seq[VkDescriptorSetLayoutBinding]
-        forDescriptorFields(descriptorSet, fieldValue, descriptorType, descriptorCount, descriptorBindingNumber):
-          layoutbindings.add VkDescriptorSetLayoutBinding(
-            binding: descriptorBindingNumber,
-            descriptorType: descriptorType,
-            descriptorCount: descriptorCount,
-            stageFlags: VkShaderStageFlags(VK_SHADER_STAGE_ALL_GRAPHICS),
-            pImmutableSamplers: nil,
-          )
-        var layoutCreateInfo = VkDescriptorSetLayoutCreateInfo(
-          sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
-          bindingCount: layoutbindings.len.uint32,
-          pBindings: layoutbindings.ToCPointer
+    when hasCustomPragma(value, DescriptorSet):
+      var layoutbindings: seq[VkDescriptorSetLayoutBinding]
+      forDescriptorFields(value, fieldValue, descriptorType, descriptorCount, descriptorBindingNumber):
+        layoutbindings.add VkDescriptorSetLayoutBinding(
+          binding: descriptorBindingNumber,
+          descriptorType: descriptorType,
+          descriptorCount: descriptorCount,
+          stageFlags: VkShaderStageFlags(VK_SHADER_STAGE_ALL_GRAPHICS),
+          pImmutableSamplers: nil,
         )
-        checkVkResult vkCreateDescriptorSetLayout(
-          vulkan.device,
-          addr(layoutCreateInfo),
-          nil,
-          addr(result[setNumber])
-        )
-        inc setNumber
+      var layoutCreateInfo = VkDescriptorSetLayoutCreateInfo(
+        sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+        bindingCount: layoutbindings.len.uint32,
+        pBindings: layoutbindings.ToCPointer
+      )
+      checkVkResult vkCreateDescriptorSetLayout(
+        vulkan.device,
+        addr(layoutCreateInfo),
+        nil,
+        addr(result[value.getCustomPragmaVal(DescriptorSet)])
+      )
 
 proc createPipeline*[TShader](
   renderPass: RenderPass,
@@ -373,8 +360,11 @@
   const shader = default(TShader)
   (result.vertexShaderModule, result.fragmentShaderModule) = compileShader(shader)
 
-  var nSets = getDescriptorSetCount[TShader]()
   result.descriptorSetLayouts = createDescriptorSetLayouts[TShader]()
+  var layouts: seq[VkDescriptorSetLayout]
+  for l in result.descriptorSetLayouts:
+    if l.Valid:
+      layouts.add l
 
   let pushConstant = VkPushConstantRange(
     stageFlags: VkShaderStageFlags(VK_SHADER_STAGE_ALL_GRAPHICS),
@@ -384,8 +374,8 @@
 
   let pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
     sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
-    setLayoutCount: nSets,
-    pSetLayouts: if nSets == 0: nil else: result.descriptorSetLayouts.ToCPointer,
+    setLayoutCount: layouts.len.uint32,
+    pSetLayouts: layouts.ToCPointer,
     pushConstantRangeCount: 1,
     pPushConstantRanges: addr(pushConstant),
   )
--- a/semicongine/rendering/vulkan_wrappers.nim	Thu Aug 15 12:12:27 2024 +0700
+++ b/semicongine/rendering/vulkan_wrappers.nim	Thu Aug 15 18:30:00 2024 +0700
@@ -215,6 +215,18 @@
     pDynamicOffsets = nil
   )
 
+proc svkCmdBindDescriptorSet(commandBuffer: VkCommandBuffer, descriptorSet: VkDescriptorSet, index: DescriptorSetIndex, layout: VkPipelineLayout) =
+  vkCmdBindDescriptorSets(
+    commandBuffer = commandBuffer,
+    pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+    layout = layout,
+    firstSet = index.uint32,
+    descriptorSetCount = 1,
+    pDescriptorSets = addr(descriptorSet),
+    dynamicOffsetCount = 0,
+    pDynamicOffsets = nil
+  )
+
 
 proc svkCreateRenderPass(
   attachments: openArray[VkAttachmentDescription],
--- a/semicongine/text.nim	Thu Aug 15 12:12:27 2024 +0700
+++ b/semicongine/text.nim	Thu Aug 15 18:30:00 2024 +0700
@@ -53,7 +53,7 @@
     uv {.VertexAttribute.}: Vec2f # TODO: maybe we can keep the uvs in a uniform buffer and just pass an index
     fragmentUv {.Pass.}: Vec2f
     color {.ShaderOutput.}: Vec4f
-    descriptorSets {.DescriptorSets.}: (TextboxDescriptorSet, )
+    descriptorSets {.DescriptorSet: 0.}: TextboxDescriptorSet
     vertexCode* = """void main() {
   gl_Position = vec4(position * vec3(1 / textbox.aspectratio, 1, 1) * textbox.scale + textbox.position, 1.0);
   fragmentUv = uv;
--- a/semicongine/text/textbox.nim	Thu Aug 15 12:12:27 2024 +0700
+++ b/semicongine/text/textbox.nim	Thu Aug 15 18:30:00 2024 +0700
@@ -17,7 +17,7 @@
     position: GPUArray[Vec3f, VertexBuffer]
     uv: GPUArray[Vec2f, VertexBuffer]
     indices: GPUArray[uint16, IndexBuffer]
-    shaderdata: DescriptorSet[TextboxDescriptorSet]
+    shaderdata: DescriptorSetData[TextboxDescriptorSet]
 
 proc `=copy`(dest: var Textbox; source: Textbox) {.error.}
 
@@ -176,8 +176,8 @@
     textbox.dirtyGeometry = false
 
 proc render*(commandbuffer: VkCommandBuffer, pipeline: Pipeline, textbox: Textbox) =
-  withBind(commandbuffer, (textbox.shaderdata, ), pipeline):
-    render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = textbox)
+  bindDescriptorSet(commandbuffer, textbox.shaderdata, 0, pipeline)
+  render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = textbox)
 
 proc initTextbox*[T: string | seq[Rune]](
   renderdata: var RenderData,
@@ -204,7 +204,7 @@
     position: asGPUArray(newSeq[Vec3f](int(maxLen * 4)), VertexBuffer),
     uv: asGPUArray(newSeq[Vec2f](int(maxLen * 4)), VertexBuffer),
     indices: asGPUArray(newSeq[uint16](int(maxLen * 6)), IndexBuffer),
-    shaderdata: asDescriptorSet(
+    shaderdata: asDescriptorSetData(
       TextboxDescriptorSet(
         textbox: asGPUValue(TextboxData(
           scale: scale,
--- a/tests/test_rendering.nim	Thu Aug 15 12:12:27 2024 +0700
+++ b/tests/test_rendering.nim	Thu Aug 15 18:30:00 2024 +0700
@@ -17,7 +17,7 @@
     Shader = object
       position {.VertexAttribute.}: Vec3f
       color {.VertexAttribute.}: Vec3f
-      pushConstant {.PushConstantAttribute.}: PushConstant
+      pushConstant {.PushConstant.}: PushConstant
       fragmentColor {.Pass.}: Vec3f
       outColor {.ShaderOutput.}: Vec4f
       # code
@@ -144,7 +144,7 @@
       fragmentColor {.Pass.}: Vec3f
       uv {.Pass.}: Vec2f
       outColor {.ShaderOutput.}: Vec4f
-      descriptorSets {.DescriptorSets.}: (Uniforms, )
+      descriptorSets {.DescriptorSet: 0.}: Uniforms
       # code
       vertexCode: string = """void main() {
       fragmentColor = material.baseColor;
@@ -167,13 +167,13 @@
       position: asGPUArray([vec3(-0.5, -0.5, 0), vec3(-0.5, 0.5, 0), vec3(0.5, 0.5, 0), vec3(0.5, -0.5, 0)], VertexBuffer),
       indices: asGPUArray([0'u16, 1'u16, 2'u16, 2'u16, 3'u16, 0'u16], IndexBuffer),
     )
-    uniforms1 = asDescriptorSet(
+    uniforms1 = asDescriptorSetData(
       Uniforms(
         material: asGPUValue(Material(baseColor: vec3(1, 1, 1)), UniformBuffer),
         texture1: Image[BGRA](width: 3, height: 3, data: @[R, G, B, G, B, R, B, R, G], minInterpolation: VK_FILTER_NEAREST, magInterpolation: VK_FILTER_NEAREST),
       )
     )
-    uniforms2 = asDescriptorSet(
+    uniforms2 = asDescriptorSetData(
       Uniforms(
         material: asGPUValue(Material(baseColor: vec3(0.5, 0.5, 0.5)), UniformBuffer),
         texture1: Image[BGRA](width: 2, height: 2, data: @[R, G, B, W]),
@@ -201,11 +201,11 @@
 
         withPipeline(commandbuffer, pipeline):
 
-          withBind(commandbuffer, (uniforms1, ), pipeline):
-            render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
+          bindDescriptorSet(commandbuffer, uniforms1, 0, pipeline)
+          render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
 
-          withBind(commandbuffer, (uniforms2, ), pipeline):
-            render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
+          bindDescriptorSet(commandbuffer, uniforms2, 0, pipeline)
+          render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
 
   # cleanup
   checkVkResult vkDeviceWaitIdle(vulkan.device)
@@ -240,7 +240,9 @@
       fragmentColor {.Pass.}: Vec3f
       uv {.Pass.}: Vec2f
       outColor {.ShaderOutput.}: Vec4f
-      descriptorSets {.DescriptorSets.}: (ConstSet, MainSet, OtherSet)
+      descriptorSets0 {.DescriptorSet: 0.}: ConstSet
+      descriptorSets1 {.DescriptorSet: 1.}: MainSet
+      descriptorSets2 {.DescriptorSet: 2.}: OtherSet
       # code
       vertexCode: string = """void main() {
       fragmentColor = material[objectSettings.materialIndex].baseColor * renderSettings.brigthness;
@@ -260,14 +262,14 @@
     position: asGPUArray([vec3(-0.5, -0.5), vec3(-0.5, 0.5), vec3(0.5, 0.5), vec3(0.5, -0.5)], VertexBuffer),
     indices: asGPUArray([0'u16, 1'u16, 2'u16, 2'u16, 3'u16, 0'u16], IndexBuffer),
   )
-  var constset = asDescriptorSet(
+  var constset = asDescriptorSetData(
     ConstSet(
       constants: asGPUValue(Constants(offset: vec2(-0.3, 0.2)), UniformBuffer),
     )
   )
   let G = Gray([50'u8])
   let W = Gray([255'u8])
-  var mainset = asDescriptorSet(
+  var mainset = asDescriptorSetData(
     MainSet(
       renderSettings: asGPUValue(RenderSettings(brigthness: 0), UniformBufferMapped),
       material: [
@@ -280,12 +282,12 @@
     ],
   ),
   )
-  var otherset1 = asDescriptorSet(
+  var otherset1 = asDescriptorSetData(
     OtherSet(
       objectSettings: asGPUValue(ObjectSettings(scale: 1.0, materialIndex: 0), UniformBufferMapped),
     )
   )
-  var otherset2 = asDescriptorSet(
+  var otherset2 = asDescriptorSetData(
     OtherSet(
       objectSettings: asGPUValue(ObjectSettings(scale: 1.0, materialIndex: 1), UniformBufferMapped),
     )
@@ -310,16 +312,18 @@
   while ((getMonoTime() - start).inMilliseconds().int / 1000) < time:
 
     withNextFrame(framebuffer, commandbuffer):
+      bindDescriptorSet(commandbuffer, constset, 0, pipeline)
+      bindDescriptorSet(commandbuffer, mainset, 1, pipeline)
 
       withRenderPass(vulkan.swapchain.renderPass, framebuffer, commandbuffer, vulkan.swapchain.width, vulkan.swapchain.height, vec4(0, 0, 0, 0)):
 
         withPipeline(commandbuffer, pipeline):
 
-          withBind(commandbuffer, (constset, mainset, otherset1), pipeline):
-            render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
+          bindDescriptorSet(commandbuffer, otherset1, 2, pipeline)
+          render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
 
-          withBind(commandbuffer, (constset, mainset, otherset2), pipeline):
-            render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
+          bindDescriptorSet(commandbuffer, otherset2, 2, pipeline)
+          render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = quad)
 
     mainset.data.renderSettings.data.brigthness = ((getMonoTime() - start).inMilliseconds().int / 1000) / time
     otherset1.data.objectSettings.data.scale = 0.5 + ((getMonoTime() - start).inMilliseconds().int / 1000) / time
@@ -344,7 +348,7 @@
       color {.VertexAttribute.}: Vec4f
       fragmentColor {.Pass.}: Vec4f
       outColor {.ShaderOutput.}: Vec4f
-      descriptorSets {.DescriptorSets.}: (Uniforms, )
+      descriptorSets {.DescriptorSet: 0.}: Uniforms
       # code
       vertexCode = """void main() {
     fragmentColor = color;
@@ -417,7 +421,7 @@
   )
   assignBuffers(renderdata, floor)
 
-  var uniforms1 = asDescriptorSet(
+  var uniforms1 = asDescriptorSetData(
     Uniforms(
       data: asGPUValue(UniformData(mvp: Unit4), UniformBufferMapped)
     )
@@ -449,9 +453,9 @@
       withRenderPass(vulkan.swapchain.renderPass, framebuffer, commandbuffer, vulkan.swapchain.width, vulkan.swapchain.height, vec4(0, 0, 0, 0)):
         withPipeline(commandbuffer, pipeline):
 
-          withBind(commandbuffer, (uniforms1, ), pipeline):
-            render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = mesh)
-            render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = floor)
+          bindDescriptorSet(commandbuffer, uniforms1, 0, pipeline)
+          render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = mesh)
+          render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = floor)
 
     let tEndLoop = getMonoTime() - tStart
     let looptime = tEndLoop - tStartLoop
@@ -532,7 +536,7 @@
       uv {.VertexAttribute.}: Vec2f
       fragmentUv {.Pass.}: Vec2f
       outColor {.ShaderOutput.}: Vec4f
-      descriptorSets {.DescriptorSets.}: (Uniforms, )
+      descriptorSets {.DescriptorSet: 0.}: Uniforms
       # code
       vertexCode: string = """
 void main() {
@@ -560,7 +564,7 @@
   renderdata.flushAllMemory()
 
   var pipeline = createPipeline[Shader](renderPass = vulkan.swapchain.renderPass)
-  var uniforms1 = asDescriptorSet(
+  var uniforms1 = asDescriptorSetData(
     Uniforms(
       texture1: loadImage[BGRA]("art.png"),
     )
@@ -577,8 +581,8 @@
 
         withPipeline(commandbuffer, pipeline):
 
-          withBind(commandbuffer, (uniforms1, ), pipeline):
-            render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = mesh)
+          bindDescriptorSet(commandbuffer, uniforms1, 0, pipeline)
+          render(commandbuffer = commandbuffer, pipeline = pipeline, mesh = mesh)
 
   # cleanup
   checkVkResult vkDeviceWaitIdle(vulkan.device)
@@ -610,7 +614,7 @@
       position {.VertexAttribute.}: Vec2f
       uv {.Pass.}: Vec2f
       outColor {.ShaderOutput.}: Vec4f
-      descriptorSets {.DescriptorSets.}: (Uniforms, )
+      descriptorSets {.DescriptorSet: 0.}: Uniforms
       # code
       vertexCode: string = """void main() {
       uv = ((position + 1) * 0.5) * vec2(1, -1);
@@ -641,7 +645,7 @@
     position: asGPUArray([vec2(-1, -1), vec2(-1, 1), vec2(1, 1), vec2(1, -1)], VertexBuffer),
     indices: asGPUArray([0'u16, 1'u16, 2'u16, 2'u16, 3'u16, 0'u16], IndexBuffer),
   )
-  var uniforms1 = asDescriptorSet(
+  var uniforms1 = asDescriptorSetData(
     Uniforms(
       frameTexture: Image[BGRA](width: vulkan.swapchain.width, height: vulkan.swapchain.height, isRenderTarget: true),
     )
@@ -744,8 +748,8 @@
 
         withPipeline(commandbuffer, presentPipeline):
 
-          withBind(commandbuffer, (uniforms1, ), presentPipeline):
-            render(commandbuffer = commandbuffer, pipeline = presentPipeline, mesh = quad)
+          bindDescriptorSet(commandbuffer, uniforms1, 0, presentPipeline)
+          render(commandbuffer = commandbuffer, pipeline = presentPipeline, mesh = quad)
 
   # cleanup
   checkVkResult vkDeviceWaitIdle(vulkan.device)