changeset 1186:52e926efaac5 compiletime-tests

sync from bedroom to office
author sam <sam@basx.dev>
date Sun, 07 Jul 2024 00:36:15 +0700
parents 565fcfde427a
children b14861786b61
files static_utils.nim
diffstat 1 files changed, 185 insertions(+), 344 deletions(-) [+]
line wrap: on
line diff
--- a/static_utils.nim	Sat Jul 06 15:05:28 2024 +0700
+++ b/static_utils.nim	Sun Jul 07 00:36:15 2024 +0700
@@ -1,3 +1,4 @@
+import std/algorithm
 import std/os
 import std/enumerate
 import std/hashes
@@ -19,11 +20,12 @@
 template Pass {.pragma.}
 template PassFlat {.pragma.}
 template ShaderOutput {.pragma.}
-template VertexIndices {.pragma.}
 
 const INFLIGHTFRAMES = 2'u32
 const MEMORY_ALIGNMENT = 65536'u64 # Align buffers inside memory along this alignment
 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
 
 # some globals that will (likely?) never change during the life time of the engine
 type
@@ -37,23 +39,28 @@
   IndexType = enum
     None, UInt8, UInt16, UInt32
 
-  IndirectGPUMemory = object
-    vk: VkDeviceMemory
-    size: uint64
-    needsTransfer: bool # usually true
-  DirectGPUMemory = object
+  MemoryBlock = object
     vk: VkDeviceMemory
     size: uint64
-    rawPointer: pointer
-  GPUMemory = IndirectGPUMemory | DirectGPUMemory
+    rawPointer: pointer # if not nil, this is mapped memory
+    offsetNextFree: uint64
 
-  Buffer[TMemory: GPUMemory] = object
+  BufferType = enum
+    VertexBuffer
+    VertexBufferMapped
+    IndexBuffer
+    IndexBufferMapped
+    UniformBuffer
+    UniformBufferMapped
+  Buffer = object
     vk: VkBuffer
     size: uint64
-    rawPointer: pointer
-  Texture[T: TextureType, TMemory: GPUMemory] = object
+    rawPointer: pointer # if not nil, buffer is using mapped memory
+    offsetNextFree: uint64
+
+  Texture[T: TextureType] = object
     vk: VkImage
-    memory: TMemory
+    memory: MemoryBlock
     format: VkFormat
     imageview: VkImageView
     sampler: VkSampler
@@ -63,13 +70,13 @@
     height: uint32
     data: seq[T]
 
-  GPUArray[T: SupportedGPUType, TMemory: GPUMemory] = object
+  GPUArray[T: SupportedGPUType, TBuffer: static BufferType] = object
     data: seq[T]
-    buffer: Buffer[TMemory]
+    buffer: Buffer
     offset: uint64
-  GPUValue[T: object|array, TMemory: GPUMemory] = object
+  GPUValue[T: object|array, TBuffer: static BufferType] = object
     data: T
-    buffer: Buffer[TMemory]
+    buffer: Buffer
     offset: uint64
   GPUData = GPUArray | GPUValue
 
@@ -84,15 +91,10 @@
     vk: VkPipeline
     layout: VkPipelineLayout
     descriptorSetLayouts: array[DescriptorSetType, VkDescriptorSetLayout]
-  BufferType = enum
-    VertexBuffer, IndexBuffer, UniformBuffer
   RenderData = object
     descriptorPool: VkDescriptorPool
-    # tuple is memory and offset to next free allocation in that memory
-    indirectMemory: seq[tuple[memory: IndirectGPUMemory, usedOffset: uint64]]
-    directMemory: seq[tuple[memory: DirectGPUMemory, usedOffset: uint64]]
-    indirectBuffers: seq[tuple[buffer: Buffer[IndirectGPUMemory], btype: BufferType, usedOffset: uint64]]
-    directBuffers: seq[tuple[buffer: Buffer[DirectGPUMemory], btype: BufferType, usedOffset: uint64]]
+    memory: array[VK_MAX_MEMORY_TYPES.int, seq[MemoryBlock]]
+    buffers: array[BufferType, seq[Buffer]]
 
 func size(texture: Texture): uint64 =
   texture.data.len * sizeof(elementType(texture.data))
@@ -100,9 +102,17 @@
 func depth(texture: Texture): int =
   default(elementType(texture.data)).len
 
-func pointerOffset[T: SomeInteger](p: pointer, offset: T): pointer =
+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(depth: int, usage: openArray[VkImageUsageFlagBits]): VkFormat =
   const DEPTH_FORMAT_MAP = [
@@ -322,10 +332,12 @@
 template sType(descriptorSet: DescriptorSet): untyped =
   get(genericParams(typeof(gpuData)), 1)
 
-template UsesIndirectMemory(gpuData: GPUData): untyped =
-  get(genericParams(typeof(gpuData)), 1) is IndirectGPUMemory
-template UsesDirectMemory(gpuData: GPUData): untyped =
-  get(genericParams(typeof(gpuData)), 1) is DirectGPUMemory
+template bufferType(gpuData: GPUData): untyped =
+  get(genericParams(typeof(gpuData)), 1)
+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
@@ -337,16 +349,6 @@
 template rawPointer(gpuValue: GPUValue): pointer =
   addr(gpuValue.data)
 
-proc RequiredMemorySize(buffer: VkBuffer): uint64 =
-  var req: VkMemoryRequirements
-  vkGetBufferMemoryRequirements(vulkan.device, buffer, addr(req))
-  return req.size
-
-proc RequiredMemorySize(image: VkImage): uint64 =
-  var req: VkMemoryRequirements
-  vkGetImageMemoryRequirements(vulkan.device, image, addr req)
-  return req.size
-
 proc GetPhysicalDevice(instance: VkInstance): VkPhysicalDevice =
   var nDevices: uint32
   checkVkResult vkEnumeratePhysicalDevices(instance, addr(nDevices), nil)
@@ -371,22 +373,11 @@
 
   assert score > 0, "Unable to find integrated or discrete GPU"
 
-
-proc GetDirectMemoryType(): uint32 =
+proc IsMappable(memoryTypeIndex: uint32): bool =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
   vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr(physicalProperties))
-
-  var biggestHeap: uint64 = 0
-  result = high(uint32)
-  # try to find host-visible type
-  for i in 0'u32 ..< physicalProperties.memoryTypeCount:
-    let flags = toEnums(physicalProperties.memoryTypes[i].propertyFlags)
-    if VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in flags:
-      let size = physicalProperties.memoryHeaps[physicalProperties.memoryTypes[i].heapIndex].size
-      if size > biggestHeap:
-        biggestHeap = size
-        result = i
-  assert result != high(uint32), "There is not host visible memory. This is likely a driver bug."
+  let flags = toEnums(physicalProperties.memoryTypes[memoryTypeIndex].propertyFlags)
+  return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in flags
 
 proc GetQueueFamily(pDevice: VkPhysicalDevice, qType: VkQueueFlagBits): uint32 =
   var nQueuefamilies: uint32
@@ -402,20 +393,6 @@
   # EVERY windows driver and almost every linux driver should support this
   VK_FORMAT_B8G8R8A8_SRGB
 
-proc UpdateGPUBuffer(gpuData: GPUData) =
-  if gpuData.size == 0:
-    return
-  when UsesDirectMemory(gpuData):
-    copyMem(pointerOffset(gpuData.buffer.rawPointer, gpuData.offset), gpuData.rawPointer, gpuData.size)
-  else:
-    WithStagingBuffer((gpuData.buffer.vk, gpuData.offset), gpuData.buffer.vk.RequiredMemorySize(), GetDirectMemoryType(), 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)
-
 proc InitDescriptorSet(
   renderData: RenderData,
   layout: VkDescriptorSetLayout,
@@ -425,11 +402,11 @@
   for name, value in descriptorSet.data.fieldPairs:
     when typeof(value) is GPUValue:
       assert value.buffer.vk.Valid
-    # TODO:
-    # when typeof(value) is Texture:
-    # assert value.texture.vk.Valid
+    elif typeof(value) is Texture:
+      assert value.vk.Valid
+      assert value.imageview.Valid
+      assert value.sampler.Valid
 
-  # TODO: missing stuff here? only for FiF, but not multiple different sets?
   # allocate
   var layouts = newSeqWith(descriptorSet.vk.len, layout)
   var allocInfo = VkDescriptorSetAllocateInfo(
@@ -896,116 +873,119 @@
     addr(result.vk)
   )
 
-proc AllocateIndirectMemory(size: uint64): IndirectGPUMemory =
-  # chooses biggest memory type that has NOT VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
-  result.size = size
-  result.needsTransfer = true
-
-  # find a good memory type
-  var physicalProperties: VkPhysicalDeviceMemoryProperties
-  vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr physicalProperties)
-
-  var biggestHeap: uint64 = 0
-  var memoryTypeIndex = high(uint32)
-  # try to find non-host-visible type
-  for i in 0'u32 ..< physicalProperties.memoryTypeCount:
-    if not (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in toEnums(physicalProperties.memoryTypes[i].propertyFlags)):
-      let size = physicalProperties.memoryHeaps[physicalProperties.memoryTypes[i].heapIndex].size
-      if size > biggestHeap:
-        biggestHeap = size
-        memoryTypeIndex = i
+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)
+    )
 
-  # If we did not found a device-only memory type, let's just take the biggest overall
-  if memoryTypeIndex == high(uint32):
-    result.needsTransfer = false
-    for i in 0'u32 ..< physicalProperties.memoryTypeCount:
-      let size = physicalProperties.memoryHeaps[physicalProperties.memoryTypes[i].heapIndex].size
-      if size > biggestHeap:
-        biggestHeap = size
-        memoryTypeIndex = i
-
-  assert memoryTypeIndex != high(uint32), "Unable to find indirect memory type"
-  result.vk = svkAllocateMemory(result.size, memoryTypeIndex)
-
-proc AllocateDirectMemory(size: uint64): DirectGPUMemory =
-  result.size = size
+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: memoryBlock.offsetNextFree,
+        )
+  if flushRegions.len > 0:
+    checkVkResult vkFlushMappedMemoryRanges(vulkan.device, flushRegions.len.uint32, flushRegions.ToCPointer())
 
-  # find a good memory type
-  var physicalProperties: VkPhysicalDeviceMemoryProperties
-  vkGetPhysicalDeviceMemoryProperties(vulkan.physicalDevice, addr physicalProperties)
+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)
 
-  var biggestHeap: uint64 = 0
-  var memoryTypeIndex = high(uint32)
-  # try to find host-visible type
-  for i in 0 ..< physicalProperties.memoryTypeCount:
-    let flags = toEnums(physicalProperties.memoryTypes[i].propertyFlags)
-    if VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in flags:
-      let size = physicalProperties.memoryHeaps[physicalProperties.memoryTypes[i].heapIndex].size
-      if size > biggestHeap:
-        biggestHeap = size
-        memoryTypeIndex = i
+  # 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(size, MEMORY_BLOCK_ALLOCATION_SIZE),
+      mType = memoryType
+    )
 
-  assert memoryTypeIndex != high(uint32), "Unable to find indirect memory type"
-  result.vk = svkAllocateMemory(result.size, GetDirectMemoryType())
-  checkVkResult vkMapMemory(
-    device = vulkan.device,
-    memory = result.vk,
-    offset = 0'u64,
-    size = result.size,
-    flags = VkMemoryMapFlags(0),
-    ppData = addr(result.rawPointer)
+  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 AllocateIndirectBuffer(renderData: var RenderData, size: uint64, btype: BufferType) =
-  if size == 0:
-    return
-  var buffer = Buffer[IndirectGPUMemory](size: size, rawPointer: nil)
+proc AssignBuffers[T](renderdata: var RenderData, data: var T) =
+  for name, value in fieldPairs(data):
+    when typeof(value) is GPUData:
 
-  let usageFlags = case btype:
-    of VertexBuffer: [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 UniformBuffer: [VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
+      # 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
 
-  # iterate through memory areas to find big enough free space
-  # TODO: dynamically expand memory allocations
-  # TODO: use RequiredMemorySize()
-  for (memory, usedOffset) in renderData.indirectMemory.mitems:
-    if memory.size - usedOffset >= size:
-      # create buffer
-      buffer.vk = svkCreateBuffer(buffer.size, usageFlags)
-      checkVkResult vkBindBufferMemory(vulkan.device, buffer.vk, memory.vk, usedOffset)
-      renderData.indirectBuffers.add (buffer, btype, 0'u64)
-      # update memory area offset
-      usedOffset = alignedTo(usedOffset + size, MEMORY_ALIGNMENT)
-      return
-
-  assert false, "Did not find allocated memory region with enough space"
-
-proc AllocateDirectBuffer(renderData: var RenderData, size: uint64, btype: BufferType) =
-  if size == 0:
-    return
+      # 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),
+          bType = value.bufferType,
+          mappable = value.NeedsMapping,
+        )
 
-  var buffer = Buffer[DirectGPUMemory](size: size)
-
-  let usageFlags = case btype:
-    of VertexBuffer: [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 UniformBuffer: [VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT]
+      # 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
 
-  # iterate through memory areas to find big enough free space
-  # TODO: dynamically expand memory allocations
-  # TODO: use RequiredMemorySize()
-  for (memory, usedOffset) in renderData.directMemory.mitems:
-    if memory.size - usedOffset >= size:
-      buffer.vk = svkCreateBuffer(buffer.size, usageFlags)
-      checkVkResult vkBindBufferMemory(vulkan.device, buffer.vk, memory.vk, usedOffset)
-      buffer.rawPointer = pointerOffset(memory.rawPointer, usedOffset)
-      renderData.directBuffers.add (buffer, btype, 0'u64)
-      # update memory area offset
-      usedOffset = alignedTo(usedOffset + size, MEMORY_ALIGNMENT)
-      return
+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.buffer.size, stagingPtr):
+      copyMem(stagingPtr, gpuData.rawPointer, gpuData.size)
 
-  assert false, "Did not find allocated memory region with enough space"
+proc UpdateAllGPUBuffers[T](value: T) =
+  for name, fieldvalue in value.fieldPairs():
+    when typeof(fieldvalue) is GPUData:
+      UpdateGPUBuffer(fieldvalue)
+
 
 proc InitRenderData(descriptorPoolLimit = 1024'u32): RenderData =
   # allocate descriptor pools
@@ -1021,95 +1001,6 @@
   )
   checkVkResult vkCreateDescriptorPool(vulkan.device, addr(poolInfo), nil, addr(result.descriptorPool))
 
-  # allocate some memory
-  var initialAllocationSize = 1_000_000_000'u64 # TODO: make this more dynamic or something?
-  result.indirectMemory = @[(memory: AllocateIndirectMemory(size = initialAllocationSize), usedOffset: 0'u64)]
-  result.directMemory = @[(memory: AllocateDirectMemory(size = initialAllocationSize), usedOffset: 0'u64)]
-
-proc FlushDirectMemory(renderData: RenderData) =
-  var flushRegions = newSeqOfCap[VkMappedMemoryRange](renderData.directMemory.len)
-  for entry in renderData.directMemory:
-    if entry.usedOffset > 0:
-      flushRegions.add VkMappedMemoryRange(
-        sType: VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
-        memory: entry.memory.vk,
-        size: entry.usedOffset,
-      )
-  if flushRegions.len > 0:
-    checkVkResult vkFlushMappedMemoryRanges(vulkan.device, flushRegions.len.uint32, flushRegions.ToCPointer())
-
-# For the Get*BufferSize:
-# BUFFER_ALIGNMENT is just added for a rough estimate, to ensure we have enough space to align when binding
-proc GetIndirectBufferSizes[T](data: T): uint64 =
-  for name, value in fieldPairs(data):
-    when not hasCustomPragma(value, VertexIndices):
-      when typeof(value) is GPUData:
-        when UsesIndirectMemory(value):
-          result += value.size + BUFFER_ALIGNMENT
-proc GetIndirectBufferSizes(data: DescriptorSet): uint64 =
-  GetIndirectBufferSizes(data.data)
-proc GetDirectBufferSizes[T](data: T): uint64 =
-  for name, value in fieldPairs(data):
-    when not hasCustomPragma(value, VertexIndices):
-      when typeof(value) is GPUData:
-        when UsesDirectMemory(value):
-          result += value.size + BUFFER_ALIGNMENT
-proc GetDirectBufferSizes(data: DescriptorSet): uint64 =
-  GetDirectBufferSizes(data.data)
-proc GetIndirectIndexBufferSizes[T](data: T): uint64 =
-  for name, value in fieldPairs(data):
-    when hasCustomPragma(value, VertexIndices):
-      static: assert typeof(value) is GPUArray, "Index buffers must be of type GPUArray"
-      static: assert elementType(value.data) is uint8 or elementType(value.data) is uint16 or elementType(value.data) is uint32
-      when UsesIndirectMemory(value):
-        result += value.size + BUFFER_ALIGNMENT
-proc GetDirectIndexBufferSizes[T](data: T): uint64 =
-  for name, value in fieldPairs(data):
-    when hasCustomPragma(value, VertexIndices):
-      static: assert typeof(value) is GPUArray, "Index buffers must be of type GPUArray"
-      static: assert elementType(value.data) is uint8 or elementType(value.data) is uint16 or elementType(value.data) is uint32
-      when UsesDirectMemory(value):
-        result += value.size + BUFFER_ALIGNMENT
-
-proc AssignIndirectBuffers[T](renderdata: var RenderData, btype: BufferType, data: var T) =
-  for name, value in fieldPairs(data):
-    when typeof(value) is GPUData:
-      when UsesIndirectMemory(value):
-        # find next buffer of correct type with enough free space
-        if btype == IndexBuffer == value.hasCustomPragma(VertexIndices):
-          var foundBuffer = false
-          for (buffer, bt, offset) in renderData.indirectBuffers.mitems:
-            if bt == btype and buffer.size - offset >= value.size:
-              assert not value.buffer.vk.Valid, "GPUData-Buffer has already been assigned"
-              assert buffer.vk.Valid, "RenderData-Buffer has not yet been created"
-              value.buffer = buffer
-              value.offset = offset
-              offset = alignedTo(offset + value.size, BUFFER_ALIGNMENT)
-              foundBuffer = true
-              break
-          assert foundBuffer, &"Unable to find large enough '{btype}' for '{data}'"
-proc AssignIndirectBuffers(renderdata: var RenderData, btype: BufferType, data: var DescriptorSet) =
-  AssignIndirectBuffers(renderdata, btype, data.data)
-proc AssignDirectBuffers[T](renderdata: var RenderData, btype: BufferType, data: var T) =
-  for name, value in fieldPairs(data):
-    when typeof(value) is GPUData:
-      when UsesDirectMemory(value):
-        # find next buffer of correct type with enough free space
-        if btype == IndexBuffer == value.hasCustomPragma(VertexIndices):
-          var foundBuffer = false
-          for (buffer, bt, offset) in renderData.directBuffers.mitems:
-            if bt == btype and buffer.size - offset >= value.size:
-              assert not value.buffer.vk.Valid, "GPUData-Buffer has already been assigned"
-              assert buffer.vk.Valid, "RenderData-Buffer has not yet been created"
-              value.buffer = buffer
-              value.offset = offset
-              offset = alignedTo(offset + value.size, BUFFER_ALIGNMENT)
-              foundBuffer = true
-              break
-          assert foundBuffer, &"Unable to find large enough '{btype}' for '{data}'"
-proc AssignDirectBuffers(renderdata: var RenderData, btype: BufferType, data: var DescriptorSet) =
-  AssignDirectBuffers(renderdata, btype, data.data)
-
 proc TransitionImageLayout(image: VkImage, oldLayout, newLayout: VkImageLayout) =
   var
     barrier = VkImageMemoryBarrier(
@@ -1213,24 +1104,17 @@
 
   texture.format = GetVkFormat(texture.depth, usage = usage)
   texture.vk = svkCreate2DImage(texture.width, texture.height, texture.format, usage)
-  let size = texture.vk.RequiredMemorySize()
+  let reqs = texture.vk.svkGetImageMemoryRequirements()
+
+  # TODO
 
-  when genericParams(typeof(texture)).get(1) is IndirectGPUMemory:
-    for (memory, usedOffset) in renderData.indirectMemory.mitems:
-      if memory.size - usedOffset >= size:
-        texture.memory = memory
-        texture.offset = usedOffset
-        # update memory area offset
-        usedOffset = alignedTo(usedOffset + size, MEMORY_ALIGNMENT)
-        break
-  elif genericParams(typeof(texture)).get(1) is DirectGPUMemory:
-    for (memory, usedOffset) in renderData.directMemory.mitems:
-      if memory.size - usedOffset >= size:
-        texture.memory = memory
-        texture.offset = usedOffset
-        # update memory area offset
-        usedOffset = alignedTo(usedOffset + size, MEMORY_ALIGNMENT)
-        break
+  for (memory, usedOffset) in renderData.indirectMemory.mitems:
+    if memory.size - usedOffset >= size:
+      texture.memory = memory
+      texture.offset = usedOffset
+      # update memory area offset
+      usedOffset = alignedTo(usedOffset + size, MEMORY_ALIGNMENT)
+      break
 
   checkVkResult vkBindImageMemory(
     vulkan.device,
@@ -1241,7 +1125,7 @@
 
   # data transfer and layout transition
   TransitionImageLayout(texture.vk, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
-  WithStagingBuffer((texture.vk, texture.imageview, texture.width, texture.height), size, GetDirectMemoryType(), stagingPtr):
+  WithStagingBuffer((texture.vk, texture.imageview, texture.width, texture.height), size, stagingPtr):
     copyMem(stagingPtr, texture.data.ToCPointer, size)
     TransitionImageLayout(texture.vk, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
 
@@ -1376,24 +1260,24 @@
 
   type
     MeshA = object
-      position: GPUArray[Vec3f, IndirectGPUMemory]
-      indices {.VertexIndices.}: GPUArray[uint16, IndirectGPUMemory]
+      position: GPUArray[Vec3f, VertexBuffer]
+      indices: GPUArray[uint16, IndexBuffer]
     InstanceA = object
-      rotation: GPUArray[Vec4f, IndirectGPUMemory]
-      objPosition: GPUArray[Vec3f, IndirectGPUMemory]
+      rotation: GPUArray[Vec4f, VertexBuffer]
+      objPosition: GPUArray[Vec3f, VertexBuffer]
     MaterialA = object
       reflection: float32
       baseColor: Vec3f
     UniformsA = object
-      defaultTexture: Texture[TVec3[uint8], IndirectGPUMemory]
-      defaultMaterial: GPUValue[MaterialA, IndirectGPUMemory]
-      materials: GPUValue[array[3, MaterialA], IndirectGPUMemory]
-      materialTextures: array[3, Texture[TVec3[uint8], IndirectGPUMemory]]
+      defaultTexture: Texture[TVec3[uint8]]
+      defaultMaterial: GPUValue[MaterialA, UniformBuffer]
+      materials: GPUValue[array[3, MaterialA], UniformBuffer]
+      materialTextures: array[3, Texture[TVec3[uint8]]]
     ShaderSettings = object
       gamma: float32
     GlobalsA = object
-      fontAtlas: Texture[TVec3[uint8], IndirectGPUMemory]
-      settings: GPUValue[ShaderSettings, IndirectGPUMemory]
+      fontAtlas: Texture[TVec3[uint8]]
+      settings: GPUValue[ShaderSettings, UniformBuffer]
 
     ShaderA = object
       # vertex input
@@ -1442,31 +1326,31 @@
   )
 
   var myMesh1 = MeshA(
-    position: GPUArray[Vec3f, IndirectGPUMemory](data: @[NewVec3f(0, 0, ), NewVec3f(0, 0, ), NewVec3f(0, 0, )]),
+    position: GPUArray[Vec3f, VertexBuffer](data: @[NewVec3f(0, 0, ), NewVec3f(0, 0, ), NewVec3f(0, 0, )]),
   )
   var uniforms1 = DescriptorSet[UniformsA, MaterialSet](
     data: UniformsA(
-      defaultTexture: Texture[TVec3[uint8], IndirectGPUMemory](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
-      materials: GPUValue[array[3, MaterialA], IndirectGPUMemory](data: [
+      defaultTexture: Texture[TVec3[uint8]](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
+      materials: GPUValue[array[3, MaterialA], UniformBuffer](data: [
         MaterialA(reflection: 0, baseColor: NewVec3f(1, 0, 0)),
         MaterialA(reflection: 0.1, baseColor: NewVec3f(0, 1, 0)),
         MaterialA(reflection: 0.5, baseColor: NewVec3f(0, 0, 1)),
     ]),
     materialTextures: [
-      Texture[TVec3[uint8], IndirectGPUMemory](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
-      Texture[TVec3[uint8], IndirectGPUMemory](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
-      Texture[TVec3[uint8], IndirectGPUMemory](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
+      Texture[TVec3[uint8]](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
+      Texture[TVec3[uint8]](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
+      Texture[TVec3[uint8]](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
     ]
   )
   )
   var instances1 = InstanceA(
-    rotation: GPUArray[Vec4f, IndirectGPUMemory](data: @[NewVec4f(1, 0, 0, 0.1), NewVec4f(0, 1, 0, 0.1)]),
-    objPosition: GPUArray[Vec3f, IndirectGPUMemory](data: @[NewVec3f(0, 0, 0), NewVec3f(1, 1, 1)]),
+    rotation: GPUArray[Vec4f, VertexBuffer](data: @[NewVec4f(1, 0, 0, 0.1), NewVec4f(0, 1, 0, 0.1)]),
+    objPosition: GPUArray[Vec3f, VertexBuffer](data: @[NewVec3f(0, 0, 0), NewVec3f(1, 1, 1)]),
   )
   var myGlobals = DescriptorSet[GlobalsA, GlobalSet](
     data: GlobalsA(
-      fontAtlas: Texture[TVec3[uint8], IndirectGPUMemory](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
-      settings: GPUValue[ShaderSettings, IndirectGPUMemory](data: ShaderSettings(gamma: 1.0))
+      fontAtlas: Texture[TVec3[uint8]](width: 1, height: 1, data: @[TVec3[uint8]([0'u8, 0'u8, 0'u8])]),
+      settings: GPUValue[ShaderSettings, UniformBuffer](data: ShaderSettings(gamma: 1.0))
     )
   )
 
@@ -1480,56 +1364,13 @@
 
   var renderdata = InitRenderData()
 
-  # buffer allocation
-  echo "Allocating GPU buffers"
-
-  var indirectVertexSizes = 0'u64
-  indirectVertexSizes += GetIndirectBufferSizes(myMesh1)
-  indirectVertexSizes += GetIndirectBufferSizes(instances1)
-  AllocateIndirectBuffer(renderdata, indirectVertexSizes, VertexBuffer)
-
-  var directVertexSizes = 0'u64
-  directVertexSizes += GetDirectBufferSizes(myMesh1)
-  directVertexSizes += GetDirectBufferSizes(instances1)
-  AllocateDirectBuffer(renderdata, directVertexSizes, VertexBuffer)
-
-  var indirectIndexSizes = 0'u64
-  indirectIndexSizes += GetIndirectIndexBufferSizes(myMesh1)
-  AllocateIndirectBuffer(renderdata, indirectIndexSizes, IndexBuffer)
-
-  var directIndexSizes = 0'u64
-  directIndexSizes += GetDirectIndexBufferSizes(myMesh1)
-  AllocateDirectBuffer(renderdata, directIndexSizes, IndexBuffer)
-
-  var indirectUniformSizes = 0'u64
-  indirectUniformSizes += GetIndirectBufferSizes(uniforms1)
-  indirectUniformSizes += GetIndirectBufferSizes(myGlobals)
-  AllocateIndirectBuffer(renderdata, indirectUniformSizes, UniformBuffer)
-
-  var directUniformSizes = 0'u64
-  directUniformSizes += GetDirectBufferSizes(uniforms1)
-  directUniformSizes += GetDirectBufferSizes(myGlobals)
-  AllocateDirectBuffer(renderdata, directUniformSizes, UniformBuffer)
-
-
   # buffer assignment
   echo "Assigning buffers to GPUData fields"
 
-  # for meshes we do:
-  renderdata.AssignIndirectBuffers(VertexBuffer, myMesh1)
-  renderdata.AssignDirectBuffers(VertexBuffer, myMesh1)
-  renderdata.AssignIndirectBuffers(IndexBuffer, myMesh1)
-  renderdata.AssignDirectBuffers(IndexBuffer, myMesh1)
-
-  # for instances we do:
-  renderdata.AssignIndirectBuffers(VertexBuffer, instances1)
-  renderdata.AssignDirectBuffers(VertexBuffer, instances1)
-
-  # for uniforms/globals we do:
-  renderdata.AssignIndirectBuffers(UniformBuffer, uniforms1)
-  renderdata.AssignDirectBuffers(UniformBuffer, uniforms1)
-  renderdata.AssignIndirectBuffers(UniformBuffer, myGlobals)
-  renderdata.AssignDirectBuffers(UniformBuffer, myGlobals)
+  renderdata.AssignBuffers(myMesh1)
+  renderdata.AssignBuffers(instances1)
+  renderdata.AssignBuffers(myGlobals)
+  renderdata.AssignBuffers(uniforms1)
 
   renderdata.UploadTextures(myGlobals)
   renderdata.UploadTextures(uniforms1)
@@ -1540,7 +1381,7 @@
   UpdateAllGPUBuffers(instances1)
   UpdateAllGPUBuffers(uniforms1)
   UpdateAllGPUBuffers(myGlobals)
-  renderdata.FlushDirectMemory()
+  renderdata.FlushAllMemory()
 
 
   # descriptors