changeset 108:70f92081d2c1

did: reorganize code
author Sam <sam@basx.dev>
date Tue, 28 Mar 2023 00:20:49 +0700
parents 2d0351a68a4e
children 8d24727c9795
files src/semicongine/buffer.nim src/semicongine/descriptor.nim src/semicongine/engine.nim src/semicongine/entity.nim src/semicongine/glsl_helpers.nim src/semicongine/image.nim src/semicongine/legacy/buffer.nim src/semicongine/legacy/descriptor.nim src/semicongine/legacy/engine.nim src/semicongine/legacy/glsl_helpers.nim src/semicongine/legacy/image.nim src/semicongine/legacy/shader.nim src/semicongine/legacy/thing.nim src/semicongine/legacy/vertex.nim src/semicongine/legacy/vulkan_helpers.nim src/semicongine/mesh.nim src/semicongine/shader.nim src/semicongine/thing.nim src/semicongine/vertex.nim src/semicongine/vulkan/api.nim src/semicongine/vulkan/commandbuffer.nim src/semicongine/vulkan/descriptor.nim src/semicongine/vulkan/pipeline.nim src/semicongine/vulkan/renderpass.nim src/semicongine/vulkan/swapchain.nim src/semicongine/vulkan_helpers.nim tests/test_vulkan_wrapper.nim
diffstat 27 files changed, 2221 insertions(+), 2065 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/buffer.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-import std/typetraits
-
-import ./vulkan
-import ./vulkan_helpers
-
-type
-  BufferType* = enum
-    None = 0
-    TransferSrc = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
-    TransferDst = VK_BUFFER_USAGE_TRANSFER_DST_BIT
-    UniformBuffer = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
-    IndexBuffer = VK_BUFFER_USAGE_INDEX_BUFFER_BIT
-    VertexBuffer = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
-  MemoryProperty* = enum
-    DeviceLocal = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
-    HostVisible = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
-    HostCoherent = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
-  MemoryProperties* = set[MemoryProperty]
-  Buffer* = object
-    device*: VkDevice
-    vkBuffer*: VkBuffer
-    size*: uint64
-    memoryRequirements*: VkMemoryRequirements
-    memoryProperties*: MemoryProperties
-    memory*: VkDeviceMemory
-    bufferTypes*: set[BufferType]
-    data*: pointer
-
-proc trash*(buffer: var Buffer) =
-  if int64(buffer.vkBuffer) != 0 and int64(buffer.memory) != 0:
-    vkUnmapMemory(buffer.device, buffer.memory)
-  if int64(buffer.vkBuffer) != 0:
-    vkDestroyBuffer(buffer.device, buffer.vkBuffer, nil)
-    buffer.vkBuffer = VkBuffer(0)
-  if int64(buffer.memory) != 0:
-    vkFreeMemory(buffer.device, buffer.memory, nil)
-    buffer.memory = VkDeviceMemory(0)
-
-proc findMemoryType*(memoryRequirements: VkMemoryRequirements,
-    physicalDevice: VkPhysicalDevice, properties: MemoryProperties): uint32 =
-  var physicalProperties: VkPhysicalDeviceMemoryProperties
-  vkGetPhysicalDeviceMemoryProperties(physicalDevice, addr(physicalProperties))
-
-  for i in 0'u32 ..< physicalProperties.memoryTypeCount:
-    if bool(memoryRequirements.memoryTypeBits and (1'u32 shl i)):
-      if (uint32(physicalProperties.memoryTypes[i].propertyFlags) and cast[
-          uint32](properties)) == cast[uint32](properties):
-        return i
-
-proc InitBuffer*(
-  device: VkDevice,
-  physicalDevice: VkPhysicalDevice,
-  size: uint64,
-  bufferTypes: set[BufferType],
-  properties: MemoryProperties,
-): Buffer =
-  result = Buffer(device: device, size: size, bufferTypes: bufferTypes,
-      memoryProperties: properties)
-  var usageFlags = 0
-  for usage in bufferTypes:
-    usageFlags = ord(usageFlags) or ord(usage)
-  var bufferInfo = VkBufferCreateInfo(
-    sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
-    size: VkDeviceSize(result.size),
-    usage: VkBufferUsageFlags(usageFlags),
-    sharingMode: VK_SHARING_MODE_EXCLUSIVE,
-  )
-  checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr(
-      result.vkBuffer))
-  vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr(
-      result.memoryRequirements))
-
-  var allocInfo = VkMemoryAllocateInfo(
-    sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
-    allocationSize: result.memoryRequirements.size,
-    memoryTypeIndex: result.memoryRequirements.findMemoryType(physicalDevice, properties)
-  )
-  if result.size > 0:
-    checkVkResult result.device.vkAllocateMemory(addr(allocInfo), nil, addr(result.memory))
-  checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory,
-      VkDeviceSize(0))
-  checkVkResult vkMapMemory(
-    result.device,
-    result.memory,
-    offset = VkDeviceSize(0),
-    VkDeviceSize(result.size),
-    VkMemoryMapFlags(0),
-    addr(result.data)
-  )
-
-
-proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src,
-    dst: Buffer, size: uint64) =
-  assert uint64(src.device) == uint64(dst.device)
-  assert TransferSrc in src.bufferTypes
-  assert TransferDst in dst.bufferTypes
-  var
-    allocInfo = VkCommandBufferAllocateInfo(
-      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
-      level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
-      commandPool: commandPool,
-      commandBufferCount: 1,
-    )
-    commandBuffer: VkCommandBuffer
-  checkVkResult vkAllocateCommandBuffers(src.device, addr(allocInfo), addr(commandBuffer))
-
-  var beginInfo = VkCommandBufferBeginInfo(
-    sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
-    flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
-  )
-  checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo))
-  var copyRegion = VkBufferCopy(size: VkDeviceSize(size))
-  vkCmdCopyBuffer(commandBuffer, src.vkBuffer, dst.vkBuffer, 1, addr(copyRegion))
-  checkVkResult vkEndCommandBuffer(commandBuffer)
-
-  var submitInfo = VkSubmitInfo(
-    sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
-    commandBufferCount: 1,
-    pCommandBuffers: addr(commandBuffer),
-  )
-
-  checkVkResult vkQueueSubmit(queue, 1, addr(submitInfo), VkFence(0))
-  checkVkResult vkQueueWaitIdle(queue)
-  vkFreeCommandBuffers(src.device, commandPool, 1, addr(commandBuffer))
-
--- a/src/semicongine/descriptor.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-import std/strutils
-import std/unicode
-import std/strformat
-import std/typetraits
-
-import ./vulkan
-import ./vulkan_helpers
-import ./math/vector
-import ./math/matrix
-import ./buffer
-import ./glsl_helpers
-
-# TODO: check for alignment in uniform blocks
-#
-type
-  DescriptorType = SomeNumber|TVec|TMat
-  Descriptor*[T: DescriptorType] = object
-    value*: T
-  ViewProjectionTransform* = Descriptor[Mat44]
-
-proc createUniformDescriptorLayout*(device: VkDevice,
-    shaderStage: VkShaderStageFlags, binding: uint32): VkDescriptorSetLayout =
-  var
-    layoutbinding = VkDescriptorSetLayoutBinding(
-      binding: binding,
-      descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
-      descriptorCount: 1,
-      stageFlags: shaderStage,
-      pImmutableSamplers: nil,
-    )
-    layoutInfo = VkDescriptorSetLayoutCreateInfo(
-      sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
-      bindingCount: 1,
-      pBindings: addr(layoutbinding)
-    )
-  checkVkResult device.vkCreateDescriptorSetLayout(addr(layoutInfo), nil, addr(result))
-
-proc createUniformBuffers*[nBuffers: static int, Uniforms](device: VkDevice,
-    physicalDevice: VkPhysicalDevice): array[nBuffers, Buffer] =
-  let size = sizeof(Uniforms)
-  for i in 0 ..< nBuffers:
-    var buffer = InitBuffer(
-      device,
-      physicalDevice,
-      uint64(size),
-      {UniformBuffer},
-      {HostVisible, HostCoherent},
-    )
-    result[i] = buffer
-
-template getDescriptorType*(v: Descriptor): auto = get(genericParams(typeof(v)), 0)
-
-func generateGLSLUniformDeclarations*[Uniforms](binding: int = 0): string {.compileTime.} =
-  var stmtList: seq[string]
-
-  when not (Uniforms is void):
-    let uniformTypeName = name(Uniforms).toUpper()
-    let uniformInstanceName = name(Uniforms).toLower()
-    stmtList.add(&"layout(binding = {binding}) uniform {uniformTypeName} {{")
-    for fieldname, value in Uniforms().fieldPairs:
-      when typeof(value) is Descriptor:
-        let glsltype = getGLSLType[getDescriptorType(value)]()
-        let n = fieldname
-        stmtList.add(&"    {glsltype} {n};")
-    stmtList.add(&"}} {uniformInstanceName};")
-
-  return stmtList.join("\n")
--- a/src/semicongine/engine.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,870 +0,0 @@
-import std/options
-import std/os
-import std/times
-import std/typetraits
-import std/strformat
-import std/enumerate
-import std/logging
-
-
-import ./math/vector
-import ./vulkan
-import ./vulkan_helpers
-import ./platform/window
-import ./events
-import ./shader
-import ./vertex
-import ./buffer
-import ./thing
-import ./descriptor
-import ./mesh
-
-const MAX_FRAMES_IN_FLIGHT = 2
-const DEBUG_LOG = not defined(release)
-
-var logger = newConsoleLogger()
-addHandler(logger)
-
-
-const VULKAN_VERSION = VK_MAKE_API_VERSION(0'u32, 1'u32, 2'u32, 0'u32)
-const ENGINE_NAME = "zamkongine"
-const ENGINE_VERSION = "0.1"
-const BUILD_VERSION = ENGINE_VERSION & '-' & gorge("git log -1 --format=format:'%H'")
-echo "Engine: " & ENGINE_NAME & " " & BUILD_VERSION
-
-type
-  Device = object
-    device*: VkDevice
-    physicalDevice*: PhysicalDevice
-    graphicsQueueFamily*: uint32
-    presentationQueueFamily*: uint32
-    graphicsQueue*: VkQueue
-    presentationQueue*: VkQueue
-    commandPool*: VkCommandPool
-    commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]
-  Swapchain = object
-    swapchain: VkSwapchainKHR
-    images: seq[VkImage]
-    imageviews: seq[VkImageView]
-  RenderPipeline*[VertexType, Uniforms] = object
-    device*: VkDevice
-    shaders*: seq[ShaderProgram[VertexType, Uniforms]]
-    layout*: VkPipelineLayout
-    pipeline*: VkPipeline
-    vertexBuffers*: seq[(seq[Buffer], bool, Buffer, uint32, VkIndexType)]
-    descriptorSetLayout*: VkDescriptorSetLayout
-    uniformBuffers*: array[MAX_FRAMES_IN_FLIGHT, Buffer]
-    descriptorPool*: VkDescriptorPool
-    descriptors: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSet]
-    clearColor*: Vec4
-  QueueFamily = object
-    properties*: VkQueueFamilyProperties
-    hasSurfaceSupport*: bool
-  PhysicalDevice = object
-    device*: VkPhysicalDevice
-    extensions*: seq[string]
-    properties*: VkPhysicalDeviceProperties
-    features*: VkPhysicalDeviceFeatures
-    queueFamilies*: seq[QueueFamily]
-    formats: seq[VkSurfaceFormatKHR]
-    presentModes: seq[VkPresentModeKHR]
-  Vulkan* = object
-    debugMessenger*: VkDebugUtilsMessengerEXT
-    instance*: VkInstance
-    deviceList*: seq[PhysicalDevice]
-    device*: Device
-    surface*: VkSurfaceKHR
-    surfaceFormat*: VkSurfaceFormatKHR
-    frameSize*: TVec2[uint32]
-    swapchain*: Swapchain
-    framebuffers*: seq[VkFramebuffer]
-    renderPass*: VkRenderPass
-    imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
-    renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
-    inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence]
-  Input* = object
-    keysDown*: set[Key]
-    keysPressed*: set[Key]
-    keysReleased*: set[Key]
-    mouseDown*: set[MouseButton]
-    mousePressed*: set[MouseButton]
-    mouseReleased*: set[MouseButton]
-    mousePos*: Vec2
-  Engine* = object
-    vulkan*: Vulkan
-    window*: NativeWindow
-    currentscenedata*: Thing
-    input*: Input
-    maxFPS*: uint
-
-
-method update*(thing: Thing, engine: Engine, t, dt: float32) {.base.} = discard
-method update*(part: Part, engine: Engine, t, dt: float32) {.base.} = discard
-
-method update*[T, U](mesh: Mesh[T, U], engine: Engine, t, dt: float32) =
-  let transform = @[mesh.thing.getModelTransform().transposed()]
-  for name, value in mesh.vertexData.fieldPairs:
-    when value is ModelTransformAttribute:
-      value.data = transform
-      engine.vulkan.device.updateVertexData(value)
-
-proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[
-    PhysicalDevice] =
-  for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance):
-    var device = PhysicalDevice(device: vulkanPhysicalDevice, extensions: vulkan.getDeviceExtensions(vulkanPhysicalDevice))
-    vkGetPhysicalDeviceProperties(vulkanPhysicalDevice, addr(device.properties))
-    vkGetPhysicalDeviceFeatures(vulkanPhysicalDevice, addr(device.features))
-    device.formats = vulkanPhysicalDevice.getDeviceSurfaceFormats(surface)
-    device.presentModes = vulkanPhysicalDevice.getDeviceSurfacePresentModes(surface)
-
-    debug(&"Physical device nr {int(vulkanPhysicalDevice)} {cleanString(device.properties.deviceName)}")
-    for i, queueFamilyProperty in enumerate(getQueueFamilies(vulkanPhysicalDevice)):
-      var hasSurfaceSupport: VkBool32 = VK_FALSE
-      checkVkResult vkGetPhysicalDeviceSurfaceSupportKHR(vulkanPhysicalDevice, uint32(i), surface, addr(hasSurfaceSupport))
-      device.queueFamilies.add(QueueFamily(properties: queueFamilyProperty, hasSurfaceSupport: bool(hasSurfaceSupport)))
-      debug(&"  Queue family {i} {queueFamilyProperty}")
-
-    result.add(device)
-
-proc filterForDevice(devices: seq[PhysicalDevice]): seq[(PhysicalDevice, uint32, uint32)] =
-  for device in devices:
-    if not (device.formats.len > 0 and device.presentModes.len > 0 and "VK_KHR_swapchain" in device.extensions):
-      continue
-    var graphicsQueueFamily = high(uint32)
-    var presentationQueueFamily = high(uint32)
-    for i, queueFamily in enumerate(device.queueFamilies):
-      if queueFamily.hasSurfaceSupport:
-        presentationQueueFamily = uint32(i)
-      if bool(uint32(queueFamily.properties.queueFlags) and ord(VK_QUEUE_GRAPHICS_BIT)):
-        graphicsQueueFamily = uint32(i)
-    if graphicsQueueFamily != high(uint32) and presentationQueueFamily != high(uint32):
-      result.add((device, graphicsQueueFamily, presentationQueueFamily))
-
-  for (device, graphicsQueueFamily, presentationQueueFamily) in result:
-    debug(&"Viable device: {cleanString(device.properties.deviceName)} (graphics queue family {graphicsQueueFamily}, presentation queue family {presentationQueueFamily})")
-
-
-proc getFrameDimension(window: NativeWindow, device: VkPhysicalDevice,
-    surface: VkSurfaceKHR): TVec2[uint32] =
-  let capabilities = device.getSurfaceCapabilities(surface)
-  if capabilities.currentExtent.width != high(uint32):
-    return TVec2[uint32]([capabilities.currentExtent.width,
-        capabilities.currentExtent.height])
-  else:
-    let (width, height) = window.size()
-    return TVec2[uint32]([
-      min(max(uint32(width), capabilities.minImageExtent.width),
-          capabilities.maxImageExtent.width),
-      min(max(uint32(height), capabilities.minImageExtent.height),
-          capabilities.maxImageExtent.height),
-    ])
-
-when DEBUG_LOG:
-  proc setupDebugLog(instance: VkInstance): VkDebugUtilsMessengerEXT =
-    var createInfo = VkDebugUtilsMessengerCreateInfoEXT(
-      sType: VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
-      messageSeverity: VkDebugUtilsMessageSeverityFlagsEXT(
-        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) or
-        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) or
-        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
-      ),
-      messageType: VkDebugUtilsMessageTypeFlagsEXT(
-        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) or
-        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) or
-        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
-      ),
-      pfnUserCallback: debugCallback,
-      pUserData: nil,
-    )
-    checkVkResult instance.vkCreateDebugUtilsMessengerEXT(addr(createInfo), nil, addr(result))
-
-proc setupVulkanDeviceAndQueues(instance: VkInstance, surface: VkSurfaceKHR): Device =
-  let usableDevices = instance.getAllPhysicalDevices(surface).filterForDevice()
-  if len(usableDevices) == 0:
-    raise newException(Exception, "No suitable graphics device found")
-  result.physicalDevice = usableDevices[0][0]
-  result.graphicsQueueFamily = usableDevices[0][1]
-  result.presentationQueueFamily = usableDevices[0][2]
-
-  debug(&"Chose device {cleanString(result.physicalDevice.properties.deviceName)}")
-
-  (result.device, result.graphicsQueue, result.presentationQueue) = getVulcanDevice(
-    result.physicalDevice.device,
-    result.physicalDevice.features,
-    result.graphicsQueueFamily,
-    result.presentationQueueFamily,
-  )
-
-proc setupSwapChain(device: VkDevice, physicalDevice: PhysicalDevice, surface: VkSurfaceKHR, dimension: TVec2[uint32], surfaceFormat: VkSurfaceFormatKHR): Swapchain =
-
-  let capabilities = physicalDevice.device.getSurfaceCapabilities(surface)
-  var selectedPresentationMode = getPresentMode(physicalDevice.presentModes)
-  var imageCount = capabilities.minImageCount + 1
-  if capabilities.maxImageCount > 0:
-    imageCount = min(capabilities.maxImageCount, imageCount)
-  # TODO: something not working on window..., likely the extent
-  var extent = VkExtent2D(
-    width: if dimension[0] > 0: dimension[0] else: 1,
-    height: if dimension[1] > 0: dimension[1] else: 1,
-  )
-  # setup swapchain
-  var swapchainCreateInfo = VkSwapchainCreateInfoKHR(
-    sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
-    surface: surface,
-    minImageCount: imageCount,
-    imageFormat: surfaceFormat.format,
-    imageColorSpace: surfaceFormat.colorSpace,
-    imageExtent: extent,
-    imageArrayLayers: 1,
-    imageUsage: VkImageUsageFlags(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
-    # VK_SHARING_MODE_CONCURRENT no supported (i.e cannot use different queue families for drawing to swap surface?)
-    imageSharingMode: VK_SHARING_MODE_EXCLUSIVE,
-    preTransform: capabilities.currentTransform,
-    compositeAlpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
-    presentMode: selectedPresentationMode,
-    clipped: VK_TRUE,
-    oldSwapchain: VkSwapchainKHR(0),
-  )
-  checkVkResult device.vkCreateSwapchainKHR(addr(swapchainCreateInfo), nil,
-      addr(result.swapchain))
-  result.images = device.getSwapChainImages(result.swapchain)
-
-  # setup swapchian image views
-
-  result.imageviews = newSeq[VkImageView](result.images.len)
-  for i, image in enumerate(result.images):
-    var imageViewCreateInfo = VkImageViewCreateInfo(
-      sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
-      image: image,
-      viewType: VK_IMAGE_VIEW_TYPE_2D,
-      format: surfaceFormat.format,
-      components: VkComponentMapping(
-        r: VK_COMPONENT_SWIZZLE_IDENTITY,
-        g: VK_COMPONENT_SWIZZLE_IDENTITY,
-        b: VK_COMPONENT_SWIZZLE_IDENTITY,
-        a: VK_COMPONENT_SWIZZLE_IDENTITY,
-      ),
-      subresourceRange: VkImageSubresourceRange(
-        aspectMask: VkImageAspectFlags(VK_IMAGE_ASPECT_COLOR_BIT),
-        baseMipLevel: 0,
-        levelCount: 1,
-        baseArrayLayer: 0,
-        layerCount: 1,
-      ),
-    )
-    checkVkResult device.vkCreateImageView(addr(imageViewCreateInfo), nil, addr(result.imageviews[i]))
-
-proc setupRenderPass(device: VkDevice, format: VkFormat): VkRenderPass =
-  var
-    colorAttachment = VkAttachmentDescription(
-      format: format,
-      samples: VK_SAMPLE_COUNT_1_BIT,
-      loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR,
-      storeOp: VK_ATTACHMENT_STORE_OP_STORE,
-      stencilLoadOp: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
-      stencilStoreOp: VK_ATTACHMENT_STORE_OP_DONT_CARE,
-      initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
-      finalLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
-    )
-    colorAttachmentRef = VkAttachmentReference(
-      attachment: 0,
-      layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
-    )
-    subpass = VkSubpassDescription(
-      pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS,
-      colorAttachmentCount: 1,
-      pColorAttachments: addr(colorAttachmentRef)
-    )
-    dependency = VkSubpassDependency(
-      srcSubpass: VK_SUBPASS_EXTERNAL,
-      dstSubpass: 0,
-      srcStageMask: VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT),
-      srcAccessMask: VkAccessFlags(0),
-      dstStageMask: VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT),
-      dstAccessMask: VkAccessFlags(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
-    )
-    renderPassCreateInfo = VkRenderPassCreateInfo(
-      sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
-      attachmentCount: 1,
-      pAttachments: addr(colorAttachment),
-      subpassCount: 1,
-      pSubpasses: addr(subpass),
-      dependencyCount: 1,
-      pDependencies: addr(dependency),
-    )
-  checkVkResult device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result))
-
-proc initRenderPipeline[VertexType, Uniforms](device: VkDevice, frameSize: TVec2[uint32], renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, Uniforms] =
-  # load shaders
-  result.device = device
-  result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader))
-  result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader))
-
-  var
-    # define which parts can be dynamic (pipeline is fixed after setup)
-    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: addr(dynamicStates[0]),
-    )
-    vertexbindings = generateInputVertexBinding[VertexType]()
-    attributebindings = generateInputAttributeBinding[VertexType]()
-
-    # define input data format
-    vertexInputInfo = VkPipelineVertexInputStateCreateInfo(
-      sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
-      vertexBindingDescriptionCount: uint32(vertexbindings.len),
-      pVertexBindingDescriptions: addr(vertexbindings[0]),
-      vertexAttributeDescriptionCount: uint32(attributebindings.len),
-      pVertexAttributeDescriptions: addr(attributebindings[0]),
-    )
-    inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
-      sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
-      topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
-      primitiveRestartEnable: VK_FALSE,
-    )
-
-  # setup viewport
-  var viewportState = VkPipelineViewportStateCreateInfo(
-    sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
-    viewportCount: 1,
-    scissorCount: 1,
-  )
-
-  # rasterizerization config
-  var
-    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: VkCullModeFlags(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: VkColorComponentFlags(
-        ord(VK_COLOR_COMPONENT_R_BIT) or
-        ord(VK_COLOR_COMPONENT_G_BIT) or
-        ord(VK_COLOR_COMPONENT_B_BIT) or
-        ord(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: VK_TRUE,
-      logicOp: VK_LOGIC_OP_COPY,
-      attachmentCount: 1,
-      pAttachments: addr(colorBlendAttachment),
-      blendConstants: [0.0'f, 0.0'f, 0.0'f, 0.0'f],
-    )
-
-  result.descriptorSetLayout = device.createUniformDescriptorLayout(
-      VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), 0)
-  var
-    # "globals" that go into the shader, uniforms etc.
-    pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
-      sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
-      setLayoutCount: 1,
-      pSetLayouts: addr(result.descriptorSetLayout),
-      pushConstantRangeCount: 0,
-      pPushConstantRanges: nil,
-    )
-  checkVkResult vkCreatePipelineLayout(device, addr(pipelineLayoutInfo), nil, addr(result.layout))
-
-  var stages: seq[VkPipelineShaderStageCreateInfo]
-  for shader in result.shaders:
-    stages.add(shader.shader)
-  var pipelineInfo = VkGraphicsPipelineCreateInfo(
-    sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
-    stageCount: uint32(stages.len),
-    pStages: addr(stages[0]),
-    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(pipelineInfo),
-    nil,
-    addr(result.pipeline)
-  )
-
-proc setupFramebuffers(device: VkDevice, swapchain: var Swapchain,
-    renderPass: VkRenderPass, dimension: TVec2[uint32]): seq[VkFramebuffer] =
-  result = newSeq[VkFramebuffer](swapchain.images.len)
-  for i, imageview in enumerate(swapchain.imageviews):
-    var framebufferInfo = VkFramebufferCreateInfo(
-      sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
-      renderPass: renderPass,
-      attachmentCount: 1,
-      pAttachments: addr(swapchain.imageviews[i]),
-      width: dimension[0],
-      height: dimension[1],
-      layers: 1,
-    )
-    checkVkResult device.vkCreateFramebuffer(addr(framebufferInfo), nil, addr(
-        result[i]))
-
-proc trash(device: VkDevice, swapchain: Swapchain, framebuffers: seq[
-    VkFramebuffer]) =
-  for framebuffer in framebuffers:
-    device.vkDestroyFramebuffer(framebuffer, nil)
-  for imageview in swapchain.imageviews:
-    device.vkDestroyImageView(imageview, nil)
-  device.vkDestroySwapchainKHR(swapchain.swapchain, nil)
-
-proc recreateSwapchain(vulkan: Vulkan): (Swapchain, seq[VkFramebuffer]) =
-  if vulkan.frameSize.x == 0 or vulkan.frameSize.y == 0:
-    return (vulkan.swapchain, vulkan.framebuffers)
-  debug(&"Recreate swapchain with dimension {vulkan.frameSize}")
-  checkVkResult vulkan.device.device.vkDeviceWaitIdle()
-
-  vulkan.device.device.trash(vulkan.swapchain, vulkan.framebuffers)
-
-  result[0] = vulkan.device.device.setupSwapChain(
-    vulkan.device.physicalDevice,
-    vulkan.surface,
-    vulkan.frameSize,
-    vulkan.surfaceFormat
-  )
-  result[1] = vulkan.device.device.setupFramebuffers(
-    result[0],
-    vulkan.renderPass,
-    vulkan.frameSize
-  )
-
-
-proc setupCommandBuffers(device: VkDevice, graphicsQueueFamily: uint32): (
-    VkCommandPool, array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]) =
-  # set up command buffer
-  var poolInfo = VkCommandPoolCreateInfo(
-    sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
-    flags: VkCommandPoolCreateFlags(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT),
-    queueFamilyIndex: graphicsQueueFamily,
-  )
-  checkVkResult device.vkCreateCommandPool(addr(poolInfo), nil, addr(result[0]))
-
-  var allocInfo = VkCommandBufferAllocateInfo(
-    sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
-    commandPool: result[0],
-    level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
-    commandBufferCount: result[1].len.uint32,
-  )
-  checkVkResult device.vkAllocateCommandBuffers(addr(allocInfo), addr(result[1][0]))
-
-proc setupSyncPrimitives(device: VkDevice): (
-    array[MAX_FRAMES_IN_FLIGHT, VkSemaphore],
-    array[MAX_FRAMES_IN_FLIGHT, VkSemaphore],
-    array[MAX_FRAMES_IN_FLIGHT, VkFence],
-) =
-  var semaphoreInfo = VkSemaphoreCreateInfo(
-      sType: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO)
-  var fenceInfo = VkFenceCreateInfo(
-    sType: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
-    flags: VkFenceCreateFlags(VK_FENCE_CREATE_SIGNALED_BIT)
-  )
-  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
-    checkVkResult device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(
-        result[0][i]))
-    checkVkResult device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(
-        result[1][i]))
-    checkVkResult device.vkCreateFence(addr(fenceInfo), nil, addr(result[2][i]))
-
-proc igniteEngine*(windowTitle: string): Engine =
-
-  result.window = createWindow(windowTitle)
-  let mousepos = result.window.getMousePosition()
-  if mousepos.isSome():
-    result.input.mousePos = mousePos.get()
-
-
-  # create vulkan instance
-  result.vulkan.instance = createVulkanInstance(VULKAN_VERSION)
-
-  # setup vulkan functions
-  loadVulkan(result.vulkan.instance)
-
-  when DEBUG_LOG:
-    result.vulkan.debugMessenger = result.vulkan.instance.setupDebugLog()
-  result.vulkan.surface = result.vulkan.instance.createVulkanSurface(result.window)
-  result.vulkan.device = result.vulkan.instance.setupVulkanDeviceAndQueues(result.vulkan.surface)
-
-  # get basic frame information
-  result.vulkan.surfaceFormat = result.vulkan.device.physicalDevice.formats.getSuitableSurfaceFormat()
-  result.vulkan.frameSize = result.window.getFrameDimension(result.vulkan.device.physicalDevice.device, result.vulkan.surface)
-
-  # setup swapchain and render pipeline
-  result.vulkan.swapchain = result.vulkan.device.device.setupSwapChain(
-    result.vulkan.device.physicalDevice,
-    result.vulkan.surface,
-    result.vulkan.frameSize,
-    result.vulkan.surfaceFormat
-  )
-  result.vulkan.renderPass = result.vulkan.device.device.setupRenderPass(
-      result.vulkan.surfaceFormat.format)
-  result.vulkan.framebuffers = result.vulkan.device.device.setupFramebuffers(
-    result.vulkan.swapchain,
-    result.vulkan.renderPass,
-    result.vulkan.frameSize
-  )
-  (
-    result.vulkan.device.commandPool,
-    result.vulkan.device.commandBuffers,
-  ) = result.vulkan.device.device.setupCommandBuffers(
-      result.vulkan.device.graphicsQueueFamily)
-
-  (
-    result.vulkan.imageAvailableSemaphores,
-    result.vulkan.renderFinishedSemaphores,
-    result.vulkan.inFlightFences,
-  ) = result.vulkan.device.device.setupSyncPrimitives()
-
-
-proc setupPipeline*[VertexType; UniformType; IndexType: uint16|uint32](engine: var Engine, scenedata: Thing, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, UniformType] =
-  engine.currentscenedata = scenedata
-  result = initRenderPipeline[VertexType, UniformType](
-    engine.vulkan.device.device,
-    engine.vulkan.frameSize,
-    engine.vulkan.renderPass,
-    vertexShader,
-    fragmentShader,
-  )
-
-  for mesh in allPartsOfType[Mesh[VertexType, IndexType]](
-      engine.currentscenedata):
-    result.vertexBuffers.add createIndexedVertexBuffers(mesh,
-        result.device, engine.vulkan.device.physicalDevice.device,
-        engine.vulkan.device.commandPool, engine.vulkan.device.graphicsQueue)
-
-  # uniform buffers
-  when not (UniformType is void):
-    result.uniformBuffers = createUniformBuffers[MAX_FRAMES_IN_FLIGHT, UniformType](
-      result.device,
-      engine.vulkan.device.physicalDevice.device
-    )
-
-  var
-    poolSize = VkDescriptorPoolSize(
-      thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
-      descriptorCount: uint32(MAX_FRAMES_IN_FLIGHT),
-    )
-    poolInfo = VkDescriptorPoolCreateInfo(
-      sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
-      poolSizeCount: 1,
-      pPoolSizes: addr(poolSize),
-      maxSets: uint32(MAX_FRAMES_IN_FLIGHT),
-    )
-  checkVkResult vkCreateDescriptorPool(result.device, addr(poolInfo), nil, addr(result.descriptorPool))
-
-  var layouts: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSetLayout]
-  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
-    layouts[i] = result.descriptorSetLayout
-  var allocInfo = VkDescriptorSetAllocateInfo(
-    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
-    descriptorPool: result.descriptorPool,
-    descriptorSetCount: uint32(MAX_FRAMES_IN_FLIGHT),
-    pSetLayouts: addr(layouts[0]),
-  )
-
-  checkVkResult vkAllocateDescriptorSets(result.device, addr(allocInfo), addr(result.descriptors[0]))
-
-  when not (UniformType is void):
-    var bufferInfos: array[MAX_FRAMES_IN_FLIGHT, array[1, VkDescriptorBufferInfo]] # because we use only one Uniform atm
-    for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
-      bufferInfos[i][0] = VkDescriptorBufferInfo(
-        buffer: result.uniformBuffers[i].vkBuffer,
-        offset: VkDeviceSize(0),
-        range: VkDeviceSize(sizeof(UniformType)),
-      )
-      var descriptorWrite = VkWriteDescriptorSet(
-          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
-          dstSet: result.descriptors[i],
-          dstBinding: 0,
-          dstArrayElement: 0,
-          descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
-          descriptorCount: 1,
-          pBufferInfo: addr(bufferInfos[i][0]),
-        )
-      vkUpdateDescriptorSets(result.device, 1, addr(descriptorWrite), 0, nil)
-
-proc updateBufferData*[T](device: Device, buffer: Buffer, data: var T) =
-  when stripGenericParams(T) is seq: # seq needs special treatment for automated data uploading
-    assert data.len > 0
-    let size = data.len * sizeof(get(genericParams(typeof(data)), 0))
-    let dataptr = addr(data[0])
-  else:
-    let size = sizeof(data)
-    let dataptr = addr(data)
-  if not (HostVisible in buffer.memoryProperties):
-    if not (TransferDst in buffer.bufferTypes):
-      raise newException(Exception, "Buffer cannot be updated")
-    var stagingBuffer = device.device.InitBuffer(device.physicalDevice.device,
-        uint64(size), {TransferSrc}, {HostVisible, HostCoherent})
-    copyMem(stagingBuffer.data, dataptr, size)
-    transferBuffer(device.commandPool, device.graphicsQueue, stagingBuffer,
-        buffer, uint64(size))
-    stagingBuffer.trash()
-  else:
-    copyMem(buffer.data, dataptr, size)
-
-proc updateVertexData*[T: VertexAttribute](device: Device,
-    vertexAttribute: var T) =
-  device.updateBufferData(vertexAttribute.buffer, vertexAttribute.data)
-
-proc updateUniformData*[VertexType, Uniforms](device: Device,
-    pipeline: RenderPipeline[VertexType, Uniforms], data: var Uniforms) =
-  for buffer in pipeline.uniformBuffers:
-    device.updateBufferData(buffer, data)
-
-
-proc runPipeline[VertexType; Uniforms](commandBuffer: VkCommandBuffer, pipeline: var RenderPipeline[VertexType, Uniforms], currentFrame: int) =
-  vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline)
-  vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(pipeline.descriptors[currentFrame]), 0, nil)
-
-  for (vertexBufferSet, indexed, indexBuffer, count, indexType) in pipeline.vertexBuffers:
-    var
-      vertexBuffers: seq[VkBuffer]
-      offsets: seq[VkDeviceSize]
-    for buffer in vertexBufferSet:
-      vertexBuffers.add buffer.vkBuffer
-      offsets.add VkDeviceSize(0)
-
-    vkCmdBindVertexBuffers(commandBuffer, firstBinding = 0'u32, bindingCount = uint32(vertexBuffers.len), pBuffers = addr(vertexBuffers[ 0]), pOffsets = addr(offsets[0]))
-    if indexed:
-      vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType)
-      vkCmdDrawIndexed(commandBuffer, count, 1, 0, 0, 0)
-    else:
-      vkCmdDraw(commandBuffer, vertexCount = count, instanceCount = 1, firstVertex = 0, firstInstance = 0)
-
-proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline,
-    commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer,
-    frameSize: TVec2[uint32], currentFrame: int) =
-  var
-    beginInfo = VkCommandBufferBeginInfo(
-      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
-      pInheritanceInfo: nil,
-    )
-    clearColor = VkClearValue(color: VkClearColorValue(float32: pipeline.clearColor))
-    renderPassInfo = VkRenderPassBeginInfo(
-      sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
-      renderPass: renderPass,
-      framebuffer: framebuffer,
-      renderArea: VkRect2D(
-        offset: VkOffset2D(x: 0, y: 0),
-        extent: VkExtent2D(width: frameSize.x, height: frameSize.y),
-      ),
-      clearValueCount: 1,
-      pClearValues: addr(clearColor),
-    )
-    viewport = VkViewport(
-      x: 0.0,
-      y: 0.0,
-      width: (float)frameSize.x,
-      height: (float)frameSize.y,
-      minDepth: 0.0,
-      maxDepth: 1.0,
-    )
-    scissor = VkRect2D(
-      offset: VkOffset2D(x: 0, y: 0),
-      extent: VkExtent2D(width: frameSize.x, height: frameSize.y)
-    )
-  checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo))
-  block:
-    vkCmdBeginRenderPass(commandBuffer, addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE)
-    vkCmdSetViewport(commandBuffer, firstViewport = 0, viewportCount = 1, addr(viewport))
-    vkCmdSetScissor(commandBuffer, firstScissor = 0, scissorCount = 1, addr(scissor))
-    runPipeline(commandBuffer, pipeline, currentFrame)
-    vkCmdEndRenderPass(commandBuffer)
-  checkVkResult vkEndCommandBuffer(commandBuffer)
-
-proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: var RenderPipeline) =
-
-  checkVkResult vkWaitForFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64))
-
-  var bufferImageIndex: uint32
-  let nextImageResult = vkAcquireNextImageKHR(
-    vulkan.device.device,
-    vulkan.swapchain.swapchain,
-    high(uint64),
-    vulkan.imageAvailableSemaphores[currentFrame],
-    VkFence(0),
-    addr(bufferImageIndex)
-  )
-  if nextImageResult == VK_ERROR_OUT_OF_DATE_KHR:
-    vulkan.frameSize = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
-    (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
-  elif not (nextImageResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]):
-    raise newException(Exception, "Vulkan error: vkAcquireNextImageKHR returned " & $nextImageResult)
-  checkVkResult vkResetFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]))
-
-  checkVkResult vkResetCommandBuffer(vulkan.device.commandBuffers[currentFrame], VkCommandBufferResetFlags(0))
-  vulkan.renderPass.recordCommandBuffer(
-    pipeline,
-    vulkan.device.commandBuffers[currentFrame],
-    vulkan.framebuffers[bufferImageIndex], vulkan.frameSize,
-    currentFrame
-  )
-  var
-    waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]]
-    waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
-    signalSemaphores = [vulkan.renderFinishedSemaphores[currentFrame]]
-    submitInfo = VkSubmitInfo(
-      sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
-      waitSemaphoreCount: 1,
-      pWaitSemaphores: addr(waitSemaphores[0]),
-      pWaitDstStageMask: addr(waitStages[0]),
-      commandBufferCount: 1,
-      pCommandBuffers: addr(vulkan.device.commandBuffers[currentFrame]),
-      signalSemaphoreCount: 1,
-      pSignalSemaphores: addr(signalSemaphores[0]),
-    )
-  checkVkResult vkQueueSubmit(vulkan.device.graphicsQueue, 1, addr(submitInfo), vulkan.inFlightFences[currentFrame])
-
-  var presentInfo = VkPresentInfoKHR(
-    sType: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
-    waitSemaphoreCount: 1,
-    pWaitSemaphores: addr(signalSemaphores[0]),
-    swapchainCount: 1,
-    pSwapchains: addr(vulkan.swapchain.swapchain),
-    pImageIndices: addr(bufferImageIndex),
-    pResults: nil,
-  )
-  let presentResult = vkQueuePresentKHR(vulkan.device.presentationQueue, addr(presentInfo))
-  if presentResult == VK_ERROR_OUT_OF_DATE_KHR or presentResult == VK_SUBOPTIMAL_KHR or resized:
-    vulkan.frameSize = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
-    (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
-
-
-func frametime(engine: Engine): auto =
-  if engine.maxFPS == 0: 0'f
-  else: 1'f / float32(engine.maxFPS)
-
-proc run*(engine: var Engine, pipeline: var RenderPipeline, globalUpdate: proc(
-    engine: var Engine, t, dt: float32)) =
-  var
-    currentFrame = 0
-    resized = false
-    lastUpdate = cpuTime()
-    lastframe = 0'f
-
-  while true:
-    # process input
-    engine.input.keysPressed = {}
-    engine.input.keysReleased = {}
-    engine.input.mousePressed = {}
-    engine.input.mouseReleased = {}
-    var killed = false
-    for event in engine.window.pendingEvents():
-      case event.eventType:
-        of Quit:
-          killed = true
-        of ResizedWindow:
-          resized = true
-        of KeyPressed:
-          engine.input.keysPressed.incl event.key
-          engine.input.keysDown.incl event.key
-        of KeyReleased:
-          engine.input.keysReleased.incl event.key
-          engine.input.keysDown.excl event.key
-        of MousePressed:
-          engine.input.mousePressed.incl event.button
-          engine.input.mouseDown.incl event.button
-        of MouseReleased:
-          engine.input.mouseReleased.incl event.button
-          engine.input.mouseDown.excl event.button
-        of MouseMoved:
-          engine.input.mousePos = Vec2([float32(event.x), float32(event.y)])
-    if killed: # at least on windows we should return immediately as swapchain recreation will fail after kill
-      break
-
-    # game logic update
-    let
-      now = cpuTime()
-      dt = now - lastUpdate
-    lastUpdate = now
-    engine.globalUpdate(now, dt)
-    for thing in allThings(engine.currentscenedata):
-      for part in thing.parts:
-        update(part, engine, now, dt)
-      update(thing, engine, now, dt)
-
-    # submit frame for drawing
-    if engine.maxFPS == 0 or (now - lastframe >= engine.frametime): # framerate limit
-      engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline)
-      lastframe = now
-      currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT
-    resized = false
-
-  checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device)
-
-proc trash*(pipeline: var RenderPipeline) =
-  vkDestroyDescriptorPool(pipeline.device, pipeline.descriptorPool, nil)
-  vkDestroyDescriptorSetLayout(pipeline.device, pipeline.descriptorSetLayout, nil)
-  vkDestroyPipeline(pipeline.device, pipeline.pipeline, nil)
-  vkDestroyPipelineLayout(pipeline.device, pipeline.layout, nil)
-  for shader in pipeline.shaders:
-    vkDestroyShaderModule(pipeline.device, shader.shader.module, nil)
-
-  for (bufferset, indexed, indexbuffer, cnt, t) in
-    pipeline.vertexBuffers.mitems:
-    if indexed:
-      indexbuffer.trash()
-    for buffer in bufferset.mitems:
-      buffer.trash()
-  for buffer in pipeline.uniformBuffers.mitems:
-    buffer.trash()
-
-proc trash*(engine: var Engine) =
-  checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device)
-  engine.vulkan.device.device.trash(engine.vulkan.swapchain,
-      engine.vulkan.framebuffers)
-
-  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
-    engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.imageAvailableSemaphores[i], nil)
-    engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.renderFinishedSemaphores[i], nil)
-    engine.vulkan.device.device.vkDestroyFence(engine.vulkan.inFlightFences[i], nil)
-
-  engine.vulkan.device.device.vkDestroyRenderPass(engine.vulkan.renderPass, nil)
-  engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.device.commandPool, nil)
-
-  engine.vulkan.instance.vkDestroySurfaceKHR(engine.vulkan.surface, nil)
-  engine.vulkan.device.device.vkDestroyDevice(nil)
-  when DEBUG_LOG:
-    engine.vulkan.instance.vkDestroyDebugUtilsMessengerEXT(engine.vulkan.debugMessenger, nil)
-  engine.window.trash()
-  # needs to happen after window is trashed as the driver might have a hook registered for the window destruction
-  engine.vulkan.instance.vkDestroyInstance(nil)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/entity.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,131 @@
+import std/strformat
+import std/typetraits
+
+import ./math/matrix
+
+type
+  Component* = ref object of RootObj
+    thing*: Entity
+
+  Entity* = ref object of RootObj
+    name*: string
+    transform*: Mat44 # todo: cache transform + only update VBO when transform changed
+    parent*: Entity
+    children*: seq[Entity]
+    parts*: seq[Component]
+
+
+func `$`*(thing: Entity): string = thing.name
+method `$`*(part: Component): string {.base.} =
+  if part.thing != nil:
+    &"{part.thing} -> Component"
+  else:
+    &"Standalone Component"
+
+proc add*(thing: Entity, child: Entity) =
+  child.parent = thing
+  thing.children.add child
+proc add*(thing: Entity, part: Component) =
+  part.thing = thing
+  thing.parts.add part
+proc add*(thing: Entity, children: seq[Entity]) =
+  for child in children:
+    child.parent = thing
+    thing.children.add child
+proc add*(thing: Entity, parts: seq[Component]) =
+  for part in parts:
+    part.thing = thing
+    thing.parts.add part
+
+func newThing*(name: string = ""): Entity =
+  result = new Entity
+  result.name = name
+  result.transform = Unit44
+  if result.name == "":
+    result.name = &"Entity[{$(cast[ByteAddress](result))}]"
+
+func newThing*(name: string, firstChild: Entity, children: varargs[
+    Entity]): Entity =
+  result = new Entity
+  result.add firstChild
+  for child in children:
+    result.add child
+  result.name = name
+  result.transform = Unit44
+  if result.name == "":
+    result.name = &"Entity[{$(cast[ByteAddress](result))}]"
+
+proc newThing*(name: string, firstPart: Component, parts: varargs[Component]): Entity =
+  result = new Entity
+  result.name = name
+  result.add firstPart
+  for part in parts:
+    result.add part
+  if result.name == "":
+    result.name = &"Entity[{$(cast[ByteAddress](result))}]"
+  result.transform = Unit44
+
+func getModelTransform*(thing: Entity): Mat44 =
+  result = Unit44
+  var currentThing = thing
+  while currentThing != nil:
+    result = currentThing.transform * result
+    currentThing = currentThing.parent
+
+iterator allPartsOfType*[T: Component](root: Entity): T =
+  var queue = @[root]
+  while queue.len > 0:
+    let thing = queue.pop
+    for part in thing.parts:
+      if part of T:
+        yield T(part)
+    for i in countdown(thing.children.len - 1, 0):
+      queue.add thing.children[i]
+
+func firstWithName*(root: Entity, name: string): Entity =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        return child
+      queue.add child
+
+func firstPartWithName*[T: Component](root: Entity, name: string): T =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        for part in child.parts:
+          if part of T:
+            return T(part)
+      queue.add child
+
+func allWithName*(root: Entity, name: string): seq[Entity] =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        result.add child
+      queue.add child
+
+func allPartsWithName*[T: Component](root: Entity, name: string): seq[T] =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        for part in child.parts:
+          if part of T:
+            result.add T(part)
+      queue.add child
+
+iterator allThings*(root: Entity): Entity =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      queue.add child
+    yield next
--- a/src/semicongine/glsl_helpers.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-import ./math/vector
-import ./math/matrix
-
-func getGLSLType*[T](): 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..
-  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"
--- a/src/semicongine/image.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-import ./buffer
-import ./math/vector
-import ./vulkan
-import ./vulkan_helpers
-
-type
-  ImageUsage* = enum
-    TransferDst = VK_IMAGE_USAGE_TRANSFER_DST_BIT
-    SampledBit = VK_IMAGE_USAGE_SAMPLED_BIT
-  ImageUsages = set[ImageUsage]
-  Image = object
-    buffer: Buffer
-    image: VkImage
-    memory: VkDeviceMemory
-
-proc InitImage(data: var seq[byte], size: TVec2[uint32], format: VkFormat,
-    tiling: VkImageTiling, usage: ImageUsages, properties: MemoryProperties,
-        device: VkDevice,
-    physicalDevice: VkPhysicalDevice): Image =
-  result.buffer = InitBuffer(device, physicalDevice, uint64(data.len), {
-      TransferSrc}, {HostVisible, HostCoherent})
-  copyMem(result.buffer.data, addr(data[0]), data.len)
-
-  var imageInfo = VkImageCreateInfo(
-    sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
-    imageType: VK_IMAGE_TYPE_2D,
-    extent: VkExtent3D(width: size.x, height: size.y, depth: 1),
-    mipLevels: 1,
-    arrayLayers: 1,
-    format: format,
-    tiling: tiling,
-    initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
-    usage: cast[VkImageUsageFlags](usage),
-    sharingMode: VK_SHARING_MODE_EXCLUSIVE,
-    samples: VK_SAMPLE_COUNT_1_BIT,
-  )
-  checkVkResult vkCreateImage(device, addr(imageInfo), nil, addr(result.image))
-
-  var memRequirements: VkMemoryRequirements
-  vkGetImageMemoryRequirements(device, result.image, addr(memRequirements))
-
-  var allocInfo = VkMemoryAllocateInfo(
-    sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
-    allocationSize: memRequirements.size,
-    memoryTypeIndex: memRequirements.findMemoryType(physicalDevice, properties)
-  )
-
-  checkVkResult vkAllocateMemory(device, addr(allocInfo), nil, addr(result.memory))
-  checkVkResult vkBindImageMemory(device, result.image, result.memory,
-      VkDeviceSize(0))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/buffer.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,125 @@
+import std/typetraits
+
+import ./vulkan
+import ./vulkan_helpers
+
+type
+  BufferType* = enum
+    None = 0
+    TransferSrc = VK_BUFFER_USAGE_TRANSFER_SRC_BIT
+    TransferDst = VK_BUFFER_USAGE_TRANSFER_DST_BIT
+    UniformBuffer = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
+    IndexBuffer = VK_BUFFER_USAGE_INDEX_BUFFER_BIT
+    VertexBuffer = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
+  MemoryProperty* = enum
+    DeviceLocal = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
+    HostVisible = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
+    HostCoherent = VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+  MemoryProperties* = set[MemoryProperty]
+  Buffer* = object
+    device*: VkDevice
+    vkBuffer*: VkBuffer
+    size*: uint64
+    memoryRequirements*: VkMemoryRequirements
+    memoryProperties*: MemoryProperties
+    memory*: VkDeviceMemory
+    bufferTypes*: set[BufferType]
+    data*: pointer
+
+proc trash*(buffer: var Buffer) =
+  if int64(buffer.vkBuffer) != 0 and int64(buffer.memory) != 0:
+    vkUnmapMemory(buffer.device, buffer.memory)
+  if int64(buffer.vkBuffer) != 0:
+    vkDestroyBuffer(buffer.device, buffer.vkBuffer, nil)
+    buffer.vkBuffer = VkBuffer(0)
+  if int64(buffer.memory) != 0:
+    vkFreeMemory(buffer.device, buffer.memory, nil)
+    buffer.memory = VkDeviceMemory(0)
+
+proc findMemoryType*(memoryRequirements: VkMemoryRequirements,
+    physicalDevice: VkPhysicalDevice, properties: MemoryProperties): uint32 =
+  var physicalProperties: VkPhysicalDeviceMemoryProperties
+  vkGetPhysicalDeviceMemoryProperties(physicalDevice, addr(physicalProperties))
+
+  for i in 0'u32 ..< physicalProperties.memoryTypeCount:
+    if bool(memoryRequirements.memoryTypeBits and (1'u32 shl i)):
+      if (uint32(physicalProperties.memoryTypes[i].propertyFlags) and cast[
+          uint32](properties)) == cast[uint32](properties):
+        return i
+
+proc InitBuffer*(
+  device: VkDevice,
+  physicalDevice: VkPhysicalDevice,
+  size: uint64,
+  bufferTypes: set[BufferType],
+  properties: MemoryProperties,
+): Buffer =
+  result = Buffer(device: device, size: size, bufferTypes: bufferTypes,
+      memoryProperties: properties)
+  var usageFlags = 0
+  for usage in bufferTypes:
+    usageFlags = ord(usageFlags) or ord(usage)
+  var bufferInfo = VkBufferCreateInfo(
+    sType: VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
+    size: VkDeviceSize(result.size),
+    usage: VkBufferUsageFlags(usageFlags),
+    sharingMode: VK_SHARING_MODE_EXCLUSIVE,
+  )
+  checkVkResult vkCreateBuffer(result.device, addr(bufferInfo), nil, addr(
+      result.vkBuffer))
+  vkGetBufferMemoryRequirements(result.device, result.vkBuffer, addr(
+      result.memoryRequirements))
+
+  var allocInfo = VkMemoryAllocateInfo(
+    sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+    allocationSize: result.memoryRequirements.size,
+    memoryTypeIndex: result.memoryRequirements.findMemoryType(physicalDevice, properties)
+  )
+  if result.size > 0:
+    checkVkResult result.device.vkAllocateMemory(addr(allocInfo), nil, addr(result.memory))
+  checkVkResult result.device.vkBindBufferMemory(result.vkBuffer, result.memory,
+      VkDeviceSize(0))
+  checkVkResult vkMapMemory(
+    result.device,
+    result.memory,
+    offset = VkDeviceSize(0),
+    VkDeviceSize(result.size),
+    VkMemoryMapFlags(0),
+    addr(result.data)
+  )
+
+
+proc transferBuffer*(commandPool: VkCommandPool, queue: VkQueue, src,
+    dst: Buffer, size: uint64) =
+  assert uint64(src.device) == uint64(dst.device)
+  assert TransferSrc in src.bufferTypes
+  assert TransferDst in dst.bufferTypes
+  var
+    allocInfo = VkCommandBufferAllocateInfo(
+      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+      level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+      commandPool: commandPool,
+      commandBufferCount: 1,
+    )
+    commandBuffer: VkCommandBuffer
+  checkVkResult vkAllocateCommandBuffers(src.device, addr(allocInfo), addr(commandBuffer))
+
+  var beginInfo = VkCommandBufferBeginInfo(
+    sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+    flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT),
+  )
+  checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo))
+  var copyRegion = VkBufferCopy(size: VkDeviceSize(size))
+  vkCmdCopyBuffer(commandBuffer, src.vkBuffer, dst.vkBuffer, 1, addr(copyRegion))
+  checkVkResult vkEndCommandBuffer(commandBuffer)
+
+  var submitInfo = VkSubmitInfo(
+    sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
+    commandBufferCount: 1,
+    pCommandBuffers: addr(commandBuffer),
+  )
+
+  checkVkResult vkQueueSubmit(queue, 1, addr(submitInfo), VkFence(0))
+  checkVkResult vkQueueWaitIdle(queue)
+  vkFreeCommandBuffers(src.device, commandPool, 1, addr(commandBuffer))
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/descriptor.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,67 @@
+import std/strutils
+import std/unicode
+import std/strformat
+import std/typetraits
+
+import ./vulkan
+import ./vulkan_helpers
+import ./math/vector
+import ./math/matrix
+import ./buffer
+import ./glsl_helpers
+
+# TODO: check for alignment in uniform blocks
+#
+type
+  DescriptorType = SomeNumber|TVec|TMat
+  Descriptor*[T: DescriptorType] = object
+    value*: T
+  ViewProjectionTransform* = Descriptor[Mat44]
+
+proc createUniformDescriptorLayout*(device: VkDevice,
+    shaderStage: VkShaderStageFlags, binding: uint32): VkDescriptorSetLayout =
+  var
+    layoutbinding = VkDescriptorSetLayoutBinding(
+      binding: binding,
+      descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+      descriptorCount: 1,
+      stageFlags: shaderStage,
+      pImmutableSamplers: nil,
+    )
+    layoutInfo = VkDescriptorSetLayoutCreateInfo(
+      sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+      bindingCount: 1,
+      pBindings: addr(layoutbinding)
+    )
+  checkVkResult device.vkCreateDescriptorSetLayout(addr(layoutInfo), nil, addr(result))
+
+proc createUniformBuffers*[nBuffers: static int, Uniforms](device: VkDevice,
+    physicalDevice: VkPhysicalDevice): array[nBuffers, Buffer] =
+  let size = sizeof(Uniforms)
+  for i in 0 ..< nBuffers:
+    var buffer = InitBuffer(
+      device,
+      physicalDevice,
+      uint64(size),
+      {UniformBuffer},
+      {HostVisible, HostCoherent},
+    )
+    result[i] = buffer
+
+template getDescriptorType*(v: Descriptor): auto = get(genericParams(typeof(v)), 0)
+
+func generateGLSLUniformDeclarations*[Uniforms](binding: int = 0): string {.compileTime.} =
+  var stmtList: seq[string]
+
+  when not (Uniforms is void):
+    let uniformTypeName = name(Uniforms).toUpper()
+    let uniformInstanceName = name(Uniforms).toLower()
+    stmtList.add(&"layout(binding = {binding}) uniform {uniformTypeName} {{")
+    for fieldname, value in Uniforms().fieldPairs:
+      when typeof(value) is Descriptor:
+        let glsltype = getGLSLType[getDescriptorType(value)]()
+        let n = fieldname
+        stmtList.add(&"    {glsltype} {n};")
+    stmtList.add(&"}} {uniformInstanceName};")
+
+  return stmtList.join("\n")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/engine.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,870 @@
+import std/options
+import std/os
+import std/times
+import std/typetraits
+import std/strformat
+import std/enumerate
+import std/logging
+
+
+import ./math/vector
+import ./vulkan
+import ./vulkan_helpers
+import ./platform/window
+import ./events
+import ./shader
+import ./vertex
+import ./buffer
+import ./thing
+import ./descriptor
+import ./mesh
+
+const MAX_FRAMES_IN_FLIGHT = 2
+const DEBUG_LOG = not defined(release)
+
+var logger = newConsoleLogger()
+addHandler(logger)
+
+
+const VULKAN_VERSION = VK_MAKE_API_VERSION(0'u32, 1'u32, 2'u32, 0'u32)
+const ENGINE_NAME = "zamkongine"
+const ENGINE_VERSION = "0.1"
+const BUILD_VERSION = ENGINE_VERSION & '-' & gorge("git log -1 --format=format:'%H'")
+echo "Engine: " & ENGINE_NAME & " " & BUILD_VERSION
+
+type
+  Device = object
+    device*: VkDevice
+    physicalDevice*: PhysicalDevice
+    graphicsQueueFamily*: uint32
+    presentationQueueFamily*: uint32
+    graphicsQueue*: VkQueue
+    presentationQueue*: VkQueue
+    commandPool*: VkCommandPool
+    commandBuffers*: array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]
+  Swapchain = object
+    swapchain: VkSwapchainKHR
+    images: seq[VkImage]
+    imageviews: seq[VkImageView]
+  RenderPipeline*[VertexType, Uniforms] = object
+    device*: VkDevice
+    shaders*: seq[ShaderProgram[VertexType, Uniforms]]
+    layout*: VkPipelineLayout
+    pipeline*: VkPipeline
+    vertexBuffers*: seq[(seq[Buffer], bool, Buffer, uint32, VkIndexType)]
+    descriptorSetLayout*: VkDescriptorSetLayout
+    uniformBuffers*: array[MAX_FRAMES_IN_FLIGHT, Buffer]
+    descriptorPool*: VkDescriptorPool
+    descriptors: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSet]
+    clearColor*: Vec4
+  QueueFamily = object
+    properties*: VkQueueFamilyProperties
+    hasSurfaceSupport*: bool
+  PhysicalDevice = object
+    device*: VkPhysicalDevice
+    extensions*: seq[string]
+    properties*: VkPhysicalDeviceProperties
+    features*: VkPhysicalDeviceFeatures
+    queueFamilies*: seq[QueueFamily]
+    formats: seq[VkSurfaceFormatKHR]
+    presentModes: seq[VkPresentModeKHR]
+  Vulkan* = object
+    debugMessenger*: VkDebugUtilsMessengerEXT
+    instance*: VkInstance
+    deviceList*: seq[PhysicalDevice]
+    device*: Device
+    surface*: VkSurfaceKHR
+    surfaceFormat*: VkSurfaceFormatKHR
+    frameSize*: TVec2[uint32]
+    swapchain*: Swapchain
+    framebuffers*: seq[VkFramebuffer]
+    renderPass*: VkRenderPass
+    imageAvailableSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
+    renderFinishedSemaphores*: array[MAX_FRAMES_IN_FLIGHT, VkSemaphore]
+    inFlightFences*: array[MAX_FRAMES_IN_FLIGHT, VkFence]
+  Input* = object
+    keysDown*: set[Key]
+    keysPressed*: set[Key]
+    keysReleased*: set[Key]
+    mouseDown*: set[MouseButton]
+    mousePressed*: set[MouseButton]
+    mouseReleased*: set[MouseButton]
+    mousePos*: Vec2
+  Engine* = object
+    vulkan*: Vulkan
+    window*: NativeWindow
+    currentscenedata*: Thing
+    input*: Input
+    maxFPS*: uint
+
+
+method update*(thing: Thing, engine: Engine, t, dt: float32) {.base.} = discard
+method update*(part: Part, engine: Engine, t, dt: float32) {.base.} = discard
+
+method update*[T, U](mesh: Mesh[T, U], engine: Engine, t, dt: float32) =
+  let transform = @[mesh.thing.getModelTransform().transposed()]
+  for name, value in mesh.vertexData.fieldPairs:
+    when value is ModelTransformAttribute:
+      value.data = transform
+      engine.vulkan.device.updateVertexData(value)
+
+proc getAllPhysicalDevices(instance: VkInstance, surface: VkSurfaceKHR): seq[
+    PhysicalDevice] =
+  for vulkanPhysicalDevice in getVulkanPhysicalDevices(instance):
+    var device = PhysicalDevice(device: vulkanPhysicalDevice, extensions: vulkan.getDeviceExtensions(vulkanPhysicalDevice))
+    vkGetPhysicalDeviceProperties(vulkanPhysicalDevice, addr(device.properties))
+    vkGetPhysicalDeviceFeatures(vulkanPhysicalDevice, addr(device.features))
+    device.formats = vulkanPhysicalDevice.getDeviceSurfaceFormats(surface)
+    device.presentModes = vulkanPhysicalDevice.getDeviceSurfacePresentModes(surface)
+
+    debug(&"Physical device nr {int(vulkanPhysicalDevice)} {cleanString(device.properties.deviceName)}")
+    for i, queueFamilyProperty in enumerate(getQueueFamilies(vulkanPhysicalDevice)):
+      var hasSurfaceSupport: VkBool32 = VK_FALSE
+      checkVkResult vkGetPhysicalDeviceSurfaceSupportKHR(vulkanPhysicalDevice, uint32(i), surface, addr(hasSurfaceSupport))
+      device.queueFamilies.add(QueueFamily(properties: queueFamilyProperty, hasSurfaceSupport: bool(hasSurfaceSupport)))
+      debug(&"  Queue family {i} {queueFamilyProperty}")
+
+    result.add(device)
+
+proc filterForDevice(devices: seq[PhysicalDevice]): seq[(PhysicalDevice, uint32, uint32)] =
+  for device in devices:
+    if not (device.formats.len > 0 and device.presentModes.len > 0 and "VK_KHR_swapchain" in device.extensions):
+      continue
+    var graphicsQueueFamily = high(uint32)
+    var presentationQueueFamily = high(uint32)
+    for i, queueFamily in enumerate(device.queueFamilies):
+      if queueFamily.hasSurfaceSupport:
+        presentationQueueFamily = uint32(i)
+      if bool(uint32(queueFamily.properties.queueFlags) and ord(VK_QUEUE_GRAPHICS_BIT)):
+        graphicsQueueFamily = uint32(i)
+    if graphicsQueueFamily != high(uint32) and presentationQueueFamily != high(uint32):
+      result.add((device, graphicsQueueFamily, presentationQueueFamily))
+
+  for (device, graphicsQueueFamily, presentationQueueFamily) in result:
+    debug(&"Viable device: {cleanString(device.properties.deviceName)} (graphics queue family {graphicsQueueFamily}, presentation queue family {presentationQueueFamily})")
+
+
+proc getFrameDimension(window: NativeWindow, device: VkPhysicalDevice,
+    surface: VkSurfaceKHR): TVec2[uint32] =
+  let capabilities = device.getSurfaceCapabilities(surface)
+  if capabilities.currentExtent.width != high(uint32):
+    return TVec2[uint32]([capabilities.currentExtent.width,
+        capabilities.currentExtent.height])
+  else:
+    let (width, height) = window.size()
+    return TVec2[uint32]([
+      min(max(uint32(width), capabilities.minImageExtent.width),
+          capabilities.maxImageExtent.width),
+      min(max(uint32(height), capabilities.minImageExtent.height),
+          capabilities.maxImageExtent.height),
+    ])
+
+when DEBUG_LOG:
+  proc setupDebugLog(instance: VkInstance): VkDebugUtilsMessengerEXT =
+    var createInfo = VkDebugUtilsMessengerCreateInfoEXT(
+      sType: VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
+      messageSeverity: VkDebugUtilsMessageSeverityFlagsEXT(
+        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) or
+        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) or
+        ord(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
+      ),
+      messageType: VkDebugUtilsMessageTypeFlagsEXT(
+        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) or
+        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) or
+        ord(VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
+      ),
+      pfnUserCallback: debugCallback,
+      pUserData: nil,
+    )
+    checkVkResult instance.vkCreateDebugUtilsMessengerEXT(addr(createInfo), nil, addr(result))
+
+proc setupVulkanDeviceAndQueues(instance: VkInstance, surface: VkSurfaceKHR): Device =
+  let usableDevices = instance.getAllPhysicalDevices(surface).filterForDevice()
+  if len(usableDevices) == 0:
+    raise newException(Exception, "No suitable graphics device found")
+  result.physicalDevice = usableDevices[0][0]
+  result.graphicsQueueFamily = usableDevices[0][1]
+  result.presentationQueueFamily = usableDevices[0][2]
+
+  debug(&"Chose device {cleanString(result.physicalDevice.properties.deviceName)}")
+
+  (result.device, result.graphicsQueue, result.presentationQueue) = getVulcanDevice(
+    result.physicalDevice.device,
+    result.physicalDevice.features,
+    result.graphicsQueueFamily,
+    result.presentationQueueFamily,
+  )
+
+proc setupSwapChain(device: VkDevice, physicalDevice: PhysicalDevice, surface: VkSurfaceKHR, dimension: TVec2[uint32], surfaceFormat: VkSurfaceFormatKHR): Swapchain =
+
+  let capabilities = physicalDevice.device.getSurfaceCapabilities(surface)
+  var selectedPresentationMode = getPresentMode(physicalDevice.presentModes)
+  var imageCount = capabilities.minImageCount + 1
+  if capabilities.maxImageCount > 0:
+    imageCount = min(capabilities.maxImageCount, imageCount)
+  # TODO: something not working on window..., likely the extent
+  var extent = VkExtent2D(
+    width: if dimension[0] > 0: dimension[0] else: 1,
+    height: if dimension[1] > 0: dimension[1] else: 1,
+  )
+  # setup swapchain
+  var swapchainCreateInfo = VkSwapchainCreateInfoKHR(
+    sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+    surface: surface,
+    minImageCount: imageCount,
+    imageFormat: surfaceFormat.format,
+    imageColorSpace: surfaceFormat.colorSpace,
+    imageExtent: extent,
+    imageArrayLayers: 1,
+    imageUsage: VkImageUsageFlags(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
+    # VK_SHARING_MODE_CONCURRENT no supported (i.e cannot use different queue families for drawing to swap surface?)
+    imageSharingMode: VK_SHARING_MODE_EXCLUSIVE,
+    preTransform: capabilities.currentTransform,
+    compositeAlpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+    presentMode: selectedPresentationMode,
+    clipped: VK_TRUE,
+    oldSwapchain: VkSwapchainKHR(0),
+  )
+  checkVkResult device.vkCreateSwapchainKHR(addr(swapchainCreateInfo), nil,
+      addr(result.swapchain))
+  result.images = device.getSwapChainImages(result.swapchain)
+
+  # setup swapchian image views
+
+  result.imageviews = newSeq[VkImageView](result.images.len)
+  for i, image in enumerate(result.images):
+    var imageViewCreateInfo = VkImageViewCreateInfo(
+      sType: VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+      image: image,
+      viewType: VK_IMAGE_VIEW_TYPE_2D,
+      format: surfaceFormat.format,
+      components: VkComponentMapping(
+        r: VK_COMPONENT_SWIZZLE_IDENTITY,
+        g: VK_COMPONENT_SWIZZLE_IDENTITY,
+        b: VK_COMPONENT_SWIZZLE_IDENTITY,
+        a: VK_COMPONENT_SWIZZLE_IDENTITY,
+      ),
+      subresourceRange: VkImageSubresourceRange(
+        aspectMask: VkImageAspectFlags(VK_IMAGE_ASPECT_COLOR_BIT),
+        baseMipLevel: 0,
+        levelCount: 1,
+        baseArrayLayer: 0,
+        layerCount: 1,
+      ),
+    )
+    checkVkResult device.vkCreateImageView(addr(imageViewCreateInfo), nil, addr(result.imageviews[i]))
+
+proc setupRenderPass(device: VkDevice, format: VkFormat): VkRenderPass =
+  var
+    colorAttachment = VkAttachmentDescription(
+      format: format,
+      samples: VK_SAMPLE_COUNT_1_BIT,
+      loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR,
+      storeOp: VK_ATTACHMENT_STORE_OP_STORE,
+      stencilLoadOp: VK_ATTACHMENT_LOAD_OP_DONT_CARE,
+      stencilStoreOp: VK_ATTACHMENT_STORE_OP_DONT_CARE,
+      initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
+      finalLayout: VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+    )
+    colorAttachmentRef = VkAttachmentReference(
+      attachment: 0,
+      layout: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+    )
+    subpass = VkSubpassDescription(
+      pipelineBindPoint: VK_PIPELINE_BIND_POINT_GRAPHICS,
+      colorAttachmentCount: 1,
+      pColorAttachments: addr(colorAttachmentRef)
+    )
+    dependency = VkSubpassDependency(
+      srcSubpass: VK_SUBPASS_EXTERNAL,
+      dstSubpass: 0,
+      srcStageMask: VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT),
+      srcAccessMask: VkAccessFlags(0),
+      dstStageMask: VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT),
+      dstAccessMask: VkAccessFlags(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT),
+    )
+    renderPassCreateInfo = VkRenderPassCreateInfo(
+      sType: VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
+      attachmentCount: 1,
+      pAttachments: addr(colorAttachment),
+      subpassCount: 1,
+      pSubpasses: addr(subpass),
+      dependencyCount: 1,
+      pDependencies: addr(dependency),
+    )
+  checkVkResult device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result))
+
+proc initRenderPipeline[VertexType, Uniforms](device: VkDevice, frameSize: TVec2[uint32], renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, Uniforms] =
+  # load shaders
+  result.device = device
+  result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader))
+  result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader))
+
+  var
+    # define which parts can be dynamic (pipeline is fixed after setup)
+    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: addr(dynamicStates[0]),
+    )
+    vertexbindings = generateInputVertexBinding[VertexType]()
+    attributebindings = generateInputAttributeBinding[VertexType]()
+
+    # define input data format
+    vertexInputInfo = VkPipelineVertexInputStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+      vertexBindingDescriptionCount: uint32(vertexbindings.len),
+      pVertexBindingDescriptions: addr(vertexbindings[0]),
+      vertexAttributeDescriptionCount: uint32(attributebindings.len),
+      pVertexAttributeDescriptions: addr(attributebindings[0]),
+    )
+    inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+      topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
+      primitiveRestartEnable: VK_FALSE,
+    )
+
+  # setup viewport
+  var viewportState = VkPipelineViewportStateCreateInfo(
+    sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+    viewportCount: 1,
+    scissorCount: 1,
+  )
+
+  # rasterizerization config
+  var
+    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: VkCullModeFlags(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: VkColorComponentFlags(
+        ord(VK_COLOR_COMPONENT_R_BIT) or
+        ord(VK_COLOR_COMPONENT_G_BIT) or
+        ord(VK_COLOR_COMPONENT_B_BIT) or
+        ord(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: VK_TRUE,
+      logicOp: VK_LOGIC_OP_COPY,
+      attachmentCount: 1,
+      pAttachments: addr(colorBlendAttachment),
+      blendConstants: [0.0'f, 0.0'f, 0.0'f, 0.0'f],
+    )
+
+  result.descriptorSetLayout = device.createUniformDescriptorLayout(
+      VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), 0)
+  var
+    # "globals" that go into the shader, uniforms etc.
+    pipelineLayoutInfo = VkPipelineLayoutCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+      setLayoutCount: 1,
+      pSetLayouts: addr(result.descriptorSetLayout),
+      pushConstantRangeCount: 0,
+      pPushConstantRanges: nil,
+    )
+  checkVkResult vkCreatePipelineLayout(device, addr(pipelineLayoutInfo), nil, addr(result.layout))
+
+  var stages: seq[VkPipelineShaderStageCreateInfo]
+  for shader in result.shaders:
+    stages.add(shader.shader)
+  var pipelineInfo = VkGraphicsPipelineCreateInfo(
+    sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+    stageCount: uint32(stages.len),
+    pStages: addr(stages[0]),
+    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(pipelineInfo),
+    nil,
+    addr(result.pipeline)
+  )
+
+proc setupFramebuffers(device: VkDevice, swapchain: var Swapchain,
+    renderPass: VkRenderPass, dimension: TVec2[uint32]): seq[VkFramebuffer] =
+  result = newSeq[VkFramebuffer](swapchain.images.len)
+  for i, imageview in enumerate(swapchain.imageviews):
+    var framebufferInfo = VkFramebufferCreateInfo(
+      sType: VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+      renderPass: renderPass,
+      attachmentCount: 1,
+      pAttachments: addr(swapchain.imageviews[i]),
+      width: dimension[0],
+      height: dimension[1],
+      layers: 1,
+    )
+    checkVkResult device.vkCreateFramebuffer(addr(framebufferInfo), nil, addr(
+        result[i]))
+
+proc trash(device: VkDevice, swapchain: Swapchain, framebuffers: seq[
+    VkFramebuffer]) =
+  for framebuffer in framebuffers:
+    device.vkDestroyFramebuffer(framebuffer, nil)
+  for imageview in swapchain.imageviews:
+    device.vkDestroyImageView(imageview, nil)
+  device.vkDestroySwapchainKHR(swapchain.swapchain, nil)
+
+proc recreateSwapchain(vulkan: Vulkan): (Swapchain, seq[VkFramebuffer]) =
+  if vulkan.frameSize.x == 0 or vulkan.frameSize.y == 0:
+    return (vulkan.swapchain, vulkan.framebuffers)
+  debug(&"Recreate swapchain with dimension {vulkan.frameSize}")
+  checkVkResult vulkan.device.device.vkDeviceWaitIdle()
+
+  vulkan.device.device.trash(vulkan.swapchain, vulkan.framebuffers)
+
+  result[0] = vulkan.device.device.setupSwapChain(
+    vulkan.device.physicalDevice,
+    vulkan.surface,
+    vulkan.frameSize,
+    vulkan.surfaceFormat
+  )
+  result[1] = vulkan.device.device.setupFramebuffers(
+    result[0],
+    vulkan.renderPass,
+    vulkan.frameSize
+  )
+
+
+proc setupCommandBuffers(device: VkDevice, graphicsQueueFamily: uint32): (
+    VkCommandPool, array[MAX_FRAMES_IN_FLIGHT, VkCommandBuffer]) =
+  # set up command buffer
+  var poolInfo = VkCommandPoolCreateInfo(
+    sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+    flags: VkCommandPoolCreateFlags(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT),
+    queueFamilyIndex: graphicsQueueFamily,
+  )
+  checkVkResult device.vkCreateCommandPool(addr(poolInfo), nil, addr(result[0]))
+
+  var allocInfo = VkCommandBufferAllocateInfo(
+    sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+    commandPool: result[0],
+    level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+    commandBufferCount: result[1].len.uint32,
+  )
+  checkVkResult device.vkAllocateCommandBuffers(addr(allocInfo), addr(result[1][0]))
+
+proc setupSyncPrimitives(device: VkDevice): (
+    array[MAX_FRAMES_IN_FLIGHT, VkSemaphore],
+    array[MAX_FRAMES_IN_FLIGHT, VkSemaphore],
+    array[MAX_FRAMES_IN_FLIGHT, VkFence],
+) =
+  var semaphoreInfo = VkSemaphoreCreateInfo(
+      sType: VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO)
+  var fenceInfo = VkFenceCreateInfo(
+    sType: VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+    flags: VkFenceCreateFlags(VK_FENCE_CREATE_SIGNALED_BIT)
+  )
+  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
+    checkVkResult device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(
+        result[0][i]))
+    checkVkResult device.vkCreateSemaphore(addr(semaphoreInfo), nil, addr(
+        result[1][i]))
+    checkVkResult device.vkCreateFence(addr(fenceInfo), nil, addr(result[2][i]))
+
+proc igniteEngine*(windowTitle: string): Engine =
+
+  result.window = createWindow(windowTitle)
+  let mousepos = result.window.getMousePosition()
+  if mousepos.isSome():
+    result.input.mousePos = mousePos.get()
+
+
+  # create vulkan instance
+  result.vulkan.instance = createVulkanInstance(VULKAN_VERSION)
+
+  # setup vulkan functions
+  loadVulkan(result.vulkan.instance)
+
+  when DEBUG_LOG:
+    result.vulkan.debugMessenger = result.vulkan.instance.setupDebugLog()
+  result.vulkan.surface = result.vulkan.instance.createVulkanSurface(result.window)
+  result.vulkan.device = result.vulkan.instance.setupVulkanDeviceAndQueues(result.vulkan.surface)
+
+  # get basic frame information
+  result.vulkan.surfaceFormat = result.vulkan.device.physicalDevice.formats.getSuitableSurfaceFormat()
+  result.vulkan.frameSize = result.window.getFrameDimension(result.vulkan.device.physicalDevice.device, result.vulkan.surface)
+
+  # setup swapchain and render pipeline
+  result.vulkan.swapchain = result.vulkan.device.device.setupSwapChain(
+    result.vulkan.device.physicalDevice,
+    result.vulkan.surface,
+    result.vulkan.frameSize,
+    result.vulkan.surfaceFormat
+  )
+  result.vulkan.renderPass = result.vulkan.device.device.setupRenderPass(
+      result.vulkan.surfaceFormat.format)
+  result.vulkan.framebuffers = result.vulkan.device.device.setupFramebuffers(
+    result.vulkan.swapchain,
+    result.vulkan.renderPass,
+    result.vulkan.frameSize
+  )
+  (
+    result.vulkan.device.commandPool,
+    result.vulkan.device.commandBuffers,
+  ) = result.vulkan.device.device.setupCommandBuffers(
+      result.vulkan.device.graphicsQueueFamily)
+
+  (
+    result.vulkan.imageAvailableSemaphores,
+    result.vulkan.renderFinishedSemaphores,
+    result.vulkan.inFlightFences,
+  ) = result.vulkan.device.device.setupSyncPrimitives()
+
+
+proc setupPipeline*[VertexType; UniformType; IndexType: uint16|uint32](engine: var Engine, scenedata: Thing, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, UniformType] =
+  engine.currentscenedata = scenedata
+  result = initRenderPipeline[VertexType, UniformType](
+    engine.vulkan.device.device,
+    engine.vulkan.frameSize,
+    engine.vulkan.renderPass,
+    vertexShader,
+    fragmentShader,
+  )
+
+  for mesh in allPartsOfType[Mesh[VertexType, IndexType]](
+      engine.currentscenedata):
+    result.vertexBuffers.add createIndexedVertexBuffers(mesh,
+        result.device, engine.vulkan.device.physicalDevice.device,
+        engine.vulkan.device.commandPool, engine.vulkan.device.graphicsQueue)
+
+  # uniform buffers
+  when not (UniformType is void):
+    result.uniformBuffers = createUniformBuffers[MAX_FRAMES_IN_FLIGHT, UniformType](
+      result.device,
+      engine.vulkan.device.physicalDevice.device
+    )
+
+  var
+    poolSize = VkDescriptorPoolSize(
+      thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+      descriptorCount: uint32(MAX_FRAMES_IN_FLIGHT),
+    )
+    poolInfo = VkDescriptorPoolCreateInfo(
+      sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
+      poolSizeCount: 1,
+      pPoolSizes: addr(poolSize),
+      maxSets: uint32(MAX_FRAMES_IN_FLIGHT),
+    )
+  checkVkResult vkCreateDescriptorPool(result.device, addr(poolInfo), nil, addr(result.descriptorPool))
+
+  var layouts: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSetLayout]
+  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
+    layouts[i] = result.descriptorSetLayout
+  var allocInfo = VkDescriptorSetAllocateInfo(
+    sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
+    descriptorPool: result.descriptorPool,
+    descriptorSetCount: uint32(MAX_FRAMES_IN_FLIGHT),
+    pSetLayouts: addr(layouts[0]),
+  )
+
+  checkVkResult vkAllocateDescriptorSets(result.device, addr(allocInfo), addr(result.descriptors[0]))
+
+  when not (UniformType is void):
+    var bufferInfos: array[MAX_FRAMES_IN_FLIGHT, array[1, VkDescriptorBufferInfo]] # because we use only one Uniform atm
+    for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
+      bufferInfos[i][0] = VkDescriptorBufferInfo(
+        buffer: result.uniformBuffers[i].vkBuffer,
+        offset: VkDeviceSize(0),
+        range: VkDeviceSize(sizeof(UniformType)),
+      )
+      var descriptorWrite = VkWriteDescriptorSet(
+          sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
+          dstSet: result.descriptors[i],
+          dstBinding: 0,
+          dstArrayElement: 0,
+          descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+          descriptorCount: 1,
+          pBufferInfo: addr(bufferInfos[i][0]),
+        )
+      vkUpdateDescriptorSets(result.device, 1, addr(descriptorWrite), 0, nil)
+
+proc updateBufferData*[T](device: Device, buffer: Buffer, data: var T) =
+  when stripGenericParams(T) is seq: # seq needs special treatment for automated data uploading
+    assert data.len > 0
+    let size = data.len * sizeof(get(genericParams(typeof(data)), 0))
+    let dataptr = addr(data[0])
+  else:
+    let size = sizeof(data)
+    let dataptr = addr(data)
+  if not (HostVisible in buffer.memoryProperties):
+    if not (TransferDst in buffer.bufferTypes):
+      raise newException(Exception, "Buffer cannot be updated")
+    var stagingBuffer = device.device.InitBuffer(device.physicalDevice.device,
+        uint64(size), {TransferSrc}, {HostVisible, HostCoherent})
+    copyMem(stagingBuffer.data, dataptr, size)
+    transferBuffer(device.commandPool, device.graphicsQueue, stagingBuffer,
+        buffer, uint64(size))
+    stagingBuffer.trash()
+  else:
+    copyMem(buffer.data, dataptr, size)
+
+proc updateVertexData*[T: VertexAttribute](device: Device,
+    vertexAttribute: var T) =
+  device.updateBufferData(vertexAttribute.buffer, vertexAttribute.data)
+
+proc updateUniformData*[VertexType, Uniforms](device: Device,
+    pipeline: RenderPipeline[VertexType, Uniforms], data: var Uniforms) =
+  for buffer in pipeline.uniformBuffers:
+    device.updateBufferData(buffer, data)
+
+
+proc runPipeline[VertexType; Uniforms](commandBuffer: VkCommandBuffer, pipeline: var RenderPipeline[VertexType, Uniforms], currentFrame: int) =
+  vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline)
+  vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(pipeline.descriptors[currentFrame]), 0, nil)
+
+  for (vertexBufferSet, indexed, indexBuffer, count, indexType) in pipeline.vertexBuffers:
+    var
+      vertexBuffers: seq[VkBuffer]
+      offsets: seq[VkDeviceSize]
+    for buffer in vertexBufferSet:
+      vertexBuffers.add buffer.vkBuffer
+      offsets.add VkDeviceSize(0)
+
+    vkCmdBindVertexBuffers(commandBuffer, firstBinding = 0'u32, bindingCount = uint32(vertexBuffers.len), pBuffers = addr(vertexBuffers[ 0]), pOffsets = addr(offsets[0]))
+    if indexed:
+      vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType)
+      vkCmdDrawIndexed(commandBuffer, count, 1, 0, 0, 0)
+    else:
+      vkCmdDraw(commandBuffer, vertexCount = count, instanceCount = 1, firstVertex = 0, firstInstance = 0)
+
+proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline,
+    commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer,
+    frameSize: TVec2[uint32], currentFrame: int) =
+  var
+    beginInfo = VkCommandBufferBeginInfo(
+      sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+      pInheritanceInfo: nil,
+    )
+    clearColor = VkClearValue(color: VkClearColorValue(float32: pipeline.clearColor))
+    renderPassInfo = VkRenderPassBeginInfo(
+      sType: VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+      renderPass: renderPass,
+      framebuffer: framebuffer,
+      renderArea: VkRect2D(
+        offset: VkOffset2D(x: 0, y: 0),
+        extent: VkExtent2D(width: frameSize.x, height: frameSize.y),
+      ),
+      clearValueCount: 1,
+      pClearValues: addr(clearColor),
+    )
+    viewport = VkViewport(
+      x: 0.0,
+      y: 0.0,
+      width: (float)frameSize.x,
+      height: (float)frameSize.y,
+      minDepth: 0.0,
+      maxDepth: 1.0,
+    )
+    scissor = VkRect2D(
+      offset: VkOffset2D(x: 0, y: 0),
+      extent: VkExtent2D(width: frameSize.x, height: frameSize.y)
+    )
+  checkVkResult vkBeginCommandBuffer(commandBuffer, addr(beginInfo))
+  block:
+    vkCmdBeginRenderPass(commandBuffer, addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE)
+    vkCmdSetViewport(commandBuffer, firstViewport = 0, viewportCount = 1, addr(viewport))
+    vkCmdSetScissor(commandBuffer, firstScissor = 0, scissorCount = 1, addr(scissor))
+    runPipeline(commandBuffer, pipeline, currentFrame)
+    vkCmdEndRenderPass(commandBuffer)
+  checkVkResult vkEndCommandBuffer(commandBuffer)
+
+proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: var RenderPipeline) =
+
+  checkVkResult vkWaitForFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64))
+
+  var bufferImageIndex: uint32
+  let nextImageResult = vkAcquireNextImageKHR(
+    vulkan.device.device,
+    vulkan.swapchain.swapchain,
+    high(uint64),
+    vulkan.imageAvailableSemaphores[currentFrame],
+    VkFence(0),
+    addr(bufferImageIndex)
+  )
+  if nextImageResult == VK_ERROR_OUT_OF_DATE_KHR:
+    vulkan.frameSize = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
+    (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
+  elif not (nextImageResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]):
+    raise newException(Exception, "Vulkan error: vkAcquireNextImageKHR returned " & $nextImageResult)
+  checkVkResult vkResetFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]))
+
+  checkVkResult vkResetCommandBuffer(vulkan.device.commandBuffers[currentFrame], VkCommandBufferResetFlags(0))
+  vulkan.renderPass.recordCommandBuffer(
+    pipeline,
+    vulkan.device.commandBuffers[currentFrame],
+    vulkan.framebuffers[bufferImageIndex], vulkan.frameSize,
+    currentFrame
+  )
+  var
+    waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]]
+    waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)]
+    signalSemaphores = [vulkan.renderFinishedSemaphores[currentFrame]]
+    submitInfo = VkSubmitInfo(
+      sType: VK_STRUCTURE_TYPE_SUBMIT_INFO,
+      waitSemaphoreCount: 1,
+      pWaitSemaphores: addr(waitSemaphores[0]),
+      pWaitDstStageMask: addr(waitStages[0]),
+      commandBufferCount: 1,
+      pCommandBuffers: addr(vulkan.device.commandBuffers[currentFrame]),
+      signalSemaphoreCount: 1,
+      pSignalSemaphores: addr(signalSemaphores[0]),
+    )
+  checkVkResult vkQueueSubmit(vulkan.device.graphicsQueue, 1, addr(submitInfo), vulkan.inFlightFences[currentFrame])
+
+  var presentInfo = VkPresentInfoKHR(
+    sType: VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
+    waitSemaphoreCount: 1,
+    pWaitSemaphores: addr(signalSemaphores[0]),
+    swapchainCount: 1,
+    pSwapchains: addr(vulkan.swapchain.swapchain),
+    pImageIndices: addr(bufferImageIndex),
+    pResults: nil,
+  )
+  let presentResult = vkQueuePresentKHR(vulkan.device.presentationQueue, addr(presentInfo))
+  if presentResult == VK_ERROR_OUT_OF_DATE_KHR or presentResult == VK_SUBOPTIMAL_KHR or resized:
+    vulkan.frameSize = window.getFrameDimension(vulkan.device.physicalDevice.device, vulkan.surface)
+    (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain()
+
+
+func frametime(engine: Engine): auto =
+  if engine.maxFPS == 0: 0'f
+  else: 1'f / float32(engine.maxFPS)
+
+proc run*(engine: var Engine, pipeline: var RenderPipeline, globalUpdate: proc(
+    engine: var Engine, t, dt: float32)) =
+  var
+    currentFrame = 0
+    resized = false
+    lastUpdate = cpuTime()
+    lastframe = 0'f
+
+  while true:
+    # process input
+    engine.input.keysPressed = {}
+    engine.input.keysReleased = {}
+    engine.input.mousePressed = {}
+    engine.input.mouseReleased = {}
+    var killed = false
+    for event in engine.window.pendingEvents():
+      case event.eventType:
+        of Quit:
+          killed = true
+        of ResizedWindow:
+          resized = true
+        of KeyPressed:
+          engine.input.keysPressed.incl event.key
+          engine.input.keysDown.incl event.key
+        of KeyReleased:
+          engine.input.keysReleased.incl event.key
+          engine.input.keysDown.excl event.key
+        of MousePressed:
+          engine.input.mousePressed.incl event.button
+          engine.input.mouseDown.incl event.button
+        of MouseReleased:
+          engine.input.mouseReleased.incl event.button
+          engine.input.mouseDown.excl event.button
+        of MouseMoved:
+          engine.input.mousePos = Vec2([float32(event.x), float32(event.y)])
+    if killed: # at least on windows we should return immediately as swapchain recreation will fail after kill
+      break
+
+    # game logic update
+    let
+      now = cpuTime()
+      dt = now - lastUpdate
+    lastUpdate = now
+    engine.globalUpdate(now, dt)
+    for thing in allThings(engine.currentscenedata):
+      for part in thing.parts:
+        update(part, engine, now, dt)
+      update(thing, engine, now, dt)
+
+    # submit frame for drawing
+    if engine.maxFPS == 0 or (now - lastframe >= engine.frametime): # framerate limit
+      engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline)
+      lastframe = now
+      currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT
+    resized = false
+
+  checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device)
+
+proc trash*(pipeline: var RenderPipeline) =
+  vkDestroyDescriptorPool(pipeline.device, pipeline.descriptorPool, nil)
+  vkDestroyDescriptorSetLayout(pipeline.device, pipeline.descriptorSetLayout, nil)
+  vkDestroyPipeline(pipeline.device, pipeline.pipeline, nil)
+  vkDestroyPipelineLayout(pipeline.device, pipeline.layout, nil)
+  for shader in pipeline.shaders:
+    vkDestroyShaderModule(pipeline.device, shader.shader.module, nil)
+
+  for (bufferset, indexed, indexbuffer, cnt, t) in
+    pipeline.vertexBuffers.mitems:
+    if indexed:
+      indexbuffer.trash()
+    for buffer in bufferset.mitems:
+      buffer.trash()
+  for buffer in pipeline.uniformBuffers.mitems:
+    buffer.trash()
+
+proc trash*(engine: var Engine) =
+  checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device)
+  engine.vulkan.device.device.trash(engine.vulkan.swapchain,
+      engine.vulkan.framebuffers)
+
+  for i in 0 ..< MAX_FRAMES_IN_FLIGHT:
+    engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.imageAvailableSemaphores[i], nil)
+    engine.vulkan.device.device.vkDestroySemaphore(engine.vulkan.renderFinishedSemaphores[i], nil)
+    engine.vulkan.device.device.vkDestroyFence(engine.vulkan.inFlightFences[i], nil)
+
+  engine.vulkan.device.device.vkDestroyRenderPass(engine.vulkan.renderPass, nil)
+  engine.vulkan.device.device.vkDestroyCommandPool(engine.vulkan.device.commandPool, nil)
+
+  engine.vulkan.instance.vkDestroySurfaceKHR(engine.vulkan.surface, nil)
+  engine.vulkan.device.device.vkDestroyDevice(nil)
+  when DEBUG_LOG:
+    engine.vulkan.instance.vkDestroyDebugUtilsMessengerEXT(engine.vulkan.debugMessenger, nil)
+  engine.window.trash()
+  # needs to happen after window is trashed as the driver might have a hook registered for the window destruction
+  engine.vulkan.instance.vkDestroyInstance(nil)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/glsl_helpers.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,67 @@
+import ./math/vector
+import ./math/matrix
+
+func getGLSLType*[T](): 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..
+  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"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/image.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,50 @@
+import ./buffer
+import ./math/vector
+import ./vulkan
+import ./vulkan_helpers
+
+type
+  ImageUsage* = enum
+    TransferDst = VK_IMAGE_USAGE_TRANSFER_DST_BIT
+    SampledBit = VK_IMAGE_USAGE_SAMPLED_BIT
+  ImageUsages = set[ImageUsage]
+  Image = object
+    buffer: Buffer
+    image: VkImage
+    memory: VkDeviceMemory
+
+proc InitImage(data: var seq[byte], size: TVec2[uint32], format: VkFormat,
+    tiling: VkImageTiling, usage: ImageUsages, properties: MemoryProperties,
+        device: VkDevice,
+    physicalDevice: VkPhysicalDevice): Image =
+  result.buffer = InitBuffer(device, physicalDevice, uint64(data.len), {
+      TransferSrc}, {HostVisible, HostCoherent})
+  copyMem(result.buffer.data, addr(data[0]), data.len)
+
+  var imageInfo = VkImageCreateInfo(
+    sType: VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+    imageType: VK_IMAGE_TYPE_2D,
+    extent: VkExtent3D(width: size.x, height: size.y, depth: 1),
+    mipLevels: 1,
+    arrayLayers: 1,
+    format: format,
+    tiling: tiling,
+    initialLayout: VK_IMAGE_LAYOUT_UNDEFINED,
+    usage: cast[VkImageUsageFlags](usage),
+    sharingMode: VK_SHARING_MODE_EXCLUSIVE,
+    samples: VK_SAMPLE_COUNT_1_BIT,
+  )
+  checkVkResult vkCreateImage(device, addr(imageInfo), nil, addr(result.image))
+
+  var memRequirements: VkMemoryRequirements
+  vkGetImageMemoryRequirements(device, result.image, addr(memRequirements))
+
+  var allocInfo = VkMemoryAllocateInfo(
+    sType: VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+    allocationSize: memRequirements.size,
+    memoryTypeIndex: memRequirements.findMemoryType(physicalDevice, properties)
+  )
+
+  checkVkResult vkAllocateMemory(device, addr(allocInfo), nil, addr(result.memory))
+  checkVkResult vkBindImageMemory(device, result.image, result.memory,
+      VkDeviceSize(0))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/shader.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,172 @@
+import std/os
+import std/typetraits
+import std/hashes
+import std/strformat
+import std/strutils
+import std/tables
+import std/compilesettings
+
+import ./vulkan_helpers
+import ./glsl_helpers
+import ./vulkan
+import ./vertex
+import ./descriptor
+import ./math/vector
+
+type
+  AllowedUniformType = SomeNumber|TVec
+  UniformSlot *[T: AllowedUniformType] = object
+  ShaderProgram*[VertexType, Uniforms] = object
+    entryPoint*: string
+    programType*: VkShaderStageFlagBits
+    shader*: VkPipelineShaderStageCreateInfo
+    uniforms*: Uniforms
+
+proc staticExecChecked(command: string, input = ""): string {.compileTime.} =
+  let (output, exitcode) = gorgeEx(
+      command = command,
+      input = input)
+  if exitcode != 0:
+    raise newException(Exception, &"Running '{command}' produced exit code: {exitcode}" & output)
+  return output
+
+
+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: ""
+
+proc compileGLSLToSPIRV(stage: static VkShaderStageFlagBits, shaderSource: static string, entrypoint: string): seq[uint32] {.compileTime.} =
+  when defined(nimcheck): # will not run if nimcheck is running
+    return result
+  const
+    stagename = stage2string(stage)
+    shaderHash = hash(shaderSource)
+    # cross compilation for windows workaround, sorry computer
+    shaderfile = getTempDir() / fmt"shader_{shaderHash}.{stagename}"
+    projectPath = querySetting(projectPath)
+
+  discard staticExecChecked(
+      command = fmt"{projectPath}/glslangValidator --entry-point {entrypoint} -V --stdin -S {stagename} -o {shaderfile}",
+      input = shaderSource
+  )
+
+  when defined(mingw) and defined(linux): # required for crosscompilation, path separators get messed up
+    let shaderbinary = staticRead shaderfile.replace("\\", "/")
+  else:
+    let shaderbinary = staticRead shaderfile
+  when defined(linux):
+    discard staticExecChecked(command = fmt"rm {shaderfile}")
+  elif defined(windows):
+    discard staticExecChecked(command = fmt"cmd.exe /c del {shaderfile}")
+  else:
+    raise newException(Exception, "Unsupported operating system")
+
+  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 initShaderProgram*[VertexType, Uniforms](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string = "main"): ShaderProgram[VertexType, Uniforms] =
+  result.entryPoint = entryPoint
+  result.programType = programType
+
+  const constcode = compileGLSLToSPIRV(programType, shader, entryPoint)
+  var code = constcode
+  var createInfo = VkShaderModuleCreateInfo(
+    sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+    codeSize: uint(code.len * sizeof(uint32)),
+    pCode: if code.len > 0: addr(code[0]) else: nil,
+  )
+  var shaderModule: VkShaderModule
+  checkVkResult vkCreateShaderModule(device, addr(createInfo), nil, addr(shaderModule))
+
+  result.shader = VkPipelineShaderStageCreateInfo(
+    sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+    stage: programType,
+    module: shaderModule,
+    pName: cstring(result.entryPoint), # entry point for shader
+  )
+
+func generateVertexShaderCode*[VertexType, Uniforms](
+  shaderBody: static string = "",
+  entryPoint: static string = "main",
+  glslVersion: static string = "450"
+): string {.compileTime.} =
+  var lines: seq[string]
+  lines.add "#version " & glslVersion
+  lines.add "layout(row_major) uniform;"
+  lines.add generateGLSLUniformDeclarations[Uniforms]()
+  lines.add generateGLSLVertexDeclarations[VertexType]()
+  lines.add "layout(location = 0) out vec4 fragColor;"
+  lines.add "void " & entryPoint & "() {"
+
+  var viewprojection = ""
+
+  var hasPosition = 0
+  var hasColor = 0
+  for attrname, value in VertexType().fieldPairs:
+    when typeof(value) is PositionAttribute:
+      let glsltype = getGLSLType[getAttributeType(value)]()
+      lines.add &"    {glsltype} in_position = " & attrname & ";"
+      if getAttributeType(value) is TVec2:
+        lines.add "    vec4 out_position = vec4(in_position, 0.0, 1.0);"
+      elif getAttributeType(value) is TVec3:
+        lines.add "    vec4 out_position = vec4(in_position, 1.0);"
+      elif getAttributeType(value) is TVec4:
+        lines.add "    vec4 out_position = in_position;"
+      hasPosition += 1
+    when typeof(value) is ModelTransformAttribute:
+      lines.add &"    out_position = " & attrname & " * out_position;"
+    when typeof(value) is ColorAttribute:
+      let glsltype = getGLSLType[getAttributeType(value)]()
+      lines.add &"    {glsltype} in_color = " & attrname & ";"
+      if getAttributeType(value) is TVec3:
+        lines.add &"    vec4 out_color = vec4(in_color, 1);";
+      elif getAttributeType(value) is TVec4:
+        lines.add &"    vec4 out_color = in_color;";
+      hasColor += 1
+  when not (Uniforms is void):
+    let uniformBlockName = name(Uniforms).toLower()
+    for attrname, value in Uniforms().fieldPairs:
+      when typeof(value) is ViewProjectionTransform:
+        lines.add "out_position = " & uniformBlockName & "." & attrname & " * out_position;"
+
+  lines.add shaderBody
+  lines.add "    gl_Position = out_position;"
+  lines.add "    fragColor = out_color;"
+  lines.add "}"
+  if hasPosition != 1:
+    raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type PositionAttribute (has {hasPosition})")
+  if hasColor != 1:
+    raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type ColorAttribute (has {hasColor})")
+  return lines.join("\n")
+
+func generateFragmentShaderCode*[VertexType](
+  shaderBody: static string = "",
+  entryPoint: static string = "main",
+  glslVersion: static string = "450"
+): string {.compileTime.} =
+  var lines: seq[string]
+  lines.add "#version " & glslVersion
+  lines.add "layout(row_major) uniform;"
+  lines.add "layout(location = 0) in vec4 fragColor;"
+  lines.add "layout(location = 0) out vec4 outColor;"
+  lines.add "void " & entryPoint & "() {"
+  lines.add "    vec4 in_color = fragColor;"
+  lines.add "    vec4 out_color = in_color;"
+  lines.add shaderBody
+  lines.add "    outColor = out_color;"
+  lines.add "}"
+
+  return lines.join("\n")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/thing.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,131 @@
+import std/strformat
+import std/typetraits
+
+import ./math/matrix
+
+type
+  Part* = ref object of RootObj
+    thing*: Thing
+
+  Thing* = ref object of RootObj
+    name*: string
+    transform*: Mat44 # todo: cache transform + only update VBO when transform changed
+    parent*: Thing
+    children*: seq[Thing]
+    parts*: seq[Part]
+
+
+func `$`*(thing: Thing): string = thing.name
+method `$`*(part: Part): string {.base.} =
+  if part.thing != nil:
+    &"{part.thing} -> Part"
+  else:
+    &"Standalone Part"
+
+proc add*(thing: Thing, child: Thing) =
+  child.parent = thing
+  thing.children.add child
+proc add*(thing: Thing, part: Part) =
+  part.thing = thing
+  thing.parts.add part
+proc add*(thing: Thing, children: seq[Thing]) =
+  for child in children:
+    child.parent = thing
+    thing.children.add child
+proc add*(thing: Thing, parts: seq[Part]) =
+  for part in parts:
+    part.thing = thing
+    thing.parts.add part
+
+func newThing*(name: string = ""): Thing =
+  result = new Thing
+  result.name = name
+  result.transform = Unit44
+  if result.name == "":
+    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
+
+func newThing*(name: string, firstChild: Thing, children: varargs[
+    Thing]): Thing =
+  result = new Thing
+  result.add firstChild
+  for child in children:
+    result.add child
+  result.name = name
+  result.transform = Unit44
+  if result.name == "":
+    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
+
+proc newThing*(name: string, firstPart: Part, parts: varargs[Part]): Thing =
+  result = new Thing
+  result.name = name
+  result.add firstPart
+  for part in parts:
+    result.add part
+  if result.name == "":
+    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
+  result.transform = Unit44
+
+func getModelTransform*(thing: Thing): Mat44 =
+  result = Unit44
+  var currentThing = thing
+  while currentThing != nil:
+    result = currentThing.transform * result
+    currentThing = currentThing.parent
+
+iterator allPartsOfType*[T: Part](root: Thing): T =
+  var queue = @[root]
+  while queue.len > 0:
+    let thing = queue.pop
+    for part in thing.parts:
+      if part of T:
+        yield T(part)
+    for i in countdown(thing.children.len - 1, 0):
+      queue.add thing.children[i]
+
+func firstWithName*(root: Thing, name: string): Thing =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        return child
+      queue.add child
+
+func firstPartWithName*[T: Part](root: Thing, name: string): T =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        for part in child.parts:
+          if part of T:
+            return T(part)
+      queue.add child
+
+func allWithName*(root: Thing, name: string): seq[Thing] =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        result.add child
+      queue.add child
+
+func allPartsWithName*[T: Part](root: Thing, name: string): seq[T] =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      if child.name == name:
+        for part in child.parts:
+          if part of T:
+            result.add T(part)
+      queue.add child
+
+iterator allThings*(root: Thing): Thing =
+  var queue = @[root]
+  while queue.len > 0:
+    let next = queue.pop
+    for child in next.children:
+      queue.add child
+    yield next
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/vertex.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,181 @@
+import std/options
+import std/macros
+import std/strutils
+import std/strformat
+import std/typetraits
+
+import ./math/vector
+import ./math/matrix
+import ./vulkan
+import ./buffer
+import ./glsl_helpers
+
+type
+  VertexAttributeType = SomeNumber|TVec|TMat
+  # useOnDeviceMemory can be used to make sure the attribute buffer memory will
+  # be on the device. Data will be faster to access but much slower to update
+  GenericAttribute*[T: VertexAttributeType] = object
+    data*: seq[T]
+    buffer*: Buffer
+    useOnDeviceMemory*: bool
+  PositionAttribute*[T: TVec] = object
+    data*: seq[T]
+    buffer*: Buffer
+    useOnDeviceMemory*: bool
+  ColorAttribute*[T: TVec] = object
+    data*: seq[T]
+    buffer*: Buffer
+    useOnDeviceMemory*: bool
+  GenericInstanceAttribute*[T: VertexAttributeType] = object
+    data*: seq[T]
+    buffer*: Buffer
+    useOnDeviceMemory*: bool
+  ModelTransformAttribute* = GenericInstanceAttribute[Mat44]
+  InstanceAttribute* = GenericInstanceAttribute|ModelTransformAttribute
+  VertexAttribute* = GenericAttribute|PositionAttribute|ColorAttribute|InstanceAttribute
+
+template getAttributeType*(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0)
+
+func datasize*(attribute: VertexAttribute): uint64 =
+  uint64(sizeof(getAttributeType(attribute))) * uint64(attribute.data.len)
+
+# from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
+func nLocationSlots[T: VertexAttributeType](): int =
+  when (T is TVec3[float64] or T is TVec3[uint64] or T is TVec4[float64] or
+      T is TVec4[float64]):
+    2
+  elif T is SomeNumber or T is TVec:
+    1
+  else:
+    {.error: "Unsupported vertex attribute type".}
+
+# numbers
+func getVkFormat[T: VertexAttributeType](): 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".}
+
+
+func VertexCount*[T](t: T): uint32 =
+  for name, value in t.fieldPairs:
+    when typeof(value) is VertexAttribute and not (typeof(
+        value) is InstanceAttribute):
+      if result == 0:
+        result = uint32(value.data.len)
+      else:
+        assert result == uint32(value.data.len)
+
+
+func hasAttributeType*[T, AT](t: T): uint32 =
+  for name, value in t.fieldPairs:
+    when typeof(value) is AT:
+      return true
+  return false
+
+# helper function to make sure matrices are correctly
+# divided into multiple vector attributes
+template compositeAttributeType[T]() =
+  when T is TMat33[float32]:
+    Vec3
+  elif T is TMat44[float32]:
+    Vec4
+  else:
+    T
+func compositeAttributesNumber[T](): int =
+  when T is TMat33[float32]:
+    3
+  elif T is TMat44[float32]:
+    4
+  else:
+    1
+
+
+func generateGLSLVertexDeclarations*[T](): string =
+  var stmtList: seq[string]
+  var i = 0
+  for name, value in T().fieldPairs:
+    when typeof(value) is VertexAttribute:
+      let glsltype = getGLSLType[getAttributeType(value)]()
+      let n = name
+      stmtList.add(&"layout(location = {i}) in {glsltype} {n};")
+      for j in 0 ..< compositeAttributesNumber[getAttributeType(value)]():
+        i += nLocationSlots[compositeAttributeType(getAttributeType(value))]()
+
+  return stmtList.join("\n")
+
+
+func generateInputVertexBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputBindingDescription] =
+  # packed attribute data, not interleaved (aks "struct of arrays")
+  var binding = bindingoffset
+  for name, value in T().fieldPairs:
+    when typeof(value) is InstanceAttribute:
+      let inputRate = VK_VERTEX_INPUT_RATE_INSTANCE
+    elif typeof(value) is VertexAttribute:
+      let inputRate = VK_VERTEX_INPUT_RATE_VERTEX
+    else:
+      {.error: "Unsupported vertex attribute type".}
+    result.add(
+      VkVertexInputBindingDescription(
+        binding: uint32(binding),
+        stride: uint32(sizeof(getAttributeType(value))),
+        inputRate: inputRate,
+      )
+    )
+    binding += 1
+
+
+func generateInputAttributeBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputAttributeDescription] =
+  # TODO:
+  var location = 0
+  var binding = bindingoffset
+  for name, value in T().fieldPairs:
+    when typeof(value) is VertexAttribute:
+      for i in 0 ..< compositeAttributesNumber[getAttributeType(value)]():
+        result.add(
+          VkVertexInputAttributeDescription(
+            binding: uint32(binding),
+            location: uint32(location),
+            format: getVkFormat[compositeAttributeType(getAttributeType(value))](),
+            offset: uint32(i * sizeof(compositeAttributeType(getAttributeType(value)))),
+          )
+        )
+        location += nLocationSlots[compositeAttributeType(getAttributeType(value))]()
+      binding += 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/legacy/vulkan_helpers.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -0,0 +1,215 @@
+import std/tables
+import std/strutils
+import std/strformat
+import std/logging
+import std/macros
+
+import ./vulkan
+import ./window
+
+# the included code need checkVkResult, therefore having the template above
+when defined(linux):
+  include ./platform/linux/vulkan
+when defined(windows):
+  include ./platform/windows/vulkan
+
+const ENABLEVULKANVALIDATIONLAYERS* = not defined(release)
+
+func addrOrNil[T](obj: var openArray[T]): ptr T =
+  if obj.len > 0: addr(obj[0]) else: nil
+
+func filterForSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] =
+  for format in formats:
+    if format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
+      result.add(format)
+
+func getSuitableSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR =
+  let usableSurfaceFormats = filterForSurfaceFormat(formats)
+  if len(usableSurfaceFormats) == 0:
+    raise newException(Exception, "No suitable surface formats found")
+  return usableSurfaceFormats[0]
+
+
+func cleanString*(str: openArray[char]): string =
+  for i in 0 ..< len(str):
+    if str[i] == char(0):
+      result = join(str[0 ..< i])
+      break
+
+proc getInstanceExtensions*(): seq[string] =
+  var extensionCount: uint32
+  checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr( extensionCount), nil)
+  var extensions = newSeq[VkExtensionProperties](extensionCount)
+  checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr( extensionCount), addrOrNil(extensions))
+
+  for extension in extensions:
+    result.add(cleanString(extension.extensionName))
+
+
+proc getDeviceExtensions*(device: VkPhysicalDevice): seq[string] =
+  var extensionCount: uint32
+  checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(
+      extensionCount), nil)
+  var extensions = newSeq[VkExtensionProperties](extensionCount)
+  checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(
+      extensionCount), addrOrNil(extensions))
+
+  for extension in extensions:
+    result.add(cleanString(extension.extensionName))
+
+
+proc getValidationLayers*(): seq[string] =
+  var n_layers: uint32
+  checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), nil)
+  var layers = newSeq[VkLayerProperties](n_layers)
+  checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), addrOrNil(layers))
+
+  for layer in layers:
+    result.add(cleanString(layer.layerName))
+
+
+proc getVulkanPhysicalDevices*(instance: VkInstance): seq[VkPhysicalDevice] =
+  var n_devices: uint32
+  checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), nil)
+  result = newSeq[VkPhysicalDevice](n_devices)
+  checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), addrOrNil(result))
+
+
+proc getQueueFamilies*(device: VkPhysicalDevice): seq[VkQueueFamilyProperties] =
+  var n_queuefamilies: uint32
+  vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), nil)
+  result = newSeq[VkQueueFamilyProperties](n_queuefamilies)
+  vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), addrOrNil(result))
+
+
+proc getDeviceSurfaceFormats*(device: VkPhysicalDevice, surface: VkSurfaceKHR): seq[VkSurfaceFormatKHR] =
+  var n_formats: uint32
+  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), nil)
+  result = newSeq[VkSurfaceFormatKHR](n_formats)
+  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), addrOrNil(result))
+
+
+proc getDeviceSurfacePresentModes*(device: VkPhysicalDevice,
+    surface: VkSurfaceKHR): seq[VkPresentModeKHR] =
+  var n_modes: uint32
+  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(
+      n_modes), nil)
+  result = newSeq[VkPresentModeKHR](n_modes)
+  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(
+      n_modes), addrOrNil(result))
+
+
+proc getSwapChainImages*(device: VkDevice, swapChain: VkSwapchainKHR): seq[VkImage] =
+  var n_images: uint32
+  checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), nil)
+  result = newSeq[VkImage](n_images)
+  checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), addr(
+      result[0]))
+
+
+func getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR =
+  let preferredModes = [
+    VK_PRESENT_MODE_MAILBOX_KHR,      # triple buffering
+    VK_PRESENT_MODE_FIFO_RELAXED_KHR, # double duffering
+    VK_PRESENT_MODE_FIFO_KHR,         # double duffering
+    VK_PRESENT_MODE_IMMEDIATE_KHR,    # single buffering
+  ]
+  for preferredMode in preferredModes:
+    for mode in modes:
+      if preferredMode == mode:
+        return mode
+  # should never be reached, but seems to be garuanteed by vulkan specs to always be available
+  return VK_PRESENT_MODE_FIFO_KHR
+
+
+proc createVulkanInstance*(vulkanVersion: uint32): VkInstance =
+
+  var requiredExtensions = @["VK_KHR_surface".cstring] & REQUIRED_PLATFORM_EXTENSIONS
+  when ENABLEVULKANVALIDATIONLAYERS:
+    requiredExtensions.add("VK_EXT_debug_utils".cstring)
+
+  let availableExtensions = getInstanceExtensions()
+  for extension in requiredExtensions:
+    assert $extension in availableExtensions, $extension
+
+  let availableLayers = getValidationLayers()
+  var usableLayers = newSeq[cstring]()
+
+  when ENABLEVULKANVALIDATIONLAYERS:
+    const desiredLayers = ["VK_LAYER_KHRONOS_validation".cstring, "VK_LAYER_MESA_overlay".cstring]
+  else:
+    const desiredLayers: array[0, string] = []
+  for layer in desiredLayers:
+    if $layer in availableLayers:
+      usableLayers.add(layer)
+
+  echo "Available validation layers: ", availableLayers
+  echo "Using validation layers: ", usableLayers
+  echo "Available extensions: ", availableExtensions
+  echo "Using instance extensions: ", requiredExtensions
+
+  var appinfo = VkApplicationInfo(
+    sType: VK_STRUCTURE_TYPE_APPLICATION_INFO,
+    pApplicationName: "Hello Triangle",
+    pEngineName: "Custom engine",
+    apiVersion: vulkanVersion,
+  )
+  var createinfo = VkInstanceCreateInfo(
+    sType: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+    pApplicationInfo: addr(appinfo),
+    enabledLayerCount: usableLayers.len.uint32,
+    ppEnabledLayerNames: cast[ptr UncheckedArray[cstring]](addrOrNil(
+        usableLayers)),
+    enabledExtensionCount: requiredExtensions.len.uint32,
+    ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(
+        requiredExtensions[0]))
+  )
+  checkVkResult vkCreateInstance(addr(createinfo), nil, addr(result))
+  let other_extensions = @["VK_KHR_swapchain".cstring]
+  for extension in requiredExtensions & other_extensions:
+    result.loadExtension($extension)
+
+proc getVulcanDevice*(
+  physicalDevice: var VkPhysicalDevice,
+  features: var VkPhysicalDeviceFeatures,
+  graphicsQueueFamily: uint32,
+  presentationQueueFamily: uint32,
+): (VkDevice, VkQueue, VkQueue) =
+  # setup queue and device
+  # TODO: need check this, possibly wrong logic, see Vulkan tutorial
+  var priority = 1.0'f32
+  var queueCreateInfo = [
+    VkDeviceQueueCreateInfo(
+      sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+      queueFamilyIndex: graphicsQueueFamily,
+      queueCount: 1,
+      pQueuePriorities: addr(priority),
+    ),
+  ]
+
+  var requiredExtensions = ["VK_KHR_swapchain".cstring]
+  var deviceCreateInfo = VkDeviceCreateInfo(
+    sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+    queueCreateInfoCount: uint32(queueCreateInfo.len),
+    pQueueCreateInfos: addrOrNil(queueCreateInfo),
+    pEnabledFeatures: addr(features),
+    enabledExtensionCount: requiredExtensions.len.uint32,
+    ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions))
+  )
+  checkVkResult vkCreateDevice(physicalDevice, addr(deviceCreateInfo), nil,
+      addr(result[0]))
+  vkGetDeviceQueue(result[0], graphicsQueueFamily, 0'u32, addr(result[1]))
+  vkGetDeviceQueue(result[0], presentationQueueFamily, 0'u32, addr(result[2]))
+
+proc debugCallback*(
+  messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT,
+  messageTypes: VkDebugUtilsMessageTypeFlagsEXT,
+  pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT,
+  userData: pointer
+): VkBool32 {.cdecl.} =
+  echo &"{messageSeverity}: {VkDebugUtilsMessageTypeFlagBitsEXT(messageTypes)}: {pCallbackData.pMessage}"
+  return false
+
+proc getSurfaceCapabilities*(device: VkPhysicalDevice,
+    surface: VkSurfaceKHR): VkSurfaceCapabilitiesKHR =
+  checkVkResult device.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(surface, addr(result))
--- a/src/semicongine/mesh.nim	Mon Mar 27 21:01:32 2023 +0700
+++ b/src/semicongine/mesh.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -5,7 +5,6 @@
 import ./vulkan
 import ./thing
 import ./buffer
-import ./vertex
 import ./math/vector
 
 type
@@ -23,10 +22,9 @@
   var indexoffset = U(0)
   for mesh in meshes:
     for srcname, srcvalue in mesh.vertexData.fieldPairs:
-      when typeof(srcvalue) is VertexAttribute:
-        for dstname, dstvalue in result.vertexData.fieldPairs:
-          when srcname == dstname:
-            dstvalue.data.add srcvalue.data
+      for dstname, dstvalue in result.vertexData.fieldPairs:
+        when srcname == dstname:
+          dstvalue.data.add srcvalue.data
       var indexdata: seq[array[3, U]]
       for i in mesh.indices:
         indexdata.add [i[0] + indexoffset, i[1] + indexoffset, i[2] + indexoffset]
@@ -47,24 +45,23 @@
 ): (seq[Buffer], uint32) =
   result[1] = mesh.vertexData.VertexCount
   for name, value in mesh.vertexData.fieldPairs:
-    when typeof(value) is VertexAttribute:
-      assert value.data.len > 0
-      var flags = if value.useOnDeviceMemory: {TransferSrc} else: {VertexBuffer}
-      var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize,
-          flags, {HostVisible, HostCoherent})
-      copyMem(stagingBuffer.data, addr(value.data[0]), value.datasize)
+    assert value.data.len > 0
+    var flags = if value.useOnDeviceMemory: {TransferSrc} else: {VertexBuffer}
+    var stagingBuffer = device.InitBuffer(physicalDevice, value.datasize,
+        flags, {HostVisible, HostCoherent})
+    copyMem(stagingBuffer.data, addr(value.data[0]), value.datasize)
 
-      if value.useOnDeviceMemory:
-        var finalBuffer = device.InitBuffer(physicalDevice, value.datasize, {
-            TransferDst, VertexBuffer}, {DeviceLocal})
-        transferBuffer(commandPool, queue, stagingBuffer, finalBuffer,
-            value.datasize)
-        stagingBuffer.trash()
-        result[0].add(finalBuffer)
-        value.buffer = finalBuffer
-      else:
-        result[0].add(stagingBuffer)
-        value.buffer = stagingBuffer
+    if value.useOnDeviceMemory:
+      var finalBuffer = device.InitBuffer(physicalDevice, value.datasize, {
+          TransferDst, VertexBuffer}, {DeviceLocal})
+      transferBuffer(commandPool, queue, stagingBuffer, finalBuffer,
+          value.datasize)
+      stagingBuffer.trash()
+      result[0].add(finalBuffer)
+      value.buffer = finalBuffer
+    else:
+      result[0].add(stagingBuffer)
+      value.buffer = stagingBuffer
 
 proc createIndexBuffer*(
   mesh: Mesh,
--- a/src/semicongine/shader.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-import std/os
-import std/typetraits
-import std/hashes
-import std/strformat
-import std/strutils
-import std/tables
-import std/compilesettings
-
-import ./vulkan_helpers
-import ./glsl_helpers
-import ./vulkan
-import ./vertex
-import ./descriptor
-import ./math/vector
-
-type
-  AllowedUniformType = SomeNumber|TVec
-  UniformSlot *[T: AllowedUniformType] = object
-  ShaderProgram*[VertexType, Uniforms] = object
-    entryPoint*: string
-    programType*: VkShaderStageFlagBits
-    shader*: VkPipelineShaderStageCreateInfo
-    uniforms*: Uniforms
-
-proc staticExecChecked(command: string, input = ""): string {.compileTime.} =
-  let (output, exitcode) = gorgeEx(
-      command = command,
-      input = input)
-  if exitcode != 0:
-    raise newException(Exception, &"Running '{command}' produced exit code: {exitcode}" & output)
-  return output
-
-
-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: ""
-
-proc compileGLSLToSPIRV(stage: static VkShaderStageFlagBits, shaderSource: static string, entrypoint: string): seq[uint32] {.compileTime.} =
-  when defined(nimcheck): # will not run if nimcheck is running
-    return result
-  const
-    stagename = stage2string(stage)
-    shaderHash = hash(shaderSource)
-    # cross compilation for windows workaround, sorry computer
-    shaderfile = getTempDir() / fmt"shader_{shaderHash}.{stagename}"
-    projectPath = querySetting(projectPath)
-
-  discard staticExecChecked(
-      command = fmt"{projectPath}/glslangValidator --entry-point {entrypoint} -V --stdin -S {stagename} -o {shaderfile}",
-      input = shaderSource
-  )
-
-  when defined(mingw) and defined(linux): # required for crosscompilation, path separators get messed up
-    let shaderbinary = staticRead shaderfile.replace("\\", "/")
-  else:
-    let shaderbinary = staticRead shaderfile
-  when defined(linux):
-    discard staticExecChecked(command = fmt"rm {shaderfile}")
-  elif defined(windows):
-    discard staticExecChecked(command = fmt"cmd.exe /c del {shaderfile}")
-  else:
-    raise newException(Exception, "Unsupported operating system")
-
-  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 initShaderProgram*[VertexType, Uniforms](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string = "main"): ShaderProgram[VertexType, Uniforms] =
-  result.entryPoint = entryPoint
-  result.programType = programType
-
-  const constcode = compileGLSLToSPIRV(programType, shader, entryPoint)
-  var code = constcode
-  var createInfo = VkShaderModuleCreateInfo(
-    sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
-    codeSize: uint(code.len * sizeof(uint32)),
-    pCode: if code.len > 0: addr(code[0]) else: nil,
-  )
-  var shaderModule: VkShaderModule
-  checkVkResult vkCreateShaderModule(device, addr(createInfo), nil, addr(shaderModule))
-
-  result.shader = VkPipelineShaderStageCreateInfo(
-    sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
-    stage: programType,
-    module: shaderModule,
-    pName: cstring(result.entryPoint), # entry point for shader
-  )
-
-func generateVertexShaderCode*[VertexType, Uniforms](
-  shaderBody: static string = "",
-  entryPoint: static string = "main",
-  glslVersion: static string = "450"
-): string {.compileTime.} =
-  var lines: seq[string]
-  lines.add "#version " & glslVersion
-  lines.add "layout(row_major) uniform;"
-  lines.add generateGLSLUniformDeclarations[Uniforms]()
-  lines.add generateGLSLVertexDeclarations[VertexType]()
-  lines.add "layout(location = 0) out vec4 fragColor;"
-  lines.add "void " & entryPoint & "() {"
-
-  var viewprojection = ""
-
-  var hasPosition = 0
-  var hasColor = 0
-  for attrname, value in VertexType().fieldPairs:
-    when typeof(value) is PositionAttribute:
-      let glsltype = getGLSLType[getAttributeType(value)]()
-      lines.add &"    {glsltype} in_position = " & attrname & ";"
-      if getAttributeType(value) is TVec2:
-        lines.add "    vec4 out_position = vec4(in_position, 0.0, 1.0);"
-      elif getAttributeType(value) is TVec3:
-        lines.add "    vec4 out_position = vec4(in_position, 1.0);"
-      elif getAttributeType(value) is TVec4:
-        lines.add "    vec4 out_position = in_position;"
-      hasPosition += 1
-    when typeof(value) is ModelTransformAttribute:
-      lines.add &"    out_position = " & attrname & " * out_position;"
-    when typeof(value) is ColorAttribute:
-      let glsltype = getGLSLType[getAttributeType(value)]()
-      lines.add &"    {glsltype} in_color = " & attrname & ";"
-      if getAttributeType(value) is TVec3:
-        lines.add &"    vec4 out_color = vec4(in_color, 1);";
-      elif getAttributeType(value) is TVec4:
-        lines.add &"    vec4 out_color = in_color;";
-      hasColor += 1
-  when not (Uniforms is void):
-    let uniformBlockName = name(Uniforms).toLower()
-    for attrname, value in Uniforms().fieldPairs:
-      when typeof(value) is ViewProjectionTransform:
-        lines.add "out_position = " & uniformBlockName & "." & attrname & " * out_position;"
-
-  lines.add shaderBody
-  lines.add "    gl_Position = out_position;"
-  lines.add "    fragColor = out_color;"
-  lines.add "}"
-  if hasPosition != 1:
-    raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type PositionAttribute (has {hasPosition})")
-  if hasColor != 1:
-    raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type ColorAttribute (has {hasColor})")
-  return lines.join("\n")
-
-func generateFragmentShaderCode*[VertexType](
-  shaderBody: static string = "",
-  entryPoint: static string = "main",
-  glslVersion: static string = "450"
-): string {.compileTime.} =
-  var lines: seq[string]
-  lines.add "#version " & glslVersion
-  lines.add "layout(row_major) uniform;"
-  lines.add "layout(location = 0) in vec4 fragColor;"
-  lines.add "layout(location = 0) out vec4 outColor;"
-  lines.add "void " & entryPoint & "() {"
-  lines.add "    vec4 in_color = fragColor;"
-  lines.add "    vec4 out_color = in_color;"
-  lines.add shaderBody
-  lines.add "    outColor = out_color;"
-  lines.add "}"
-
-  return lines.join("\n")
--- a/src/semicongine/thing.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-import std/strformat
-import std/typetraits
-
-import ./math/matrix
-
-type
-  Part* = ref object of RootObj
-    thing*: Thing
-
-  Thing* = ref object of RootObj
-    name*: string
-    transform*: Mat44 # todo: cache transform + only update VBO when transform changed
-    parent*: Thing
-    children*: seq[Thing]
-    parts*: seq[Part]
-
-
-func `$`*(thing: Thing): string = thing.name
-method `$`*(part: Part): string {.base.} =
-  if part.thing != nil:
-    &"{part.thing} -> Part"
-  else:
-    &"Standalone Part"
-
-proc add*(thing: Thing, child: Thing) =
-  child.parent = thing
-  thing.children.add child
-proc add*(thing: Thing, part: Part) =
-  part.thing = thing
-  thing.parts.add part
-proc add*(thing: Thing, children: seq[Thing]) =
-  for child in children:
-    child.parent = thing
-    thing.children.add child
-proc add*(thing: Thing, parts: seq[Part]) =
-  for part in parts:
-    part.thing = thing
-    thing.parts.add part
-
-func newThing*(name: string = ""): Thing =
-  result = new Thing
-  result.name = name
-  result.transform = Unit44
-  if result.name == "":
-    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
-func newThing*(name: string, firstChild: Thing, children: varargs[
-    Thing]): Thing =
-  result = new Thing
-  result.add firstChild
-  for child in children:
-    result.add child
-  result.name = name
-  result.transform = Unit44
-  if result.name == "":
-    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
-proc newThing*(name: string, firstPart: Part, parts: varargs[Part]): Thing =
-  result = new Thing
-  result.name = name
-  result.add firstPart
-  for part in parts:
-    result.add part
-  if result.name == "":
-    result.name = &"Thing[{$(cast[ByteAddress](result))}]"
-  result.transform = Unit44
-
-func getModelTransform*(thing: Thing): Mat44 =
-  result = Unit44
-  var currentThing = thing
-  while currentThing != nil:
-    result = currentThing.transform * result
-    currentThing = currentThing.parent
-
-iterator allPartsOfType*[T: Part](root: Thing): T =
-  var queue = @[root]
-  while queue.len > 0:
-    let thing = queue.pop
-    for part in thing.parts:
-      if part of T:
-        yield T(part)
-    for i in countdown(thing.children.len - 1, 0):
-      queue.add thing.children[i]
-
-func firstWithName*(root: Thing, name: string): Thing =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      if child.name == name:
-        return child
-      queue.add child
-
-func firstPartWithName*[T: Part](root: Thing, name: string): T =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      if child.name == name:
-        for part in child.parts:
-          if part of T:
-            return T(part)
-      queue.add child
-
-func allWithName*(root: Thing, name: string): seq[Thing] =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      if child.name == name:
-        result.add child
-      queue.add child
-
-func allPartsWithName*[T: Part](root: Thing, name: string): seq[T] =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      if child.name == name:
-        for part in child.parts:
-          if part of T:
-            result.add T(part)
-      queue.add child
-
-iterator allThings*(root: Thing): Thing =
-  var queue = @[root]
-  while queue.len > 0:
-    let next = queue.pop
-    for child in next.children:
-      queue.add child
-    yield next
--- a/src/semicongine/vertex.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-import std/options
-import std/macros
-import std/strutils
-import std/strformat
-import std/typetraits
-
-import ./math/vector
-import ./math/matrix
-import ./vulkan
-import ./buffer
-import ./glsl_helpers
-
-type
-  VertexAttributeType = SomeNumber|TVec|TMat
-  # useOnDeviceMemory can be used to make sure the attribute buffer memory will
-  # be on the device. Data will be faster to access but much slower to update
-  GenericAttribute*[T: VertexAttributeType] = object
-    data*: seq[T]
-    buffer*: Buffer
-    useOnDeviceMemory*: bool
-  PositionAttribute*[T: TVec] = object
-    data*: seq[T]
-    buffer*: Buffer
-    useOnDeviceMemory*: bool
-  ColorAttribute*[T: TVec] = object
-    data*: seq[T]
-    buffer*: Buffer
-    useOnDeviceMemory*: bool
-  GenericInstanceAttribute*[T: VertexAttributeType] = object
-    data*: seq[T]
-    buffer*: Buffer
-    useOnDeviceMemory*: bool
-  ModelTransformAttribute* = GenericInstanceAttribute[Mat44]
-  InstanceAttribute* = GenericInstanceAttribute|ModelTransformAttribute
-  VertexAttribute* = GenericAttribute|PositionAttribute|ColorAttribute|InstanceAttribute
-
-template getAttributeType*(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0)
-
-func datasize*(attribute: VertexAttribute): uint64 =
-  uint64(sizeof(getAttributeType(attribute))) * uint64(attribute.data.len)
-
-# from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html
-func nLocationSlots[T: VertexAttributeType](): int =
-  when (T is TVec3[float64] or T is TVec3[uint64] or T is TVec4[float64] or
-      T is TVec4[float64]):
-    2
-  elif T is SomeNumber or T is TVec:
-    1
-  else:
-    {.error: "Unsupported vertex attribute type".}
-
-# numbers
-func getVkFormat[T: VertexAttributeType](): 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".}
-
-
-func VertexCount*[T](t: T): uint32 =
-  for name, value in t.fieldPairs:
-    when typeof(value) is VertexAttribute and not (typeof(
-        value) is InstanceAttribute):
-      if result == 0:
-        result = uint32(value.data.len)
-      else:
-        assert result == uint32(value.data.len)
-
-
-func hasAttributeType*[T, AT](t: T): uint32 =
-  for name, value in t.fieldPairs:
-    when typeof(value) is AT:
-      return true
-  return false
-
-# helper function to make sure matrices are correctly
-# divided into multiple vector attributes
-template compositeAttributeType[T]() =
-  when T is TMat33[float32]:
-    Vec3
-  elif T is TMat44[float32]:
-    Vec4
-  else:
-    T
-func compositeAttributesNumber[T](): int =
-  when T is TMat33[float32]:
-    3
-  elif T is TMat44[float32]:
-    4
-  else:
-    1
-
-
-func generateGLSLVertexDeclarations*[T](): string =
-  var stmtList: seq[string]
-  var i = 0
-  for name, value in T().fieldPairs:
-    when typeof(value) is VertexAttribute:
-      let glsltype = getGLSLType[getAttributeType(value)]()
-      let n = name
-      stmtList.add(&"layout(location = {i}) in {glsltype} {n};")
-      for j in 0 ..< compositeAttributesNumber[getAttributeType(value)]():
-        i += nLocationSlots[compositeAttributeType(getAttributeType(value))]()
-
-  return stmtList.join("\n")
-
-
-func generateInputVertexBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputBindingDescription] =
-  # packed attribute data, not interleaved (aks "struct of arrays")
-  var binding = bindingoffset
-  for name, value in T().fieldPairs:
-    when typeof(value) is InstanceAttribute:
-      let inputRate = VK_VERTEX_INPUT_RATE_INSTANCE
-    elif typeof(value) is VertexAttribute:
-      let inputRate = VK_VERTEX_INPUT_RATE_VERTEX
-    else:
-      {.error: "Unsupported vertex attribute type".}
-    result.add(
-      VkVertexInputBindingDescription(
-        binding: uint32(binding),
-        stride: uint32(sizeof(getAttributeType(value))),
-        inputRate: inputRate,
-      )
-    )
-    binding += 1
-
-
-func generateInputAttributeBinding*[T](bindingoffset: int = 0, locationoffset: int = 0): seq[VkVertexInputAttributeDescription] =
-  # TODO:
-  var location = 0
-  var binding = bindingoffset
-  for name, value in T().fieldPairs:
-    when typeof(value) is VertexAttribute:
-      for i in 0 ..< compositeAttributesNumber[getAttributeType(value)]():
-        result.add(
-          VkVertexInputAttributeDescription(
-            binding: uint32(binding),
-            location: uint32(location),
-            format: getVkFormat[compositeAttributeType(getAttributeType(value))](),
-            offset: uint32(i * sizeof(compositeAttributeType(getAttributeType(value)))),
-          )
-        )
-        location += nLocationSlots[compositeAttributeType(getAttributeType(value))]()
-      binding += 1
--- a/src/semicongine/vulkan/api.nim	Mon Mar 27 21:01:32 2023 +0700
+++ b/src/semicongine/vulkan/api.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -25,7 +25,7 @@
     var callstr = astToStr(call).replace("\n", "")
     while callstr.find("  ") >= 0:
       callstr = callstr.replace("  ", " ")
-    debug "CALLING vulkan: ", callstr
+    debug "Calling vulkan: ", callstr
     let value = call
     if value != VK_SUCCESS:
       error "Vulkan error: ", astToStr(call), " returned ", $value
@@ -12065,4 +12065,4 @@
 
 converter VkBool2NimBool*(a: VkBool32): bool = a > 0
 converter NimBool2VkBool*(a: bool): VkBool32 = VkBool32(a)
-proc `$`*(x: uint32): string {.raises: [].} = addInt(result, x)
\ No newline at end of file
+proc `$`*(x: uint32): string {.raises: [].} = addInt(result, x)
--- a/src/semicongine/vulkan/commandbuffer.nim	Mon Mar 27 21:01:32 2023 +0700
+++ b/src/semicongine/vulkan/commandbuffer.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -14,7 +14,7 @@
     buffers*: seq[VkCommandBuffer]
     device: Device
 
-proc createCommandBufferPool*(device: Device, family: QueueFamily, nBuffers: uint32): CommandBufferPool =
+proc createCommandBufferPool*(device: Device, family: QueueFamily, nBuffers: int): CommandBufferPool =
   assert device.vk.valid
   var createInfo = VkCommandPoolCreateInfo(
     sType: VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
@@ -29,7 +29,7 @@
     sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
     commandPool: result.vk,
     level: VK_COMMAND_BUFFER_LEVEL_PRIMARY,
-    commandBufferCount: nBuffers,
+    commandBufferCount: uint32(nBuffers),
   )
   result.buffers = newSeq[VkCommandBuffer](nBuffers)
   checkVkResult device.vk.vkAllocateCommandBuffers(addr(allocInfo), result.buffers.toCPointer)
--- a/src/semicongine/vulkan/descriptor.nim	Mon Mar 27 21:01:32 2023 +0700
+++ b/src/semicongine/vulkan/descriptor.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -83,7 +83,7 @@
   pool.device.vk.vkDestroyDescriptorPool(pool.vk, nil)
   pool.vk.reset
 
-proc allocateDescriptorSet*(pool: DescriptorPool, layout: DescriptorSetLayout, nframes: uint32): seq[DescriptorSet] =
+proc allocateDescriptorSet*(pool: DescriptorPool, layout: DescriptorSetLayout, nframes: int): seq[DescriptorSet] =
   assert pool.device.vk.valid
   assert pool.vk.valid
   assert layout.device.vk.valid
--- a/src/semicongine/vulkan/pipeline.nim	Mon Mar 27 21:01:32 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -1,152 +1,30 @@
 import ./api
 import ./utils
-import ./renderpass
 import ./device
 import ./shader
 import ./descriptor
 
 type
   Pipeline* = object
-    device: Device
+    device*: Device
     vk*: VkPipeline
-    layout: VkPipelineLayout
+    layout*: VkPipelineLayout
     descriptorSetLayout*: DescriptorSetLayout
-
-
-proc createPipeline*[VertexShader: Shader, FragmentShader: Shader](renderPass: RenderPass, vertexShader: VertexShader, fragmentShader: FragmentShader): Pipeline =
-  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
-
-  result.device = renderPass.device
-  
-  var descriptors = @[Descriptor(
-    thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
-    count: 1,
-    stages: @[VK_SHADER_STAGE_VERTEX_BIT],
-    itemsize: uint32(sizeof(shaderUniforms(vertexShader)))
-  )]
-  when shaderUniforms(vertexShader) is shaderUniforms(fragmentShader):
-    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)))
-    )
-  result.descriptorSetLayout = renderPass.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(renderPass.device.vk, addr(pipelineLayoutInfo), nil, addr(result.layout))
+    descriptorPool*: DescriptorPool
+    descriptorSets*: seq[DescriptorSet]
 
-  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.vk,
-      subpass: 0,
-      basePipelineHandle: VkPipeline(0),
-      basePipelineIndex: -1,
-    )
-  checkVkResult vkCreateGraphicsPipelines(
-    renderPass.device.vk,
-    VkPipelineCache(0),
-    1,
-    addr(createInfo),
-    nil,
-    addr(result.vk)
-  )
+proc run*(pipeline: Pipeline, commandBuffer: VkCommandBuffer) =
+  # pipeline an descriptors bound
+  echo "Running pipeline"
 
 proc destroy*(pipeline: var Pipeline) =
   assert pipeline.device.vk.valid
   assert pipeline.vk.valid
   assert pipeline.layout.valid
   assert pipeline.descriptorSetLayout.vk.valid
-
+  
+  if pipeline.descriptorPool.vk.valid:
+    pipeline.descriptorPool.destroy()
   pipeline.descriptorSetLayout.destroy()
   pipeline.device.vk.vkDestroyPipelineLayout(pipeline.layout, nil)
   pipeline.device.vk.vkDestroyPipeline(pipeline.vk, nil)
--- a/src/semicongine/vulkan/renderpass.nim	Mon Mar 27 21:01:32 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -3,6 +3,9 @@
 import ./api
 import ./utils
 import ./device
+import ./pipeline
+import ./shader
+import ./descriptor
 import ../math
 
 type
@@ -18,13 +21,16 @@
   RenderPass* = object
     vk*: VkRenderPass
     device*: Device
+    inFlightFrames*: int
     subpasses*: seq[Subpass]
+    pipelines*: seq[Pipeline]
 
 proc createRenderPass*(
   device: Device,
   attachments: seq[VkAttachmentDescription],
   subpasses: seq[Subpass],
-  dependencies: seq[VkSubpassDependency]
+  dependencies: seq[VkSubpassDependency],
+  inFlightFrames: int,
 ): RenderPass =
   assert device.vk.valid
   var pAttachments = attachments
@@ -56,10 +62,143 @@
       pDependencies: pDependencies.toCPointer,
     )
   result.device = device
+  result.inFlightFrames = inFlightFrames
   result.subpasses = pSubpasses
+  result.pipelines = newSeq[Pipeline](pSubpasses.len)
   checkVkResult device.vk.vkCreateRenderPass(addr(createInfo), nil, addr(result.vk))
 
-proc simpleForwardRenderPass*(device: Device, format: VkFormat, clearColor=Vec4([0.5'f32, 0.5'f32, 0.5'f32, 1'f32])): RenderPass =
+proc attachPipeline[VertexShader: Shader, FragmentShader: Shader](renderPass: var RenderPass, vertexShader: VertexShader, fragmentShader: FragmentShader, 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)
+  
+  var descriptors = @[Descriptor(
+    thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+    count: 1,
+    stages: @[VK_SHADER_STAGE_VERTEX_BIT],
+    itemsize: uint32(sizeof(shaderUniforms(vertexShader)))
+  )]
+  when shaderUniforms(vertexShader) is shaderUniforms(fragmentShader):
+    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)))
+    )
+  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.pipelines[subpass] = 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 =
   assert device.vk.valid
   var
     attachments = @[VkAttachmentDescription(
@@ -80,10 +219,14 @@
       )
     ]
     dependencies: seq[VkSubpassDependency]
-  result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies)
+  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 pipeline in renderpass.pipelines.mitems:
+    pipeline.destroy()
+  renderpass.pipelines = @[]
--- a/src/semicongine/vulkan/swapchain.nim	Mon Mar 27 21:01:32 2023 +0700
+++ b/src/semicongine/vulkan/swapchain.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -23,8 +23,8 @@
     nImages*: uint32
     imageviews*: seq[ImageView]
     framebuffers*: seq[Framebuffer]
-    nInFlight*: uint32
-    currentInFlight*: uint32
+    currentInFlight*: int
+    framesRendered*: int
     queueFinishedFence: seq[Fence]
     imageAvailableSemaphore*: seq[Semaphore]
     renderFinishedSemaphore*: seq[Semaphore]
@@ -37,13 +37,12 @@
   surfaceFormat: VkSurfaceFormatKHR,
   queueFamily: QueueFamily,
   desiredNumberOfImages=3'u32,
-  framesInFlight=2'u32,
   presentationMode: VkPresentModeKHR=VK_PRESENT_MODE_MAILBOX_KHR
 ): (Swapchain, VkResult) =
   assert device.vk.valid
   assert device.physicalDevice.vk.valid
   assert renderPass.vk.valid
-  assert framesInFlight > 0
+  assert renderPass.inFlightFrames > 0
 
   var capabilities = device.physicalDevice.getSurfaceCapabilities()
 
@@ -76,7 +75,6 @@
     swapchain = Swapchain(
       device: device,
       format: surfaceFormat.format,
-      nInFlight: framesInFlight,
       dimension: TVec2[uint32]([capabilities.currentExtent.width, capabilities.currentExtent.height]),
       renderPass: renderPass,
     )
@@ -93,21 +91,21 @@
       let imageview = image.createImageView()
       swapChain.imageviews.add imageview
       swapChain.framebuffers.add swapchain.device.createFramebuffer(renderPass, [imageview], swapchain.dimension)
-    for i in 0 ..< swapchain.nInFlight:
+    for i in 0 ..< swapchain.renderPass.inFlightFrames:
       swapchain.queueFinishedFence.add device.createFence()
       swapchain.imageAvailableSemaphore.add device.createSemaphore()
       swapchain.renderFinishedSemaphore.add device.createSemaphore()
-    swapchain.commandBufferPool = device.createCommandBufferPool(queueFamily, swapchain.nInFlight)
+    swapchain.commandBufferPool = device.createCommandBufferPool(queueFamily, swapchain.renderPass.inFlightFrames)
 
   return (swapchain, createResult)
 
-proc drawNextFrame*(swapchain: var Swapchain, pipeline: Pipeline): bool =
+proc drawNextFrame*(swapchain: var Swapchain): bool =
   assert swapchain.device.vk.valid
   assert swapchain.vk.valid
   assert swapchain.device.firstGraphicsQueue().isSome
   assert swapchain.device.firstPresentationQueue().isSome
 
-  swapchain.currentInFlight = (swapchain.currentInFlight + 1) mod swapchain.nInFlight
+  swapchain.currentInFlight = (swapchain.currentInFlight + 1) mod swapchain.renderPass.inFlightFrames
   swapchain.queueFinishedFence[swapchain.currentInFlight].wait()
 
   var currentFramebufferIndex: uint32
@@ -124,13 +122,22 @@
 
   swapchain.queueFinishedFence[swapchain.currentInFlight].reset()
 
+  var commandBuffer = swapchain.commandBufferPool.buffers[swapchain.currentInFlight]
+
   renderCommands(
-    swapchain.commandBufferPool.buffers[swapchain.currentInFlight],
+    commandBuffer,
     swapchain.renderpass,
     swapchain.framebuffers[currentFramebufferIndex]
   ):
-    swapchain.commandBufferPool.buffers[swapchain.currentInFlight].vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.vk)
-    echo "TODO: Draw calls here"
+    for i in 0 ..< swapchain.renderpass.subpasses.len:
+      var pipeline = swapchain.renderpass.pipelines[i]
+      commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.vk)
+      commandBuffer.vkCmdBindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(pipeline.descriptorSets[swapchain.currentInFlight].vk), 0, nil)
+      pipeline.run(commandBuffer)
+
+      swapchain.renderpass.pipelines[i].run(commandBuffer)
+      if i < swapchain.renderpass.subpasses.len - 1:
+        commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE)
 
   var
     waitSemaphores = [swapchain.imageAvailableSemaphore[swapchain.currentInFlight].vk]
@@ -141,7 +148,7 @@
       pWaitSemaphores: addr(waitSemaphores[0]),
       pWaitDstStageMask: addr(waitStages[0]),
       commandBufferCount: 1,
-      pCommandBuffers: addr(swapchain.commandBufferPool.buffers[swapchain.currentInFlight]),
+      pCommandBuffers: addr(commandBuffer),
       signalSemaphoreCount: 1,
       pSignalSemaphores: addr(swapchain.renderFinishedSemaphore[swapchain.currentInFlight].vk),
     )
@@ -165,6 +172,8 @@
   if not (presentResult in [VK_SUCCESS, VK_SUBOPTIMAL_KHR]):
     return false
 
+  inc swapchain.framesRendered
+
   return true
 
 
@@ -179,7 +188,7 @@
     assert framebuffer.vk.valid
     framebuffer.destroy()
   swapchain.commandBufferPool.destroy()
-  for i in 0 ..< swapchain.nInFlight:
+  for i in 0 ..< swapchain.renderPass.inFlightFrames:
     assert swapchain.queueFinishedFence[i].vk.valid
     assert swapchain.imageAvailableSemaphore[i].vk.valid
     assert swapchain.renderFinishedSemaphore[i].vk.valid
--- a/src/semicongine/vulkan_helpers.nim	Mon Mar 27 21:01:32 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-import std/tables
-import std/strutils
-import std/strformat
-import std/logging
-import std/macros
-
-import ./vulkan
-import ./window
-
-# the included code need checkVkResult, therefore having the template above
-when defined(linux):
-  include ./platform/linux/vulkan
-when defined(windows):
-  include ./platform/windows/vulkan
-
-const ENABLEVULKANVALIDATIONLAYERS* = not defined(release)
-
-func addrOrNil[T](obj: var openArray[T]): ptr T =
-  if obj.len > 0: addr(obj[0]) else: nil
-
-func filterForSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): seq[VkSurfaceFormatKHR] =
-  for format in formats:
-    if format.format == VK_FORMAT_B8G8R8A8_SRGB and format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
-      result.add(format)
-
-func getSuitableSurfaceFormat*(formats: seq[VkSurfaceFormatKHR]): VkSurfaceFormatKHR =
-  let usableSurfaceFormats = filterForSurfaceFormat(formats)
-  if len(usableSurfaceFormats) == 0:
-    raise newException(Exception, "No suitable surface formats found")
-  return usableSurfaceFormats[0]
-
-
-func cleanString*(str: openArray[char]): string =
-  for i in 0 ..< len(str):
-    if str[i] == char(0):
-      result = join(str[0 ..< i])
-      break
-
-proc getInstanceExtensions*(): seq[string] =
-  var extensionCount: uint32
-  checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr( extensionCount), nil)
-  var extensions = newSeq[VkExtensionProperties](extensionCount)
-  checkVkResult vkEnumerateInstanceExtensionProperties(nil, addr( extensionCount), addrOrNil(extensions))
-
-  for extension in extensions:
-    result.add(cleanString(extension.extensionName))
-
-
-proc getDeviceExtensions*(device: VkPhysicalDevice): seq[string] =
-  var extensionCount: uint32
-  checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(
-      extensionCount), nil)
-  var extensions = newSeq[VkExtensionProperties](extensionCount)
-  checkVkResult vkEnumerateDeviceExtensionProperties(device, nil, addr(
-      extensionCount), addrOrNil(extensions))
-
-  for extension in extensions:
-    result.add(cleanString(extension.extensionName))
-
-
-proc getValidationLayers*(): seq[string] =
-  var n_layers: uint32
-  checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), nil)
-  var layers = newSeq[VkLayerProperties](n_layers)
-  checkVkResult vkEnumerateInstanceLayerProperties(addr(n_layers), addrOrNil(layers))
-
-  for layer in layers:
-    result.add(cleanString(layer.layerName))
-
-
-proc getVulkanPhysicalDevices*(instance: VkInstance): seq[VkPhysicalDevice] =
-  var n_devices: uint32
-  checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), nil)
-  result = newSeq[VkPhysicalDevice](n_devices)
-  checkVkResult vkEnumeratePhysicalDevices(instance, addr(n_devices), addrOrNil(result))
-
-
-proc getQueueFamilies*(device: VkPhysicalDevice): seq[VkQueueFamilyProperties] =
-  var n_queuefamilies: uint32
-  vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), nil)
-  result = newSeq[VkQueueFamilyProperties](n_queuefamilies)
-  vkGetPhysicalDeviceQueueFamilyProperties(device, addr(n_queuefamilies), addrOrNil(result))
-
-
-proc getDeviceSurfaceFormats*(device: VkPhysicalDevice, surface: VkSurfaceKHR): seq[VkSurfaceFormatKHR] =
-  var n_formats: uint32
-  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), nil)
-  result = newSeq[VkSurfaceFormatKHR](n_formats)
-  checkVkResult vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, addr(n_formats), addrOrNil(result))
-
-
-proc getDeviceSurfacePresentModes*(device: VkPhysicalDevice,
-    surface: VkSurfaceKHR): seq[VkPresentModeKHR] =
-  var n_modes: uint32
-  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(
-      n_modes), nil)
-  result = newSeq[VkPresentModeKHR](n_modes)
-  checkVkResult vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, addr(
-      n_modes), addrOrNil(result))
-
-
-proc getSwapChainImages*(device: VkDevice, swapChain: VkSwapchainKHR): seq[VkImage] =
-  var n_images: uint32
-  checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), nil)
-  result = newSeq[VkImage](n_images)
-  checkVkResult vkGetSwapchainImagesKHR(device, swapChain, addr(n_images), addr(
-      result[0]))
-
-
-func getPresentMode*(modes: seq[VkPresentModeKHR]): VkPresentModeKHR =
-  let preferredModes = [
-    VK_PRESENT_MODE_MAILBOX_KHR,      # triple buffering
-    VK_PRESENT_MODE_FIFO_RELAXED_KHR, # double duffering
-    VK_PRESENT_MODE_FIFO_KHR,         # double duffering
-    VK_PRESENT_MODE_IMMEDIATE_KHR,    # single buffering
-  ]
-  for preferredMode in preferredModes:
-    for mode in modes:
-      if preferredMode == mode:
-        return mode
-  # should never be reached, but seems to be garuanteed by vulkan specs to always be available
-  return VK_PRESENT_MODE_FIFO_KHR
-
-
-proc createVulkanInstance*(vulkanVersion: uint32): VkInstance =
-
-  var requiredExtensions = @["VK_KHR_surface".cstring] & REQUIRED_PLATFORM_EXTENSIONS
-  when ENABLEVULKANVALIDATIONLAYERS:
-    requiredExtensions.add("VK_EXT_debug_utils".cstring)
-
-  let availableExtensions = getInstanceExtensions()
-  for extension in requiredExtensions:
-    assert $extension in availableExtensions, $extension
-
-  let availableLayers = getValidationLayers()
-  var usableLayers = newSeq[cstring]()
-
-  when ENABLEVULKANVALIDATIONLAYERS:
-    const desiredLayers = ["VK_LAYER_KHRONOS_validation".cstring, "VK_LAYER_MESA_overlay".cstring]
-  else:
-    const desiredLayers: array[0, string] = []
-  for layer in desiredLayers:
-    if $layer in availableLayers:
-      usableLayers.add(layer)
-
-  echo "Available validation layers: ", availableLayers
-  echo "Using validation layers: ", usableLayers
-  echo "Available extensions: ", availableExtensions
-  echo "Using instance extensions: ", requiredExtensions
-
-  var appinfo = VkApplicationInfo(
-    sType: VK_STRUCTURE_TYPE_APPLICATION_INFO,
-    pApplicationName: "Hello Triangle",
-    pEngineName: "Custom engine",
-    apiVersion: vulkanVersion,
-  )
-  var createinfo = VkInstanceCreateInfo(
-    sType: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
-    pApplicationInfo: addr(appinfo),
-    enabledLayerCount: usableLayers.len.uint32,
-    ppEnabledLayerNames: cast[ptr UncheckedArray[cstring]](addrOrNil(
-        usableLayers)),
-    enabledExtensionCount: requiredExtensions.len.uint32,
-    ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(
-        requiredExtensions[0]))
-  )
-  checkVkResult vkCreateInstance(addr(createinfo), nil, addr(result))
-  let other_extensions = @["VK_KHR_swapchain".cstring]
-  for extension in requiredExtensions & other_extensions:
-    result.loadExtension($extension)
-
-proc getVulcanDevice*(
-  physicalDevice: var VkPhysicalDevice,
-  features: var VkPhysicalDeviceFeatures,
-  graphicsQueueFamily: uint32,
-  presentationQueueFamily: uint32,
-): (VkDevice, VkQueue, VkQueue) =
-  # setup queue and device
-  # TODO: need check this, possibly wrong logic, see Vulkan tutorial
-  var priority = 1.0'f32
-  var queueCreateInfo = [
-    VkDeviceQueueCreateInfo(
-      sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
-      queueFamilyIndex: graphicsQueueFamily,
-      queueCount: 1,
-      pQueuePriorities: addr(priority),
-    ),
-  ]
-
-  var requiredExtensions = ["VK_KHR_swapchain".cstring]
-  var deviceCreateInfo = VkDeviceCreateInfo(
-    sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
-    queueCreateInfoCount: uint32(queueCreateInfo.len),
-    pQueueCreateInfos: addrOrNil(queueCreateInfo),
-    pEnabledFeatures: addr(features),
-    enabledExtensionCount: requiredExtensions.len.uint32,
-    ppEnabledExtensionNames: cast[ptr UncheckedArray[cstring]](addr(requiredExtensions))
-  )
-  checkVkResult vkCreateDevice(physicalDevice, addr(deviceCreateInfo), nil,
-      addr(result[0]))
-  vkGetDeviceQueue(result[0], graphicsQueueFamily, 0'u32, addr(result[1]))
-  vkGetDeviceQueue(result[0], presentationQueueFamily, 0'u32, addr(result[2]))
-
-proc debugCallback*(
-  messageSeverity: VkDebugUtilsMessageSeverityFlagBitsEXT,
-  messageTypes: VkDebugUtilsMessageTypeFlagsEXT,
-  pCallbackData: ptr VkDebugUtilsMessengerCallbackDataEXT,
-  userData: pointer
-): VkBool32 {.cdecl.} =
-  echo &"{messageSeverity}: {VkDebugUtilsMessageTypeFlagBitsEXT(messageTypes)}: {pCallbackData.pMessage}"
-  return false
-
-proc getSurfaceCapabilities*(device: VkPhysicalDevice,
-    surface: VkSurfaceKHR): VkSurfaceCapabilitiesKHR =
-  checkVkResult device.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(surface, addr(result))
--- a/tests/test_vulkan_wrapper.nim	Mon Mar 27 21:01:32 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Tue Mar 28 00:20:49 2023 +0700
@@ -64,32 +64,28 @@
     selectedPhysicalDevice.filterForGraphicsPresentationQueues()
   )
 
-  var surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat()
-  var renderpass = device.simpleForwardRenderPass(surfaceFormat.format)
-  echo renderpass
-  var (swapchain, res) = device.createSwapchain(renderpass, surfaceFormat, device.firstGraphicsQueue().get().family)
-  if res != VK_SUCCESS:
-    raise newException(Exception, "Unable to create swapchain")
-
   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);")
   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)
-    pipeline = renderpass.createPipeline(vertexshader, fragmentshader)
-    descriptorPool = device.createDescriptorSetPool(@[(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1'u32)])
-    descriptorSet = descriptorPool.allocateDescriptorSet(pipeline.descriptorSetLayout, 1)
+    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)
+  if res != VK_SUCCESS:
+    raise newException(Exception, "Unable to create swapchain")
 
   echo "All successfull"
-  discard swapchain.drawNextFrame(pipeline)
+  for i in 0 ..< 2:
+    discard swapchain.drawNextFrame()
+  echo "Rendered ", swapchain.framesRendered, " frames"
   echo "Start cleanup"
 
+
   # cleanup
   checkVkResult device.vk.vkDeviceWaitIdle()
-  descriptorPool.destroy()
   vertexshader.destroy()
   fragmentshader.destroy()
-  pipeline.destroy()
   renderpass.destroy()
   swapchain.destroy()
   device.destroy()