changeset 1171:dc6e1660831d compiletime-tests

merge
author sam <sam@basx.dev>
date Wed, 26 Jun 2024 17:46:36 +0700
parents 58694b30b9cb (diff) 2addc5f6804f (current diff)
children 2e0b527c2753
files semicongine/renderer.nim semicongine/vulkan/renderpass.nim
diffstat 6 files changed, 809 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/core/imagetypes.nim	Wed Jun 26 05:14:30 2024 +0700
+++ b/semicongine/core/imagetypes.nim	Wed Jun 26 17:46:36 2024 +0700
@@ -8,11 +8,12 @@
   RGBAPixel* = array[4, uint8]
   GrayPixel* = uint8
   Pixel* = RGBAPixel or GrayPixel
-  ImageObject*[T: Pixel] = object
+  # ImageObject*[T: Pixel] = object
+  Image*[T: Pixel] = object
     width*: uint32
     height*: uint32
     imagedata*: seq[T]
-  Image*[T: Pixel] = ref ImageObject[T]
+  # Image*[T: Pixel] = ref ImageObject[T]
 
   Sampler* = object
     magnification*: VkFilter = VK_FILTER_LINEAR
@@ -81,7 +82,6 @@
   assert width > 0 and height > 0
   assert imagedata.len.uint32 == width * height or imagedata.len == 0
 
-  result = new Image[T]
   result.imagedata = (if imagedata.len == 0: newSeq[T](width * height) else: @imagedata)
   assert width * height == result.imagedata.len.uint32
 
--- a/semicongine/mesh.nim	Wed Jun 26 05:14:30 2024 +0700
+++ b/semicongine/mesh.nim	Wed Jun 26 17:46:36 2024 +0700
@@ -18,7 +18,6 @@
 type
   MeshIndexType* = enum
     None
-    Tiny  # up to 2^8 vertices # TODO: need to check and enable support for this
     Small # up to 2^16 vertices
     Big   # up to 2^32 vertices
   MeshObject* = object
@@ -26,7 +25,6 @@
     vertexCount*: int
     case indexType*: MeshIndexType
       of None: discard
-      of Tiny: tinyIndices*: seq[array[3, uint8]]
       of Small: smallIndices*: seq[array[3, uint16]]
       of Big: bigIndices*: seq[array[3, uint32]]
     material*: MaterialData
@@ -65,7 +63,6 @@
   (
     case mesh.indexType
     of None: 0
-    of Tiny: mesh.tinyIndices.len
     of Small: mesh.smallIndices.len
     of Big: mesh.bigIndices.len
   ) * 3
@@ -93,7 +90,6 @@
 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
 
@@ -154,9 +150,7 @@
   var indexType = None
   if indices.len > 0:
     indexType = Big
-    if autoResize and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support
-      indexType = Tiny
-    elif autoResize and uint32(positions.len) < uint32(high(uint16)):
+    if autoResize and uint32(positions.len) < uint32(high(uint16)):
       indexType = Small
 
   result = Mesh(
@@ -178,10 +172,7 @@
     assert int(i[2]) < result[].vertexCount
 
   # cast index values to appropiate type
-  if result[].indexType == Tiny and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support
-    for i, tri in enumerate(indices):
-      result[].tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])]
-  elif result[].indexType == Small and uint32(positions.len) < uint32(high(uint16)):
+  if result[].indexType == Small and uint32(positions.len) < uint32(high(uint16)):
     for i, tri in enumerate(indices):
       result[].smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])]
   elif result[].indexType == Big:
@@ -228,7 +219,6 @@
 func IndexSize*(mesh: MeshObject): uint64 =
   case mesh.indexType
     of None: 0'u64
-    of Tiny: uint64(mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0)))
     of Small: uint64(mesh.smallIndices.len * sizeof(get(genericParams(typeof(mesh.smallIndices)), 0)))
     of Big: uint64(mesh.bigIndices.len * sizeof(get(genericParams(typeof(mesh.bigIndices)), 0)))
 
@@ -241,7 +231,6 @@
 func GetRawIndexData*(mesh: MeshObject): (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)
 
@@ -352,7 +341,6 @@
 proc AppendIndicesData*(mesh: var MeshObject, v1, v2, v3: int) =
   case mesh.indexType
   of None: raise newException(Exception, "Mesh does not support indexed data")
-  of Tiny: mesh.tinyIndices.add([uint8(v1), uint8(v2), uint8(v3)])
   of Small: mesh.smallIndices.add([uint16(v1), uint16(v2), uint16(v3)])
   of Big: mesh.bigIndices.add([uint32(v1), uint32(v2), uint32(v3)])
 
@@ -424,13 +412,6 @@
     result.instanceData[attribute] = datalist.Copy()
   var i = 0
   case mesh.indexType
-  of Tiny:
-    for indices in mesh.tinyIndices:
-      for attribute, value in mesh.vertexData.pairs:
-        result.vertexData[attribute].AppendFrom(i, mesh.vertexData[attribute], int(indices[0]))
-        result.vertexData[attribute].AppendFrom(i + 1, mesh.vertexData[attribute], int(indices[1]))
-        result.vertexData[attribute].AppendFrom(i + 2, mesh.vertexData[attribute], int(indices[2]))
-      i += 3
   of Small:
     for indices in mesh.smallIndices:
       for attribute, value in mesh.vertexData.pairs:
@@ -586,10 +567,6 @@
   case a.indexType:
     of None:
       discard
-    of Tiny:
-      let offset = uint8(originalOffset)
-      for i in b.tinyIndices:
-        a.tinyIndices.add [i[0] + offset, i[1] + offset, i[2] + offset]
     of Small:
       let offset = uint16(originalOffset)
       for i in b.smallIndices:
--- a/semicongine/renderer.nim	Wed Jun 26 05:14:30 2024 +0700
+++ b/semicongine/renderer.nim	Wed Jun 26 17:46:36 2024 +0700
@@ -132,7 +132,6 @@
     if mesh[].indexType != MeshIndexType.None:
       let indexAlignment = case mesh[].indexType
         of MeshIndexType.None: 0'u64
-        of Tiny: 1'u64
         of Small: 2'u64
         of Big: 4'u64
       # index value alignment required by Vulkan
@@ -212,7 +211,6 @@
     if indexed:
       let indexAlignment = case mesh.indexType
         of MeshIndexType.None: 0'u64
-        of Tiny: 1'u64
         of Small: 2'u64
         of Big: 4'u64
       # index value alignment required by Vulkan
--- a/semicongine/vulkan/descriptor.nim	Wed Jun 26 05:14:30 2024 +0700
+++ b/semicongine/vulkan/descriptor.nim	Wed Jun 26 17:46:36 2024 +0700
@@ -106,7 +106,6 @@
   assert layout.vk.Valid
 
   var layouts: seq[VkDescriptorSetLayout]
-  var descriptorSets = newSeq[VkDescriptorSet](nframes)
   for i in 0 ..< nframes:
     layouts.add layout.vk
   var allocInfo = VkDescriptorSetAllocateInfo(
@@ -116,6 +115,7 @@
     pSetLayouts: layouts.ToCPointer,
   )
 
+  var descriptorSets = newSeq[VkDescriptorSet](nframes)
   checkVkResult vkAllocateDescriptorSets(pool.device.vk, addr(allocInfo), descriptorSets.ToCPointer)
   for descriptorSet in descriptorSets:
     result.add DescriptorSet(vk: descriptorSet, layout: layout)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static_utils.nim	Wed Jun 26 17:46:36 2024 +0700
@@ -0,0 +1,762 @@
+import std/os
+import std/enumerate
+import std/hashes
+import std/macros
+import std/strformat
+import std/strutils
+import std/sequtils
+import std/typetraits as tt
+
+import semicongine/core/utils
+import semicongine/core/imagetypes
+import semicongine/core/vector
+import semicongine/core/matrix
+import semicongine/core/vulkanapi
+import semicongine/vulkan/buffer
+
+template VertexAttribute* {.pragma.}
+template InstanceAttribute* {.pragma.}
+template Pass* {.pragma.}
+template PassFlat* {.pragma.}
+template ShaderOutput* {.pragma.}
+
+const INFLIGHTFRAMES = 2'u32
+type
+  SupportedGPUType* = float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] | TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] | TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] | TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] | TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] | TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[float32] | TMat43[float64] | TMat4[float32] | TMat4[float64]
+  ShaderObject*[TShader] = object
+    vertexShader: VkShaderModule
+    fragmentShader: VkShaderModule
+
+func VkType[T: SupportedGPUType](value: T): VkFormat =
+  when T is float32: VK_FORMAT_R32_SFLOAT
+  elif T is float64: VK_FORMAT_R64_SFLOAT
+  elif T is int8: VK_FORMAT_R8_SINT
+  elif T is int16: VK_FORMAT_R16_SINT
+  elif T is int32: VK_FORMAT_R32_SINT
+  elif T is int64: VK_FORMAT_R64_SINT
+  elif T is uint8: VK_FORMAT_R8_UINT
+  elif T is uint16: VK_FORMAT_R16_UINT
+  elif T is uint32: VK_FORMAT_R32_UINT
+  elif T is uint64: VK_FORMAT_R64_UINT
+  elif T is TVec2[int32]: VK_FORMAT_R32G32_SINT
+  elif T is TVec2[int64]: VK_FORMAT_R64G64_SINT
+  elif T is TVec3[int32]: VK_FORMAT_R32G32B32_SINT
+  elif T is TVec3[int64]: VK_FORMAT_R64G64B64_SINT
+  elif T is TVec4[int32]: VK_FORMAT_R32G32B32A32_SINT
+  elif T is TVec4[int64]: VK_FORMAT_R64G64B64A64_SINT
+  elif T is TVec2[uint32]: VK_FORMAT_R32G32_UINT
+  elif T is TVec2[uint64]: VK_FORMAT_R64G64_UINT
+  elif T is TVec3[uint32]: VK_FORMAT_R32G32B32_UINT
+  elif T is TVec3[uint64]: VK_FORMAT_R64G64B64_UINT
+  elif T is TVec4[uint32]: VK_FORMAT_R32G32B32A32_UINT
+  elif T is TVec4[uint64]: VK_FORMAT_R64G64B64A64_UINT
+  elif T is TVec2[float32]: VK_FORMAT_R32G32_SFLOAT
+  elif T is TVec2[float64]: VK_FORMAT_R64G64_SFLOAT
+  elif T is TVec3[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is TVec3[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is TVec4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
+  elif T is TVec4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
+  elif T is TMat2[float32]: VK_FORMAT_R32G32_SFLOAT
+  elif T is TMat2[float64]: VK_FORMAT_R64G64_SFLOAT
+  elif T is TMat23[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is TMat23[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is TMat32[float32]: VK_FORMAT_R32G32_SFLOAT
+  elif T is TMat32[float64]: VK_FORMAT_R64G64_SFLOAT
+  elif T is TMat3[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is TMat3[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is TMat34[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
+  elif T is TMat34[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
+  elif T is TMat43[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is TMat43[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is TMat4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
+  elif T is TMat4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
+  else: {.error: "Unsupported data type on GPU".}
+
+func GlslType[T: SupportedGPUType|Texture](value: T): string =
+  when T is float32: "float"
+  elif T is float64: "double"
+  elif T is int8 or T is int16 or T is int32 or T is int64: "int"
+  elif T is uint8 or T is uint16 or T is uint32 or T is uint64: "uint"
+  elif T is TVec2[int32]: "ivec2"
+  elif T is TVec2[int64]: "ivec2"
+  elif T is TVec3[int32]: "ivec3"
+  elif T is TVec3[int64]: "ivec3"
+  elif T is TVec4[int32]: "ivec4"
+  elif T is TVec4[int64]: "ivec4"
+  elif T is TVec2[uint32]: "uvec2"
+  elif T is TVec2[uint64]: "uvec2"
+  elif T is TVec3[uint32]: "uvec3"
+  elif T is TVec3[uint64]: "uvec3"
+  elif T is TVec4[uint32]: "uvec4"
+  elif T is TVec4[uint64]: "uvec4"
+  elif T is TVec2[float32]: "vec2"
+  elif T is TVec2[float64]: "dvec2"
+  elif T is TVec3[float32]: "vec3"
+  elif T is TVec3[float64]: "dvec3"
+  elif T is TVec4[float32]: "vec4"
+  elif T is TVec4[float64]: "dvec4"
+  elif T is TMat2[float32]: "mat2"
+  elif T is TMat2[float64]: "dmat2"
+  elif T is TMat23[float32]: "mat23"
+  elif T is TMat23[float64]: "dmat23"
+  elif T is TMat32[float32]: "mat32"
+  elif T is TMat32[float64]: "dmat32"
+  elif T is TMat3[float32]: "mat3"
+  elif T is TMat3[float64]: "dmat3"
+  elif T is TMat34[float32]: "mat34"
+  elif T is TMat34[float64]: "dmat34"
+  elif T is TMat43[float32]: "mat43"
+  elif T is TMat43[float64]: "dmat43"
+  elif T is TMat4[float32]: "mat4"
+  elif T is TMat4[float64]: "dmat4"
+  elif T is Texture: "sampler2D"
+  else: {.error: "Unsupported data type on GPU".}
+
+template ForVertexDataFields*(inputData: typed, fieldname, valuename, isinstancename, body: untyped): untyped =
+  for theFieldname, value in fieldPairs(inputData):
+    when hasCustomPragma(value, VertexAttribute) or hasCustomPragma(value, InstanceAttribute):
+      when not typeof(value) is seq:
+        {.error: "field '" & theFieldname & "' needs to be a seq".}
+      when not typeof(value) is SupportedGPUType:
+        {.error: "field '" & theFieldname & "' is not a supported GPU type".}
+      block:
+        let `fieldname` {.inject.} = theFieldname
+        let `valuename` {.inject.} = value
+        let `isinstancename` {.inject.} = hasCustomPragma(value, InstanceAttribute)
+        body
+
+template ForDescriptorFields*(inputData: typed, typename, countname, body: untyped): untyped =
+  for theFieldname, value in fieldPairs(inputData):
+    when typeof(value) is Texture:
+      block:
+        let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
+        let `countname` {.inject.} = 1'u32
+        body
+    elif typeof(value) is object:
+      block:
+        let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
+        let `countname` {.inject.} = 1'u32
+        body
+    elif typeof(value) is array:
+      when elementType(value) is Texture:
+        block:
+          let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
+          let `countname` {.inject.} = uint32(typeof(value).len)
+          body
+      elif elementType(value) is object:
+        block:
+          let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
+          let `countname` {.inject.} = uint32(typeof(value).len)
+          body
+
+func NumberOfVertexInputAttributeDescriptors[T: SupportedGPUType|Texture](value: T): uint32 =
+  when T is TMat2[float32] or T is TMat2[float64] or T is TMat23[float32] or T is TMat23[float64]:
+    2
+  elif T is TMat32[float32] or T is TMat32[float64] or T is TMat3[float32] or T is TMat3[float64] or T is TMat34[float32] or T is TMat34[float64]:
+    3
+  elif T is TMat43[float32] or T is TMat43[float64] or T is TMat4[float32] or T is TMat4[float64]:
+    4
+  else:
+    1
+
+func NLocationSlots[T: SupportedGPUType|Texture](value: T): uint32 =
+  #[
+  single location:
+    - any scalar
+    - any 16-bit vector
+    - any 32-bit vector
+    - any 64-bit vector that has max. 2 components
+    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
+  ]#
+  when T is TVec3[int64] or
+    T is TVec4[int64] or
+    T is TVec3[uint64] or
+    T is TVec4[uint64] or
+    T is TVec3[float64] or
+    T is TVec4[float64] or
+    T is TMat23[float64] or
+    T is TMat3[float64] or
+    T is TMat34[float64] or
+    T is TMat43[float64] or
+    T is TMat4[float64]:
+    return 2
+  else:
+    return 1
+
+type
+  IndexType = enum
+    None, UInt8, UInt16, UInt32
+  RenderBuffers = object
+    deviceBuffers: seq[Buffer]      # for fast reads
+    hostVisibleBuffers: seq[Buffer] # for fast writes
+  Renderable[TMesh, TInstance] = object
+    vertexBuffers: seq[VkBuffer]
+    bufferOffsets: seq[VkDeviceSize]
+    instanceCount: uint32
+    case indexType: IndexType
+      of None:
+        vertexCount: uint32
+      else:
+        indexBuffer: VkBuffer
+        indexCount: uint32
+        indexBufferOffset: VkDeviceSize
+  Pipeline[TShader] = object
+    pipeline: VkPipeline
+    layout: VkPipelineLayout
+    descriptorSets: array[INFLIGHTFRAMES, VkDescriptorSet]
+
+converter toVkIndexType(indexType: IndexType): VkIndexType =
+  case indexType:
+    of None: VK_INDEX_TYPE_NONE_KHR
+    of UInt8: VK_INDEX_TYPE_UINT8_EXT
+    of UInt16: VK_INDEX_TYPE_UINT16
+    of UInt32: VK_INDEX_TYPE_UINT32
+
+proc compileGlslToSPIRV(stage: VkShaderStageFlagBits, shaderSource: string): seq[uint32] {.compileTime.} =
+  func stage2string(stage: VkShaderStageFlagBits): string {.compileTime.} =
+    case stage
+    of VK_SHADER_STAGE_VERTEX_BIT: "vert"
+    of VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: "tesc"
+    of VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: "tese"
+    of VK_SHADER_STAGE_GEOMETRY_BIT: "geom"
+    of VK_SHADER_STAGE_FRAGMENT_BIT: "frag"
+    of VK_SHADER_STAGE_COMPUTE_BIT: "comp"
+    else: ""
+
+  when defined(nimcheck): # will not run if nimcheck is running
+    return result
+
+  let
+    stagename = stage2string(stage)
+    shaderHash = hash(shaderSource)
+    shaderfile = getTempDir() / &"shader_{shaderHash}.{stagename}"
+
+  if not shaderfile.fileExists:
+    echo "shader of type ", stage
+    for i, line in enumerate(shaderSource.splitlines()):
+      echo "  ", i + 1, " ", line
+    # var glslExe = currentSourcePath.parentDir.parentDir.parentDir / "tools" / "glslangValidator"
+    var glslExe = currentSourcePath.parentDir / "tools" / "glslangValidator"
+    when defined(windows):
+      glslExe = glslExe & "." & ExeExt
+    let command = &"{glslExe} --entry-point main -V --stdin -S {stagename} -o {shaderfile}"
+    echo "run: ", command
+    discard StaticExecChecked(
+        command = command,
+        input = shaderSource
+    )
+  else:
+    echo &"shaderfile {shaderfile} is up-to-date"
+
+  when defined(mingw) and defined(linux): # required for crosscompilation, path separators get messed up
+    let shaderbinary = staticRead shaderfile.replace("\\", "/")
+  else:
+    let shaderbinary = staticRead shaderfile
+
+  var i = 0
+  while i < shaderbinary.len:
+    result.add(
+      (uint32(shaderbinary[i + 0]) shl 0) or
+      (uint32(shaderbinary[i + 1]) shl 8) or
+      (uint32(shaderbinary[i + 2]) shl 16) or
+      (uint32(shaderbinary[i + 3]) shl 24)
+    )
+    i += 4
+
+proc generateShaderSource[TShader](shader: TShader): (string, string) {.compileTime.} =
+  const GLSL_VERSION = "450"
+  var vsInput: seq[string]
+  var vsOutput: seq[string]
+  var fsInput: seq[string]
+  var fsOutput: seq[string]
+  var uniforms: seq[string]
+  var samplers: seq[string]
+  var vsInputLocation = 0'u32
+  var passLocation = 0
+  var fsOutputLocation = 0
+  var descriptorBinding = 0
+
+  for fieldname, value in fieldPairs(shader):
+    # vertex shader inputs
+    when hasCustomPragma(value, VertexAttribute) or hasCustomPragma(value, InstanceAttribute):
+      assert typeof(value) is SupportedGPUType
+      vsInput.add "layout(location = " & $vsInputLocation & ") in " & GlslType(value) & " " & fieldname & ";"
+      for j in 0 ..< NumberOfVertexInputAttributeDescriptors(value):
+        vsInputLocation += NLocationSlots(value)
+    # intermediate values, passed between shaders
+    elif hasCustomPragma(value, Pass) or hasCustomPragma(value, PassFlat):
+      let flat = if hasCustomPragma(value, PassFlat): "flat " else: ""
+      vsOutput.add "layout(location = " & $passLocation & ") " & flat & "out " & GlslType(value) & " " & fieldname & ";"
+      fsInput.add "layout(location = " & $passLocation & ") " & flat & "in " & GlslType(value) & " " & fieldname & ";"
+      passLocation.inc
+    elif hasCustomPragma(value, ShaderOutput):
+      fsOutput.add &"layout(location = " & $fsOutputLocation & ") out " & GlslType(value) & " " & fieldname & ";"
+      fsOutputLocation.inc
+    elif typeof(value) is Texture:
+      samplers.add "layout(binding = " & $descriptorBinding & ") uniform " & GlslType(value) & " " & fieldname & ";"
+      descriptorBinding.inc
+    elif typeof(value) is object:
+      # TODO
+      uniforms.add ""
+      descriptorBinding.inc
+    elif typeof(value) is array:
+      when elementType(value) is Texture:
+        let arrayDecl = "[" & $typeof(value).len & "]"
+        samplers.add "layout(binding = " & $descriptorBinding & ") uniform " & GlslType(default(elementType(value))) & " " & fieldname & "" & arrayDecl & ";"
+        descriptorBinding.inc
+      elif elementType(value) is object:
+        # TODO
+        let arrayDecl = "[" & $typeof(value).len & "]"
+        # uniforms.add "layout(binding = " & $descriptorBinding & ") uniform " & GlslType(elementType(value)) & " " & fieldname & "" & arrayDecl & ";"
+        descriptorBinding.inc
+      else:
+        {.error: "Unsupported shader field " & fieldname.}
+    elif fieldname in ["vertexCode", "fragmentCode"]:
+      discard
+    else:
+      {.error: "Unsupported shader field '" & tt.name(TShader) & "." & fieldname & "' of type " & tt.name(typeof(value)).}
+
+  result[0] = (@[&"#version {GLSL_VERSION}", "#extension GL_EXT_scalar_block_layout : require", ""] &
+    vsInput &
+    uniforms &
+    samplers &
+    vsOutput &
+    @[shader.vertexCode]).join("\n")
+
+  result[1] = (@[&"#version {GLSL_VERSION}", "#extension GL_EXT_scalar_block_layout : require", ""] &
+    fsInput &
+    uniforms &
+    samplers &
+    fsOutput &
+    @[shader.fragmentCode]).join("\n")
+
+# proc CompileShader[TShader](shader: static TShader): (seq[uint32], seq[uint32]) {.compileTime.}=
+proc CompileShader[TShader](device: VkDevice, shader: static TShader): ShaderObject[TShader] =
+  const (vertexShaderSource, fragmentShaderSource) = generateShaderSource(shader)
+
+  let vertexBinary = compileGlslToSPIRV(VK_SHADER_STAGE_VERTEX_BIT, vertexShaderSource)
+  let fragmentBinary = compileGlslToSPIRV(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShaderSource)
+
+  var createInfoVertex = VkShaderModuleCreateInfo(
+    sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+    codeSize: csize_t(vertexBinary.len * sizeof(uint32)),
+    pCode: vertexBinary.ToCPointer,
+  )
+  checkVkResult device.vkCreateShaderModule(addr(createInfoVertex), nil, addr(result.vertexShader))
+  var createInfoFragment = VkShaderModuleCreateInfo(
+    sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+    codeSize: csize_t(fragmentBinary.len * sizeof(uint32)),
+    pCode: fragmentBinary.ToCPointer,
+  )
+  checkVkResult device.vkCreateShaderModule(addr(createInfoFragment), nil, addr(result.fragmentShader))
+
+
+proc CreatePipeline*[TShader](
+  device: VkDevice,
+  renderPass: VkRenderPass,
+  shader: ShaderObject[TShader],
+  topology: VkPrimitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+  polygonMode: VkPolygonMode = VK_POLYGON_MODE_FILL,
+  cullMode: VkCullModeFlagBits = VK_CULL_MODE_BACK_BIT,
+  frontFace: VkFrontFace = VK_FRONT_FACE_CLOCKWISE,
+): Pipeline[TShader] =
+  # assumptions/limitations:
+  # - we are only using vertex and fragment shaders (2 stages)
+  # - we only support one subpass
+  # = we only support one Uniform-Block
+
+  # create pipeline
+  var layoutbindings: seq[VkDescriptorSetLayoutBinding]
+  var descriptorBindingNumber = 0'u32
+  ForDescriptorFields(default(TShader), descriptorType, descriptorCount):
+    layoutbindings.add VkDescriptorSetLayoutBinding(
+      binding: descriptorBindingNumber,
+      descriptorType: descriptorType,
+      descriptorCount: descriptorCount,
+      stageFlags: VkShaderStageFlags(VK_SHADER_STAGE_ALL_GRAPHICS),
+      pImmutableSamplers: nil,
+    )
+    inc descriptorBindingNumber
+  var layoutCreateInfo = VkDescriptorSetLayoutCreateInfo(
+    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+    bindingCount: uint32(layoutbindings.len),
+    pBindings: layoutbindings.ToCPointer
+  )
+  var descriptorSetLayout: VkDescriptorSetLayout
+  checkVkResult vkCreateDescriptorSetLayout(device, addr(layoutCreateInfo), nil, addr(descriptorSetLayout))
+  let pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
+    sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+    setLayoutCount: 1,
+    pSetLayouts: addr(descriptorSetLayout),
+    # pushConstantRangeCount: uint32(pushConstants.len),
+      # pPushConstantRanges: pushConstants.ToCPointer,
+  )
+  checkVkResult vkCreatePipelineLayout(device, addr(pipelineLayoutInfo), nil, addr(result.layout))
+
+  let stages = [
+    VkPipelineShaderStageCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+      stage: VK_SHADER_STAGE_VERTEX_BIT,
+      module: shader.vertexShader,
+      pName: "main",
+    ),
+    VkPipelineShaderStageCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+      stage: VK_SHADER_STAGE_FRAGMENT_BIT,
+      module: shader.fragmentShader,
+      pName: "main",
+    ),
+  ]
+  var
+    bindings: seq[VkVertexInputBindingDescription]
+    attributes: seq[VkVertexInputAttributeDescription]
+  var inputBindingNumber = 0'u32
+  var location = 0'u32
+  ForVertexDataFields(default(TShader), fieldname, value, isInstanceAttr):
+    bindings.add VkVertexInputBindingDescription(
+      binding: inputBindingNumber,
+      stride: sizeof(value).uint32,
+      inputRate: if isInstanceAttr: 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
+    let perDescriptorSize = sizeof(value).uint32 div NumberOfVertexInputAttributeDescriptors(value)
+    for i in 0'u32 ..< NumberOfVertexInputAttributeDescriptors(value):
+      attributes.add VkVertexInputAttributeDescription(
+        binding: inputBindingNumber,
+        location: location,
+        format: VkType(value),
+        offset: i * perDescriptorSize,
+      )
+      location += NLocationSlots(value)
+    inc inputBindingNumber
+
+  let
+    vertexInputInfo = 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,
+    )
+    inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+      topology: topology,
+      primitiveRestartEnable: false,
+    )
+    viewportState = VkPipelineViewportStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+      viewportCount: 1,
+      scissorCount: 1,
+    )
+    rasterizer = VkPipelineRasterizationStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+      depthClampEnable: VK_FALSE,
+      rasterizerDiscardEnable: VK_FALSE,
+      polygonMode: polygonMode,
+      lineWidth: 1.0,
+      cullMode: toBits [cullMode],
+      frontFace: frontFace,
+      depthBiasEnable: VK_FALSE,
+      depthBiasConstantFactor: 0.0,
+      depthBiasClamp: 0.0,
+      depthBiasSlopeFactor: 0.0,
+    )
+    multisampling = VkPipelineMultisampleStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+      sampleShadingEnable: VK_FALSE,
+      rasterizationSamples: VK_SAMPLE_COUNT_1_BIT,
+      minSampleShading: 1.0,
+      pSampleMask: nil,
+      alphaToCoverageEnable: VK_FALSE,
+      alphaToOneEnable: VK_FALSE,
+    )
+    colorBlendAttachment = VkPipelineColorBlendAttachmentState(
+      colorWriteMask: toBits [VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT, VK_COLOR_COMPONENT_A_BIT],
+      blendEnable: VK_TRUE,
+      srcColorBlendFactor: VK_BLEND_FACTOR_SRC_ALPHA,
+      dstColorBlendFactor: VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+      colorBlendOp: VK_BLEND_OP_ADD,
+      srcAlphaBlendFactor: VK_BLEND_FACTOR_ONE,
+      dstAlphaBlendFactor: VK_BLEND_FACTOR_ZERO,
+      alphaBlendOp: VK_BLEND_OP_ADD,
+    )
+    colorBlending = VkPipelineColorBlendStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+      logicOpEnable: false,
+      attachmentCount: 1,
+      pAttachments: addr(colorBlendAttachment),
+    )
+    dynamicStates = [VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR]
+    dynamicState = VkPipelineDynamicStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+      dynamicStateCount: dynamicStates.len.uint32,
+      pDynamicStates: dynamicStates.ToCPointer,
+    )
+  let createInfo = VkGraphicsPipelineCreateInfo(
+    sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+    stageCount: 2,
+    pStages: stages.ToCPointer,
+    pVertexInputState: addr(vertexInputInfo),
+    pInputAssemblyState: addr(inputAssembly),
+    pViewportState: addr(viewportState),
+    pRasterizationState: addr(rasterizer),
+    pMultisampleState: addr(multisampling),
+    pDepthStencilState: nil,
+    pColorBlendState: addr(colorBlending),
+    pDynamicState: addr(dynamicState),
+    layout: result.layout,
+    renderPass: renderPass,
+    subpass: 0,
+    basePipelineHandle: VkPipeline(0),
+    basePipelineIndex: -1,
+  )
+  checkVkResult vkCreateGraphicsPipelines(
+    device,
+    VkPipelineCache(0),
+    1,
+    addr(createInfo),
+    nil,
+    addr(result.pipeline)
+  )
+
+  # create descriptors, one per frame-in-flight
+  let nSamplers = 0'u32
+  let nUniformBuffers = 0'u32
+
+  if nSamplers + nUniformBuffers > 0:
+    var poolSizes: seq[VkDescriptorPoolSize]
+    if nUniformBuffers > 0:
+      poolSizes.add VkDescriptorPoolSize(thetype: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, descriptorCount: nSamplers * INFLIGHTFRAMES)
+    if nSamplers > 0:
+      poolSizes.add VkDescriptorPoolSize(thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: nUniformBuffers * INFLIGHTFRAMES)
+    var poolInfo = VkDescriptorPoolCreateInfo(
+      sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+      poolSizeCount: uint32(poolSizes.len),
+      pPoolSizes: poolSizes.ToCPointer,
+      maxSets: (nUniformBuffers + nSamplers) * INFLIGHTFRAMES * 2, # good formula? no idea...
+    )
+    var pool: VkDescriptorPool
+    checkVkResult vkCreateDescriptorPool(device, addr(poolInfo), nil, addr(pool))
+
+    var layouts = newSeqWith(result.descriptorSets.len, descriptorSetLayout)
+    var allocInfo = VkDescriptorSetAllocateInfo(
+      sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+      descriptorPool: pool,
+      descriptorSetCount: uint32(layouts.len),
+      pSetLayouts: layouts.ToCPointer,
+    )
+    checkVkResult vkAllocateDescriptorSets(device, addr(allocInfo), result.descriptorSets.ToCPointer)
+
+  # write descriptor sets
+  # TODO
+  var descriptorSetWrites: seq[VkWriteDescriptorSet]
+  for XY in descriptors?:
+
+      bufferInfos.add VkDescriptorBufferInfo(
+        buffer: descriptor.buffer.vk,
+        offset: descriptor.offset,
+        range: descriptor.size,
+      )
+      descriptorSetWrites.add VkWriteDescriptorSet(
+          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+          dstSet: descriptorSet.vk,
+          dstBinding: i,
+          dstArrayElement: 0,
+          descriptorType: descriptor.vkType,
+          descriptorCount: uint32(descriptor.count),
+          pBufferInfo: addr bufferInfos[^1],
+        )
+
+  vkUpdateDescriptorSets(device, uint32(descriptorSetWrites.len), descriptorSetWrites.ToCPointer, 0, nil)
+
+proc CreateRenderable[TMesh, TInstance](
+  mesh: TMesh,
+  instance: TInstance,
+  buffers: RenderBuffers,
+): Renderable[TMesh, TInstance] =
+  result.indexType = None
+
+proc Bind(pipeline: Pipeline, commandBuffer: VkCommandBuffer, currentFrameInFlight: int) =
+  commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline)
+  if pipeline.descriptorSets[currentFrameInFlight] != VkDescriptorSet(0):
+    commandBuffer.vkCmdBindDescriptorSets(
+      VK_PIPELINE_BIND_POINT_GRAPHICS,
+      pipeline.layout,
+      0,
+      1,
+      addr pipeline.descriptorSets[currentFrameInFlight],
+      0,
+      nil,
+    )
+
+proc AssertCompatible(TShader, TMesh, TInstance, TGlobals: typedesc) =
+  # assert seq-fields of TMesh|TInstance == seq-fields of TShader
+  # assert normal fields of TMesh|Globals == normal fields of TShaderDescriptors
+  for inputName, inputValue in default(TShader).fieldPairs:
+    var foundField = false
+    when hasCustomPragma(inputValue, VertexAttribute):
+      assert typeof(inputValue) is SupportedGPUType
+      for meshName, meshValue in default(TMesh).fieldPairs:
+        when meshName == inputName:
+          assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once"
+          assert elementType(meshValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but mesh attribute is of type '" & tt.name(elementType(meshValue)) & "'"
+          foundField = true
+      assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "'"
+    elif hasCustomPragma(inputValue, InstanceAttribute):
+      assert typeof(inputValue) is SupportedGPUType
+      for instanceName, instanceValue in default(TInstance).fieldPairs:
+        when instanceName == inputName:
+          assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once"
+          assert elementType(instanceValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but instance attribute is of type '" & tt.name(elementType(instanceValue)) & "'"
+          foundField = true
+      assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TInstance) & "'"
+    elif typeof(inputValue) is Texture or typeof(inputValue) is object:
+      for meshName, meshValue in default(TMesh).fieldPairs:
+        when meshName == inputName:
+          assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once"
+          assert typeof(meshValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but mesh attribute is of type '" & tt.name(elementType(meshValue)) & "'"
+          foundField = true
+      for globalName, globalValue in default(TGlobals).fieldPairs:
+        when globalName == inputName:
+          assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once"
+          assert typeof(globalValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but global attribute is of type '" & tt.name(typeof(globalValue)) & "'"
+          foundField = true
+      assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "|" & tt.name(TGlobals) & "'"
+    elif typeof(inputValue) is array:
+      when (elementType(inputValue) is Texture or elementType(inputValue) is object):
+        for meshName, meshValue in default(TMesh).fieldPairs:
+          when meshName == inputName:
+            assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once"
+            assert typeof(meshValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but mesh attribute is of type '" & tt.name(elementType(meshValue)) & "'"
+            foundField = true
+        for globalName, globalValue in default(TGlobals).fieldPairs:
+          when globalName == inputName:
+            assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once"
+            assert typeof(globalValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but global attribute is of type '" & tt.name(typeof(globalValue)) & "'"
+            foundField = true
+        assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "|" & tt.name(TGlobals) & "'"
+
+
+proc Render[TShader, TMesh, TInstance, TGlobals](
+  pipeline: Pipeline[TShader],
+  renderable: Renderable[TMesh, TInstance],
+  globals: TGlobals,
+  commandBuffer: VkCommandBuffer,
+) =
+  {.error: "Need to write descriptor sets".}
+  static: AssertCompatible(TShader, TMesh, TInstance, TGlobals)
+  if renderable.vertexBuffers.len > 0:
+    commandBuffer.vkCmdBindVertexBuffers(
+      firstBinding = 0'u32,
+      bindingCount = uint32(renderable.vertexBuffers.len),
+      pBuffers = renderable.vertexBuffers.ToCPointer(),
+      pOffsets = renderable.bufferOffsets.ToCPointer()
+    )
+  if renderable.indexType != None:
+    commandBuffer.vkCmdBindIndexBuffer(
+      renderable.indexBuffer,
+      renderable.indexBufferOffset,
+      renderable.indexType,
+    )
+    commandBuffer.vkCmdDrawIndexed(
+      indexCount = renderable.indexCount,
+      instanceCount = renderable.instanceCount,
+      firstIndex = 0,
+      vertexOffset = 0,
+      firstInstance = 0
+    )
+  else:
+    commandBuffer.vkCmdDraw(
+      vertexCount = renderable.vertexCount,
+      instanceCount = renderable.instanceCount,
+      firstVertex = 0,
+      firstInstance = 0
+    )
+
+when isMainModule:
+  import semicongine/platform/window
+  import semicongine/vulkan/instance
+  import semicongine/vulkan/device
+  import semicongine/vulkan/physicaldevice
+  import semicongine/vulkan/renderpass
+  import semicongine/vulkan/commandbuffer
+  import std/options
+
+  type
+    MaterialA = object
+      reflection: float32
+      baseColor: Vec3f
+    ShaderSettings = object
+      brightness: float32
+    MeshA = object
+      position: seq[Vec3f]
+      transparency: float
+      material: array[3, MaterialA]
+      materialTextures: array[3, Texture]
+    InstanceA = object
+      transform: seq[Mat4]
+      position: seq[Vec3f]
+    Globals = object
+      fontAtlas: Texture
+      settings: ShaderSettings
+
+    ShaderA = object
+      # vertex input
+      position {.VertexAttribute.}: Vec3f
+      transform {.InstanceAttribute.}: Mat4
+      # intermediate
+      test {.Pass.}: float32
+      test1 {.PassFlat.}: Vec3f
+      # output
+      color {.ShaderOutput.}: Vec4f
+      # uniforms
+      material: array[3, MaterialA]
+      settings: ShaderSettings
+      # textures
+      fontAtlas: Texture
+      materialTextures: array[3, Texture]
+      # code
+      vertexCode: string = "void main() {}"
+      fragmentCode: string = "void main() {}"
+
+  let w = CreateWindow("test2")
+  putEnv("VK_LAYER_ENABLES", "VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD,VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_NVIDIA,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXTVK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT")
+  let i = w.CreateInstance(
+    vulkanVersion = VK_MAKE_API_VERSION(0, 1, 3, 0),
+    instanceExtensions = @[],
+    layers = @["VK_LAYER_KHRONOS_validation"],
+  )
+
+
+  let selectedPhysicalDevice = i.GetPhysicalDevices().FilterBestGraphics()
+  let d = i.CreateDevice(
+    selectedPhysicalDevice,
+    enabledExtensions = @[],
+    selectedPhysicalDevice.FilterForGraphicsPresentationQueues()
+  )
+
+  var r: Renderable[MeshA, InstanceA]
+  var g: Globals
+
+  const shader = ShaderA()
+  let shaderObject = d.vk.CompileShader(shader)
+  let rp = d.vk.CreateRenderPass(d.physicalDevice.GetSurfaceFormats().FilterSurfaceFormat().format)
+  var p = CreatePipeline(d.vk, renderPass = rp, shaderObject)
+
+  let commandBufferPool = d.CreateCommandBufferPool(d.FirstGraphicsQueue().get().family, INFLIGHTFRAMES)
+  let cmd = commandBufferPool.buffers[0]
+
+  checkVkResult cmd.vkResetCommandBuffer(VkCommandBufferResetFlags(0))
+  let beginInfo = VkCommandBufferBeginInfo(
+    sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+    flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
+  )
+  checkVkResult cmd.vkBeginCommandBuffer(addr(beginInfo))
+  p.Bind(cmd, currentFrameInFlight = 0)
+  p.Render(r, g, cmd)
+
+  checkVkResult cmd.vkEndCommandBuffer()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test2.nim	Wed Jun 26 17:46:36 2024 +0700
@@ -0,0 +1,41 @@
+import std/os
+
+import semicongine/platform/window
+import semicongine/core/vulkanapi
+import semicongine/vulkan/instance
+import semicongine/vulkan/device
+import semicongine/vulkan/physicaldevice
+# import ./vulkan/shader
+
+import semicongine/core/vector
+import semicongine/core/matrix
+
+type
+  MeshA = object
+    positions: seq[Vec3f]
+    colors: seq[Vec3f]
+    transparency: float32
+  InstanceDataA = object
+    transforms: seq[Vec3f]
+
+  Enemy = object
+    mesh: MeshA
+    enemies: InstanceDataA
+
+let e = Enemy()
+echo e
+
+let w = CreateWindow("test2")
+putEnv("VK_LAYER_ENABLES", "VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD,VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_NVIDIA,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXTVK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT")
+let i = w.CreateInstance(
+  vulkanVersion = VK_MAKE_API_VERSION(0, 1, 3, 0),
+  instanceExtensions = @[],
+  layers = @["VK_LAYER_KHRONOS_validation"],
+)
+
+let selectedPhysicalDevice = i.GetPhysicalDevices().FilterBestGraphics()
+let d = i.CreateDevice(
+  selectedPhysicalDevice,
+  enabledExtensions = @[],
+  selectedPhysicalDevice.FilterForGraphicsPresentationQueues()
+)