changeset 363:451b7ccfe722

improve 2D collision, add some vector functionality, allow shaders/pipelines to be ordered for deterministic rendering order
author Sam <sam@basx.dev>
date Sun, 01 Oct 2023 20:53:35 +0700
parents 3eccdbf32c53
children eef6cc3e1104
files src/semicongine/collision.nim src/semicongine/core/vector.nim src/semicongine/engine.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/vulkan/pipeline.nim src/semicongine/vulkan/renderpass.nim
diffstat 10 files changed, 95 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/collision.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/collision.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -272,7 +272,7 @@
   result = (normal: minNormal, penetrationDepth: minDistance + 0.001'f32)
 
 
-func collisionPoint2D(polytopeIn: seq[Vec3f], a, b: Collider): tuple[normal: Vec2f, penetrationDepth: float32] =
+func collisionPoint2D(polytopeIn: seq[Vec3f], a, b: Collider): tuple[normal: Vec3f, penetrationDepth: float32] =
   var
     polytope = polytopeIn
     minIndex = 0
@@ -309,11 +309,11 @@
       polytope.insert(support, minIndex)
     inc iterCount
 
-  result = (normal: minNormal, penetrationDepth: minDistance + 0.001'f32)
+  result = (normal: newVec3f(minNormal.x, minNormal.y), penetrationDepth: minDistance + 0.001'f32)
 
-func intersects*(a, b: Collider): bool =
+func intersects*(a, b: Collider, as2D=false): bool =
   var
-    support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0.5786)) # just random initial vector
+    support = supportPoint(a, b, newVec3f(0.8153, -0.4239, if as2D: 0.0 else: 0.5786)) # just random initial vector
     simplex = newSeq[Vec3f]()
     direction = -support
     n = 0
@@ -323,16 +323,16 @@
     if support.dot(direction) <= 0:
         return false
     simplex.insert(support, 0)
-    if nextSimplex(simplex, direction):
+    if nextSimplex(simplex, direction, twoDimensional=as2D):
       return true
     # prevent numeric instability
     if direction == newVec3f(0, 0, 0):
       direction[0] = 0.0001
     inc n
 
-func collision*(a, b: Collider): tuple[hasCollision: bool, normal: Vec3f, penetrationDepth: float32] =
+func collision*(a, b: Collider, as2D=false): tuple[hasCollision: bool, normal: Vec3f, penetrationDepth: float32] =
   var
-    support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0.5786)) # just random initial vector
+    support = supportPoint(a, b, newVec3f(0.8153, -0.4239, if as2D: 0.0 else: 0.5786)) # just random initial vector
     simplex = newSeq[Vec3f]()
     direction = -support
     n = 0
@@ -342,28 +342,8 @@
     if support.dot(direction) <= 0:
         return result
     simplex.insert(support, 0)
-    if nextSimplex(simplex, direction):
-      let (normal, depth) = collisionPoint3D(simplex, a, b)
-      return (true, normal, depth)
-    # prevent numeric instability
-    if direction == newVec3f(0, 0, 0):
-      direction[0] = 0.0001
-    inc n
-
-func collision2D*(a, b: Collider): tuple[hasCollision: bool, normal: Vec2f, penetrationDepth: float32] =
-  var
-    support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0)) # just random initial vector
-    simplex = newSeq[Vec3f]()
-    direction = -support
-    n = 0
-  simplex.insert(support, 0)
-  while n < MAX_COLLISON_DETECTION_ITERATIONS:
-    support = supportPoint(a, b, direction)
-    if support.dot(direction) <= 0:
-        return result
-    simplex.insert(support, 0)
-    if nextSimplex(simplex, direction, twoDimensional=true):
-      let (normal, depth) = collisionPoint2D(simplex, a, b)
+    if nextSimplex(simplex, direction, twoDimensional=as2D):
+      let (normal, depth) = if as2D: collisionPoint2D(simplex, a, b) else: collisionPoint3D(simplex, a, b)
       return (true, normal, depth)
     # prevent numeric instability
     if direction == newVec3f(0, 0, 0):
--- a/src/semicongine/core/vector.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/core/vector.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -321,3 +321,6 @@
 
 converter Vec2VkExtent*(vec: TVec2[uint32]): VkExtent2D = VkExtent2D(width: vec[0], height: vec[1])
 converter Vec3VkExtent*(vec: TVec2[uint32]): VkExtent3D = VkExtent3D(width: vec[0], height: vec[1], depth: vec[2])
+
+func angleBetween*(a, b: Vec3f): float32 =
+  arccos(a.dot(b) / (a.length * b.length))
--- a/src/semicongine/engine.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/engine.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -103,12 +103,12 @@
   )
   startMixerThread()
 
-proc initRenderer*(engine: var Engine, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) =
+proc initRenderer*(engine: var Engine, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true) =
 
   assert not engine.renderer.isSome
   var allShaders = @shaders
   allShaders.add (TEXT_MATERIAL, TEXT_SHADER)
-  engine.renderer = some(engine.device.initRenderer(shaders=allShaders.toTable, clearColor=clearColor))
+  engine.renderer = some(engine.device.initRenderer(shaders=allShaders, clearColor=clearColor, backFaceCulling=backFaceCulling))
 
 proc initRenderer*(engine: var Engine, clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) =
   engine.initRenderer(@[], clearColor)
--- a/src/semicongine/mesh.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/mesh.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -10,6 +10,8 @@
 import ./core
 import ./collision
 
+var instanceCounter = 0
+
 type
   MeshIndexType* = enum
     None
@@ -17,12 +19,13 @@
     Small # up to 2^16 vertices
     Big # up to 2^32 vertices
   MeshObject* = object
+    name*: string
     vertexCount*: int
     case indexType*: MeshIndexType
       of None: discard
-      of Tiny: tinyIndices: seq[array[3, uint8]]
-      of Small: smallIndices: seq[array[3, uint16]]
-      of Big: bigIndices: seq[array[3, uint32]]
+      of Tiny: tinyIndices*: seq[array[3, uint8]]
+      of Small: smallIndices*: seq[array[3, uint16]]
+      of Big: bigIndices*: seq[array[3, uint32]]
     materials*: seq[Material]
     transform*: Mat4 = Unit4
     instanceTransforms*: seq[Mat4]
@@ -57,9 +60,9 @@
 
 func `$`*(mesh: MeshObject): string =
   if mesh.indexType == None:
-    &"Mesh(vertexCount: {mesh.vertexCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})"
+    &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, materials: {mesh.materials})"
   else:
-    &"Mesh(vertexCount: {mesh.vertexCount}, indexCount: {mesh.indicesCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})"
+    &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, indexCount: {mesh.indicesCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, materials: {mesh.materials})"
 func `$`*(mesh: Mesh): string =
   $mesh[]
 
@@ -108,6 +111,7 @@
   mesh.vertexData[attribute] = newDataList(thetype=datatype)
   mesh.vertexData[attribute].setLen(mesh.vertexCount)
 
+
 proc initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) =
   assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
   mesh.instanceData[attribute] = newDataList(thetype=getDataType[T]())
@@ -131,9 +135,14 @@
   instanceTransforms: openArray[Mat4]=[Unit4F32],
   material: Material=DEFAULT_MATERIAL,
   autoResize=true,
+  name: string=""
 ): Mesh =
   assert colors.len == 0 or colors.len == positions.len
   assert uvs.len == 0 or uvs.len == positions.len
+  var theName = name
+  if theName == "":
+    theName = &"mesh-{instanceCounter}"
+    inc instanceCounter
 
   # determine index type (uint8, uint16, uint32)
   var indexType = None
@@ -145,6 +154,7 @@
       indexType = Small
 
   result = Mesh(
+    name: theName,
     indexType: indexType,
     vertexCount: positions.len,
     instanceTransforms: @instanceTransforms,
@@ -180,6 +190,7 @@
   transform: Mat4=Unit4F32,
   instanceTransforms: openArray[Mat4]=[Unit4F32],
   material: Material=DEFAULT_MATERIAL,
+  name: string="",
 ): Mesh =
   newMesh(
     positions=positions,
@@ -189,6 +200,7 @@
     transform=transform,
     instanceTransforms=instanceTransforms,
     material=material,
+    name=name,
   )
 
 func attributeSize*(mesh: MeshObject, attribute: string): int =
@@ -319,6 +331,14 @@
   if not mesh.dirtyAttributes.contains(attribute):
     mesh.dirtyAttributes.add attribute
 
+proc removeAttribute*(mesh: var MeshObject, attribute: string) =
+  if mesh.vertexData.contains(attribute):
+    mesh.vertexData.del(attribute)
+  elif mesh.instanceData.contains(attribute):
+    mesh.instanceData.del(attribute)
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+
 proc appendIndicesData*(mesh: var MeshObject, v1, v2, v3: int) =
   case mesh.indexType
   of None: raise newException(Exception, "Mesh does not support indexed data")
@@ -366,6 +386,9 @@
   for p in mesh[positionAttribute, Vec3f][]:
     result.add mesh.transform * p
 
+func getCollider*(mesh: MeshObject, positionAttribute="position"): Collider =
+  return mesh.getCollisionPoints(positionAttribute).calculateCollider(Points)
+
 proc asNonIndexedMesh*(mesh: MeshObject): MeshObject =
   if mesh.indexType == None:
     return mesh
@@ -418,8 +441,10 @@
     instanceTransforms: @[Unit4F32],
     indexType: Small,
     smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]],
-    materials: @[DEFAULT_MATERIAL]
+    materials: @[DEFAULT_MATERIAL],
+    name: &"rect-{instanceCounter}",
   )
+  inc instanceCounter
 
   let
     half_w = width / 2
@@ -432,7 +457,13 @@
   result[].initVertexAttribute("uv", @[newVec2f(0, 0), newVec2f(1, 0), newVec2f(1, 1), newVec2f(0, 1)])
 
 proc tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh =
-  result = Mesh(vertexCount: 3, instanceTransforms: @[Unit4F32], materials: @[DEFAULT_MATERIAL])
+  result = Mesh(
+    vertexCount: 3,
+    instanceTransforms: @[Unit4F32],
+    materials: @[DEFAULT_MATERIAL],
+    name: &"tri-{instanceCounter}",
+  )
+  inc instanceCounter
   let
     half_w = width / 2
     half_h = height / 2
@@ -443,7 +474,14 @@
 
 proc circle*(width=1'f32, height=1'f32, nSegments=12, color="ffffffff"): Mesh =
   assert nSegments >= 3
-  result = Mesh(vertexCount: 3 + nSegments, instanceTransforms: @[Unit4F32], indexType: Small, materials: @[DEFAULT_MATERIAL])
+  result = Mesh(
+    vertexCount: 3 + nSegments,
+    instanceTransforms: @[Unit4F32],
+    indexType: Small,
+    materials: @[DEFAULT_MATERIAL],
+    name: &"circle-{instanceCounter}",
+  )
+  inc instanceCounter
 
   let
     half_w = width / 2
--- a/src/semicongine/renderer.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/renderer.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -43,11 +43,11 @@
     scenedata: Table[Scene, SceneData]
     emptyTexture: VulkanTexture
 
-proc initRenderer*(device: Device, shaders: Table[string, ShaderConfiguration], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): Renderer =
+proc initRenderer*(device: Device, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true): Renderer =
   assert device.vk.valid
   
   result.device = device
-  result.renderPass = device.simpleForwardRenderPass(shaders, clearColor=clearColor)
+  result.renderPass = device.simpleForwardRenderPass(shaders, clearColor=clearColor, backFaceCulling=backFaceCulling)
   result.surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat()
   # use last renderpass as output for swapchain
   let swapchain = device.createSwapchain(result.renderPass.vk, result.surfaceFormat, device.firstGraphicsQueue().get().family)
@@ -60,7 +60,7 @@
 func inputs(renderer: Renderer, scene: Scene): seq[ShaderAttribute] =
   var found: Table[string, ShaderAttribute]
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
+    for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines:
       if scene.usesMaterial(materialName):
         for input in pipeline.inputs:
           if found.contains(input.name):
@@ -74,7 +74,7 @@
 
 func samplers(renderer: Renderer, scene: Scene): seq[ShaderAttribute] =
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
+    for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines:
       if scene.usesMaterial(materialName):
         result.add pipeline.samplers
 
@@ -131,7 +131,7 @@
   var foundRenderableObject = false
   var shaderTypes: seq[string]
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
+    for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines:
       shaderTypes.add materialName
       for mesh in scene.meshes:
         if mesh.materials.anyIt(it.name == materialName):
@@ -252,7 +252,7 @@
     # fill offsets per pipeline (as sequence corresponds to shader input binding)
     var offsets: Table[VkPipeline, seq[(string, MemoryPerformanceHint, int)]]
     for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
-      for materialName, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs:
+      for (materialName, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines:
         if scene.usesMaterial(materialName):
           offsets[pipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]()
           for attribute in pipeline.inputs:
@@ -284,7 +284,7 @@
 
   # setup uniforms and samplers
   for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
-    for materialName, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs:
+    for (materialName, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines:
       if scene.usesMaterial(materialName):
         var uniformBufferSize = 0
         for uniform in pipeline.uniforms:
@@ -359,7 +359,7 @@
 
   # loop over all used shaders/pipelines
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
+    for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines:
       if (
         scene.usesMaterial(materialName) and
         renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and
@@ -425,7 +425,7 @@
   debug "  Index buffer: ", renderer.scenedata[scene].indexBuffer
 
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs:
+    for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines:
       if scene.usesMaterial(materialName):
         debug &"Start pipeline for '{materialName}'"
         commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.vk)
--- a/src/semicongine/resources.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/resources.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -136,6 +136,9 @@
 proc loadMeshes*(path: string): seq[MeshTree] =
   loadResource_intern(path).readglTF()
 
+proc loadFirstMesh*(path: string): Mesh =
+  loadResource_intern(path).readglTF()[0].toSeq[0]
+
 proc modList*(): seq[string] =
   modList_intern()
 
--- a/src/semicongine/resources/mesh.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/resources/mesh.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -135,6 +135,7 @@
 proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: seq[uint8]): Texture =
   let textureNode = root["textures"][textureIndex]
   result.image = loadImage(root, textureNode["source"].getInt(), mainBuffer)
+  result.name = root["images"][textureNode["source"].getInt()]["name"].getStr()
 
   if textureNode.hasKey("sampler"):
     let sampler = root["samplers"][textureNode["sampler"].getInt()]
@@ -271,7 +272,11 @@
     else:
       indexType = Big
 
-  result = Mesh(instanceTransforms: @[Unit4F32], indexType: indexType)
+  result = Mesh(
+    instanceTransforms: @[Unit4F32],
+    indexType: indexType,
+    name: meshNode["name"].getStr()
+  )
 
   # check we have the same attributes for all primitives
   let attributes = meshNode["primitives"][0]["attributes"].keys.toSeq
@@ -286,6 +291,7 @@
   # add all mesh data
   for primitive in meshNode["primitives"]:
     result.addPrimitive(root, primitive, mainBuffer)
+  transform[Vec3f](result[], "position", scale(1, -1, 1))
 
   var maxMaterialIndex = 0
   for material in result[].materials:
@@ -341,7 +347,6 @@
   result = MeshTree()
   for nodeId in scenenode["nodes"]:
     result.children.add loadNode(root, root["nodes"][nodeId.getInt()], mainBuffer)
-  result.transform = scale(1, -1, 1)
   result.updateTransforms()
 
 
--- a/src/semicongine/scene.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/scene.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -25,6 +25,12 @@
   assert not mesh.isNil, "Cannot add a mesh that is 'nil'"
   scene.meshes.add mesh
 
+proc add*(scene: var Scene, meshes: seq[Mesh]) =
+  assert not scene.loaded, &"Scene {scene.name} has already been loaded, cannot add meshes"
+  for mesh in meshes:
+    assert not mesh.isNil, "Cannot add a mesh that is 'nil'"
+  scene.meshes.add meshes
+
 # generic way to add objects that have a mesh-attribute
 proc add*[T](scene: var Scene, obj: T) =
   assert not scene.loaded, &"Scene {scene.name} has already been loaded, cannot add meshes"
--- a/src/semicongine/vulkan/pipeline.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -55,7 +55,7 @@
             descriptor.imageviews.add emptyTexture.imageView
             descriptor.samplers.add emptyTexture.sampler
 
-proc createPipeline*(device: Device, renderPass: VkRenderPass, shaderConfiguration: ShaderConfiguration, inFlightFrames: int, subpass = 0'u32): Pipeline =
+proc createPipeline*(device: Device, renderPass: VkRenderPass, shaderConfiguration: ShaderConfiguration, inFlightFrames: int, subpass = 0'u32, backFaceCulling=true): Pipeline =
   assert renderPass.valid
   assert device.vk.valid
 
@@ -118,7 +118,7 @@
       rasterizerDiscardEnable: VK_FALSE,
       polygonMode: VK_POLYGON_MODE_FILL,
       lineWidth: 1.0,
-      cullMode: toBits [VK_CULL_MODE_BACK_BIT],
+      cullMode: if backFaceCulling: toBits [VK_CULL_MODE_BACK_BIT] else: VkCullModeFlags(0),
       frontFace: VK_FRONT_FACE_CLOCKWISE,
       depthBiasEnable: VK_FALSE,
       depthBiasConstantFactor: 0.0,
--- a/src/semicongine/vulkan/renderpass.nim	Fri Sep 29 19:30:07 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Sun Oct 01 20:53:35 2023 +0700
@@ -15,7 +15,7 @@
     flags: VkSubpassDescriptionFlags
     outputs: seq[VkAttachmentReference]
     depthStencil: Option[VkAttachmentReference]
-    pipelines*: Table[string, Pipeline]
+    pipelines*: seq[(string, Pipeline)]
   RenderPass* = object
     vk*: VkRenderPass
     device*: Device
@@ -62,15 +62,16 @@
 
 proc simpleForwardRenderPass*(
   device: Device,
-  shaders: Table[string, ShaderConfiguration],
+  shaders: openArray[(string, ShaderConfiguration)],
   inFlightFrames=2,
-  clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])
+  clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]),
+  backFaceCulling=true,
 ): RenderPass =
   # TODO: check wether materials are compatible with the assigned shaders
   {.warning: "Need to implement material -> shader compatability" .}
   
   assert device.vk.valid
-  for shaderconfig in shaders.values:
+  for (material, shaderconfig) in shaders:
     assert shaderconfig.outputs.len == 1
   var
     attachments = @[VkAttachmentDescription(
@@ -100,8 +101,8 @@
       dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT],
     )]
   result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies)
-  for material, shaderconfig in shaders.pairs:
-    result.subpasses[0].pipelines[material] = device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0)
+  for (material, shaderconfig) in shaders:
+    result.subpasses[0].pipelines.add (material, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling=backFaceCulling))
 
 
 proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer) =
@@ -160,6 +161,6 @@
   renderPass.device.vk.vkDestroyRenderPass(renderPass.vk, nil)
   renderPass.vk.reset
   for i in 0 ..< renderPass.subpasses.len:
-    for pipeline in renderPass.subpasses[i].pipelines.mvalues:
+    for material, pipeline in renderPass.subpasses[i].pipelines.mitems:
       pipeline.destroy()
   renderPass.subpasses = @[]