changeset 575:eaedc0369c38

yay: first triangle rendering with new engine implmentation
author Sam <sam@basx.dev>
date Mon, 03 Apr 2023 00:06:24 +0700
parents bbeec60e25ca
children 677e4f7fb567
files src/semicongine/gpu_data.nim src/semicongine/mesh.nim src/semicongine/scene.nim src/semicongine/vulkan/buffer.nim src/semicongine/vulkan/instance.nim src/semicongine/vulkan/memory.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 11 files changed, 263 insertions(+), 221 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/gpu_data.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/gpu_data.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -1,3 +1,4 @@
+import std/sequtils
 import std/typetraits
 import std/strformat
 import std/tables
@@ -28,6 +29,9 @@
   AttributeGroup* = object
     attributes*: seq[Attribute]
 
+func initAttributeGroup*(attrs: varargs[Attribute]): auto =
+  AttributeGroup(attributes: attrs.toSeq)
+
 func vertexInputs*(group: AttributeGroup): seq[Attribute] =
   for attr in group.attributes:
     if attr.perInstance == false:
--- a/src/semicongine/mesh.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/mesh.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -41,29 +41,35 @@
 method `$`*(mesh: Mesh): string =
   &"Mesh ({mesh.vertexCount})"
 
-func newMesh*(vertices: openArray[Vec3f]): auto =
-  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: vertices.toSeq)}.toTable
-  Mesh(vertexCount: uint32(vertices.len), data: meshdata, indexType: None)
+func initMesh*(positions: openArray[Vec3f], colors: openArray[Vec3f]=[]): Mesh =
+  assert colors.len == 0 or colors.len == positions.len
+  result = new Mesh
+  result.vertexCount = uint32(positions.len)
+  result.indexType = None
+  result.data[asAttribute(default(Vec3f), "position")] = MeshData(thetype: Position, position: positions.toSeq)
+  if colors.len > 0:
+    result.data[asAttribute(default(Vec3f), "color")] = MeshData(thetype: Color, color: colors.toSeq)
 
-func newMesh*(vertices: openArray[Vec3f], indices: openArray[array[3, uint32|int32]]): auto =
-  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: vertices.toSeq)}.toTable
-  if uint16(vertices.len) < high(uint16):
+
+func initMesh*(positions: openArray[Vec3f], indices: openArray[array[3, uint32|int32]]): auto =
+  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: positions.toSeq)}.toTable
+  if uint16(positions.len) < high(uint16):
     var smallIndices = newSeq[array[3, uint16]](indices.len)
     for i, tri in enumerate(indices):
       smallIndices[i] = [uint16(tri[0]), uint16(tri[1]), uint16(tri[3])]
-    Mesh(vertexCount: uint32(vertices.len), data: meshdata, indexType: Small, smallIndices: smallIndices)
+    Mesh(vertexCount: uint32(positions.len), data: meshdata, 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(vertexCount: uint32(vertices.len), data: meshdata, indexType: Big, bigIndices: bigIndices)
+    Mesh(vertexCount: uint32(positions.len), data: meshdata, indexType: Big, bigIndices: bigIndices)
 
-func newMesh*(vertices: openArray[Vec3f], indices: openArray[array[3, uint16|int16]]): auto =
-  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: vertices.toSeq)}.toTable
+func initMesh*(positions: openArray[Vec3f], indices: openArray[array[3, uint16|int16]]): auto =
+  let meshdata = {asAttribute(default(Vec3f), "position"): MeshData(thetype: Position, position: positions.toSeq)}.toTable
   var smallIndices = newSeq[array[3, uint16]](indices.len)
   for i, tri in enumerate(indices):
     smallIndices[i] = [uint16(tri[0]), uint16(tri[1]), uint16(tri[3])]
-  Mesh(vertexCount: vertices.len, data: meshdata, indexType: Small, smallIndices: smallIndices)
+  Mesh(vertexCount: positions.len, data: meshdata, indexType: Small, smallIndices: smallIndices)
 
 
 func size*(meshdata: MeshData): uint64 =
@@ -85,7 +91,7 @@
     result += d.size
 
 proc rawData[T: seq](value: var T): (pointer, uint64) =
-  (pointer(addr(value)), uint64(sizeof(get(genericParams(typeof(value)), 0)) * value.len))
+  (pointer(addr(value[0])), uint64(sizeof(get(genericParams(typeof(value)), 0)) * value.len))
 
 proc getRawData(data: var MeshData): (pointer, uint64) =
   case data.thetype:
--- a/src/semicongine/scene.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/scene.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -11,7 +11,8 @@
 
 type
   Drawable* = object
-    buffers*: seq[(Buffer, uint64)] # buffer + offset from buffer
+    buffer*: Buffer # buffer
+    offsets*: seq[uint64] # offsets from buffer
     elementCount*: uint32 # number of vertices or indices
     instanceCount*: uint32 # number of instance
     case indexed*: bool
@@ -28,14 +29,20 @@
 
 func `$`*(drawable: Drawable): string =
   if drawable.indexed:
-    &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffers: {drawable.buffers}, indexType: {drawable.indexType})"
+    &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffer: {drawable.buffer}, offsets: {drawable.offsets}, indexType: {drawable.indexType})"
   else:
-    &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffers: {drawable.buffers})"
+    &"Drawable(elementCount: {drawable.elementCount}, instanceCount: {drawable.instanceCount}, buffer: {drawable.buffer}, offsets: {drawable.offsets})"
+
+proc destroy(drawable: var Drawable) =
+  drawable.buffer.destroy()
+  if drawable.indexed:
+    drawable.indexBuffer.destroy()
 
 proc setupDrawables(scene: var Scene, pipeline: Pipeline) =
   assert pipeline.device.vk.valid
-  assert not (pipeline.vk in scene.drawables)
-
+  if pipeline.vk in scene.drawables:
+    for drawable in scene.drawables[pipeline.vk].mitems:
+      drawable.destroy()
   scene.drawables[pipeline.vk] = @[]
 
   var
@@ -63,9 +70,9 @@
         memoryFlags=[VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT],
       )
     var offset = 0'u64
-    var drawable = Drawable(elementCount: vertexCount, indexed: false, instanceCount: 1)
+    var drawable = Drawable(elementCount: vertexCount, buffer: buffer, indexed: false, instanceCount: 1)
     for inputAttr in pipeline.inputs.vertexInputs:
-      drawable.buffers.add (buffer, offset)
+      drawable.offsets.add offset
       for mesh in nonIMeshes:
         var (pdata, size) = mesh.getRawData(inputAttr)
         buffer.setData(pdata, size, offset)
@@ -110,8 +117,4 @@
 proc destroy*(scene: var Scene) =
   for drawables in scene.drawables.mvalues:
     for drawable in drawables.mitems:
-      for (buffer, offset) in drawable.buffers.mitems:
-        # if buffer.vk.valid: # required because we allow duplicates in drawable.buffers
-        buffer.destroy()
-      if drawable.indexed:
-        drawable.indexBuffer.destroy()
+      drawable.destroy()
--- a/src/semicongine/vulkan/buffer.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/vulkan/buffer.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -118,4 +118,4 @@
     assert buffer.memory.vk.valid
     buffer.memory.free
   buffer.device.vk.vkDestroyBuffer(buffer.vk, nil)
-  buffer.vk.reset()
+  buffer = default(Buffer)
--- a/src/semicongine/vulkan/instance.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/vulkan/instance.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -106,6 +106,8 @@
 ): VkBool32 {.cdecl.} =
 
   log LEVEL_MAPPING[messageSeverity], &"{toEnums messageTypes}: {pCallbackData.pMessage}"
+  if messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+    echo getStackTrace()
   return false
 
 proc createDebugMessenger*(
--- a/src/semicongine/vulkan/memory.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/vulkan/memory.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -91,4 +91,4 @@
   assert memory.vk.valid
 
   memory.device.vk.vkFreeMemory(memory.vk, nil)
-  memory.vk.reset
+  memory = default(DeviceMemory)
--- a/src/semicongine/vulkan/pipeline.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -2,6 +2,7 @@
 import ./device
 import ./descriptor
 import ./shader
+import ./utils
 
 import ../gpu_data
 
@@ -20,6 +21,137 @@
     if shader.stage == VK_SHADER_STAGE_VERTEX_BIT:
       return shader.inputs
 
+proc createPipeline*(device: Device, renderPass: VkRenderPass, vertexShader: Shader, fragmentShader: Shader, inFlightFrames: int, subpass = 0'u32): Pipeline =
+  assert renderPass.valid
+  assert device.vk.valid
+  assert vertexShader.stage == VK_SHADER_STAGE_VERTEX_BIT
+  assert fragmentShader.stage == VK_SHADER_STAGE_FRAGMENT_BIT
+
+  result.device = device
+  result.shaders = @[vertexShader, fragmentShader]
+  
+  var descriptors = @[Descriptor(
+    thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+    count: 1,
+    stages: @[VK_SHADER_STAGE_VERTEX_BIT],
+    itemsize: vertexShader.uniforms.size(),
+  )]
+  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: fragmentShader.uniforms.size(),
+    )
+  result.descriptorSetLayout = device.createDescriptorSetLayout(descriptors)
+
+  # TODO: Push constants
+  # var pushConstant = VkPushConstantRange(
+    # stageFlags: toBits shaderStage,
+    # offset: 0,
+    # size: 0,
+  # )
+  var descriptorSetLayouts: seq[VkDescriptorSetLayout] = @[result.descriptorSetLayout.vk]
+  # var pushConstants: seq[VkPushConstantRange] = @[pushConstant]
+  var pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+      setLayoutCount: uint32(descriptorSetLayouts.len),
+      pSetLayouts: descriptorSetLayouts.toCPointer,
+      # pushConstantRangeCount: uint32(pushConstants.len),
+      # pPushConstantRanges: pushConstants.toCPointer,
+    )
+  checkVkResult vkCreatePipelineLayout(device.vk, addr(pipelineLayoutInfo), nil, addr(result.layout))
+
+  var
+    bindings: seq[VkVertexInputBindingDescription]
+    attributes: seq[VkVertexInputAttributeDescription]
+    vertexInputInfo = vertexShader.getVertexInputInfo(bindings, attributes)
+    inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+      topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+      primitiveRestartEnable: VK_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: VK_POLYGON_MODE_FILL,
+      lineWidth: 1.0,
+      cullMode: toBits [VK_CULL_MODE_BACK_BIT],
+      frontFace: VK_FRONT_FACE_CLOCKWISE,
+      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: uint32(dynamicStates.len),
+      pDynamicStates: dynamicStates.toCPointer,
+    )
+    stages = @[vertexShader.getPipelineInfo(), fragmentShader.getPipelineInfo()]
+    createInfo = VkGraphicsPipelineCreateInfo(
+      sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+      stageCount: uint32(stages.len),
+      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: subpass,
+      basePipelineHandle: VkPipeline(0),
+      basePipelineIndex: -1,
+    )
+  checkVkResult vkCreateGraphicsPipelines(
+    device.vk,
+    VkPipelineCache(0),
+    1,
+    addr(createInfo),
+    nil,
+    addr(result.vk)
+  )
+  result.descriptorPool = result.device.createDescriptorSetPool(@[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1'u32)])
+  result.descriptorSets = result.descriptorPool.allocateDescriptorSet(result.descriptorSetLayout, inFlightFrames)
+
 proc destroy*(pipeline: var Pipeline) =
   assert pipeline.device.vk.valid
   assert pipeline.vk.valid
--- a/src/semicongine/vulkan/renderpass.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -5,8 +5,6 @@
 import ./device
 import ./pipeline
 import ./shader
-import ./descriptor
-import ../gpu_data
 
 import ../math
 
@@ -68,137 +66,6 @@
   result.subpasses = pSubpasses
   checkVkResult device.vk.vkCreateRenderPass(addr(createInfo), nil, addr(result.vk))
 
-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
-  assert fragmentShader.stage == VK_SHADER_STAGE_FRAGMENT_BIT
-
-  var pipeline = Pipeline(device: renderPass.device, shaders: @[vertexShader, fragmentShader])
-  
-  var descriptors = @[Descriptor(
-    thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
-    count: 1,
-    stages: @[VK_SHADER_STAGE_VERTEX_BIT],
-    itemsize: vertexShader.uniforms.size(),
-  )]
-  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: fragmentShader.uniforms.size(),
-    )
-  pipeline.descriptorSetLayout = renderPass.device.createDescriptorSetLayout(descriptors)
-
-  # TODO: Push constants
-  # var pushConstant = VkPushConstantRange(
-    # stageFlags: toBits shaderStage,
-    # offset: 0,
-    # size: 0,
-  # )
-  var descriptorSetLayouts: seq[VkDescriptorSetLayout] = @[pipeline.descriptorSetLayout.vk]
-  # var pushConstants: seq[VkPushConstantRange] = @[pushConstant]
-  var pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
-      sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
-      setLayoutCount: uint32(descriptorSetLayouts.len),
-      pSetLayouts: descriptorSetLayouts.toCPointer,
-      # pushConstantRangeCount: uint32(pushConstants.len),
-      # pPushConstantRanges: pushConstants.toCPointer,
-    )
-  checkVkResult vkCreatePipelineLayout(renderPass.device.vk, addr(pipelineLayoutInfo), nil, addr(pipeline.layout))
-
-  var
-    bindings: seq[VkVertexInputBindingDescription]
-    attributes: seq[VkVertexInputAttributeDescription]
-    vertexInputInfo = vertexShader.getVertexInputInfo(bindings, attributes)
-    inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
-      sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
-      topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
-      primitiveRestartEnable: VK_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: VK_POLYGON_MODE_FILL,
-      lineWidth: 1.0,
-      cullMode: toBits [VK_CULL_MODE_BACK_BIT],
-      frontFace: VK_FRONT_FACE_CLOCKWISE,
-      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: uint32(dynamicStates.len),
-      pDynamicStates: dynamicStates.toCPointer,
-    )
-    stages = @[vertexShader.getPipelineInfo(), fragmentShader.getPipelineInfo()]
-    createInfo = VkGraphicsPipelineCreateInfo(
-      sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
-      stageCount: uint32(stages.len),
-      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: pipeline.layout,
-      renderPass: renderPass.vk,
-      subpass: subpass,
-      basePipelineHandle: VkPipeline(0),
-      basePipelineIndex: -1,
-    )
-  checkVkResult vkCreateGraphicsPipelines(
-    renderPass.device.vk,
-    VkPipelineCache(0),
-    1,
-    addr(createInfo),
-    nil,
-    addr(pipeline.vk)
-  )
-  pipeline.descriptorPool = pipeline.device.createDescriptorSetPool(@[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1'u32)])
-  pipeline.descriptorSets = pipeline.descriptorPool.allocateDescriptorSet(pipeline.descriptorSetLayout, renderPass.inFlightFrames)
-  renderPass.subpasses[subpass].pipelines.add pipeline
-
 proc simpleForwardRenderPass*(device: Device, format: VkFormat, vertexShader: Shader, fragmentShader: Shader, inFlightFrames: int, clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): RenderPass =
   assert device.vk.valid
   var
@@ -229,7 +96,7 @@
       dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
     )]
   result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies, inFlightFrames=inFlightFrames)
-  result.attachPipeline(vertexShader, fragmentShader, 0)
+  result.subpasses[0].pipelines.add device.createPipeline(result.vk, vertexShader, fragmentShader, inFlightFrames, 0)
 
 proc destroy*(renderPass: var RenderPass) =
   assert renderPass.device.vk.valid
--- a/src/semicongine/vulkan/shader.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/vulkan/shader.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -13,22 +13,31 @@
 
 import ../gpu_data
 
+const DEFAULT_SHADER_VERSION = 450
+const DEFAULT_SHADER_ENTRYPOINT = "main"
+
 let logger = newConsoleLogger()
 addHandler(logger)
 
 type
+  ShaderCode = object
+    stage: VkShaderStageFlagBits
+    entrypoint: string
+    binary: seq[uint32]
+    inputs: AttributeGroup
+    uniforms: AttributeGroup
+    outputs: AttributeGroup
   Shader* = object
     device: Device
+    vk*: VkShaderModule
     stage*: VkShaderStageFlagBits
-    vk*: VkShaderModule
     entrypoint*: string
     inputs*: AttributeGroup
     uniforms*: AttributeGroup
     outputs*: AttributeGroup
 
 
-proc compileGLSLToSPIRV*(stage: VkShaderStageFlagBits, shaderSource: string, entrypoint: string): seq[uint32] {.compileTime.} =
-
+proc compileGlslToSPIRV(stage: VkShaderStageFlagBits, shaderSource: string, entrypoint: string): seq[uint32] {.compileTime.} =
   func stage2string(stage: VkShaderStageFlagBits): string {.compileTime.} =
     case stage
     of VK_SHADER_STAGE_VERTEX_BIT: "vert"
@@ -75,56 +84,56 @@
     i += 4
 
 
-proc shaderCode*(
-  inputs: AttributeGroup,
-  uniforms: AttributeGroup,
-  outputs: AttributeGroup,
+proc compileGlslShader*(
   stage: VkShaderStageFlagBits,
-  version: int,
-  entrypoint: string,
+  inputs=AttributeGroup(),
+  uniforms=AttributeGroup(),
+  outputs=AttributeGroup(),
+  version=DEFAULT_SHADER_VERSION ,
+  entrypoint=DEFAULT_SHADER_ENTRYPOINT ,
   body: seq[string]
-): seq[uint32] {.compileTime.} =
+): ShaderCode {.compileTime.} =
   var code = @[&"#version {version}", ""] &
-    inputs.glslInput() & @[""] &
-    uniforms.glslUniforms() & @[""] &
-    outputs.glslOutput() & @[""] &
+    (if inputs.attributes.len > 0: inputs.glslInput() & @[""] else: @[]) &
+    (if uniforms.attributes.len > 0: uniforms.glslUniforms() & @[""] else: @[]) &
+    (if outputs.attributes.len > 0: outputs.glslOutput() & @[""] else: @[]) &
     @[&"void {entrypoint}(){{"] &
     body &
     @[&"}}"]
-  compileGLSLToSPIRV(stage, code.join("\n"), entrypoint)
-
-
-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*(
-  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
+  result.binary = compileGlslToSPIRV(stage, code.join("\n"), entrypoint)
+
+
+proc compileGlslShader*(
+  stage: VkShaderStageFlagBits,
+  inputs: AttributeGroup=AttributeGroup(),
+  uniforms: AttributeGroup=AttributeGroup(),
+  outputs: AttributeGroup=AttributeGroup(),
+  version=DEFAULT_SHADER_VERSION ,
+  entrypoint=DEFAULT_SHADER_ENTRYPOINT ,
+  body: string
+): ShaderCode {.compileTime.} =
+  return compileGlslShader(stage, inputs, uniforms, outputs, version, entrypoint, @[body])
+
+
+proc createShaderModule*(
+  device: Device,
+  shaderCode: ShaderCode,
+): Shader =
+  assert device.vk.valid
+  assert len(shaderCode.binary) > 0
+
+  result.device = device
+  result.inputs = shaderCode.inputs
+  result.uniforms = shaderCode.uniforms
+  result.outputs = shaderCode.outputs
+  result.entrypoint = shaderCode.entrypoint
+  result.stage = shaderCode.stage
+  var bin = shaderCode.binary
   var createInfo = VkShaderModuleCreateInfo(
     sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
     codeSize: uint(bin.len * sizeof(uint32)),
--- a/src/semicongine/vulkan/swapchain.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -160,8 +160,8 @@
     debug "Draw ", drawable
     var buffers: seq[VkBuffer]
     var offsets: seq[VkDeviceSize]
-    for (buffer, offset) in drawable.buffers:
-      buffers.add buffer.vk
+    for offset in drawable.offsets:
+      buffers.add drawable.buffer.vk
       offsets.add VkDeviceSize(offset)
     commandBuffer.vkCmdBindVertexBuffers(
       firstBinding=0'u32,
--- a/tests/test_vulkan_wrapper.nim	Sun Apr 02 01:22:09 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Mon Apr 03 00:06:24 2023 +0700
@@ -45,7 +45,7 @@
   var instance = thewindow.createInstance(
     vulkanVersion=VK_MAKE_API_VERSION(0, 1, 3, 0),
     instanceExtensions= @["VK_EXT_debug_utils"],
-    layers= @["VK_LAYER_KHRONOS_validation"]
+    layers= @["VK_LAYER_KHRONOS_validation", "VK_LAYER_MESA_overlay"]
   )
   var debugger = instance.createDebugMessenger()
 
@@ -58,45 +58,64 @@
     selectedPhysicalDevice.filterForGraphicsPresentationQueues()
   )
 
-  const inputs = AttributeGroup(attributes: @[attr(name="position", 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 = position;")
-  const fragmentBinary = shaderCode(inputs=outputs, uniforms=uniforms, outputs=fragOutput, stage=VK_SHADER_STAGE_FRAGMENT_BIT, version=450, entrypoint="main", "color = vec4(1, 1, 1, 1);")
+  const
+    vertexInput = initAttributeGroup(
+      asAttribute(default(Vec3f), "position"),
+      asAttribute(default(Vec3f), "color"),
+    )
+    vertexOutput = initAttributeGroup(asAttribute(default(Vec3f), "outcolor"))
+    fragOutput = initAttributeGroup(asAttribute(default(Vec4f), "color"))
+    vertexCode = compileGlslShader(
+      stage=VK_SHADER_STAGE_VERTEX_BIT,
+      inputs=vertexInput,
+      outputs=vertexOutput,
+      body="""gl_Position = vec4(position, 1.0); outcolor = color;"""
+    )
+    fragmentCode = compileGlslShader(
+      stage=VK_SHADER_STAGE_FRAGMENT_BIT,
+      inputs=vertexOutput,
+      outputs=fragOutput,
+      body="color = vec4(outcolor, 1);"
+    )
   var
-    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)
+    vertexshader = device.createShaderModule(vertexCode)
+    fragmentshader = device.createShaderModule(fragmentCode)
     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)
+    (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(
     name: "main",
     root: newEntity("root",
-      newEntity("triangle1", newMesh([newVec3f(-0.5, -0.5), newVec3f(0.5, 0.5), newVec3f(0.5, -0.5)])),
-      newEntity("triangle2", newMesh([newVec3f(-0.5, -0.5), newVec3f(0.5, -0.5), newVec3f(0.5, 0.5)])),
+      newEntity("triangle1", initMesh(
+        positions=[newVec3f(0.0, -0.5), newVec3f(0.5, 0.5), newVec3f(-0.5, 0.5)],
+        colors=[newVec3f(1.0, 0.0, 0.0), newVec3f(0.0, 1.0, 0.0), newVec3f(0.0, 0.0, 1.0)],
+      )),
     )
   )
   thescene.setupDrawables(renderPass)
 
   echo "Setup successfull, start rendering"
-  for i in 0 ..< 1:
+  for i in 0 ..< 1000:
     discard swapchain.drawScene(thescene)
   echo "Rendered ", swapchain.framesRendered, " frames"
+  checkVkResult device.vk.vkDeviceWaitIdle()
+
+  # cleanup
   echo "Start cleanup"
 
+  # logical
+  thescene.destroy()
 
-  # cleanup
-  checkVkResult device.vk.vkDeviceWaitIdle()
-  thescene.destroy()
+  # rendering objects
   vertexshader.destroy()
   fragmentshader.destroy()
   renderPass.destroy()
   swapchain.destroy()
+
+  # global objects
   device.destroy()
-
   debugger.destroy()
   instance.destroy()