changeset 573:f31a9821ae1c

did: real implementation of buffer and memory, getting closer to collect shit for drawing per pipeline
author Sam <sam@basx.dev>
date Sat, 01 Apr 2023 00:40:02 +0700
parents 9f1de117aaca
children bbeec60e25ca
files src/semicongine/entity.nim src/semicongine/gpu_data.nim src/semicongine/legacy/buffer.nim src/semicongine/math/vector.nim src/semicongine/mesh.nim src/semicongine/scene.nim src/semicongine/vulkan/buffer.nim src/semicongine/vulkan/commandbuffer.nim src/semicongine/vulkan/device.nim src/semicongine/vulkan/memory.nim src/semicongine/vulkan/physicaldevice.nim src/semicongine/vulkan/pipeline.nim src/semicongine/vulkan/renderpass.nim src/semicongine/vulkan/shader.nim src/semicongine/vulkan/swapchain.nim tests/test_vulkan_wrapper.nim
diffstat 16 files changed, 313 insertions(+), 134 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/entity.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/entity.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -15,12 +15,9 @@
     components*: seq[Component]
 
 
-func `$`*(entity: Entity): string = entity.name
+method `$`*(entity: Entity): string {.base.} = entity.name
 method `$`*(part: Component): string {.base.} =
-  if part.entity != nil:
-    &"{part.entity} -> Component"
-  else:
-    &"Standalone Component"
+  "Unknown Component"
 
 proc add*(entity: Entity, child: Entity) =
   child.parent = entity
@@ -44,8 +41,7 @@
   if result.name == "":
     result.name = &"Entity[{$(cast[ByteAddress](result))}]"
 
-func newEntity*(name: string, firstChild: Entity, children: varargs[
-    Entity]): Entity =
+func newEntity*(name: string, firstChild: Entity, children: varargs[Entity]): Entity =
   result = new Entity
   result.add firstChild
   for child in children:
--- a/src/semicongine/gpu_data.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/gpu_data.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -22,6 +22,7 @@
     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
   AttributeGroup* = object
     attributes*: seq[Attribute]
 
--- a/src/semicongine/legacy/buffer.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/legacy/buffer.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -54,8 +54,7 @@
   bufferTypes: set[BufferType],
   properties: MemoryProperties,
 ): Buffer =
-  result = Buffer(device: device, size: size, bufferTypes: bufferTypes,
-      memoryProperties: properties)
+  result = Buffer(device: device, size: size, bufferTypes: bufferTypes, memoryProperties: properties)
   var usageFlags = 0
   for usage in bufferTypes:
     usageFlags = ord(usageFlags) or ord(usage)
@@ -65,10 +64,8 @@
     usage: VkBufferUsageFlags(usageFlags),
     sharingMode: VK_SHARING_MODE_EXCLUSIVE,
   )
-  checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr(
-      result.vkBuffer))
-  vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr(
-      result.memoryRequirements))
+  checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr(result.vkBuffer))
+  vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr(result.memoryRequirements))
 
   var allocInfo = VkMemoryAllocateInfo(
     sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
@@ -77,8 +74,7 @@
   )
   if result.size > 0:
     checkVkResult result.device.vkAllocateMemory(addr(allocInfo), nil, addr(result.memory))
-  checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory,
-      VkDeviceSize(0))
+  checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory, VkDeviceSize(0))
   checkVkResult vkMapMemory(
     result.device,
     result.memory,
@@ -89,8 +85,7 @@
   )
 
 
-proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src,
-    dst: Buffer, size: uint64) =
+proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src, dst: Buffer, size: uint64) =
   assert uint64(src.device) == uint64(dst.device)
   assert TransferSrc in src.bufferTypes
   assert TransferDst in dst.bufferTypes
--- a/src/semicongine/math/vector.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/math/vector.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -36,6 +36,13 @@
 func ConstG[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(0), T(1), T(0)])
 func ConstB[T: SomeNumber](): auto {.compiletime.} = TVec3[T]([T(0), T(0), T(1)])
 
+func newVec2*(x=0'f32, y=0'f32): auto =
+  Vec2([x, y])
+func newVec3*(x=0'f32, y=0'f32, z=0'f32): auto =
+  Vec3([x, y, z])
+func newVec4*(x=0'f32, y=0'f32, z=0'f32, a=0'f32): auto =
+  Vec4([x, y, z, a])
+
 # generates constants: Xf, Xf32, Xf64, Xi, Xi8, Xi16, Xi32, Xi64
 # Also for Y, Z, R, G, B and One
 # not sure if this is necessary or even a good idea...
--- a/src/semicongine/mesh.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/mesh.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -1,20 +1,50 @@
-import std/math
-import std/options
-import std/typetraits
+import std/enumerate
+import std/strformat
+import std/sequtils
 
-import ./vulkan
-import ./thing
-import ./buffer
-import ./math/vector
+import ./entity
+import ./math
 
 type
-  Mesh*[T: object, U: uint16|uint32] = ref object of Part
-    vertexData*: T
-    case indexed*: bool
-    of true:
-      indices*: seq[array[3, U]]
-    of false:
+  MeshIndexType* = enum
+    None
+    Small # up to 2^16 vertices
+    Big # up to 2^32 vertices
+  Mesh* = ref object of Component
+    vertices: seq[Vec3]
+    case indexType*: MeshIndexType
+    of None:
       discard
+    of Small:
+      smallIndices*: seq[array[3, uint16]]
+    of Big:
+      bigIndices*: seq[array[3, uint32]]
+
+method `$`*(mesh: Mesh): string =
+  &"Mesh ({mesh.vertices.len})"
+
+func newMesh*(vertices: openArray[Vec3]): auto =
+  Mesh(vertices: vertices.toSeq, indexType: None)
+
+func newMesh*(vertices: openArray[Vec3], indices: openArray[array[3, uint32|int32]]): auto =
+  if uint16(vertices.len) < high(uint16):
+    var smallIndices = newSeq[array[3, uint16]](indices.len)
+    for i, tri in enumerate(indices):
+      smallIndices[i] = [uint16(tri[0]), uint16(tri[1]), uint16(tri[3])]
+    Mesh(vertices: vertices.toSeq, indexType: Small, smallIndices: smallIndices)
+  else:
+    var bigIndices = newSeq[array[3, uint32]](indices.len)
+    for i, tri in enumerate(indices):
+      bigIndices[i] = [uint32(tri[0]), uint32(tri[1]), uint32(tri[3])]
+    Mesh(vertices: vertices.toSeq, indexType: Big, bigIndices: bigIndices)
+
+func newMesh*(vertices: openArray[Vec3], indices: openArray[array[3, uint16|int16]]): auto =
+  var smallIndices = newSeq[array[3, uint16]](indices.len)
+  for i, tri in enumerate(indices):
+    smallIndices[i] = [uint16(tri[0]), uint16(tri[1]), uint16(tri[3])]
+  Mesh(vertices: vertices.toSeq, indexType: Small, smallIndices: smallIndices)
+
+#[
 
 func createUberMesh*[T: object, U: uint16|uint32](meshes: openArray[Mesh[
     T, U]]): Mesh[T, U] =
@@ -136,3 +166,4 @@
     when typeof(value) is PositionAttribute:
       value.data = data
       value.useOnDeviceMemory = true
+]#
--- a/src/semicongine/scene.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/scene.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -1,16 +1,17 @@
 import std/tables
 
 import ./vulkan/api
-import ./entity
 import ./vulkan/buffer
 import ./vulkan/pipeline
 import ./vulkan/renderpass
+import ./entity
+import ./mesh
 
 type
   Drawable* = object
     buffers*: seq[(Buffer, int)] # buffer + offset from buffer
-    elementCount*: uint32 # vertices or indices
-    instanceCount*: uint32
+    elementCount*: uint32 # number of vertices or indices
+    instanceCount*: uint32 # number of instance
     case indexed*: bool
     of true:
       indexBuffer*: Buffer
@@ -19,16 +20,54 @@
       discard
 
   Scene* = object
+    name*: string
     root*: Entity
     drawables: Table[VkPipeline, seq[Drawable]]
 
 proc setupDrawables(scene: var Scene, pipeline: Pipeline) =
-  # echo pipeline.descriptorSetLayout.descriptors
-  # thetype*: VkDescriptorType
-  # count*: uint32
-  # itemsize*: uint32
+  var meshes: seq[Mesh]
+  var smallIMeshes: seq[Mesh]
+  var bigIMeshes: seq[Mesh]
+  for mesh in allPartsOfType[Mesh](scene.root):
+    case mesh.indexType:
+      of None: meshes.add mesh
+      of Small: smallIMeshes.add mesh
+      of Big: bigIMeshes.add mesh
+  echo pipeline.inputs
+
+  # one drawable per mesh list
+    # one buffer per pipeline.input
+      # how to find data for pipeline.inputs attribute-buffer?
+      # position: get from mesh, mark attribute
+      # color/UVs: material component?
   scene.drawables[pipeline.vk] = @[]
 
+#[
+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
+]#
+
 proc setupDrawables*(scene: var Scene, renderPass: var RenderPass) =
   for subpass in renderPass.subpasses.mitems:
     for pipeline in subpass.pipelines.mitems:
--- a/src/semicongine/vulkan/buffer.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/buffer.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -1,17 +1,48 @@
+import std/sequtils
+import std/tables
+
 import ./api
 import ./device
+import ./memory
+import ./physicaldevice
+import ./commandbuffer
 
 type
   Buffer* = object
     device*: Device
     vk*: VkBuffer
     size*: uint64
+    usage*: seq[VkBufferUsageFlagBits]
+    case hasMemory*: bool
+      of false: discard
+      of true:
+        memory*: DeviceMemory
+        data*: pointer
+
+proc allocateMemory(buffer: var Buffer, flags: openArray[VkMemoryPropertyFlagBits]) =
+  assert buffer.device.vk.valid
+  assert buffer.hasMemory == false
+
+  buffer.hasMemory = 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)
-proc createBuffer(device: Device, size: uint64, flags: openArray[VkBufferCreateFlagBits], usage: openArray[VkBufferUsageFlagBits]): Buffer =
+proc createBuffer*(
+  device: Device,
+  size: uint64,
+  flags: openArray[VkBufferCreateFlagBits],
+  usage: openArray[VkBufferUsageFlagBits],
+  memoryFlags: openArray[VkMemoryPropertyFlagBits],
+): Buffer =
+  assert device.vk.valid
+  assert size > 0
+
   result.device = device
   result.size = size
+  result.usage = usage.toSeq
   var createInfo = VkBufferCreateInfo(
     sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
     flags: toBits(flags),
@@ -26,9 +57,51 @@
     pAllocator=nil,
     pBuffer=addr result.vk
   )
+  result.allocateMemory(memoryFlags)
 
-proc destroy(buffer: var Buffer) =
+
+proc copy*(src, dst: Buffer) =
+  assert src.device.vk.valid
+  assert dst.device.vk.valid
+  assert src.device == dst.device
+  assert src.size == dst.size
+  assert VK_BUFFER_USAGE_TRANSFER_SRC_BIT in src.usage
+  assert VK_BUFFER_USAGE_TRANSFER_DST_BIT in dst.usage
+
+  var queue: Queue
+  for q in src.device.queues.values:
+    if q.family.canDoTransfer:
+      queue = q
+  if not queue.vk.valid:
+    raise newException(Exception, "No queue that supports buffer transfer")
+
+  var
+    commandBufferPool = src.device.createCommandBufferPool(family=queue.family, nBuffers=1)
+    commandBuffer = commandBufferPool.buffers[0]
+
+    beginInfo = VkCommandBufferBeginInfo(
+      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+      flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
+    )
+    copyRegion = VkBufferCopy(size: VkDeviceSize(src.size))
+  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
+  commandBuffer.vkCmdCopyBuffer(src.vk, dst.vk, 1, addr(copyRegion))
+  checkVkResult commandBuffer.vkEndCommandBuffer()
+
+  var submitInfo = VkSubmitInfo(
+    sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
+    commandBufferCount: 1,
+    pCommandBuffers: addr(commandBuffer),
+  )
+  checkVkResult queue.vk.vkQueueSubmit(1, addr(submitInfo), VkFence(0))
+  checkVkResult queue.vk.vkQueueWaitIdle()
+  commandBufferPool.destroy()
+
+proc destroy*(buffer: var Buffer) =
   assert buffer.device.vk.valid
   assert buffer.vk.valid
+  if buffer.hasMemory:
+    assert buffer.memory.vk.valid
+    buffer.memory.free
   buffer.device.vk.vkDestroyBuffer(buffer.vk, nil)
   buffer.vk.reset()
--- a/src/semicongine/vulkan/commandbuffer.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/commandbuffer.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -1,18 +1,14 @@
 import ./api
 import ./device
 import ./physicaldevice
-import ./renderpass
-import ./framebuffer
 import ./utils
 
-import ../math
-
 type
   CommandBufferPool* = object
+    device: Device
     vk*: VkCommandPool
     family*: QueueFamily
     buffers*: seq[VkCommandBuffer]
-    device: Device
 
 proc createCommandBufferPool*(device: Device, family: QueueFamily, nBuffers: int): CommandBufferPool =
   assert device.vk.valid
@@ -34,61 +30,6 @@
   result.buffers = newSeq[VkCommandBuffer](nBuffers)
   checkVkResult device.vk.vkAllocateCommandBuffers(addr(allocInfo), result.buffers.toCPointer)
 
-proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer) =
-  assert commandBuffer.valid
-  assert renderpass.vk.valid
-  assert framebuffer.vk.valid
-  let
-    w = framebuffer.dimension.x
-    h = framebuffer.dimension.y
-
-
-  var clearColors: seq[VkClearValue]
-  for subpass in renderpass.subpasses:
-    clearColors.add(VkClearValue(color: VkClearColorValue(float32: subpass.clearColor)))
-  var
-    beginInfo = VkCommandBufferBeginInfo(
-      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
-      pInheritanceInfo: nil,
-    )
-    renderPassInfo = VkRenderPassBeginInfo(
-      sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
-      renderPass: renderPass.vk,
-      framebuffer: framebuffer.vk,
-      renderArea: VkRect2D(
-        offset: VkOffset2D(x: 0, y: 0),
-        extent: VkExtent2D(width: w, height: h),
-      ),
-      clearValueCount: uint32(clearColors.len),
-      pClearValues: clearColors.toCPointer(),
-    )
-    viewport = VkViewport(
-      x: 0.0,
-      y: 0.0,
-      width: (float)w,
-      height: (float)h,
-      minDepth: 0.0,
-      maxDepth: 1.0,
-    )
-    scissor = VkRect2D(
-      offset: VkOffset2D(x: 0, y: 0),
-      extent: VkExtent2D(width: w, height: h)
-    )
-  checkVkResult commandBuffer.vkResetCommandBuffer(VkCommandBufferResetFlags(0))
-  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
-  commandBuffer.vkCmdBeginRenderPass(addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE)
-  commandBuffer.vkCmdSetViewport(firstViewport=0, viewportCount=1, addr(viewport))
-  commandBuffer.vkCmdSetScissor(firstScissor=0, scissorCount=1, addr(scissor))
-
-proc endRenderCommands*(commandBuffer: VkCommandBuffer) =
-  commandBuffer.vkCmdEndRenderPass()
-  checkVkResult commandBuffer.vkEndCommandBuffer()
-
-template renderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer, body: untyped) =
-  commandBuffer.beginRenderCommands(renderpass, framebuffer)
-  body
-  commandBuffer.endRenderCommands()
-
 
 proc destroy*(commandpool: var CommandBufferPool) =
   assert commandpool.device.vk.valid
--- a/src/semicongine/vulkan/device.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/device.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -72,7 +72,7 @@
   for family in deviceQueues.keys:
     var queue: VkQueue
     vkGetDeviceQueue(result.vk, family.index, 0, addr queue)
-    result.queues[family] = Queue(vk: queue, family: family, presentation: family.hasPresentation(physicalDevice.surface), graphics: family.hasGraphics())
+    result.queues[family] = Queue(vk: queue, family: family, presentation: family.canDoPresentation(physicalDevice.surface), graphics: family.canDoGraphics())
 
 func firstGraphicsQueue*(device: Device): Option[Queue] =
   assert device.vk.valid
--- a/src/semicongine/vulkan/memory.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/memory.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -1,4 +1,7 @@
+import std/strformat
+
 import ./api
+import ./device
 
 type
   MemoryHeap = object
@@ -12,10 +15,10 @@
   PhyscialDeviceMemoryProperties = object
     heaps*: seq[MemoryHeap]
     types*: seq[MemoryType]
-  DeviceMemory = object
-    device*: VkDevice
+  DeviceMemory* = object
+    device*: Device
+    vk*: VkDeviceMemory
     size*: uint64
-    vk*: VkDeviceMemory
 
 proc getPhysicalDeviceMemoryProperties(physicalDevice: VkPhysicalDevice): PhyscialDeviceMemoryProperties =
   var physicalProperties: VkPhysicalDeviceMemoryProperties
@@ -33,19 +36,59 @@
       index: i,
     )
 
-proc allocateMemory(device: VkDevice, size: uint64, memoryType: MemoryType): DeviceMemory =
+proc allocate*(device: Device, size: uint64, flags: openArray[VkMemoryPropertyFlagBits]): DeviceMemory =
+  assert device.vk.valid
+
   result.device = device
   result.size = size
 
+  var
+    memtype: MemoryType
+    hasAllFlags: bool
+  for mtype in device.physicalDevice.vk.getPhysicalDeviceMemoryProperties.types:
+    hasAllFlags = true
+    for flag in flags:
+      if not (flag in mtype.flags):
+        hasAllFlags = false
+        break
+    if hasAllFlags:
+      memtype = mtype
+      break
+  if not hasAllFlags:
+    raise newException(Exception, &"No memory with support for {flags}")
+
   var allocationInfo = VkMemoryAllocateInfo(
     sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
     allocationSize: size,
-    memoryTypeIndex: memoryType.index,
+    memoryTypeIndex: memtype.index,
   )
 
   checkVkResult vkAllocateMemory(
-    device,
+    device.vk,
     addr allocationInfo,
     nil,
     addr result.vk
   )
+
+proc map*(memory: DeviceMemory, offset=0'u64, size=0'u64): pointer =
+  assert memory.device.vk.valid
+  assert memory.vk.valid
+  
+  var thesize = size
+  if thesize == 0:
+    thesize = memory.size
+
+  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)
+  )
+
+proc free*(memory: var DeviceMemory) =
+  assert memory.device.vk.valid
+  assert memory.vk.valid
+
+  memory.device.vk.vkFreeMemory(memory.vk, nil)
+  memory.vk.reset
--- a/src/semicongine/vulkan/physicaldevice.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/physicaldevice.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -99,37 +99,41 @@
       flags: queuFamilies[i].queueFlags.toEnums,
     )
 
-proc hasGraphics*(family: QueueFamily): bool =
+proc canDoGraphics*(family: QueueFamily): bool =
   VK_QUEUE_GRAPHICS_BIT in family.flags
-proc hasPresentation*(family: QueueFamily, surface: VkSurfaceKHR): bool =
+
+proc canDoPresentation*(family: QueueFamily, surface: VkSurfaceKHR): bool =
   assert surface.valid
   var presentation = VkBool32(false)
   checkVkResult vkGetPhysicalDeviceSurfaceSupportKHR(family.device.vk, family.index, surface, addr presentation)
   return presentation
 
+proc canDoTransfer*(family: QueueFamily): bool =
+  VK_QUEUE_TRANSFER_BIT in family.flags
+
 proc filterForGraphicsPresentationQueues*(device: PhysicalDevice): seq[QueueFamily] =
-  var hasGraphics = false
-  var hasPresentation = false
+  var canDoGraphics = false
+  var canDoPresentation = false
   var queues: Table[uint32, QueueFamily]
   for family in device.getQueueFamilies():
-    if family.hasGraphics:
+    if family.canDoGraphics:
       queues[family.index] = family
-      hasGraphics = true
-    if family.hasPresentation(device.surface):
+      canDoGraphics = true
+    if family.canDoPresentation(device.surface):
       queues[family.index] = family
-      hasPresentation = true
-    if hasGraphics and hasPresentation:
+      canDoPresentation = true
+    if canDoGraphics and canDoPresentation:
       return queues.values.toSeq
 
 proc filterGraphics(families: seq[QueueFamily]): seq[QueueFamily] =
   for family in families:
-    if family.hasGraphics:
+    if family.canDoGraphics:
       result.add family
 
 proc filterPresentation(families: seq[QueueFamily], surface: VkSurfaceKHR): seq[QueueFamily] =
   assert surface.valid
   for family in families:
-    if family.hasPresentation(surface):
+    if family.canDoPresentation(surface):
       result.add family
 
 proc rateGraphics*(device: PhysicalDevice): float =
--- a/src/semicongine/vulkan/pipeline.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -1,16 +1,24 @@
 import ./api
 import ./device
 import ./descriptor
+import ./shader
+
+import ../gpu_data
 
 type
   Pipeline* = object
     device*: Device
     vk*: VkPipeline
     layout*: VkPipelineLayout
+    shaders*: seq[Shader]
     descriptorSetLayout*: DescriptorSetLayout
     descriptorPool*: DescriptorPool
     descriptorSets*: seq[DescriptorSet]
 
+func inputs*(pipeline: Pipeline): AttributeGroup =
+  for shader in pipeline.shaders:
+    if shader.stage == VK_SHADER_STAGE_VERTEX_BIT:
+      return shader.inputs
 
 proc destroy*(pipeline: var Pipeline) =
   assert pipeline.device.vk.valid
--- a/src/semicongine/vulkan/renderpass.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -74,7 +74,7 @@
   assert vertexShader.stage == VK_SHADER_STAGE_VERTEX_BIT
   assert fragmentShader.stage == VK_SHADER_STAGE_FRAGMENT_BIT
 
-  var pipeline = Pipeline(device: renderPass.device)
+  var pipeline = Pipeline(device: renderPass.device, shaders: @[vertexShader, fragmentShader])
   
   var descriptors = @[Descriptor(
     thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
--- a/src/semicongine/vulkan/shader.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/shader.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -1,5 +1,4 @@
 import std/typetraits
-import std/macros
 import std/os
 import std/enumerate
 import std/logging
@@ -8,10 +7,8 @@
 import std/strutils
 import std/compilesettings
 
-import ../math
 import ./api
 import ./device
-import ./vertex
 import ./utils
 
 import ../gpu_data
--- a/src/semicongine/vulkan/swapchain.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -100,6 +100,60 @@
 
   return (swapchain, createResult)
 
+proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer) =
+  assert commandBuffer.valid
+  assert renderpass.vk.valid
+  assert framebuffer.vk.valid
+  let
+    w = framebuffer.dimension.x
+    h = framebuffer.dimension.y
+
+  var clearColors: seq[VkClearValue]
+  for subpass in renderpass.subpasses:
+    clearColors.add(VkClearValue(color: VkClearColorValue(float32: subpass.clearColor)))
+  var
+    beginInfo = VkCommandBufferBeginInfo(
+      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+      pInheritanceInfo: nil,
+    )
+    renderPassInfo = VkRenderPassBeginInfo(
+      sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+      renderPass: renderPass.vk,
+      framebuffer: framebuffer.vk,
+      renderArea: VkRect2D(
+        offset: VkOffset2D(x: 0, y: 0),
+        extent: VkExtent2D(width: w, height: h),
+      ),
+      clearValueCount: uint32(clearColors.len),
+      pClearValues: clearColors.toCPointer(),
+    )
+    viewport = VkViewport(
+      x: 0.0,
+      y: 0.0,
+      width: (float)w,
+      height: (float)h,
+      minDepth: 0.0,
+      maxDepth: 1.0,
+    )
+    scissor = VkRect2D(
+      offset: VkOffset2D(x: 0, y: 0),
+      extent: VkExtent2D(width: w, height: h)
+    )
+  checkVkResult commandBuffer.vkResetCommandBuffer(VkCommandBufferResetFlags(0))
+  checkVkResult commandBuffer.vkBeginCommandBuffer(addr(beginInfo))
+  commandBuffer.vkCmdBeginRenderPass(addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE)
+  commandBuffer.vkCmdSetViewport(firstViewport=0, viewportCount=1, addr(viewport))
+  commandBuffer.vkCmdSetScissor(firstScissor=0, scissorCount=1, addr(scissor))
+
+proc endRenderCommands*(commandBuffer: VkCommandBuffer) =
+  commandBuffer.vkCmdEndRenderPass()
+  checkVkResult commandBuffer.vkEndCommandBuffer()
+
+template renderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer, body: untyped) =
+  commandBuffer.beginRenderCommands(renderpass, framebuffer)
+  body
+  commandBuffer.endRenderCommands()
+
 proc draw*(commandBuffer: VkCommandBuffer, drawables: seq[Drawable], scene: Scene) =
   for drawable in drawables:
     var buffers: seq[VkBuffer]
--- a/tests/test_vulkan_wrapper.nim	Fri Mar 31 16:00:16 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Sat Apr 01 00:40:02 2023 +0700
@@ -1,4 +1,3 @@
-import std/os
 import std/options
 
 import semicongine/vulkan
@@ -7,16 +6,7 @@
 import semicongine/entity
 import semicongine/scene
 import semicongine/gpu_data
-
-type
-  Vertex = object
-    pos: Vec3
-  FragmentInput = object
-    fragpos: Vec3
-  Uniforms = object
-    time: float32
-  Pixel = object
-    color: Vec4
+import semicongine/mesh
 
 proc diagnostics(instance: Instance) =
   # diagnostic output
@@ -83,11 +73,11 @@
   if res != VK_SUCCESS:
     raise newException(Exception, "Unable to create swapchain")
 
-  var thescene = Scene(root: newEntity("scene"))
+  var thescene = Scene(name: "main", root: newEntity("triangle", newMesh([newVec3(-1, -1), newVec3(0, 1), newVec3(1, -1)])))
   thescene.setupDrawables(renderPass)
 
   echo "Setup successfull, start rendering"
-  for i in 0 ..< 10:
+  for i in 0 ..< 1:
     discard swapchain.drawScene(thescene)
   echo "Rendered ", swapchain.framesRendered, " frames"
   echo "Start cleanup"