changeset 111:6fd10b7e2d6a

did: allow runtime shader-input definitions
author Sam <sam@basx.dev>
date Fri, 31 Mar 2023 16:00:16 +0700
parents 3bbc94a83404
children 0c5a74885796
files src/semicongine/gpu_data.nim src/semicongine/legacy/glsl.nim src/semicongine/scene.nim src/semicongine/vulkan/descriptor.nim src/semicongine/vulkan/glsl.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 10 files changed, 483 insertions(+), 294 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/gpu_data.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -0,0 +1,186 @@
+import std/strformat
+import std/tables
+
+import ./vulkan/api
+
+type
+  CountType = 1'u32 .. 4'u32
+  DataType* = enum
+    Float32
+    Float64
+    Int8
+    Int16
+    Int32
+    Int64
+    UInt8
+    UInt16
+    UInt32
+    UInt64
+  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
+  AttributeGroup* = object
+    attributes*: seq[Attribute]
+
+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 size*(thetype: DataType): uint32 =
+  case thetype:
+    of Float32: 4
+    of Float64: 8
+    of Int8: 1
+    of Int16: 2
+    of Int32: 4
+    of Int64: 8
+    of UInt8: 1
+    of UInt16: 2
+    of UInt32: 4
+    of UInt64: 8
+  
+func size*(attribute: Attribute, perRow=false): uint32 =
+  if perRow:
+    attribute.thetype.size * attribute.components
+  else:
+    attribute.thetype.size * attribute.components * attribute.rows
+
+func size*(thetype: AttributeGroup): uint32 =
+  for attribute in thetype.attributes:
+    result += attribute.size
+
+const TYPEMAP = {
+  CountType(1): {
+    UInt8: VK_FORMAT_R8_UINT,
+    Int8: VK_FORMAT_R8_SINT,
+    UInt16: VK_FORMAT_R16_UINT,
+    Int16: VK_FORMAT_R16_SINT,
+    UInt32: VK_FORMAT_R32_UINT,
+    Int32: VK_FORMAT_R32_SINT,
+    UInt64: VK_FORMAT_R64_UINT,
+    Int64: VK_FORMAT_R64_SINT,
+    Float32: VK_FORMAT_R32_SFLOAT,
+    Float64: VK_FORMAT_R64_SFLOAT,
+  }.toTable,
+  CountType(2): {
+    UInt8: VK_FORMAT_R8G8_UINT,
+    Int8: VK_FORMAT_R8G8_SINT,
+    UInt16: VK_FORMAT_R16G16_UINT,
+    Int16: VK_FORMAT_R16G16_SINT,
+    UInt32: VK_FORMAT_R32G32_UINT,
+    Int32: VK_FORMAT_R32G32_SINT,
+    UInt64: VK_FORMAT_R64G64_UINT,
+    Int64: VK_FORMAT_R64G64_SINT,
+    Float32: VK_FORMAT_R32G32_SFLOAT,
+    Float64: VK_FORMAT_R64G64_SFLOAT,
+  }.toTable,
+  CountType(3): {
+    UInt8: VK_FORMAT_R8G8B8_UINT,
+    Int8: VK_FORMAT_R8G8B8_SINT,
+    UInt16: VK_FORMAT_R16G16B16_UINT,
+    Int16: VK_FORMAT_R16G16B16_SINT,
+    UInt32: VK_FORMAT_R32G32B32_UINT,
+    Int32: VK_FORMAT_R32G32B32_SINT,
+    UInt64: VK_FORMAT_R64G64B64_UINT,
+    Int64: VK_FORMAT_R64G64B64_SINT,
+    Float32: VK_FORMAT_R32G32B32_SFLOAT,
+    Float64: VK_FORMAT_R64G64B64_SFLOAT,
+  }.toTable,
+  CountType(4): {
+    UInt8: VK_FORMAT_R8G8B8A8_UINT,
+    Int8: VK_FORMAT_R8G8B8A8_SINT,
+    UInt16: VK_FORMAT_R16G16B16A16_UINT,
+    Int16: VK_FORMAT_R16G16B16A16_SINT,
+    UInt32: VK_FORMAT_R32G32B32A32_UINT,
+    Int32: VK_FORMAT_R32G32B32A32_SINT,
+    UInt64: VK_FORMAT_R64G64B64A64_UINT,
+    Int64: VK_FORMAT_R64G64B64A64_SINT,
+    Float32: VK_FORMAT_R32G32B32A32_SFLOAT,
+    Float64: VK_FORMAT_R64G64B64A64_SFLOAT,
+  }.toTable,
+}.toTable
+
+func getVkFormat*(value: Attribute): VkFormat =
+  TYPEMAP[value.components][value.thetype]
+
+# from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
+func nLocationSlots*(attribute: Attribute): uint32 =
+  #[
+  single location:
+    16-bit scalar and vector types, and
+    32-bit scalar and vector types, and
+    64-bit scalar and 2-component vector types.
+  two locations
+    64-bit three- and four-component vectors
+  ]#
+  case attribute.thetype:
+    of Float32: 1
+    of Float64: (if attribute.components < 3: 1 else: 2)
+    of Int8: 1
+    of Int16: 1
+    of Int32: 1
+    of Int64: (if attribute.components < 3: 1 else: 2)
+    of UInt8: 1
+    of UInt16: 1
+    of UInt32: 1
+    of UInt64: (if attribute.components < 3: 1 else: 2)
+
+func glslType*(attribute: Attribute): string =
+  # todo: likely not correct as we would need to enable some 
+  # extensions somewhere (Vulkan/GLSL compiler?) to have 
+  # everything work as intended. Or maybe the GPU driver does
+  # some automagic conversion stuf..
+  
+  # used to ensure square matrix get only one number for side instead of two,  e.g. mat2 instead of mat22
+  let matrixColumns = if attribute.components == attribute.rows: "" else: $attribute.components
+  case attribute.rows:
+    of 1:
+      case attribute.components:
+        of 1: # scalars
+          case attribute.thetype:
+            of Float32: "float"
+            of Float64: "double"
+            of Int8, Int16, Int32, Int64: "int"
+            of UInt8, UInt16, UInt32, UInt64: "uint"
+        else: # vectors
+          case attribute.thetype:
+            of Float32: &"vec{attribute.components}"
+            of Float64: &"dvec{attribute.components}"
+            of Int8, Int16, Int32, Int64: &"ivec{attribute.components}"
+            of UInt8, UInt16, UInt32, UInt64: &"uvec{attribute.components}"
+    else:
+      case attribute.components:
+        of 1: raise newException(Exception, &"Unsupported matrix-column-count: {attribute.components}")
+        else:
+          case attribute.thetype:
+            of Float32: &"mat{attribute.rows}{matrixColumns}"
+            of Float64: &"dmat{attribute.rows}{matrixColumns}"
+            else: raise newException(Exception, &"Unsupported matrix-component type: {attribute.thetype}")
+
+func glslInput*(group: AttributeGroup): seq[string] =
+  if group.attributes.len == 0:
+    return @[]
+  var i = 0'u32
+  for attribute in group.attributes:
+    result.add &"layout(location = {i}) in {attribute.glslType} {attribute.name};"
+    for j in 0 ..< attribute.rows:
+      i += attribute.nLocationSlots
+
+func glslUniforms*(group: AttributeGroup, blockName="Uniforms", binding=0): seq[string] =
+  if group.attributes.len == 0:
+    return @[]
+  # currently only a single uniform block supported, therefore binding = 0
+  result.add(&"layout(binding = {binding}) uniform T{blockName} {{")
+  for attribute in group.attributes:
+    result.add(&"    {attribute.glslType} {attribute.name};")
+  result.add(&"}} {blockName};")
+
+func glslOutput*(group: AttributeGroup): seq[string] =
+  if group.attributes.len == 0:
+    return @[]
+  var i = 0'u32
+  for attribute in group.attributes:
+    result.add &"layout(location = {i}) out {attribute.glslType} {attribute.name};"
+    i += 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/glsl.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -0,0 +1,132 @@
+import std/typetraits
+import std/strformat
+import ../math/vector
+import ../math/matrix
+
+
+func getGLSLType*[T](t: T): string {.compileTime.} =
+  # todo: likely not correct as we would need to enable some 
+  # extensions somewhere (Vulkan/GLSL compiler?) to have 
+  # everything work as intended. Or maybe the GPU driver does
+  # some automagic conversion stuf..
+  when T is uint8:         "uint"
+  elif T is int8:          "int"
+  elif T is uint16:        "uint"
+  elif T is int16:         "int"
+  elif T is uint32:        "uint"
+  elif T is int32:         "int"
+  elif T is uint64:        "uint"
+  elif T is int64:         "int"
+  elif T is float32:       "float"
+  elif T is float64:       "double"
+
+  elif T is TVec2[uint8]:   "uvec2"
+  elif T is TVec2[int8]:    "ivec2"
+  elif T is TVec2[uint16]:  "uvec2"
+  elif T is TVec2[int16]:   "ivec2"
+  elif T is TVec2[uint32]:  "uvec2"
+  elif T is TVec2[int32]:   "ivec2"
+  elif T is TVec2[uint64]:  "uvec2"
+  elif T is TVec2[int64]:   "ivec2"
+  elif T is TVec2[float32]: "vec2"
+  elif T is TVec2[float64]: "dvec2"
+
+  elif T is TVec3[uint8]:   "uvec3"
+  elif T is TVec3[int8]:    "ivec3"
+  elif T is TVec3[uint16]:  "uvec3"
+  elif T is TVec3[int16]:   "ivec3"
+  elif T is TVec3[uint32]:  "uvec3"
+  elif T is TVec3[int32]:   "ivec3"
+  elif T is TVec3[uint64]:  "uvec3"
+  elif T is TVec3[int64]:   "ivec3"
+  elif T is TVec3[float32]: "vec3"
+  elif T is TVec3[float64]: "dvec3"
+
+  elif T is TVec4[uint8]:   "uvec4"
+  elif T is TVec4[int8]:    "ivec4"
+  elif T is TVec4[uint16]:  "uvec4"
+  elif T is TVec4[int16]:   "ivec4"
+  elif T is TVec4[uint32]:  "uvec4"
+  elif T is TVec4[int32]:   "ivec4"
+  elif T is TVec4[uint64]:  "uvec4"
+  elif T is TVec4[int64]:   "ivec4"
+  elif T is TVec4[float32]: "vec4"
+  elif T is TVec4[float64]: "dvec4"
+
+  elif T is TMat22[float32]: "mat2"
+  elif T is TMat23[float32]: "mat32"
+  elif T is TMat32[float32]: "mat23"
+  elif T is TMat33[float32]: "mat3"
+  elif T is TMat34[float32]: "mat43"
+  elif T is TMat43[float32]: "mat34"
+  elif T is TMat44[float32]: "mat4"
+
+  elif T is TMat22[float64]: "dmat2"
+  elif T is TMat23[float64]: "dmat32"
+  elif T is TMat32[float64]: "dmat23"
+  elif T is TMat33[float64]: "dmat3"
+  elif T is TMat34[float64]: "dmat43"
+  elif T is TMat43[float64]: "dmat34"
+  elif T is TMat44[float64]: "dmat4"
+
+
+# return the number of elements into which larger types are divided
+func compositeAttributesNumber*[T](value: T): int =
+  when T is TMat33[float32]:
+    3
+  elif T is TMat44[float32]:
+    4
+  else:
+    1
+
+
+# from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
+func nLocationSlots*[T](value: T): uint32 =
+  when (T is TVec3[float64] or T is TVec3[uint64] or T is TVec4[float64] or T is TVec4[float64]):
+    return 2
+  elif T is SomeNumber or T is TVec:
+    return 1
+  else:
+    raise newException(Exception, "Unsupported vertex attribute type")
+
+
+# return the type into which larger types are divided
+func compositeAttribute*[T](value: T): auto =
+  when T is TMat33[float32]:
+    Vec3()
+  elif T is TMat44[float32]:
+    Vec4()
+  else:
+    value
+
+func glslInput*[T](): seq[string] {.compileTime.} =
+  when not (T is void):
+    var i = 0'u32
+    for fieldname, value in default(T).fieldPairs:
+      let glsltype = getGLSLType(value)
+      let thename = fieldname
+      result.add &"layout(location = {i}) in {glsltype} {thename};"
+      for j in 0 ..< compositeAttributesNumber(value):
+        i += nLocationSlots(compositeAttribute(value))
+
+func glslUniforms*[T](): seq[string] {.compileTime.} =
+  # currently only a single uniform block supported, therefore binding = 0
+  when not (T is void):
+    let uniformName = name(T)
+    result.add(&"layout(binding = 0) uniform T{uniformName} {{")
+    for fieldname, value in default(T).fieldPairs:
+      let glsltype = getGLSLType(value)
+      let thename = fieldname
+      result.add(&"    {glsltype} {thename};")
+    result.add(&"}} {uniformName};")
+
+func glslOutput*[T](): seq[string] {.compileTime.} =
+  when not (T is void):
+    var i = 0'u32
+    for fieldname, value in default(T).fieldPairs:
+      let glsltype = getGLSLType(value)
+      let thename = fieldname
+      result.add &"layout(location = {i}) out {glsltype} {thename};"
+      i += 1
+  else:
+    result
--- a/src/semicongine/scene.nim	Thu Mar 30 00:00:54 2023 +0700
+++ b/src/semicongine/scene.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -1,13 +1,15 @@
-import std/options
+import std/tables
 
 import ./vulkan/api
 import ./entity
 import ./vulkan/buffer
+import ./vulkan/pipeline
+import ./vulkan/renderpass
 
 type
   Drawable* = object
-    buffers*: seq[Buffer]
-    elementCount*: uint32
+    buffers*: seq[(Buffer, int)] # buffer + offset from buffer
+    elementCount*: uint32 # vertices or indices
     instanceCount*: uint32
     case indexed*: bool
     of true:
@@ -18,8 +20,20 @@
 
   Scene* = object
     root*: Entity
-    drawables: seq[Drawable]
+    drawables: Table[VkPipeline, seq[Drawable]]
+
+proc setupDrawables(scene: var Scene, pipeline: Pipeline) =
+  # echo pipeline.descriptorSetLayout.descriptors
+  # thetype*: VkDescriptorType
+  # count*: uint32
+  # itemsize*: uint32
+  scene.drawables[pipeline.vk] = @[]
 
-proc getDrawables*(scene: Scene): seq[Drawable] =
-  # TODO: create and fill buffers
-  result
+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, @[])
--- a/src/semicongine/vulkan/descriptor.nim	Thu Mar 30 00:00:54 2023 +0700
+++ b/src/semicongine/vulkan/descriptor.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -14,7 +14,7 @@
   DescriptorSetLayout* = object # "type description of a DescriptorSet
     device: Device
     vk*: VkDescriptorSetLayout
-    descriptors: seq[Descriptor]
+    descriptors*: seq[Descriptor]
   DescriptorSet* = object # "instance" of a DescriptorSetLayout
     vk*: VkDescriptorSet
     layout: DescriptorSetLayout
--- a/src/semicongine/vulkan/glsl.nim	Thu Mar 30 00:00:54 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,132 +0,0 @@
-import std/typetraits
-import std/strformat
-import ../math/vector
-import ../math/matrix
-
-
-func getGLSLType*[T](t: T): string {.compileTime.} =
-  # todo: likely not correct as we would need to enable some 
-  # extensions somewhere (Vulkan/GLSL compiler?) to have 
-  # everything work as intended. Or maybe the GPU driver does
-  # some automagic conversion stuf..
-  when T is uint8:         "uint"
-  elif T is int8:          "int"
-  elif T is uint16:        "uint"
-  elif T is int16:         "int"
-  elif T is uint32:        "uint"
-  elif T is int32:         "int"
-  elif T is uint64:        "uint"
-  elif T is int64:         "int"
-  elif T is float32:       "float"
-  elif T is float64:       "double"
-
-  elif T is TVec2[uint8]:   "uvec2"
-  elif T is TVec2[int8]:    "ivec2"
-  elif T is TVec2[uint16]:  "uvec2"
-  elif T is TVec2[int16]:   "ivec2"
-  elif T is TVec2[uint32]:  "uvec2"
-  elif T is TVec2[int32]:   "ivec2"
-  elif T is TVec2[uint64]:  "uvec2"
-  elif T is TVec2[int64]:   "ivec2"
-  elif T is TVec2[float32]: "vec2"
-  elif T is TVec2[float64]: "dvec2"
-
-  elif T is TVec3[uint8]:   "uvec3"
-  elif T is TVec3[int8]:    "ivec3"
-  elif T is TVec3[uint16]:  "uvec3"
-  elif T is TVec3[int16]:   "ivec3"
-  elif T is TVec3[uint32]:  "uvec3"
-  elif T is TVec3[int32]:   "ivec3"
-  elif T is TVec3[uint64]:  "uvec3"
-  elif T is TVec3[int64]:   "ivec3"
-  elif T is TVec3[float32]: "vec3"
-  elif T is TVec3[float64]: "dvec3"
-
-  elif T is TVec4[uint8]:   "uvec4"
-  elif T is TVec4[int8]:    "ivec4"
-  elif T is TVec4[uint16]:  "uvec4"
-  elif T is TVec4[int16]:   "ivec4"
-  elif T is TVec4[uint32]:  "uvec4"
-  elif T is TVec4[int32]:   "ivec4"
-  elif T is TVec4[uint64]:  "uvec4"
-  elif T is TVec4[int64]:   "ivec4"
-  elif T is TVec4[float32]: "vec4"
-  elif T is TVec4[float64]: "dvec4"
-
-  elif T is TMat22[float32]: "mat2"
-  elif T is TMat23[float32]: "mat32"
-  elif T is TMat32[float32]: "mat23"
-  elif T is TMat33[float32]: "mat3"
-  elif T is TMat34[float32]: "mat43"
-  elif T is TMat43[float32]: "mat34"
-  elif T is TMat44[float32]: "mat4"
-
-  elif T is TMat22[float64]: "dmat2"
-  elif T is TMat23[float64]: "dmat32"
-  elif T is TMat32[float64]: "dmat23"
-  elif T is TMat33[float64]: "dmat3"
-  elif T is TMat34[float64]: "dmat43"
-  elif T is TMat43[float64]: "dmat34"
-  elif T is TMat44[float64]: "dmat4"
-
-
-# return the number of elements into which larger types are divided
-func compositeAttributesNumber*[T](value: T): int =
-  when T is TMat33[float32]:
-    3
-  elif T is TMat44[float32]:
-    4
-  else:
-    1
-
-
-# from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
-func nLocationSlots*[T](value: T): uint32 =
-  when (T is TVec3[float64] or T is TVec3[uint64] or T is TVec4[float64] or T is TVec4[float64]):
-    return 2
-  elif T is SomeNumber or T is TVec:
-    return 1
-  else:
-    raise newException(Exception, "Unsupported vertex attribute type")
-
-
-# return the type into which larger types are divided
-func compositeAttribute*[T](value: T): auto =
-  when T is TMat33[float32]:
-    Vec3()
-  elif T is TMat44[float32]:
-    Vec4()
-  else:
-    value
-
-func glslInput*[T](): seq[string] {.compileTime.} =
-  when not (T is void):
-    var i = 0'u32
-    for fieldname, value in default(T).fieldPairs:
-      let glsltype = getGLSLType(value)
-      let thename = fieldname
-      result.add &"layout(location = {i}) in {glsltype} {thename};"
-      for j in 0 ..< compositeAttributesNumber(value):
-        i += nLocationSlots(compositeAttribute(value))
-
-func glslUniforms*[T](): seq[string] {.compileTime.} =
-  # currently only a single uniform block supported, therefore binding = 0
-  when not (T is void):
-    let uniformName = name(T)
-    result.add(&"layout(binding = 0) uniform T{uniformName} {{")
-    for fieldname, value in default(T).fieldPairs:
-      let glsltype = getGLSLType(value)
-      let thename = fieldname
-      result.add(&"    {glsltype} {thename};")
-    result.add(&"}} {uniformName};")
-
-func glslOutput*[T](): seq[string] {.compileTime.} =
-  when not (T is void):
-    var i = 0'u32
-    for fieldname, value in default(T).fieldPairs:
-      let glsltype = getGLSLType(value)
-      let thename = fieldname
-      result.add &"layout(location = {i}) out {glsltype} {thename};"
-      i += 1
-  else:
-    result
--- a/src/semicongine/vulkan/pipeline.nim	Thu Mar 30 00:00:54 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -1,12 +1,7 @@
-import std/options
-
 import ./api
-import ./utils
 import ./device
 import ./descriptor
 
-import ../scene
-
 type
   Pipeline* = object
     device*: Device
@@ -16,38 +11,6 @@
     descriptorPool*: DescriptorPool
     descriptorSets*: seq[DescriptorSet]
 
-proc run*(pipeline: Pipeline, commandBuffer: VkCommandBuffer, inFlightFrame: int, scene: Scene) =
-  var varPipeline = pipeline
-  commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.vk)
-  commandBuffer.vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(varPipeline.descriptorSets[inFlightFrame].vk), 0, nil)
-  for drawable in scene.getDrawables():
-    var buffers: seq[VkBuffer]
-    var offsets: seq[VkDeviceSize]
-    for buffer in drawable.buffers:
-      buffers.add buffer.vk
-      offsets.add VkDeviceSize(0)
-    commandBuffer.vkCmdBindVertexBuffers(
-      firstBinding=0'u32,
-      bindingCount=uint32(buffers.len),
-      pBuffers=buffers.toCPointer(),
-      pOffsets=offsets.toCPointer()
-    )
-    if drawable.indexed:
-      commandBuffer.vkCmdBindIndexBuffer(drawable.indexBuffer.vk, VkDeviceSize(0), drawable.indexType)
-      commandBuffer.vkCmdDrawIndexed(
-        indexCount=drawable.elementCount,
-        instanceCount=drawable.instanceCount,
-        firstIndex=0,
-        vertexOffset=0,
-        firstInstance=0
-      )
-    else:
-      commandBuffer.vkCmdDraw(
-        vertexCount=drawable.elementCount,
-        instanceCount=drawable.instanceCount,
-        firstVertex=0,
-        firstInstance=0
-      )
 
 proc destroy*(pipeline: var Pipeline) =
   assert pipeline.device.vk.valid
--- a/src/semicongine/vulkan/renderpass.nim	Thu Mar 30 00:00:54 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -6,13 +6,15 @@
 import ./pipeline
 import ./shader
 import ./descriptor
+import ../gpu_data
+
 import ../math
 
 type
   Subpass* = object
     clearColor*: Vec4
+    pipelineBindPoint*: VkPipelineBindPoint
     flags: VkSubpassDescriptionFlags
-    pipelineBindPoint: VkPipelineBindPoint
     inputs: seq[VkAttachmentReference]
     outputs: seq[VkAttachmentReference]
     resolvers: seq[VkAttachmentReference]
@@ -66,7 +68,7 @@
   result.subpasses = pSubpasses
   checkVkResult device.vk.vkCreateRenderPass(addr(createInfo), nil, addr(result.vk))
 
-proc attachPipeline[VertexShader: Shader, FragmentShader: Shader](renderPass: var RenderPass, vertexShader: VertexShader, fragmentShader: FragmentShader, subpass = 0'u32) =
+proc attachPipeline(renderPass: var RenderPass, vertexShader: Shader, fragmentShader: Shader, subpass = 0'u32) =
   assert renderPass.vk.valid
   assert renderPass.device.vk.valid
   assert vertexShader.stage == VK_SHADER_STAGE_VERTEX_BIT
@@ -78,16 +80,16 @@
     thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
     count: 1,
     stages: @[VK_SHADER_STAGE_VERTEX_BIT],
-    itemsize: uint32(sizeof(shaderUniforms(vertexShader)))
+    itemsize: vertexShader.uniforms.size(),
   )]
-  when shaderUniforms(vertexShader) is shaderUniforms(fragmentShader):
+  if vertexShader.uniforms == fragmentShader.uniforms:
     descriptors[0].stages.add VK_SHADER_STAGE_FRAGMENT_BIT
   else:
     descriptors.add Descriptor(
       thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
       count: 1,
       stages: @[VK_SHADER_STAGE_FRAGMENT_BIT],
-      itemsize: uint32(sizeof(shaderUniforms(fragmentShader)))
+      itemsize: fragmentShader.uniforms.size(),
     )
   pipeline.descriptorSetLayout = renderPass.device.createDescriptorSetLayout(descriptors)
 
@@ -197,7 +199,7 @@
   pipeline.descriptorSets = pipeline.descriptorPool.allocateDescriptorSet(pipeline.descriptorSetLayout, renderPass.inFlightFrames)
   renderPass.subpasses[subpass].pipelines.add pipeline
 
-proc simpleForwardRenderPass*[VertexShader: Shader, FragmentShader: Shader](device: Device, format: VkFormat, vertexShader: VertexShader, fragmentShader: FragmentShader, inFlightFrames: int, clearColor=Vec4([0.5'f32, 0.5'f32, 0.5'f32, 1'f32])): RenderPass =
+proc simpleForwardRenderPass*(device: Device, format: VkFormat, vertexShader: Shader, fragmentShader: Shader, inFlightFrames: int, clearColor=Vec4([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): RenderPass =
   assert device.vk.valid
   var
     attachments = @[VkAttachmentDescription(
@@ -229,12 +231,12 @@
   result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies, inFlightFrames=inFlightFrames)
   result.attachPipeline(vertexShader, fragmentShader, 0)
 
-proc destroy*(renderpass: var RenderPass) =
-  assert renderpass.device.vk.valid
-  assert renderpass.vk.valid
-  renderpass.device.vk.vkDestroyRenderPass(renderpass.vk, nil)
-  renderpass.vk.reset
-  for subpass in renderpass.subpasses.mitems:
+proc destroy*(renderPass: var RenderPass) =
+  assert renderPass.device.vk.valid
+  assert renderPass.vk.valid
+  renderPass.device.vk.vkDestroyRenderPass(renderPass.vk, nil)
+  renderPass.vk.reset
+  for subpass in renderPass.subpasses.mitems:
     for pipeline in subpass.pipelines.mitems:
       pipeline.destroy()
-  renderpass.subpasses = @[]
+  renderPass.subpasses = @[]
--- a/src/semicongine/vulkan/shader.nim	Thu Mar 30 00:00:54 2023 +0700
+++ b/src/semicongine/vulkan/shader.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -12,22 +12,22 @@
 import ./api
 import ./device
 import ./vertex
-import ./glsl
 import ./utils
 
+import ../gpu_data
+
 let logger = newConsoleLogger()
 addHandler(logger)
 
 type
-  Shader*[Inputs, Uniforms, Outputs] = object
+  Shader* = object
     device: Device
     stage*: VkShaderStageFlagBits
     vk*: VkShaderModule
     entrypoint*: string
-
-template shaderInput*[Inputs, Uniforms, Outputs](shader: Shader[Inputs, Uniforms, Outputs]): typedesc = Inputs
-template shaderOutputs*[Inputs, Uniforms, Outputs](shader: Shader[Inputs, Uniforms, Outputs]): typedesc = Outputs
-template shaderUniforms*[Inputs, Uniforms, Outputs](shader: Shader[Inputs, Uniforms, Outputs]): typedesc = Uniforms
+    inputs*: AttributeGroup
+    uniforms*: AttributeGroup
+    outputs*: AttributeGroup
 
 
 proc compileGLSLToSPIRV*(stage: VkShaderStageFlagBits, shaderSource: string, entrypoint: string): seq[uint32] {.compileTime.} =
@@ -78,26 +78,53 @@
     i += 4
 
 
-proc shaderCode*[Inputs, Uniforms, Outputs](stage: VkShaderStageFlagBits, version: int, entrypoint: string, body: seq[string]): seq[uint32] {.compileTime.} =
+proc shaderCode*(
+  inputs: AttributeGroup,
+  uniforms: AttributeGroup,
+  outputs: AttributeGroup,
+  stage: VkShaderStageFlagBits,
+  version: int,
+  entrypoint: string,
+  body: seq[string]
+): seq[uint32] {.compileTime.} =
   var code = @[&"#version {version}", ""] &
-    glslInput[Inputs]() & @[""] &
-    glslUniforms[Uniforms]() & @[""] &
-    glslOutput[Outputs]() & @[""] &
+    inputs.glslInput() & @[""] &
+    uniforms.glslUniforms() & @[""] &
+    outputs.glslOutput() & @[""] &
     @[&"void {entrypoint}(){{"] &
     body &
     @[&"}}"]
   compileGLSLToSPIRV(stage, code.join("\n"), entrypoint)
 
 
-proc shaderCode*[Inputs, Uniforms, Outputs](stage: VkShaderStageFlagBits, version: int, entrypoint: string, body: string): seq[uint32] {.compileTime.} =
-  return shaderCode[Inputs, Uniforms, Outputs](stage, version, entrypoint, @[body])
+proc shaderCode*(
+  inputs: AttributeGroup,
+  uniforms: AttributeGroup,
+  outputs: AttributeGroup,
+  stage: VkShaderStageFlagBits,
+  version: int,
+  entrypoint: string,
+  body: string
+): seq[uint32] {.compileTime.} =
+  return shaderCode(inputs, uniforms, outputs, stage, version, entrypoint, @[body])
 
 
-proc createShader*[Inputs, Uniforms, Outputs](device: Device, stage: VkShaderStageFlagBits, entrypoint: string, binary: seq[uint32]): Shader[Inputs, Uniforms, Outputs] =
+proc createShader*(
+  device: Device,
+  inputs: AttributeGroup,
+  uniforms: AttributeGroup,
+  outputs: AttributeGroup,
+  stage: VkShaderStageFlagBits,
+  entrypoint: string,
+  binary: seq[uint32]
+): Shader =
   assert device.vk.valid
   assert len(binary) > 0
 
   result.device = device
+  result.inputs = inputs
+  result.uniforms = uniforms
+  result.outputs = outputs
   result.entrypoint = entrypoint
   result.stage = stage
   var bin = binary
@@ -108,6 +135,41 @@
   )
   checkVkResult vkCreateShaderModule(device.vk, addr(createInfo), nil, addr(result.vk))
 
+proc getVertexInputInfo*(
+  shader: Shader,
+  bindings: var seq[VkVertexInputBindingDescription],
+  attributes: var seq[VkVertexInputAttributeDescription],
+  baseBinding=0'u32
+): VkPipelineVertexInputStateCreateInfo =
+  var location = 0'u32
+  var binding = baseBinding
+
+  for attribute in shader.inputs.attributes:
+    bindings.add VkVertexInputBindingDescription(
+      binding: binding,
+      stride: attribute.size,
+      inputRate: if attribute.perInstance: VK_VERTEX_INPUT_RATE_INSTANCE else: VK_VERTEX_INPUT_RATE_VERTEX,
+    )
+    # allows to submit larger data structures like Mat44, for most other types will be 1
+    for i in 0 ..< attribute.rows:
+      attributes.add VkVertexInputAttributeDescription(
+        binding: binding,
+        location: location,
+        format: getVkFormat(attribute),
+        offset: i * attribute.size(perRow=true),
+      )
+      location += attribute.nLocationSlots
+    inc binding
+
+  return VkPipelineVertexInputStateCreateInfo(
+    sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+    vertexBindingDescriptionCount: uint32(bindings.len),
+    pVertexBindingDescriptions: bindings.toCPointer,
+    vertexAttributeDescriptionCount: uint32(attributes.len),
+    pVertexAttributeDescriptions: attributes.toCPointer,
+  )
+
+
 proc getPipelineInfo*(shader: Shader): VkPipelineShaderStageCreateInfo =
   VkPipelineShaderStageCreateInfo(
     sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
@@ -121,81 +183,3 @@
   assert shader.vk.valid
   shader.device.vk.vkDestroyShaderModule(shader.vk, nil)
   shader.vk.reset
-
-
-func getVkFormat[T](value: T): VkFormat =
-  when T is uint8: VK_FORMAT_R8_UINT
-  elif T is int8: VK_FORMAT_R8_SINT
-  elif T is uint16: VK_FORMAT_R16_UINT
-  elif T is int16: VK_FORMAT_R16_SINT
-  elif T is uint32: VK_FORMAT_R32_UINT
-  elif T is int32: VK_FORMAT_R32_SINT
-  elif T is uint64: VK_FORMAT_R64_UINT
-  elif T is int64: VK_FORMAT_R64_SINT
-  elif T is float32: VK_FORMAT_R32_SFLOAT
-  elif T is float64: VK_FORMAT_R64_SFLOAT
-  elif T is TVec2[uint8]: VK_FORMAT_R8G8_UINT
-  elif T is TVec2[int8]: VK_FORMAT_R8G8_SINT
-  elif T is TVec2[uint16]: VK_FORMAT_R16G16_UINT
-  elif T is TVec2[int16]: VK_FORMAT_R16G16_SINT
-  elif T is TVec2[uint32]: VK_FORMAT_R32G32_UINT
-  elif T is TVec2[int32]: VK_FORMAT_R32G32_SINT
-  elif T is TVec2[uint64]: VK_FORMAT_R64G64_UINT
-  elif T is TVec2[int64]: VK_FORMAT_R64G64_SINT
-  elif T is TVec2[float32]: VK_FORMAT_R32G32_SFLOAT
-  elif T is TVec2[float64]: VK_FORMAT_R64G64_SFLOAT
-  elif T is TVec3[uint8]: VK_FORMAT_R8G8B8_UINT
-  elif T is TVec3[int8]: VK_FORMAT_R8G8B8_SINT
-  elif T is TVec3[uint16]: VK_FORMAT_R16G16B16_UINT
-  elif T is TVec3[int16]: VK_FORMAT_R16G16B16_SINT
-  elif T is TVec3[uint32]: VK_FORMAT_R32G32B32_UINT
-  elif T is TVec3[int32]: VK_FORMAT_R32G32B32_SINT
-  elif T is TVec3[uint64]: VK_FORMAT_R64G64B64_UINT
-  elif T is TVec3[int64]: VK_FORMAT_R64G64B64_SINT
-  elif T is TVec3[float32]: VK_FORMAT_R32G32B32_SFLOAT
-  elif T is TVec3[float64]: VK_FORMAT_R64G64B64_SFLOAT
-  elif T is TVec4[uint8]: VK_FORMAT_R8G8B8A8_UINT
-  elif T is TVec4[int8]: VK_FORMAT_R8G8B8A8_SINT
-  elif T is TVec4[uint16]: VK_FORMAT_R16G16B16A16_UINT
-  elif T is TVec4[int16]: VK_FORMAT_R16G16B16A16_SINT
-  elif T is TVec4[uint32]: VK_FORMAT_R32G32B32A32_UINT
-  elif T is TVec4[int32]: VK_FORMAT_R32G32B32A32_SINT
-  elif T is TVec4[uint64]: VK_FORMAT_R64G64B64A64_UINT
-  elif T is TVec4[int64]: VK_FORMAT_R64G64B64A64_SINT
-  elif T is TVec4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
-  elif T is TVec4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
-  else: {.error: "Unsupported vertex attribute type".}
-
-
-proc getVertexInputInfo*[Input, Uniforms, Output](
-  shader: Shader[Input, Uniforms, Output],
-  bindings: var seq[VkVertexInputBindingDescription],
-  attributes: var seq[VkVertexInputAttributeDescription],
-): VkPipelineVertexInputStateCreateInfo =
-  var location = 0'u32
-  var binding = 0'u32
-
-  for name, value in default(Input).fieldPairs:
-    bindings.add VkVertexInputBindingDescription(
-      binding: binding,
-      stride: uint32(sizeof(value)),
-      inputRate: if value.hasCustomPragma(PerInstance): VK_VERTEX_INPUT_RATE_INSTANCE else: VK_VERTEX_INPUT_RATE_VERTEX,
-    )
-    # allows to submit larger data structures like Mat44, for most other types will be 1
-    for i in 0 ..< compositeAttributesNumber(value):
-      attributes.add VkVertexInputAttributeDescription(
-        binding: binding,
-        location: location,
-        format: getVkFormat(compositeAttribute(value)),
-        offset: uint32(i * sizeof(compositeAttribute(value))),
-      )
-      location += nLocationSlots(compositeAttribute(value))
-    inc binding
-
-  return VkPipelineVertexInputStateCreateInfo(
-    sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
-    vertexBindingDescriptionCount: uint32(bindings.len),
-    pVertexBindingDescriptions: bindings.toCPointer,
-    vertexAttributeDescriptionCount: uint32(attributes.len),
-    pVertexAttributeDescriptions: attributes.toCPointer,
-  )
--- a/src/semicongine/vulkan/swapchain.nim	Thu Mar 30 00:00:54 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -100,7 +100,37 @@
 
   return (swapchain, createResult)
 
-proc drawNextFrame*(swapchain: var Swapchain, scene: Scene): bool =
+proc draw*(commandBuffer: VkCommandBuffer, drawables: seq[Drawable], scene: Scene) =
+  for drawable in drawables:
+    var buffers: seq[VkBuffer]
+    var offsets: seq[VkDeviceSize]
+    for (buffer, offset) in drawable.buffers:
+      buffers.add buffer.vk
+      offsets.add VkDeviceSize(offset)
+    commandBuffer.vkCmdBindVertexBuffers(
+      firstBinding=0'u32,
+      bindingCount=uint32(buffers.len),
+      pBuffers=buffers.toCPointer(),
+      pOffsets=offsets.toCPointer()
+    )
+    if drawable.indexed:
+      commandBuffer.vkCmdBindIndexBuffer(drawable.indexBuffer.vk, VkDeviceSize(0), drawable.indexType)
+      commandBuffer.vkCmdDrawIndexed(
+        indexCount=drawable.elementCount,
+        instanceCount=drawable.instanceCount,
+        firstIndex=0,
+        vertexOffset=0,
+        firstInstance=0
+      )
+    else:
+      commandBuffer.vkCmdDraw(
+        vertexCount=drawable.elementCount,
+        instanceCount=drawable.instanceCount,
+        firstVertex=0,
+        firstInstance=0
+      )
+
+proc drawScene*(swapchain: var Swapchain, scene: Scene): bool =
   assert swapchain.device.vk.valid
   assert swapchain.vk.valid
   assert swapchain.device.firstGraphicsQueue().isSome
@@ -131,8 +161,10 @@
     swapchain.framebuffers[currentFramebufferIndex]
   ):
     for i in 0 ..< swapchain.renderpass.subpasses.len:
-      for pipeline in swapchain.renderpass.subpasses[i].pipelines:
-        pipeline.run(commandBuffer, swapchain.currentInFlight, scene)
+      for pipeline in swapchain.renderpass.subpasses[i].pipelines.mitems:
+        commandBuffer.vkCmdBindPipeline(swapchain.renderpass.subpasses[i].pipelineBindPoint, pipeline.vk)
+        commandBuffer.vkCmdBindDescriptorSets(swapchain.renderpass.subpasses[i].pipelineBindPoint, pipeline.layout, 0, 1, addr(pipeline.descriptorSets[swapchain.currentInFlight].vk), 0, nil)
+        commandBuffer.draw(scene.getDrawables(pipeline), scene)
       if i < swapchain.renderpass.subpasses.len - 1:
         commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE)
 
@@ -194,3 +226,4 @@
     swapchain.renderFinishedSemaphore[i].destroy()
   swapchain.device.vk.vkDestroySwapchainKHR(swapchain.vk, nil)
   swapchain.vk.reset()
+
--- a/tests/test_vulkan_wrapper.nim	Thu Mar 30 00:00:54 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Fri Mar 31 16:00:16 2023 +0700
@@ -1,3 +1,4 @@
+import std/os
 import std/options
 
 import semicongine/vulkan
@@ -5,6 +6,7 @@
 import semicongine/math
 import semicongine/entity
 import semicongine/scene
+import semicongine/gpu_data
 
 type
   Vertex = object
@@ -66,22 +68,27 @@
     selectedPhysicalDevice.filterForGraphicsPresentationQueues()
   )
 
-  const vertexBinary = shaderCode[Vertex, Uniforms, FragmentInput](stage=VK_SHADER_STAGE_VERTEX_BIT, version=450, entrypoint="main", "fragpos = pos;")
-  const fragmentBinary = shaderCode[FragmentInput, void, Pixel](stage=VK_SHADER_STAGE_FRAGMENT_BIT, version=450, entrypoint="main", "color = vec4(1, 1, 1, 0);")
+  const inputs = AttributeGroup(attributes: @[attr(name="pos", thetype=Float32, components=3)])
+  const uniforms = AttributeGroup()
+  const outputs = AttributeGroup(attributes: @[attr(name="fragpos", thetype=Float32, components=3)])
+  const fragOutput = AttributeGroup(attributes: @[attr(name="color", thetype=Float32, components=4)])
+  const vertexBinary = shaderCode(inputs=inputs, uniforms=uniforms, outputs=outputs, stage=VK_SHADER_STAGE_VERTEX_BIT, version=450, entrypoint="main", "fragpos = pos;")
+  const fragmentBinary = shaderCode(inputs=outputs, uniforms=uniforms, outputs=fragOutput, stage=VK_SHADER_STAGE_FRAGMENT_BIT, version=450, entrypoint="main", "color = vec4(1, 1, 1, 0);")
   var
-    vertexshader = createShader[Vertex, Uniforms, FragmentInput](device, VK_SHADER_STAGE_VERTEX_BIT, "main", vertexBinary)
-    fragmentshader = createShader[FragmentInput, void, Pixel](device, VK_SHADER_STAGE_FRAGMENT_BIT, "main", fragmentBinary)
+    vertexshader = device.createShader(inputs, uniforms, outputs, VK_SHADER_STAGE_VERTEX_BIT, "main", vertexBinary)
+    fragmentshader = device.createShader(inputs, uniforms, outputs, VK_SHADER_STAGE_FRAGMENT_BIT, "main", fragmentBinary)
     surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat()
-    renderpass = device.simpleForwardRenderPass(surfaceFormat.format, vertexshader, fragmentshader, 2)
-  var (swapchain, res) = device.createSwapchain(renderpass, surfaceFormat, device.firstGraphicsQueue().get().family, 2)
+    renderPass = device.simpleForwardRenderPass(surfaceFormat.format, vertexshader, fragmentshader, 2)
+  var (swapchain, res) = device.createSwapchain(renderPass, surfaceFormat, device.firstGraphicsQueue().get().family, 2)
   if res != VK_SUCCESS:
     raise newException(Exception, "Unable to create swapchain")
 
   var thescene = Scene(root: newEntity("scene"))
+  thescene.setupDrawables(renderPass)
 
-  echo "All successfull"
-  for i in 0 ..< 2:
-    discard swapchain.drawNextFrame(thescene)
+  echo "Setup successfull, start rendering"
+  for i in 0 ..< 10:
+    discard swapchain.drawScene(thescene)
   echo "Rendered ", swapchain.framesRendered, " frames"
   echo "Start cleanup"
 
@@ -90,7 +97,7 @@
   checkVkResult device.vk.vkDeviceWaitIdle()
   vertexshader.destroy()
   fragmentshader.destroy()
-  renderpass.destroy()
+  renderPass.destroy()
   swapchain.destroy()
   device.destroy()