changeset 1205:f7530247a21f compiletime-tests

did: improve descriptor-set handling, add simple descriptor set test
author sam <sam@basx.dev>
date Tue, 16 Jul 2024 11:53:43 +0700
parents e2901100a596
children 77822066a419
files semicongine/rendering.nim semicongine/rendering/renderer.nim semicongine/rendering/shaders.nim semicongine/rendering/vulkan_wrappers.nim tests/test_rendering tests/test_rendering.nim
diffstat 6 files changed, 143 insertions(+), 119 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/rendering.nim	Mon Jul 15 23:51:17 2024 +0700
+++ b/semicongine/rendering.nim	Tue Jul 16 11:53:43 2024 +0700
@@ -11,6 +11,7 @@
 const BUFFER_ALIGNMENT = 64'u64 # align offsets inside buffers along this alignment
 const MEMORY_BLOCK_ALLOCATION_SIZE = 100_000_000'u64 # ca. 100mb per block, seems reasonable
 const BUFFER_ALLOCATION_SIZE = 9_000_000'u64 # ca. 9mb per block, seems reasonable, can put 10 buffers into one memory block
+const MAX_DESCRIPTORSETS = 4
 
 # custom pragmas to classify shader attributes
 template VertexAttribute* {.pragma.}
@@ -18,6 +19,7 @@
 template Pass* {.pragma.}
 template PassFlat* {.pragma.}
 template ShaderOutput* {.pragma.}
+template DescriptorSets* {.pragma.}
 
 # there is a big, bad global vulkan object
 # believe me, this makes everything much, much easier
@@ -73,13 +75,7 @@
   TextureType = TVec1[uint8] | TVec2[uint8] | TVec3[uint8] | TVec4[uint8]
 
   # shader related types
-  DescriptorSetType* = enum
-    First
-    Second
-    # only two supported for now, but more should be easy to add
-    # Third
-    # Fourth
-  DescriptorSet*[T: object, sType: static DescriptorSetType] = object
+  DescriptorSet*[T: object] = object
     data*: T
     vk: array[INFLIGHTFRAMES.int, VkDescriptorSet]
   Pipeline*[TShader] = object
@@ -87,7 +83,7 @@
     vertexShaderModule: VkShaderModule
     fragmentShaderModule: VkShaderModule
     layout: VkPipelineLayout
-    descriptorSetLayouts: array[DescriptorSetType, VkDescriptorSetLayout]
+    descriptorSetLayouts*: array[MAX_DESCRIPTORSETS, VkDescriptorSetLayout]
 
   # memory/buffer related types
   MemoryBlock* = object
@@ -133,7 +129,7 @@
     samplers: seq[VkSampler]
 
 template ForDescriptorFields(shader: typed, fieldname, valuename, typename, countname, bindingNumber, body: untyped): untyped =
-  var `bindingNumber` {.inject.} = 1'u32
+  var `bindingNumber` {.inject.} = 0'u32
   for theFieldname, value in fieldPairs(shader):
     when typeof(value) is Texture:
       block:
--- a/semicongine/rendering/renderer.nim	Mon Jul 15 23:51:17 2024 +0700
+++ b/semicongine/rendering/renderer.nim	Tue Jul 16 11:53:43 2024 +0700
@@ -247,7 +247,7 @@
   result.rawPointer = selectedBlock.rawPointer.pointerAddOffset(selectedBlock.offsetNextFree)
   renderData.memory[memoryType][selectedBlockI].offsetNextFree += memoryRequirements.size
 
-proc UpdateGPUBuffer(gpuData: GPUData) =
+proc UpdateGPUBuffer*(gpuData: GPUData) =
   if gpuData.size == 0:
     return
   when NeedsMapping(gpuData):
@@ -461,11 +461,9 @@
 proc UploadTextures*(renderdata: var RenderData, descriptorSet: var DescriptorSet) =
   for name, value in fieldPairs(descriptorSet.data):
     when typeof(value) is Texture:
-      echo "Upload texture '", name, "'"
       renderdata.createTextureImage(value)
     elif typeof(value) is array:
       when elementType(value) is Texture:
-        echo "Upload texture ARRAY '", name, "'"
         for texture in value.mitems:
           renderdata.createTextureImage(texture)
 
@@ -512,7 +510,7 @@
 
     # descriptors
     elif typeof(shaderAttribute) is DescriptorSet:
-      assert descriptorSetCount <= DescriptorSetType.high.int, typetraits.name(TShader) & ": maximum " & $DescriptorSetType.high & " allowed"
+      assert descriptorSetCount <= MAX_DESCRIPTORSETS.int, typetraits.name(TShader) & ": maximum " & $MAX_DESCRIPTORSETS & " allowed"
       descriptorSetCount.inc
 
 
@@ -524,36 +522,39 @@
         assert typeof(shaderAttribute) is TSecondDescriptorSet, "Shader has seconddescriptor type '" & typetraits.name(get(genericParams(typeof(shaderAttribute)), 0)) & "' but provided type is " & typetraits.name(TSecondDescriptorSet)
 
 
-
-template WithBind*[A, B](commandBuffer: VkCommandBuffer, firstDescriptorSet: DescriptorSet[A, First], secondDescriptorSet: DescriptorSet[B, Second], pipeline: Pipeline, currentFiF: int, body: untyped): untyped =
+template WithBind*[A, B, C, D](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B], DescriptorSet[C], DescriptorSet[D]), pipeline: Pipeline, currentFiF: int, 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
+template WithBind*[A, B, C](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B], DescriptorSet[C]), pipeline: Pipeline, currentFiF: int, body: untyped): untyped =
   block:
-    let sets = [firstDescriptorSet.vk[currentFiF], secondDescriptorSet.vk[currentFiF]]
-    vkCmdBindDescriptorSets(
-      commandBuffer = commandBuffer,
-      pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
-      layout = pipeline.layout,
-      firstSet = 0,
-      descriptorSetCount = sets.len.uint32,
-      pDescriptorSets = sets.ToCPointer,
-      dynamicOffsetCount = 0,
-      pDynamicOffsets = nil
-    )
+    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
+template WithBind*[A, B](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], DescriptorSet[B]), pipeline: Pipeline, currentFiF: int, 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
+template WithBind*[A](commandBuffer: VkCommandBuffer, sets: (DescriptorSet[A], ), pipeline: Pipeline, currentFiF: int, 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
 
-template WithBind*[A](commandBuffer: VkCommandBuffer, firstDescriptorSet: DescriptorSet[A, First], pipeline: Pipeline, currentFiF: int, body: untyped): untyped =
-  block:
-    let sets = [firstDescriptorSet.vk[currentFiF]]
-    vkCmdBindDescriptorSets(
-      commandBuffer = commandBuffer,
-      pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
-      layout = pipeline.layout,
-      firstSet = 0,
-      descriptorSetCount = sets.len.uint32,
-      pDescriptorSets = sets.ToCPointer,
-      dynamicOffsetCount = 0,
-      pDynamicOffsets = nil
-    )
-    body
 
 proc Render*[TFirstDescriptorSet, TSecondDescriptorSet: DescriptorSet, TShader, TMesh, TInstance](
   commandBuffer: VkCommandBuffer,
@@ -642,8 +643,7 @@
     )
 
 type EMPTY = object
-type EMPTY_FIRST_DESCRIPTORSET = DescriptorSet[EMPTY, First]
-type EMPTY_SECOND_DESCRIPTORSET = DescriptorSet[EMPTY, Second]
+type EMPTY_DESCRIPTORSET = DescriptorSet[EMPTY]
 
 proc Render*[TFirstDescriptorSet, TSecondDescriptorSet: DescriptorSet, TShader, TMesh](
   commandBuffer: VkCommandBuffer,
@@ -659,13 +659,13 @@
   firstSet: TFirstDescriptorSet,
   mesh: TMesh,
 ) =
-  Render(commandBuffer, pipeline, firstSet, EMPTY_SECOND_DESCRIPTORSET(), mesh, EMPTY())
+  Render(commandBuffer, pipeline, firstSet, EMPTY_DESCRIPTORSET(), mesh, EMPTY())
 proc Render*[TShader, TMesh](
   commandBuffer: VkCommandBuffer,
   pipeline: Pipeline[TShader],
   mesh: TMesh,
 ) =
-  Render(commandBuffer, pipeline, EMPTY_FIRST_DESCRIPTORSET(), EMPTY_SECOND_DESCRIPTORSET(), mesh, EMPTY())
+  Render(commandBuffer, pipeline, EMPTY_DESCRIPTORSET(), EMPTY_DESCRIPTORSET(), mesh, EMPTY())
 proc Render*[TFirstDescriptorSet: DescriptorSet, TMesh, TShader, TInstance](
   commandBuffer: VkCommandBuffer,
   pipeline: Pipeline[TShader],
@@ -673,14 +673,14 @@
   mesh: TMesh,
   instances: TInstance,
 ) =
-  Render(commandBuffer, pipeline, firstSet, EMPTY_SECOND_DESCRIPTORSET(), mesh, instances)
+  Render(commandBuffer, pipeline, firstSet, EMPTY_DESCRIPTORSET(), mesh, instances)
 proc Render*[TShader, TMesh, TInstance](
   commandBuffer: VkCommandBuffer,
   pipeline: Pipeline[TShader],
   mesh: TMesh,
   instances: TInstance,
 ) =
-  Render(commandBuffer, pipeline, EMPTY_FIRST_DESCRIPTORSET(), EMPTY_SECOND_DESCRIPTORSET(), mesh, instances)
+  Render(commandBuffer, pipeline, EMPTY_DESCRIPTORSET(), EMPTY_DESCRIPTORSET(), mesh, instances)
 
 proc asGPUArray*[T](data: openArray[T], bufferType: static BufferType): auto =
   GPUArray[T, bufferType](data: @data)
--- a/semicongine/rendering/shaders.nim	Mon Jul 15 23:51:17 2024 +0700
+++ b/semicongine/rendering/shaders.nim	Tue Jul 16 11:53:43 2024 +0700
@@ -134,7 +134,7 @@
   var passLocation = 0
   var fsOutputLocation = 0
 
-  var descriptorSetCount = 0
+  var sawDescriptorSets = false
   for fieldname, value in fieldPairs(shader):
     # vertex shader inputs
     when hasCustomPragma(value, VertexAttribute) or hasCustomPragma(value, InstanceAttribute):
@@ -157,37 +157,42 @@
 
     # descriptor sets
     # need to consider 4 cases: uniform block, texture, uniform block array, texture array
-    elif typeof(value) is DescriptorSet:
-      assert descriptorSetCount <= DescriptorSetType.high.int, typetraits.name(TShader) & ": maximum " & $DescriptorSetType.high & " allowed"
+    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(typeof(value)) <= MAX_DESCRIPTORSETS, typetraits.name(TShader) & ": maximum " & $MAX_DESCRIPTORSETS & " allowed"
+      sawDescriptorSets = true
+      var descriptorSetIndex = 0
+      for descriptor in value.fields:
 
-      var descriptorBinding = 0
-      for descriptorName, descriptorValue in fieldPairs(value.data):
+        var descriptorBinding = 0
+        for descriptorName, descriptorValue in fieldPairs(descriptor):
 
-        when typeof(descriptorValue) is Texture:
-          samplers.add "layout(set=" & $descriptorSetCount & ", binding = " & $descriptorBinding & ") uniform " & GlslType(descriptorValue) & " " & descriptorName & ";"
-          descriptorBinding.inc
+          when typeof(descriptorValue) is Texture:
+            samplers.add "layout(set=" & $descriptorSetIndex & ", binding = " & $descriptorBinding & ") uniform " & GlslType(descriptorValue) & " " & descriptorName & ";"
+            descriptorBinding.inc
 
-        elif typeof(descriptorValue) is GPUValue:
-          uniforms.add "layout(set=" & $descriptorSetCount & ", 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 & ";"
-          elif typeof(descriptorValue.data) is array:
-            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.data.len & "];"
-          descriptorBinding.inc
-        elif typeof(descriptorValue) is array:
-          when elementType(descriptorValue) is Texture:
-            let arrayDecl = "[" & $typeof(descriptorValue).len & "]"
-            samplers.add "layout(set=" & $descriptorSetCount & ", binding = " & $descriptorBinding & ") uniform " & GlslType(default(elementType(descriptorValue))) & " " & descriptorName & "" & arrayDecl & ";"
+          elif typeof(descriptorValue) is GPUValue:
+            uniforms.add "layout(set=" & $descriptorSetIndex & ", 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 & ";"
+            elif typeof(descriptorValue.data) is array:
+              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.data.len & "];"
             descriptorBinding.inc
-          else:
-            {.error: "Unsupported shader descriptor field " & descriptorName.}
-      descriptorSetCount.inc
+          elif typeof(descriptorValue) is array:
+            when elementType(descriptorValue) is Texture:
+              let arrayDecl = "[" & $typeof(descriptorValue).len & "]"
+              samplers.add "layout(set=" & $descriptorSetIndex & ", binding = " & $descriptorBinding & ") uniform " & GlslType(default(elementType(descriptorValue))) & " " & descriptorName & "" & arrayDecl & ";"
+              descriptorBinding.inc
+            else:
+              {.error: "Unsupported shader descriptor field " & descriptorName.}
+        descriptorSetIndex.inc
     elif fieldname in ["vertexCode", "fragmentCode"]:
       discard
     else:
@@ -290,6 +295,38 @@
         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 descriptor in value.fields:
+        var layoutbindings: seq[VkDescriptorSetLayoutBinding]
+        ForDescriptorFields(descriptor, fieldName, 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
+        )
+        checkVkResult vkCreateDescriptorSetLayout(
+          vulkan.device,
+          addr(layoutCreateInfo),
+          nil,
+          addr(result[setNumber])
+        )
+        inc setNumber
+
 proc CreatePipeline*[TShader](
   renderPass: VkRenderPass,
   topology: VkPrimitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
@@ -304,34 +341,12 @@
   const shader = default(TShader)
   (result.vertexShaderModule, result.fragmentShaderModule) = CompileShader(shader)
 
-  var n = 0'u32
-  for theFieldname, value in fieldPairs(default(TShader)):
-    when typeof(value) is DescriptorSet:
-      var layoutbindings: seq[VkDescriptorSetLayoutBinding]
-      ForDescriptorFields(value.data, fieldName, 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
-      )
-      checkVkResult vkCreateDescriptorSetLayout(
-        vulkan.device,
-        addr(layoutCreateInfo),
-        nil,
-        addr(result.descriptorSetLayouts[value.sType])
-      )
-      inc n
+  var nSets = GetDescriptorSetCount[TShader]()
+  result.descriptorSetLayouts = CreateDescriptorSetLayouts[TShader]()
   let pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
     sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
-    setLayoutCount: n,
-    pSetLayouts: if n == 0: nil else: result.descriptorSetLayouts.ToCPointer,
+    setLayoutCount: nSets,
+    pSetLayouts: if nSets == 0: nil else: result.descriptorSetLayouts.ToCPointer,
     # pushConstantRangeCount: uint32(pushConstants.len),
       # pPushConstantRanges: pushConstants.ToCPointer,
   )
@@ -463,10 +478,6 @@
     addr(result.vk)
   )
 
-proc GetLayoutFor*(pipeline: Pipeline, dType: DescriptorSetType): VkDescriptorSetLayout =
-  pipeline.descriptorSetLayouts[dType]
-
-
 template WithPipeline*(commandbuffer: VkCommandBuffer, pipeline: Pipeline, body: untyped): untyped =
   block:
     vkCmdBindPipeline(commandbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.vk)
--- a/semicongine/rendering/vulkan_wrappers.nim	Mon Jul 15 23:51:17 2024 +0700
+++ b/semicongine/rendering/vulkan_wrappers.nim	Tue Jul 16 11:53:43 2024 +0700
@@ -213,6 +213,18 @@
 proc svkResetFences*(fence: VkFence) =
   checkVkResult vkResetFences(vulkan.device, 1, addr(fence))
 
+proc svkCmdBindDescriptorSets(commandBuffer: VkCommandBuffer, descriptorSets: openArray[VkDescriptorSet], layout: VkPipelineLayout) =
+  vkCmdBindDescriptorSets(
+    commandBuffer = commandBuffer,
+    pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
+    layout = layout,
+    firstSet = 0,
+    descriptorSetCount = descriptorSets.len.uint32,
+    pDescriptorSets = descriptorSets.ToCPointer,
+    dynamicOffsetCount = 0,
+    pDynamicOffsets = nil
+  )
+
 proc BestMemory*(mappable: bool, filter: seq[uint32] = @[]): uint32 =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
   vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr(physicalProperties))
Binary file tests/test_rendering has changed
--- a/tests/test_rendering.nim	Mon Jul 15 23:51:17 2024 +0700
+++ b/tests/test_rendering.nim	Tue Jul 16 11:53:43 2024 +0700
@@ -126,13 +126,13 @@
     RenderSettings = object
       gamma: float32
     FirstDS = object
-      settings: GPUValue[RenderSettings, UniformBuffer]
+      settings: GPUValue[RenderSettings, UniformBufferMapped]
     QuadShader = object
       position {.VertexAttribute.}: Vec3f
       color {.VertexAttribute.}: Vec3f
       fragmentColor {.Pass.}: Vec3f
       outColor {.ShaderOutput.}: Vec4f
-      firstDS: DescriptorSet[FirstDS, First]
+      descriptorSets {.DescriptorSets.}: (FirstDS, )
       # code
       vertexCode: string = """void main() {
       fragmentColor = vec3(pow(color.r, settings.gamma), pow(color.g, settings.gamma), pow(color.b, settings.gamma));
@@ -145,30 +145,35 @@
       indices: GPUArray[uint16, IndexBuffer]
 
   var quad = QuadMesh(
-    position: asGPUArray([NewVec3f(-0.3, -0.3), NewVec3f(-0.3, 0.3), NewVec3f(0.3, 0.3), NewVec3f(0.3, -0.3)], VertexBuffer),
+    position: asGPUArray([NewVec3f(-0.9, -0.5), NewVec3f(-0.9, 0.5), NewVec3f(0.9, 0.5), NewVec3f(0.9, -0.5)], VertexBuffer),
     indices: asGPUArray([0'u16, 1'u16, 2'u16, 2'u16, 3'u16, 0'u16], IndexBuffer),
-    color: asGPUArray([NewVec3f(1, 1, 1), NewVec3f(1, 1, 1), NewVec3f(1, 1, 1), NewVec3f(1, 1, 1)], VertexBuffer),
+    color: asGPUArray([NewVec3f(0, 0, 0), NewVec3f(0, 0, 0), NewVec3f(1, 1, 1), NewVec3f(1, 1, 1)], VertexBuffer),
   )
-  var firstDs = DescriptorSet[FirstDS, First](
+  var settings = DescriptorSet[FirstDS](
     data: FirstDS(
-      settings: asGPUValue(RenderSettings(
-          gamma: 1.0'f32
-    ), UniformBuffer)
+        settings: asGPUValue(RenderSettings(
+            gamma: 0.01'f32
+    ), UniformBufferMapped)
   )
   )
   AssignBuffers(renderdata, quad)
-  AssignBuffers(renderdata, firstDs)
+  AssignBuffers(renderdata, settings)
   renderdata.FlushAllMemory()
 
   var pipeline = CreatePipeline[QuadShader](renderPass = mainRenderpass, samples = swapchain.samples)
 
+  InitDescriptorSet(renderdata, pipeline.descriptorSetLayouts[0], settings)
+
   var c = 0
   while UpdateInputs() and c < nFrames:
     WithNextFrame(swapchain, framebuffer, commandbuffer):
-      WithBind(commandbuffer, firstDs, pipeline, swapchain.currentFiF):
+      WithBind(commandbuffer, (settings, ), pipeline, swapchain.currentFiF):
         WithRenderPass(mainRenderpass, framebuffer, commandbuffer, swapchain.width, swapchain.height, NewVec4f(0, 0, 0, 0)):
           WithPipeline(commandbuffer, pipeline):
-            Render(commandbuffer = commandbuffer, pipeline = pipeline, firstSet = firstDs, mesh = quad)
+            Render(commandbuffer = commandbuffer, pipeline = pipeline, firstSet = settings, mesh = quad)
+    settings.data.settings.data.gamma = 0.01'f32 + (c.float32 / nFrames.float32) * 5'f32
+    UpdateGPUBuffer(settings.data.settings)
+    renderdata.FlushAllMemory()
     inc c
 
   # cleanup
@@ -177,7 +182,7 @@
   DestroyRenderData(renderdata)
 
 when isMainModule:
-  var nFrames = 100
+  var nFrames = 5000
   InitVulkan()
 
   # test normal
@@ -186,10 +191,10 @@
     swapchain = InitSwapchain(renderpass = mainRenderpass).get()
 
     # tests a simple triangle with minimalistic shader and vertex format
-    test_01_triangle(nFrames)
+    # test_01_triangle(nFrames)
 
     # tests instanced triangles and quads, mixing meshes and instances
-    test_02_triangle_quad_instanced(nFrames)
+    # test_02_triangle_quad_instanced(nFrames)
 
     # tests
     test_03_global_descriptorset(nFrames)
@@ -203,7 +208,7 @@
     mainRenderpass = CreatePresentationRenderPass(samples = VK_SAMPLE_COUNT_4_BIT)
     swapchain = InitSwapchain(renderpass = mainRenderpass, samples = VK_SAMPLE_COUNT_4_BIT).get()
 
-    test_01_triangle(nFrames)
+    # test_01_triangle(nFrames)
 
     checkVkResult vkDeviceWaitIdle(vulkan.device)
     vkDestroyRenderPass(vulkan.device, mainRenderpass, nil)