diff semiconginev2/rendering/renderer.nim @ 1218:56781cc0fc7c compiletime-tests

did: renamge main package
author sam <sam@basx.dev>
date Wed, 17 Jul 2024 21:01:37 +0700
parents semicongine/rendering/renderer.nim@04e446a7eb2b
children 4e465583ea32
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/rendering/renderer.nim	Wed Jul 17 21:01:37 2024 +0700
@@ -0,0 +1,650 @@
+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, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
+    of IndexBuffer: @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
+    of IndexBufferMapped: @[VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
+    of UniformBuffer: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
+    of UniformBufferMapped: @[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
+
+proc GetVkFormat(grayscale: bool, usage: openArray[VkImageUsageFlagBits]): VkFormat =
+  let formats = if grayscale: [VK_FORMAT_R8_SRGB, VK_FORMAT_R8_UNORM]
+                else: [VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM]
+
+  var formatProperties = VkImageFormatProperties2(sType: VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2)
+  for format in formats:
+    var formatInfo = VkPhysicalDeviceImageFormatInfo2(
+      sType: VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
+      format: format,
+      thetype: VK_IMAGE_TYPE_2D,
+      tiling: VK_IMAGE_TILING_OPTIMAL,
+      usage: usage.toBits,
+    )
+    let formatCheck = vkGetPhysicalDeviceImageFormatProperties2(
+      vulkan.physicalDevice,
+      addr formatInfo,
+      addr formatProperties,
+    )
+    if formatCheck == VK_SUCCESS: # found suitable format
+      return format
+    elif formatCheck == VK_ERROR_FORMAT_NOT_SUPPORTED: # nope, try to find other format
+      continue
+    else: # raise error
+      checkVkResult formatCheck
+
+  assert false, "Unable to find format for textures"
+
+
+func alignedTo[T: SomeInteger](value: T, alignment: T): T =
+  let remainder = value mod alignment
+  if remainder == 0:
+    return value
+  else:
+    return value + alignment - remainder
+
+template sType(descriptorSet: DescriptorSet): untyped =
+  get(genericParams(typeof(descriptorSet)), 1)
+
+template bufferType(gpuData: GPUData): untyped =
+  typeof(gpuData).TBuffer
+func NeedsMapping(bType: BufferType): bool =
+  bType in [VertexBufferMapped, IndexBufferMapped, UniformBufferMapped]
+template NeedsMapping(gpuData: GPUData): untyped =
+  gpuData.bufferType.NeedsMapping
+
+template size(gpuArray: GPUArray): uint64 =
+  (gpuArray.data.len * sizeof(elementType(gpuArray.data))).uint64
+template size(gpuValue: GPUValue): uint64 =
+  sizeof(gpuValue.data).uint64
+func size(texture: Texture): uint64 =
+  texture.data.len.uint64 * sizeof(elementType(texture.data)).uint64
+
+template rawPointer(gpuArray: GPUArray): pointer =
+  addr(gpuArray.data[0])
+template rawPointer(gpuValue: GPUValue): pointer =
+  addr(gpuValue.data)
+
+proc IsMappable(memoryTypeIndex: uint32): bool =
+  var physicalProperties: VkPhysicalDeviceMemoryProperties
+  vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr(physicalProperties))
+  let flags = toEnums(physicalProperties.memoryTypes[memoryTypeIndex].propertyFlags)
+  return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in flags
+
+proc InitDescriptorSet*(
+  renderData: RenderData,
+  layout: VkDescriptorSetLayout,
+  descriptorSet: var DescriptorSet,
+) =
+
+  # santization checks
+  for theName, value in descriptorSet.data.fieldPairs:
+    when typeof(value) is GPUValue:
+      assert value.buffer.vk.Valid
+    elif typeof(value) is Texture:
+      assert value.vk.Valid
+      assert value.imageview.Valid
+      assert value.sampler.Valid
+    elif typeof(value) is array:
+      when elementType(value) is Texture:
+        for t in value:
+          assert t.vk.Valid
+          assert t.imageview.Valid
+          assert t.sampler.Valid
+      elif elementType(value) is GPUValue:
+        for t in value:
+          assert t.buffer.vk.Valid
+      else:
+        {.error: "Unsupported descriptor set field: '" & theName & "'".}
+    else:
+      {.error: "Unsupported descriptor set field: '" & theName & "'".}
+
+  # allocate
+  var layouts = newSeqWith(descriptorSet.vk.len, layout)
+  var allocInfo = VkDescriptorSetAllocateInfo(
+    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+    descriptorPool: renderData.descriptorPool,
+    descriptorSetCount: uint32(layouts.len),
+    pSetLayouts: layouts.ToCPointer,
+  )
+  checkVkResult vkAllocateDescriptorSets(vulkan.device, addr(allocInfo), descriptorSet.vk.ToCPointer)
+
+  # allocate seq with high cap to prevent realocation while adding to set
+  # (which invalidates pointers that are passed to the vulkan api call)
+  var descriptorSetWrites = newSeqOfCap[VkWriteDescriptorSet](1024)
+  var imageWrites = newSeqOfCap[VkDescriptorImageInfo](1024)
+  var bufferWrites = newSeqOfCap[VkDescriptorBufferInfo](1024)
+
+  ForDescriptorFields(descriptorSet.data, fieldName, fieldValue, descriptorType, descriptorCount, descriptorBindingNumber):
+    for i in 0 ..< descriptorSet.vk.len:
+      when typeof(fieldValue) is GPUValue:
+        bufferWrites.add VkDescriptorBufferInfo(
+          buffer: fieldValue.buffer.vk,
+          offset: fieldValue.offset,
+          range: fieldValue.size,
+        )
+        descriptorSetWrites.add VkWriteDescriptorSet(
+          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+          dstSet: descriptorSet.vk[i],
+          dstBinding: descriptorBindingNumber,
+          dstArrayElement: 0,
+          descriptorType: descriptorType,
+          descriptorCount: descriptorCount,
+          pImageInfo: nil,
+          pBufferInfo: addr(bufferWrites[^1]),
+        )
+      elif typeof(fieldValue) is Texture:
+        imageWrites.add VkDescriptorImageInfo(
+          sampler: fieldValue.sampler,
+          imageView: fieldValue.imageView,
+          imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+        )
+        descriptorSetWrites.add VkWriteDescriptorSet(
+          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+          dstSet: descriptorSet.vk[i],
+          dstBinding: descriptorBindingNumber,
+          dstArrayElement: 0,
+          descriptorType: descriptorType,
+          descriptorCount: descriptorCount,
+          pImageInfo: addr(imageWrites[^1]),
+          pBufferInfo: nil,
+        )
+      elif typeof(fieldValue) is array:
+        when elementType(fieldValue) is Texture:
+          for texture in fieldValue:
+            imageWrites.add VkDescriptorImageInfo(
+              sampler: texture.sampler,
+              imageView: texture.imageView,
+              imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+            )
+          descriptorSetWrites.add VkWriteDescriptorSet(
+            sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+            dstSet: descriptorSet.vk[i],
+            dstBinding: descriptorBindingNumber,
+            dstArrayElement: 0,
+            descriptorType: descriptorType,
+            descriptorCount: descriptorCount,
+            pImageInfo: addr(imageWrites[^descriptorCount.int]),
+            pBufferInfo: nil,
+          )
+        elif elementType(fieldValue) is GPUValue:
+          for entry in fieldValue:
+            bufferWrites.add VkDescriptorBufferInfo(
+              buffer: entry.buffer.vk,
+              offset: entry.offset,
+              range: entry.size,
+            )
+          descriptorSetWrites.add VkWriteDescriptorSet(
+            sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+            dstSet: descriptorSet.vk[i],
+            dstBinding: descriptorBindingNumber,
+            dstArrayElement: 0,
+            descriptorType: descriptorType,
+            descriptorCount: descriptorCount,
+            pImageInfo: nil,
+            pBufferInfo: addr(bufferWrites[^descriptorCount.int]),
+          )
+        else:
+          {.error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldValue)).}
+      else:
+        {.error: "Unsupported descriptor type: " & typetraits.name(typeof(fieldValue)).}
+
+  vkUpdateDescriptorSets(
+    device = vulkan.device,
+    descriptorWriteCount = descriptorSetWrites.len.uint32,
+    pDescriptorWrites = descriptorSetWrites.ToCPointer,
+    descriptorCopyCount = 0,
+    pDescriptorCopies = nil,
+  )
+
+proc AllocateNewMemoryBlock(size: uint64, mType: uint32): MemoryBlock =
+  result = MemoryBlock(
+    vk: svkAllocateMemory(size, mType),
+    size: size,
+    rawPointer: nil,
+    offsetNextFree: 0,
+  )
+  if mType.IsMappable():
+    checkVkResult vkMapMemory(
+      device = vulkan.device,
+      memory = result.vk,
+      offset = 0'u64,
+      size = result.size,
+      flags = VkMemoryMapFlags(0),
+      ppData = addr(result.rawPointer)
+    )
+
+proc FlushAllMemory*(renderData: RenderData) =
+  var flushRegions = newSeq[VkMappedMemoryRange]()
+  for memoryBlocks in renderData.memory:
+    for memoryBlock in memoryBlocks:
+      if memoryBlock.rawPointer != nil and memoryBlock.offsetNextFree > 0:
+        flushRegions.add VkMappedMemoryRange(
+          sType: VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+          memory: memoryBlock.vk,
+          size: alignedTo(memoryBlock.offsetNextFree, svkGetPhysicalDeviceProperties().limits.nonCoherentAtomSize),
+        )
+  if flushRegions.len > 0:
+    checkVkResult vkFlushMappedMemoryRanges(vulkan.device, flushRegions.len.uint32, flushRegions.ToCPointer())
+
+proc AllocateNewBuffer(renderData: var RenderData, size: uint64, bufferType: BufferType): Buffer =
+  result = Buffer(
+    vk: svkCreateBuffer(size, bufferType.usage),
+    size: size,
+    rawPointer: nil,
+    offsetNextFree: 0,
+  )
+  let memoryRequirements = svkGetBufferMemoryRequirements(result.vk)
+  let memoryType = BestMemory(mappable = bufferType.NeedsMapping, filter = memoryRequirements.memoryTypes)
+
+  # check if there is an existing allocated memory block that is large enough to be used
+  var selectedBlockI = -1
+  for i in 0 ..< renderData.memory[memoryType].len:
+    let memoryBlock = renderData.memory[memoryType][i]
+    if memoryBlock.size - alignedTo(memoryBlock.offsetNextFree, memoryRequirements.alignment) >= memoryRequirements.size:
+      selectedBlockI = i
+      break
+  # otherwise, allocate a new block of memory and use that
+  if selectedBlockI < 0:
+    selectedBlockI = renderData.memory[memoryType].len
+    renderData.memory[memoryType].add AllocateNewMemoryBlock(
+      size = max(memoryRequirements.size, MEMORY_BLOCK_ALLOCATION_SIZE),
+      mType = memoryType
+    )
+
+  let selectedBlock = renderData.memory[memoryType][selectedBlockI]
+  renderData.memory[memoryType][selectedBlockI].offsetNextFree = alignedTo(
+    selectedBlock.offsetNextFree,
+    memoryRequirements.alignment,
+  )
+  checkVkResult vkBindBufferMemory(
+    vulkan.device,
+    result.vk,
+    selectedBlock.vk,
+    selectedBlock.offsetNextFree,
+  )
+  result.rawPointer = selectedBlock.rawPointer.pointerAddOffset(selectedBlock.offsetNextFree)
+  renderData.memory[memoryType][selectedBlockI].offsetNextFree += memoryRequirements.size
+
+proc UpdateGPUBuffer*(gpuData: GPUData) =
+  if gpuData.size == 0:
+    return
+  when NeedsMapping(gpuData):
+    copyMem(pointerAddOffset(gpuData.buffer.rawPointer, gpuData.offset), gpuData.rawPointer, gpuData.size)
+  else:
+    WithStagingBuffer((gpuData.buffer.vk, gpuData.offset), gpuData.size, stagingPtr):
+      copyMem(stagingPtr, gpuData.rawPointer, gpuData.size)
+
+proc UpdateAllGPUBuffers*[T](value: T) =
+  for name, fieldvalue in value.fieldPairs():
+    when typeof(fieldvalue) is GPUData:
+      UpdateGPUBuffer(fieldvalue)
+    when typeof(fieldvalue) is array:
+      when elementType(fieldvalue) is GPUData:
+        for entry in fieldvalue:
+          UpdateGPUBuffer(entry)
+
+proc AssignGPUData(renderdata: var RenderData, value: var GPUData) =
+  # find buffer that has space
+  var selectedBufferI = -1
+
+  for i in 0 ..< renderData.buffers[value.bufferType].len:
+    let buffer = renderData.buffers[value.bufferType][i]
+    if buffer.size - alignedTo(buffer.offsetNextFree, BUFFER_ALIGNMENT) >= value.size:
+      selectedBufferI = i
+
+  # otherwise create new buffer
+  if selectedBufferI < 0:
+    selectedBufferI = renderdata.buffers[value.bufferType].len
+    renderdata.buffers[value.bufferType].add renderdata.AllocateNewBuffer(
+      size = max(value.size, BUFFER_ALLOCATION_SIZE),
+      bufferType = value.bufferType,
+    )
+
+  # assigne value
+  let selectedBuffer = renderdata.buffers[value.bufferType][selectedBufferI]
+  renderdata.buffers[value.bufferType][selectedBufferI].offsetNextFree = alignedTo(
+    selectedBuffer.offsetNextFree,
+    BUFFER_ALIGNMENT
+  )
+  value.buffer = selectedBuffer
+  value.offset = renderdata.buffers[value.bufferType][selectedBufferI].offsetNextFree
+  renderdata.buffers[value.bufferType][selectedBufferI].offsetNextFree += value.size
+
+proc AssignBuffers*[T](renderdata: var RenderData, data: var T, uploadData = true) =
+  for name, value in fieldPairs(data):
+
+    when typeof(value) is GPUData:
+      AssignGPUData(renderdata, value)
+    elif typeof(value) is array:
+      when elementType(value) is GPUValue:
+        for v in value.mitems:
+          AssignGPUData(renderdata, v)
+
+  if uploadData:
+    UpdateAllGPUBuffers(data)
+
+proc AssignBuffers*(renderdata: var RenderData, descriptorSet: var DescriptorSet, uploadData = true) =
+  AssignBuffers(renderdata, descriptorSet.data, uploadData = uploadData)
+
+proc InitRenderData*(descriptorPoolLimit = 1024'u32): RenderData =
+  # allocate descriptor pools
+  var poolSizes = [
+    VkDescriptorPoolSize(thetype: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, descriptorCount: descriptorPoolLimit),
+    VkDescriptorPoolSize(thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: descriptorPoolLimit),
+  ]
+  var poolInfo = VkDescriptorPoolCreateInfo(
+    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+    poolSizeCount: poolSizes.len.uint32,
+    pPoolSizes: poolSizes.ToCPointer,
+    maxSets: descriptorPoolLimit,
+  )
+  checkVkResult vkCreateDescriptorPool(vulkan.device, addr(poolInfo), nil, addr(result.descriptorPool))
+
+proc DestroyRenderData*(renderData: RenderData) =
+  vkDestroyDescriptorPool(vulkan.device, renderData.descriptorPool, nil)
+
+  for buffers in renderData.buffers:
+    for buffer in buffers:
+      vkDestroyBuffer(vulkan.device, buffer.vk, nil)
+
+  for imageView in renderData.imageViews:
+    vkDestroyImageView(vulkan.device, imageView, nil)
+
+  for sampler in renderData.samplers:
+    vkDestroySampler(vulkan.device, sampler, nil)
+
+  for image in renderData.images:
+    vkDestroyImage(vulkan.device, image, nil)
+
+  for memoryBlocks in renderData.memory:
+    for memory in memoryBlocks:
+      vkFreeMemory(vulkan.device, memory.vk, nil)
+
+proc TransitionImageLayout(image: VkImage, oldLayout, newLayout: VkImageLayout) =
+  var
+    barrier = VkImageMemoryBarrier(
+      sType: VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+      oldLayout: oldLayout,
+      newLayout: newLayout,
+      srcQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
+      dstQueueFamilyIndex: VK_QUEUE_FAMILY_IGNORED,
+      image: image,
+      subresourceRange: VkImageSubresourceRange(
+        aspectMask: toBits [VK_IMAGE_ASPECT_COLOR_BIT],
+        baseMipLevel: 0,
+        levelCount: 1,
+        baseArrayLayer: 0,
+        layerCount: 1,
+      ),
+    )
+    srcStage: VkPipelineStageFlagBits
+    dstStage: VkPipelineStageFlagBits
+
+  if oldLayout == VK_IMAGE_LAYOUT_UNDEFINED and newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
+    srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
+    barrier.srcAccessMask = VkAccessFlags(0)
+    dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT
+    barrier.dstAccessMask = [VK_ACCESS_TRANSFER_WRITE_BIT].toBits
+  elif oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL and newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
+    srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT
+    barrier.srcAccessMask = [VK_ACCESS_TRANSFER_WRITE_BIT].toBits
+    dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
+    barrier.dstAccessMask = [VK_ACCESS_SHADER_READ_BIT].toBits
+  else:
+    raise newException(Exception, "Unsupported layout transition!")
+
+  WithSingleUseCommandBuffer(commandBuffer):
+    vkCmdPipelineBarrier(
+      commandBuffer,
+      srcStageMask = [srcStage].toBits,
+      dstStageMask = [dstStage].toBits,
+      dependencyFlags = VkDependencyFlags(0),
+      memoryBarrierCount = 0,
+      pMemoryBarriers = nil,
+      bufferMemoryBarrierCount = 0,
+      pBufferMemoryBarriers = nil,
+      imageMemoryBarrierCount = 1,
+      pImageMemoryBarriers = addr(barrier),
+    )
+
+proc createSampler(
+  magFilter = VK_FILTER_LINEAR,
+  minFilter = VK_FILTER_LINEAR,
+  addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+  addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT,
+): VkSampler =
+
+  let samplerInfo = VkSamplerCreateInfo(
+    sType: VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
+    magFilter: magFilter,
+    minFilter: minFilter,
+    addressModeU: addressModeU,
+    addressModeV: addressModeV,
+    addressModeW: VK_SAMPLER_ADDRESS_MODE_REPEAT,
+    anisotropyEnable: vulkan.anisotropy > 0,
+    maxAnisotropy: vulkan.anisotropy,
+    borderColor: VK_BORDER_COLOR_INT_OPAQUE_BLACK,
+    unnormalizedCoordinates: VK_FALSE,
+    compareEnable: VK_FALSE,
+    compareOp: VK_COMPARE_OP_ALWAYS,
+    mipmapMode: VK_SAMPLER_MIPMAP_MODE_LINEAR,
+    mipLodBias: 0,
+    minLod: 0,
+    maxLod: 0,
+  )
+  checkVkResult vkCreateSampler(vulkan.device, addr(samplerInfo), nil, addr(result))
+
+proc createTextureImage(renderData: var RenderData, texture: var Texture) =
+  assert texture.vk == VkImage(0), "Texture has already been created"
+  var usage = @[VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_USAGE_SAMPLED_BIT]
+  if texture.isRenderTarget:
+    usage.add VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
+  let format = GetVkFormat(elementType(texture.data) is TVec1[uint8], usage = usage)
+
+  texture.vk = svkCreate2DImage(texture.width, texture.height, format, usage)
+  renderData.images.add texture.vk
+  texture.sampler = createSampler(magFilter = texture.interpolation, minFilter = texture.interpolation)
+  renderData.samplers.add texture.sampler
+
+  let memoryRequirements = texture.vk.svkGetImageMemoryRequirements()
+  let memoryType = BestMemory(mappable = false, filter = memoryRequirements.memoryTypes)
+  # check if there is an existing allocated memory block that is large enough to be used
+  var selectedBlockI = -1
+  for i in 0 ..< renderData.memory[memoryType].len:
+    let memoryBlock = renderData.memory[memoryType][i]
+    if memoryBlock.size - alignedTo(memoryBlock.offsetNextFree, memoryRequirements.alignment) >= memoryRequirements.size:
+      selectedBlockI = i
+      break
+  # otherwise, allocate a new block of memory and use that
+  if selectedBlockI < 0:
+    selectedBlockI = renderData.memory[memoryType].len
+    renderData.memory[memoryType].add AllocateNewMemoryBlock(
+      size = max(memoryRequirements.size, MEMORY_BLOCK_ALLOCATION_SIZE),
+      mType = memoryType
+    )
+  let selectedBlock = renderData.memory[memoryType][selectedBlockI]
+  renderData.memory[memoryType][selectedBlockI].offsetNextFree = alignedTo(
+    selectedBlock.offsetNextFree,
+    memoryRequirements.alignment,
+  )
+
+  checkVkResult vkBindImageMemory(
+    vulkan.device,
+    texture.vk,
+    selectedBlock.vk,
+    renderData.memory[memoryType][selectedBlockI].offsetNextFree,
+  )
+  renderData.memory[memoryType][selectedBlockI].offsetNextFree += memoryRequirements.size
+
+  # imageview can only be created after memory is bound
+  texture.imageview = svkCreate2DImageView(texture.vk, format)
+  renderData.imageViews.add texture.imageview
+
+  # data transfer and layout transition
+  TransitionImageLayout(texture.vk, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
+  WithStagingBuffer(
+    (texture.vk, texture.width, texture.height),
+    memoryRequirements.size,
+    stagingPtr
+  ):
+    copyMem(stagingPtr, texture.data.ToCPointer, texture.size)
+  TransitionImageLayout(texture.vk, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+
+
+proc UploadTextures*(renderdata: var RenderData, descriptorSet: var DescriptorSet) =
+  for name, value in fieldPairs(descriptorSet.data):
+    when typeof(value) is Texture:
+      renderdata.createTextureImage(value)
+    elif typeof(value) is array:
+      when elementType(value) is Texture:
+        for texture in value.mitems:
+          renderdata.createTextureImage(texture)
+
+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
+
+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, 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:
+    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
+
+proc Render*[TShader, TMesh, TInstance](
+  commandBuffer: VkCommandBuffer,
+  pipeline: Pipeline[TShader],
+  mesh: TMesh,
+  instances: TInstance,
+) =
+
+  var vertexBuffers: seq[VkBuffer]
+  var vertexBuffersOffsets: seq[uint64]
+  var elementCount = 0'u32
+  var instanceCount = 1'u32
+
+  for shaderAttributeName, shaderAttribute in default(TShader).fieldPairs:
+    when hasCustomPragma(shaderAttribute, VertexAttribute):
+      for meshName, meshValue in mesh.fieldPairs:
+        when meshName == shaderAttributeName:
+          vertexBuffers.add meshValue.buffer.vk
+          vertexBuffersOffsets.add meshValue.offset
+          elementCount = meshValue.data.len.uint32
+    elif hasCustomPragma(shaderAttribute, InstanceAttribute):
+      for instanceName, instanceValue in instances.fieldPairs:
+        when instanceName == shaderAttributeName:
+          vertexBuffers.add instanceValue.buffer.vk
+          vertexBuffersOffsets.add instanceValue.offset
+          instanceCount = instanceValue.data.len.uint32
+
+  if vertexBuffers.len > 0:
+    vkCmdBindVertexBuffers(
+      commandBuffer = commandBuffer,
+      firstBinding = 0'u32,
+      bindingCount = uint32(vertexBuffers.len),
+      pBuffers = vertexBuffers.ToCPointer(),
+      pOffsets = vertexBuffersOffsets.ToCPointer()
+    )
+
+  var indexBuffer: VkBuffer
+  var indexBufferOffset: uint64
+  var indexType = VK_INDEX_TYPE_NONE_KHR
+
+  for meshName, meshValue in mesh.fieldPairs:
+    when typeof(meshValue) is GPUArray[uint8, IndexBuffer]:
+      indexBuffer = meshValue.buffer.vk
+      indexBufferOffset = meshValue.offset
+      indexType = VK_INDEX_TYPE_UINT8_EXT
+      elementCount = meshValue.data.len.uint32
+    elif typeof(meshValue) is GPUArray[uint16, IndexBuffer]:
+      indexBuffer = meshValue.buffer.vk
+      indexBufferOffset = meshValue.offset
+      indexType = VK_INDEX_TYPE_UINT16
+      elementCount = meshValue.data.len.uint32
+    elif typeof(meshValue) is GPUArray[uint32, IndexBuffer]:
+      indexBuffer = meshValue.buffer.vk
+      indexBufferOffset = meshValue.offset
+      indexType = VK_INDEX_TYPE_UINT32
+      elementCount = meshValue.data.len.uint32
+
+  assert elementCount > 0
+
+  if indexType != VK_INDEX_TYPE_NONE_KHR:
+    vkCmdBindIndexBuffer(
+      commandBuffer,
+      indexBuffer,
+      indexBufferOffset,
+      indexType,
+    )
+    vkCmdDrawIndexed(
+      commandBuffer = commandBuffer,
+      indexCount = elementCount,
+      instanceCount = instanceCount,
+      firstIndex = 0,
+      vertexOffset = 0,
+      firstInstance = 0
+    )
+  else:
+    vkCmdDraw(
+      commandBuffer = commandBuffer,
+      vertexCount = elementCount,
+      instanceCount = instanceCount,
+      firstVertex = 0,
+      firstInstance = 0
+    )
+
+type EMPTY = object
+
+proc Render*[TShader, TMesh](
+  commandBuffer: VkCommandBuffer,
+  pipeline: Pipeline[TShader],
+  mesh: TMesh,
+) =
+  Render(commandBuffer, pipeline, mesh, EMPTY())
+
+proc asGPUArray*[T](data: openArray[T], bufferType: static BufferType): auto =
+  GPUArray[T, bufferType](data: @data)
+
+proc asGPUValue*[T](data: T, bufferType: static BufferType): auto =
+  GPUValue[T, bufferType](data: data)
+
+proc asDescriptorSet*[T](data: T): auto =
+  DescriptorSet[T](data: data)