changeset 120:2780d9aad142

add: better mesh support, indexed mesh
author Sam <sam@basx.dev>
date Thu, 06 Apr 2023 00:30:56 +0700
parents 5cd1b42127ba
children dfaddaf96f09
files src/semicongine/gpu_data.nim src/semicongine/mesh.nim src/semicongine/scene.nim src/semicongine/vulkan/buffer.nim src/semicongine/vulkan/memory.nim src/semicongine/vulkan/swapchain.nim tests/test_vulkan_wrapper.nim
diffstat 7 files changed, 353 insertions(+), 139 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/gpu_data.nim	Wed Apr 05 00:45:16 2023 +0700
+++ b/src/semicongine/gpu_data.nim	Thu Apr 06 00:30:56 2023 +0700
@@ -19,13 +19,15 @@
     UInt16
     UInt32
     UInt64
+  MemoryLocation* = enum
+    VRAM, VRAMVisible, RAM # VRAM is fastest, VRAMVisible allows updating memory directly, may be slower
   Attribute* = object
     name*: string
     thetype*: DataType
     components*: CountType # how many components the vectors has (1 means scalar)
     rows*: CountType # used to split matrices into rows of vectors
     perInstance*: bool
-    useGPULocalMemory*: bool
+    memoryLocation*: MemoryLocation
   AttributeGroup* = object
     attributes*: seq[Attribute]
 
@@ -42,8 +44,22 @@
     if attr.perInstance == false:
       result.add attr
 
-func attr*(name: string, thetype: DataType, components=CountType(1), rows=CountType(1), perInstance=false): auto =
-  Attribute(name: name, thetype: thetype, components: components, rows: rows, perInstance: perInstance)
+func attr*(
+  name: string,
+  thetype: DataType,
+  components=CountType(1),
+  rows=CountType(1),
+  perInstance=false,
+  memoryLocation=VRAMVisible,
+): auto =
+  Attribute(
+    name: name,
+    thetype: thetype,
+    components: components,
+    rows: rows,
+    perInstance: perInstance,
+    memoryLocation: memoryLocation,
+  )
 
 func size*(thetype: DataType): uint32 =
   case thetype:
@@ -242,3 +258,9 @@
   for attribute in group.attributes:
     result.add &"layout(location = {i}) out {attribute.glslType} {attribute.name};"
     i += 1
+
+func groupByMemoryLocation*(attributes: openArray[Attribute]): Table[MemoryLocation, seq[Attribute]] =
+  for attr in attributes:
+    if not (attr.memoryLocation in result):
+      result[attr.memoryLocation] = @[]
+    result[attr.memoryLocation].add attr
--- a/src/semicongine/mesh.nim	Wed Apr 05 00:45:16 2023 +0700
+++ b/src/semicongine/mesh.nim	Thu Apr 06 00:30:56 2023 +0700
@@ -5,15 +5,17 @@
 import std/sequtils
 
 import ./vulkan/utils
+import ./vulkan/api
 import ./gpu_data
 import ./entity
 import ./math
 
 type
   SurfaceDataType = enum
-    Position, Color, Normal, Tangent, BiTangent, TextureCoordinate, Index, BigIndex
+    Position, Color, Normal, Tangent, BiTangent, TextureCoordinate
   MeshIndexType* = enum
     None
+    Tiny # up to 2^8 vertices
     Small # up to 2^16 vertices
     Big # up to 2^32 vertices
   MeshData = object
@@ -24,19 +26,28 @@
       of Tangent: tangent: seq[Vec3f]
       of BiTangent: bitangent: seq[Vec3f]
       of TextureCoordinate: texturecoord: seq[Vec2f]
-      of Index: index: seq[uint16]
-      of BigIndex: bigindex: seq[uint32]
   Mesh* = ref object of Component
     vertexCount*: uint32
     data: Table[Attribute, MeshData]
     case indexType*: MeshIndexType
-    of None:
-      discard
-    of Small:
-      smallIndices*: seq[array[3, uint16]]
-    of Big:
-      bigIndices*: seq[array[3, uint32]]
+      of None: discard
+      of Tiny: tinyIndices*: seq[array[3, uint8]]
+      of Small: smallIndices*: seq[array[3, uint16]]
+      of Big: bigIndices*: seq[array[3, uint32]]
 
+converter toVulkan*(indexType: MeshIndexType): VkIndexType =
+  case indexType:
+    of None: VK_INDEX_TYPE_NONE_KHR
+    of Tiny: VK_INDEX_TYPE_UINT8_EXT
+    of Small: VK_INDEX_TYPE_UINT16
+    of Big: VK_INDEX_TYPE_UINT32
+
+func indicesCount*(mesh: Mesh): uint32 =
+  case mesh.indexType:
+    of None: 0
+    of Tiny: mesh.tinyIndices.len * 3
+    of Small: mesh.smallIndices.len * 3
+    of Big: mesh.bigIndices.len * 3
 
 method `$`*(mesh: Mesh): string =
   &"Mesh ({mesh.vertexCount})"
@@ -51,28 +62,70 @@
     result.data[asAttribute(default(Vec3f), "color")] = MeshData(thetype: Color, color: colors.toSeq)
 
 
-func newMesh*(positions: openArray[Vec3f], indices: openArray[array[3, uint32|int32]]): auto =
-  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: positions.toSeq)}.toTable
-  if uint16(positions.len) < high(uint16):
-    var smallIndices = newSeq[array[3, uint16]](indices.len)
+func newMesh*(positions: openArray[Vec3f], colors: openArray[Vec3f]=[], indices: openArray[array[3, uint32|int32|int]], autoResize=true): auto =
+  assert colors.len == 0 or colors.len == positions.len
+
+  result = new Mesh
+  result.vertexCount = uint32(positions.len)
+  result.data[asAttribute(default(Vec3f), "position")] = MeshData(thetype: Position, position: positions.toSeq)
+  if colors.len > 0:
+    result.data[asAttribute(default(Vec3f), "color")] = MeshData(thetype: Color, color: colors.toSeq)
+
+  for i in indices:
+    assert uint32(i[0]) < result.vertexCount
+    assert uint32(i[1]) < result.vertexCount
+    assert uint32(i[2]) < result.vertexCount
+
+  if autoResize and uint32(positions.len) < uint32(high(uint8)) and false: # todo: check feature support
+    result.indexType = Tiny
     for i, tri in enumerate(indices):
-      smallIndices[i] = [uint16(tri[0]), uint16(tri[1]), uint16(tri[3])]
-    Mesh(vertexCount: uint32(positions.len), data: meshdata, indexType: Small, smallIndices: smallIndices)
+      result.tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])]
+  elif autoResize and uint32(positions.len) < uint32(high(uint16)):
+    result.indexType = Small
+    for i, tri in enumerate(indices):
+      result.smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])]
   else:
-    var bigIndices = newSeq[array[3, uint32]](indices.len)
+    result.indexType = Big
     for i, tri in enumerate(indices):
-      bigIndices[i] = [uint32(tri[0]), uint32(tri[1]), uint32(tri[3])]
-    Mesh(vertexCount: uint32(positions.len), data: meshdata, indexType: Big, bigIndices: bigIndices)
+      result.bigIndices.add [uint32(tri[0]), uint32(tri[1]), uint32(tri[2])]
+
+func newMesh*(positions: openArray[Vec3f], colors: openArray[Vec3f]=[], indices: openArray[array[3, uint16|int16]]): auto =
+  assert colors.len == 0 or colors.len == positions.len
+
+  result = new Mesh
+  result.vertexCount = uint32(positions.len)
+  result.data[asAttribute(default(Vec3f), "position")] = MeshData(thetype: Position, position: positions.toSeq)
+  if colors.len > 0:
+    result.data[asAttribute(default(Vec3f), "color")] = MeshData(thetype: Color, color: colors.toSeq)
 
-func newMesh*(positions: openArray[Vec3f], indices: openArray[array[3, uint16|int16]]): auto =
-  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: positions.toSeq)}.toTable
-  var smallIndices = newSeq[array[3, uint16]](indices.len)
+  for i in indices:
+    assert i[0] < result.vertexCount
+    assert i[1] < result.vertexCount
+    assert i[2] < result.vertexCount
+  result.indexType = Small
   for i, tri in enumerate(indices):
-    smallIndices[i] = [uint16(tri[0]), uint16(tri[1]), uint16(tri[3])]
-  Mesh(vertexCount: positions.len, data: meshdata, indexType: Small, smallIndices: smallIndices)
+    result.smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])]
+
+func newMesh*(positions: openArray[Vec3f], colors: openArray[Vec3f]=[], indices: openArray[array[3, uint8|int8]]): auto =
+  assert colors.len == 0 or colors.len == positions.len
+  assert false # todo: check feature support
+
+  result = new Mesh
+  result.vertexCount = uint32(positions.len)
+  result.data[asAttribute(default(Vec3f), "position")] = MeshData(thetype: Position, position: positions.toSeq)
+  if colors.len > 0:
+    result.data[asAttribute(default(Vec3f), "color")] = MeshData(thetype: Color, color: colors.toSeq)
+
+  for i in indices:
+    assert i[0] < result.vertexCount
+    assert i[1] < result.vertexCount
+    assert i[2] < result.vertexCount
+  result.indexType = Tiny
+  for i, tri in enumerate(indices):
+    result.smallIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])]
 
 
-func size*(meshdata: MeshData): uint64 =
+func meshDataSize*(meshdata: MeshData): uint64 =
   case meshdata.thetype:
     of Position: meshdata.position.size
     of Color: meshdata.color.size
@@ -80,15 +133,20 @@
     of Tangent: meshdata.tangent.size
     of BiTangent: meshdata.bitangent.size
     of TextureCoordinate: meshdata.texturecoord.size
-    of Index: meshdata.index.size
-    of BigIndex: meshdata.bigindex.size
+
+func attributeSize*(mesh: Mesh, attribute: Attribute): uint64 =
+  mesh.data[attribute].meshDataSize
 
-func size*(mesh: Mesh, attribute: Attribute): uint64 =
-  mesh.data[attribute].size
+func vertexDataSize*(mesh: Mesh): uint64 =
+  for d in mesh.data.values:
+    result += d.meshDataSize
 
-func size*(mesh: Mesh): uint64 =
-  for d in mesh.data.values:
-    result += d.size
+func indexDataSize*(mesh: Mesh): uint64 =
+  case mesh.indexType
+    of None: 0
+    of Tiny: mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0))
+    of Small: mesh.smallIndices.len * sizeof(get(genericParams(typeof(mesh.smallIndices)), 0))
+    of Big: mesh.bigIndices.len * sizeof(get(genericParams(typeof(mesh.bigIndices)), 0))
 
 proc rawData[T: seq](value: var T): (pointer, uint64) =
   (pointer(addr(value[0])), uint64(sizeof(get(genericParams(typeof(value)), 0)) * value.len))
@@ -101,8 +159,13 @@
     of Tangent: rawData(data.tangent)
     of BiTangent: rawData(data.bitangent)
     of TextureCoordinate: rawData(data.texturecoord)
-    of Index: rawData(data.index)
-    of BigIndex: rawData(data.bigindex)
+
+proc getRawIndexData*(mesh: Mesh): (pointer, uint64) =
+  case mesh.indexType:
+    of None: raise newException(Exception, "Trying to get index data for non-indexed mesh")
+    of Tiny: rawData(mesh.tinyIndices)
+    of Small: rawData(mesh.smallIndices)
+    of Big: rawData(mesh.bigIndices)
 
 proc hasDataFor*(mesh: Mesh, attribute: Attribute): bool =
   assert attribute.perInstance == false, "Mesh data cannot handle per-instance attributes"
--- a/src/semicongine/scene.nim	Wed Apr 05 00:45:16 2023 +0700
+++ b/src/semicongine/scene.nim	Thu Apr 06 00:30:56 2023 +0700
@@ -19,6 +19,7 @@
     of true:
       indexBuffer*: Buffer
       indexType*: VkIndexType
+      indexOffset*: uint64
     of false:
       discard
 
@@ -29,92 +30,125 @@
 
 func `$`*(drawable: Drawable): string =
   if drawable.indexed:
-    &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffer: {drawable.buffer}, offsets: {drawable.offsets}, indexType: {drawable.indexType})"
+    &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffer: {drawable.buffer}, offsets: {drawable.offsets}, indexType: {drawable.indexType}, indexOffset: {drawable.indexOffset}, indexBuffer: {drawable.indexBuffer})"
   else:
     &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffer: {drawable.buffer}, offsets: {drawable.offsets})"
 
-proc destroy(drawable: var Drawable) =
-  drawable.buffer.destroy()
-  if drawable.indexed:
-    drawable.indexBuffer.destroy()
+proc getBuffers*(scene: Scene, pipeline: VkPipeline): seq[Buffer] =
+  var counted: seq[VkBuffer]
+  for drawable in scene.drawables[pipeline]:
+    if not (drawable.buffer.vk in counted):
+      result.add(drawable.buffer)
+      counted.add drawable.buffer.vk
+    if drawable.indexed and not (drawable.indexBuffer.vk in counted):
+      result.add(drawable.indexBuffer)
+      counted.add drawable.indexBuffer.vk
+
+proc destroy*(scene: var Scene, pipeline: VkPipeline) =
+  var buffers = scene.getBuffers(pipeline)
+  for buffer in buffers.mitems:
+      buffer.destroy()
+
+proc destroy*(scene: var Scene) =
+  for pipeline in scene.drawables.keys:
+    scene.destroy(pipeline)
 
 proc setupDrawables(scene: var Scene, pipeline: Pipeline) =
   assert pipeline.device.vk.valid
   if pipeline.vk in scene.drawables:
     for drawable in scene.drawables[pipeline.vk].mitems:
-      drawable.destroy()
+      scene.destroy(pipeline.vk)
   scene.drawables[pipeline.vk] = @[]
 
   var
-    nonIMeshes: seq[Mesh]
-    smallIMeshes: seq[Mesh]
-    bigIMeshes: seq[Mesh]
+    nonIndexedMeshes: seq[Mesh]
+    tinyIndexedMeshes: seq[Mesh]
+    smallIndexedMeshes: seq[Mesh]
+    bigIndexedMeshes: seq[Mesh]
+    allIndexedMeshes: seq[Mesh]
   for mesh in allPartsOfType[Mesh](scene.root):
     for inputAttr in pipeline.inputs.vertexInputs:
       assert mesh.hasDataFor(inputAttr), &"{mesh} missing data for {inputAttr}"
     case mesh.indexType:
-      of None: nonIMeshes.add mesh
-      of Small: smallIMeshes.add mesh
-      of Big: bigIMeshes.add mesh
+      of None: nonIndexedMeshes.add mesh
+      of Tiny: tinyIndexedMeshes.add mesh
+      of Small: smallIndexedMeshes.add mesh
+      of Big: bigIndexedMeshes.add mesh
+
+  allIndexedMeshes = bigIndexedMeshes & smallIndexedMeshes & tinyIndexedMeshes # that we don't have to care about index alignment
   
-  if nonIMeshes.len > 0:
+  var
+    indicesBufferSize = 0'u64
+    indexOffset = 0'u64
+  for mesh in allIndexedMeshes:
+    indicesBufferSize += mesh.indexDataSize
+  var indexBuffer: Buffer
+  if indicesBufferSize > 0:
+    indexBuffer = pipeline.device.createBuffer(
+      size=indicesBufferSize,
+      usage=[VK_BUFFER_USAGE_INDEX_BUFFER_BIT],
+      useVRAM=true,
+      mappable=false,
+    )
+
+  for location, attributes in pipeline.inputs.vertexInputs.groupByMemoryLocation().pairs:
+    # setup one buffer per attribute location
+    var bufferSize = 0'u64
+    for mesh in nonIndexedMeshes & allIndexedMeshes:
+      bufferSize += mesh.vertexDataSize
+    if bufferSize == 0:
+      continue
     var
-      bufferSize = 0'u64
-      vertexCount = 0'u32
-    for mesh in nonIMeshes:
-      bufferSize += mesh.size
-      vertexCount += mesh.vertexCount
-    var buffer = pipeline.device.createBuffer(
+      bufferOffset = 0'u64
+      buffer = pipeline.device.createBuffer(
         size=bufferSize,
         usage=[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT],
-        memoryFlags=[VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT],
+        useVRAM=location in [VRAM, VRAMVisible],
+        mappable=location in [VRAMVisible, RAM],
       )
-    var offset = 0'u64
-    var drawable = Drawable(elementCount: vertexCount, buffer: buffer, indexed: false, instanceCount: 1)
-    for inputAttr in pipeline.inputs.vertexInputs:
-      drawable.offsets.add offset
-      for mesh in nonIMeshes:
-        var (pdata, size) = mesh.getRawData(inputAttr)
-        buffer.setData(pdata, size, offset)
-        offset += size
-    scene.drawables[pipeline.vk].add drawable
+
+    # non-indexed mesh drawable
+    if nonIndexedMeshes.len > 0:
+      var vertexCount = 0'u32
+      for mesh in nonIndexedMeshes:
+        vertexCount += mesh.vertexCount
+      # remark: we merge all meshes into a single drawcall... smart?#
+      # I think bad for instancing...
+      var nonIndexedDrawable = Drawable(elementCount: vertexCount, buffer: buffer, indexed: false, instanceCount: 1)
+      for inputAttr in attributes:
+        nonIndexedDrawable.offsets.add bufferOffset
+        for mesh in nonIndexedMeshes:
+          var (pdata, size) = mesh.getRawData(inputAttr)
+          buffer.setData(pdata, size, bufferOffset)
+          bufferOffset += size
+      scene.drawables[pipeline.vk].add nonIndexedDrawable
 
-#[
-proc createVertexBuffers*[M: Mesh](
-  mesh: M,
-  device: VkDevice,
-  physicalDevice: VkPhysicalDevice,
-  commandPool: VkCommandPool,
-  queue: VkQueue,
-): (seq[Buffer], uint32) =
-  result[1] = mesh.vertexData.VertexCount
-  for name, value in mesh.vertexData.fieldPairs:
-    assert value.data.len > 0
-    var flags = if value.useOnDeviceMemory: {TransferSrc} else: {VertexBuffer}
-    var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize, flags, {HostVisible, HostCoherent})
-    copyMem(stagingBuffer.data, addr(value.data[0]), value.datasize)
-
-    if value.useOnDeviceMemory:
-      var finalBuffer = device.InitBuffer(physicalDevice, value.datasize, {TransferDst, VertexBuffer}, {DeviceLocal})
-      transferBuffer(commandPool, queue, stagingBuffer, finalBuffer, value.datasize)
-      stagingBuffer.trash()
-      result[0].add(finalBuffer)
-      value.buffer = finalBuffer
-    else:
-      result[0].add(stagingBuffer)
-      value.buffer = stagingBuffer
-]#
+    # indexed mesh drawable
+    for mesh in allIndexedMeshes:
+      var drawable = Drawable(
+        elementCount: mesh.indicesCount,
+        buffer: buffer,
+        indexed: true,
+        indexBuffer: indexBuffer,
+        indexOffset: indexOffset,
+        indexType: mesh.indexType,
+        instanceCount: 1
+      )
+      var (pdata, size) = mesh.getRawIndexData()
+      indexBuffer.setData(pdata, size, indexOffset)
+      indexOffset += size
+      for inputAttr in attributes:
+        drawable.offsets.add bufferOffset
+        var (pdata, size) = mesh.getRawData(inputAttr)
+        buffer.setData(pdata, size, bufferOffset)
+        bufferOffset += size
+      scene.drawables[pipeline.vk].add drawable
+  echo scene.getBuffers(pipeline.vk)
 
 proc setupDrawables*(scene: var Scene, renderPass: var RenderPass) =
   for subpass in renderPass.subpasses.mitems:
     for pipeline in subpass.pipelines.mitems:
       scene.setupDrawables(pipeline)
 
-
 proc getDrawables*(scene: Scene, pipeline: Pipeline): seq[Drawable] =
   scene.drawables.getOrDefault(pipeline.vk, @[])
-
-proc destroy*(scene: var Scene) =
-  for drawables in scene.drawables.mvalues:
-    for drawable in drawables.mitems:
-      drawable.destroy()
--- a/src/semicongine/vulkan/buffer.nim	Wed Apr 05 00:45:16 2023 +0700
+++ b/src/semicongine/vulkan/buffer.nim	Thu Apr 06 00:30:56 2023 +0700
@@ -15,32 +15,31 @@
     vk*: VkBuffer
     size*: uint64
     usage*: seq[VkBufferUsageFlagBits]
-    case hasMemory*: bool
+    case memoryAllocated*: bool
       of false: discard
       of true:
         memory*: DeviceMemory
-        data*: pointer
 
-func `$`*(buffer: Buffer): string = &"Buffer(size: {buffer.size}, usage: {buffer.usage})"
+func `$`*(buffer: Buffer): string =
+  &"Buffer(vk: {buffer.vk}, size: {buffer.size}, usage: {buffer.usage})"
 
-proc setData*(dst: Buffer, src: pointer, len: uint64, offset=0'u64) =
-  assert offset + len <= dst.size
-  copyMem(cast[pointer](cast[uint64](dst.data) + offset), src, len)
+
+proc allocateMemory(buffer: var Buffer, useVRAM: bool, mappable: bool, autoFlush: bool) =
+  assert buffer.device.vk.valid
+  assert buffer.memoryAllocated == false
 
-proc setData*[T: seq](dst: Buffer, src: ptr T, offset=0'u64) =
-  dst.setData(src, sizeof(get(genericParams(T), 0)) * src[].len, offset=offset)
-
-proc setData*[T](dst: Buffer, src: ptr T, offset=0'u64) =
-  dst.setData(src, sizeof(T), offset=offset)
+  var flags: seq[VkMemoryPropertyFlagBits]
+  if useVRAM:
+    flags.add VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+  if mappable:
+    flags.add VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+  if autoFlush:
+    flags.add VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
 
-proc allocateMemory(buffer: var Buffer, flags: openArray[VkMemoryPropertyFlagBits]) =
-  assert buffer.device.vk.valid
-  assert buffer.hasMemory == false
-
-  buffer.hasMemory = true
+  buffer.memoryAllocated = true
   buffer.memory = buffer.device.allocate(buffer.size, flags)
   checkVkResult buffer.device.vk.vkBindBufferMemory(buffer.vk, buffer.memory.vk, VkDeviceSize(0))
-  buffer.data = buffer.memory.map()
+
 
 # currently no support for extended structure and concurrent/shared use
 # (shardingMode = VK_SHARING_MODE_CONCURRENT not supported)
@@ -48,8 +47,9 @@
   device: Device,
   size: uint64,
   usage: openArray[VkBufferUsageFlagBits],
-  flags: openArray[VkBufferCreateFlagBits] = @[],
-  memoryFlags: openArray[VkMemoryPropertyFlagBits] = @[],
+  useVRAM: bool,
+  mappable: bool,
+  autoFlush=true,
 ): Buffer =
   assert device.vk.valid
   assert size > 0
@@ -57,9 +57,11 @@
   result.device = device
   result.size = size
   result.usage = usage.toSeq
+  if not (mappable or VK_BUFFER_USAGE_TRANSFER_DST_BIT in result.usage):
+    result.usage.add VK_BUFFER_USAGE_TRANSFER_DST_BIT
   var createInfo = VkBufferCreateInfo(
     sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
-    flags: toBits(flags),
+    flags: VkBufferCreateFlags(0),
     size: size,
     usage: toBits(usage),
     sharingMode: VK_SHARING_MODE_EXCLUSIVE,
@@ -71,7 +73,7 @@
     pAllocator=nil,
     pBuffer=addr result.vk
   )
-  result.allocateMemory(memoryFlags)
+  result.allocateMemory(useVRAM, mappable, autoFlush)
 
 
 proc copy*(src, dst: Buffer) =
@@ -114,8 +116,27 @@
 proc destroy*(buffer: var Buffer) =
   assert buffer.device.vk.valid
   assert buffer.vk.valid
-  if buffer.hasMemory:
+  if buffer.memoryAllocated:
     assert buffer.memory.vk.valid
     buffer.memory.free
   buffer.device.vk.vkDestroyBuffer(buffer.vk, nil)
-  buffer = default(Buffer)
+  buffer.vk.reset
+
+proc setData*(dst: Buffer, src: pointer, size: uint64, bufferOffset=0'u64) =
+  assert bufferOffset + size <= dst.size
+  if dst.memory.canMap:
+    copyMem(cast[pointer](cast[uint64](dst.memory.data) + bufferOffset), src, size)
+    if dst.memory.needsFlushing:
+      dst.memory.flush()
+  else: # use staging buffer, slower but required if memory is not host visible
+    var stagingBuffer = dst.device.createBuffer(size, [VK_BUFFER_USAGE_TRANSFER_SRC_BIT], useVRAM=false, mappable=true, autoFlush=true)
+    stagingBuffer.setData(src, size, 0)
+    stagingBuffer.copy(dst)
+    stagingBuffer.destroy()
+
+proc setData*[T: seq](dst: Buffer, src: ptr T, offset=0'u64) =
+  dst.setData(src, sizeof(get(genericParams(T), 0)) * src[].len, offset=offset)
+
+proc setData*[T](dst: Buffer, src: ptr T, offset=0'u64) =
+  dst.setData(src, sizeof(T), offset=offset)
+
--- a/src/semicongine/vulkan/memory.nim	Wed Apr 05 00:45:16 2023 +0700
+++ b/src/semicongine/vulkan/memory.nim	Thu Apr 06 00:30:56 2023 +0700
@@ -1,4 +1,5 @@
 import std/strformat
+import std/algorithm
 
 import ./api
 import ./device
@@ -19,6 +20,11 @@
     device*: Device
     vk*: VkDeviceMemory
     size*: uint64
+    memoryType*: MemoryType
+    case canMap*: bool
+      of false: discard
+      of true: data*: pointer
+    needsFlushing*: bool
 
 proc getPhysicalDeviceMemoryProperties(physicalDevice: VkPhysicalDevice): PhyscialDeviceMemoryProperties =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
@@ -38,13 +44,14 @@
 
 proc allocate*(device: Device, size: uint64, flags: openArray[VkMemoryPropertyFlagBits]): DeviceMemory =
   assert device.vk.valid
+  assert size > 0
 
   result.device = device
   result.size = size
 
   var
-    memtype: MemoryType
     hasAllFlags: bool
+    matchingTypes: seq[MemoryType]
   for mtype in device.physicalDevice.vk.getPhysicalDeviceMemoryProperties.types:
     hasAllFlags = true
     for flag in flags:
@@ -52,15 +59,19 @@
         hasAllFlags = false
         break
     if hasAllFlags:
-      memtype = mtype
-      break
-  if not hasAllFlags:
+      matchingTypes.add mtype
+  if matchingTypes.len == 0:
     raise newException(Exception, &"No memory with support for {flags}")
+  matchingTypes.sort(cmp= proc(a, b: MemoryType): int = cmp(a.heap.size, b.heap.size))
+
+  result.memoryType = matchingTypes[^1]
+  result.canMap = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in result.memoryType.flags
+  result.needsFlushing = not (VK_MEMORY_PROPERTY_HOST_COHERENT_BIT in result.memoryType.flags)
 
   var allocationInfo = VkMemoryAllocateInfo(
     sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
     allocationSize: size,
-    memoryTypeIndex: memtype.index,
+    memoryTypeIndex: result.memoryType.index,
   )
 
   checkVkResult vkAllocateMemory(
@@ -70,25 +81,62 @@
     addr result.vk
   )
 
-proc map*(memory: DeviceMemory, offset=0'u64, size=0'u64): pointer =
+  if result.canMap:
+    checkVkResult result.device.vk.vkMapMemory(
+      memory=result.vk,
+      offset=VkDeviceSize(0),
+      size=VkDeviceSize(result.size),
+      flags=VkMemoryMapFlags(0), # unused up to Vulkan 1.3
+      ppData=addr(result.data)
+    )
+
+proc allocate*(device: Device, size: uint64, useVRAM: bool, mappable: bool, autoFlush: bool): DeviceMemory =
+  var flags: seq[VkMemoryPropertyFlagBits]
+  if useVRAM:
+    flags.add VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+  if mappable:
+    flags.add VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+  if autoFlush:
+    flags.add VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+  device.allocate(size=size, flags=flags)
+
+# flush host -> device
+proc flush*(memory: DeviceMemory, offset=0'u64, size=0'u64) =
   assert memory.device.vk.valid
   assert memory.vk.valid
-  
-  var thesize = size
-  if thesize == 0:
-    thesize = memory.size
+  assert memory.needsFlushing
+
+  var actualSize = size
+  if actualSize == 0:
+    actualSize = memory.size
+  var flushrange = VkMappedMemoryRange(
+    sType: VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+    memory: memory.vk,
+    offset: VkDeviceSize(offset),
+    size: VkDeviceSize(size)
+  )
+  checkVkResult memory.device.vk.vkFlushMappedMemoryRanges(memoryRangeCount=1, pMemoryRanges=addr(flushrange))
 
-  checkVkResult memory.device.vk.vkMapMemory(
-    memory=memory.vk,
-    offset=VkDeviceSize(offset),
-    size=VkDeviceSize(thesize),
-    flags=VkMemoryMapFlags(0), # unused up to Vulkan 1.3
-    ppData=addr(result)
+# flush device -> host
+proc invalidate*(memory: DeviceMemory, offset=0'u64, size=0'u64) =
+  assert memory.device.vk.valid
+  assert memory.vk.valid
+  assert memory.needsFlushing
+
+  var actualSize = size
+  if actualSize == 0:
+    actualSize = memory.size
+  var flushrange = VkMappedMemoryRange(
+    sType: VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+    memory: memory.vk,
+    offset: VkDeviceSize(offset),
+    size: VkDeviceSize(size)
   )
+  checkVkResult memory.device.vk.vkInvalidateMappedMemoryRanges(memoryRangeCount=1, pMemoryRanges=addr(flushrange))
 
 proc free*(memory: var DeviceMemory) =
   assert memory.device.vk.valid
   assert memory.vk.valid
 
   memory.device.vk.vkFreeMemory(memory.vk, nil)
-  memory = default(DeviceMemory)
+  memory.vk.reset
--- a/src/semicongine/vulkan/swapchain.nim	Wed Apr 05 00:45:16 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Thu Apr 06 00:30:56 2023 +0700
@@ -170,7 +170,7 @@
       pOffsets=offsets.toCPointer()
     )
     if drawable.indexed:
-      commandBuffer.vkCmdBindIndexBuffer(drawable.indexBuffer.vk, VkDeviceSize(0), drawable.indexType)
+      commandBuffer.vkCmdBindIndexBuffer(drawable.indexBuffer.vk, VkDeviceSize(drawable.indexOffset), drawable.indexType)
       commandBuffer.vkCmdDrawIndexed(
         indexCount=drawable.elementCount,
         instanceCount=drawable.instanceCount,
--- a/tests/test_vulkan_wrapper.nim	Wed Apr 05 00:45:16 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Thu Apr 06 00:30:56 2023 +0700
@@ -49,7 +49,7 @@
   var device = instance.createDevice(
     selectedPhysicalDevice,
     @[],
-    @[],
+    @["VK_EXT_index_type_uint8"],
     selectedPhysicalDevice.filterForGraphicsPresentationQueues()
   )
 
@@ -90,13 +90,39 @@
         positions=[newVec3f(0.0, -0.5), newVec3f(0.5, 0.5), newVec3f(-0.5, 0.5)],
         colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
       )),
+      newEntity("triangle1b", newMesh(
+        positions=[newVec3f(0.0, -0.4), newVec3f(0.4, 0.4), newVec3f(-0.4, 0.5)],
+        colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
+      )),
+      newEntity("triangle2a", newMesh(
+        positions=[newVec3f(0.0, 0.5), newVec3f(0.5, -0.5), newVec3f(-0.5, -0.5)],
+        colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
+        indices=[[0'u16, 2'u16, 1'u16]]
+      )),
+      newEntity("triangle2b", newMesh(
+        positions=[newVec3f(0.0, 0.4), newVec3f(0.4, -0.4), newVec3f(-0.4, -0.4)],
+        colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
+        indices=[[0'u16, 2'u16, 1'u16]]
+      )),
+      newEntity("triangle3a", newMesh(
+        positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
+        colors=[newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0)],
+        indices=[[0'u32, 2'u32, 1'u32]],
+        autoResize=false
+      )),
+      newEntity("triangle3b", newMesh(
+        positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)],
+        colors=[newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0), newVec3f(1.0, 1.0, 0.0)],
+        indices=[[0'u32, 2'u32, 1'u32]],
+        autoResize=false
+      )),
     )
   )
   thescene.setupDrawables(renderPass)
 
   # MAINLOOP
   echo "Setup successfull, start rendering"
-  for i in 0 ..< 1000:
+  for i in 0 ..< 10000:
     discard swapchain.drawScene(thescene)
   echo "Rendered ", swapchain.framesRendered, " frames"
   checkVkResult device.vk.vkDeviceWaitIdle()