changeset 828:ad95479c582e

did: improve material system a ton, more to come
author Sam <sam@basx.dev>
date Sat, 21 Oct 2023 01:05:34 +0700
parents dfa722fbd035
children 0a0402d1d729
files src/semicongine/core/dynamic_arrays.nim src/semicongine/engine.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/text.nim src/semicongine/vulkan/renderpass.nim
diffstat 8 files changed, 239 insertions(+), 256 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/core/dynamic_arrays.nim	Thu Oct 12 14:54:01 2023 +0700
+++ b/src/semicongine/core/dynamic_arrays.nim	Sat Oct 21 01:05:34 2023 +0700
@@ -151,6 +151,54 @@
     of Mat4F64: return a.mat4f64 == b.mat4f64
     of TextureType: a.texture == b.texture
 
+proc setLen*(value: var DataList, len: int) =
+  value.len = len
+  case value.theType
+    of Float32: value.float32[].setLen(len)
+    of Float64: value.float64[].setLen(len)
+    of Int8: value.int8[].setLen(len)
+    of Int16: value.int16[].setLen(len)
+    of Int32: value.int32[].setLen(len)
+    of Int64: value.int64[].setLen(len)
+    of UInt8: value.uint8[].setLen(len)
+    of UInt16: value.uint16[].setLen(len)
+    of UInt32: value.uint32[].setLen(len)
+    of UInt64: value.uint64[].setLen(len)
+    of Vec2I32: value.vec2i32[].setLen(len)
+    of Vec2I64: value.vec2i64[].setLen(len)
+    of Vec3I32: value.vec3i32[].setLen(len)
+    of Vec3I64: value.vec3i64[].setLen(len)
+    of Vec4I32: value.vec4i32[].setLen(len)
+    of Vec4I64: value.vec4i64[].setLen(len)
+    of Vec2U32: value.vec2u32[].setLen(len)
+    of Vec2U64: value.vec2u64[].setLen(len)
+    of Vec3U32: value.vec3u32[].setLen(len)
+    of Vec3U64: value.vec3u64[].setLen(len)
+    of Vec4U32: value.vec4u32[].setLen(len)
+    of Vec4U64: value.vec4u64[].setLen(len)
+    of Vec2F32: value.vec2f32[].setLen(len)
+    of Vec2F64: value.vec2f64[].setLen(len)
+    of Vec3F32: value.vec3f32[].setLen(len)
+    of Vec3F64: value.vec3f64[].setLen(len)
+    of Vec4F32: value.vec4f32[].setLen(len)
+    of Vec4F64: value.vec4f64[].setLen(len)
+    of Mat2F32: value.mat2f32[].setLen(len)
+    of Mat2F64: value.mat2f64[].setLen(len)
+    of Mat23F32: value.mat23f32[].setLen(len)
+    of Mat23F64: value.mat23f64[].setLen(len)
+    of Mat32F32: value.mat32f32[].setLen(len)
+    of Mat32F64: value.mat32f64[].setLen(len)
+    of Mat3F32: value.mat3f32[].setLen(len)
+    of Mat3F64: value.mat3f64[].setLen(len)
+    of Mat34F32: value.mat34f32[].setLen(len)
+    of Mat34F64: value.mat34f64[].setLen(len)
+    of Mat43F32: value.mat43f32[].setLen(len)
+    of Mat43F64: value.mat43f64[].setLen(len)
+    of Mat4F32: value.mat4f32[].setLen(len)
+    of Mat4F64: value.mat4f64[].setLen(len)
+    of TextureType: discard
+
+
 proc setValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) =
   value.setLen(data.len)
   when T is float32: value.float32[] = data
@@ -204,7 +252,7 @@
   elif T is Texture: value.texture[] = data
   else: {. error: "Virtual datatype has no values" .}
 
-func newDataList*(theType: DataType): DataList =
+proc initDataList*(theType: DataType, len=1): DataList =
   result = DataList(theType: theType)
   case result.theType
     of Float32: result.float32 = new seq[float32]
@@ -250,19 +298,15 @@
     of Mat4F32: result.mat4f32 = new seq[TMat4[float32]]
     of Mat4F64: result.mat4f64 = new seq[TMat4[float64]]
     of TextureType: result.texture = new seq[Texture]
-
-proc newDataList*[T: GPUType](len=0): DataList =
-  result = newDataList(getDataType[T]())
-  if len > 0:
-    result.setLen(len)
+  result.setLen(len)
 
-proc newDataList*[T: GPUType](data: openArray[T]): DataList =
-  result = newDataList(getDataType[T]())
-  result.setValues(@data)
+proc initDataList*[T: GPUType](len=1): DataList =
+  result = initDataList(getDataType[T]())
+  result.setLen(len)
 
-proc toGPUValue*[T: GPUType](value: seq[T]): DataList =
-  result = newDataList[T](value.len)
-  result.setValue(value)
+proc initDataList*[T: GPUType](data: openArray[T]): DataList =
+  result = initDataList(getDataType[T]())
+  result.setValues(@data)
 
 func getValues*[T: GPUType|int|uint|float](value: DataList): ref seq[T] =
   when T is float32: value.float32
@@ -417,53 +461,6 @@
     of Mat4F64: result[0] = value.mat4f64[].toCPointer
     of TextureType: result[0] = nil
 
-proc setLen*(value: var DataList, len: int) =
-  value.len = len
-  case value.theType
-    of Float32: value.float32[].setLen(len)
-    of Float64: value.float64[].setLen(len)
-    of Int8: value.int8[].setLen(len)
-    of Int16: value.int16[].setLen(len)
-    of Int32: value.int32[].setLen(len)
-    of Int64: value.int64[].setLen(len)
-    of UInt8: value.uint8[].setLen(len)
-    of UInt16: value.uint16[].setLen(len)
-    of UInt32: value.uint32[].setLen(len)
-    of UInt64: value.uint64[].setLen(len)
-    of Vec2I32: value.vec2i32[].setLen(len)
-    of Vec2I64: value.vec2i64[].setLen(len)
-    of Vec3I32: value.vec3i32[].setLen(len)
-    of Vec3I64: value.vec3i64[].setLen(len)
-    of Vec4I32: value.vec4i32[].setLen(len)
-    of Vec4I64: value.vec4i64[].setLen(len)
-    of Vec2U32: value.vec2u32[].setLen(len)
-    of Vec2U64: value.vec2u64[].setLen(len)
-    of Vec3U32: value.vec3u32[].setLen(len)
-    of Vec3U64: value.vec3u64[].setLen(len)
-    of Vec4U32: value.vec4u32[].setLen(len)
-    of Vec4U64: value.vec4u64[].setLen(len)
-    of Vec2F32: value.vec2f32[].setLen(len)
-    of Vec2F64: value.vec2f64[].setLen(len)
-    of Vec3F32: value.vec3f32[].setLen(len)
-    of Vec3F64: value.vec3f64[].setLen(len)
-    of Vec4F32: value.vec4f32[].setLen(len)
-    of Vec4F64: value.vec4f64[].setLen(len)
-    of Mat2F32: value.mat2f32[].setLen(len)
-    of Mat2F64: value.mat2f64[].setLen(len)
-    of Mat23F32: value.mat23f32[].setLen(len)
-    of Mat23F64: value.mat23f64[].setLen(len)
-    of Mat32F32: value.mat32f32[].setLen(len)
-    of Mat32F64: value.mat32f64[].setLen(len)
-    of Mat3F32: value.mat3f32[].setLen(len)
-    of Mat3F64: value.mat3f64[].setLen(len)
-    of Mat34F32: value.mat34f32[].setLen(len)
-    of Mat34F64: value.mat34f64[].setLen(len)
-    of Mat43F32: value.mat43f32[].setLen(len)
-    of Mat43F64: value.mat43f64[].setLen(len)
-    of Mat4F32: value.mat4f32[].setLen(len)
-    of Mat4F64: value.mat4f64[].setLen(len)
-    of TextureType: discard
-
 proc appendValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) =
   value.len += data.len
   when T is float32: value.float32[].add data
@@ -718,7 +715,7 @@
     of TextureType: a.texture[i] = b.texture[j]
 
 proc copy*(datalist: DataList): DataList =
-  result = newDataList(datalist.theType)
+  result = initDataList(datalist.theType)
   result.appendValues(datalist)
 
 func `$`*(list: DataList): string =
--- a/src/semicongine/engine.nim	Thu Oct 12 14:54:01 2023 +0700
+++ b/src/semicongine/engine.nim	Sat Oct 21 01:05:34 2023 +0700
@@ -11,6 +11,7 @@
 import ./vulkan/shader
 
 import ./scene
+import ./material
 import ./renderer
 import ./events
 import ./audio
@@ -102,11 +103,11 @@
   )
   startMixerThread()
 
-proc initRenderer*(engine: var Engine, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true) =
+proc initRenderer*(engine: var Engine, shaders: openArray[(MaterialType, 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)
+  allShaders.add (TEXT_MATERIAL_TYPE, TEXT_SHADER)
   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])) =
--- a/src/semicongine/mesh.nim	Thu Oct 12 14:54:01 2023 +0700
+++ b/src/semicongine/mesh.nim	Sat Oct 21 01:05:34 2023 +0700
@@ -27,7 +27,7 @@
       of Tiny: tinyIndices*: seq[array[3, uint8]]
       of Small: smallIndices*: seq[array[3, uint16]]
       of Big: bigIndices*: seq[array[3, uint32]]
-    materials*: seq[MaterialData]
+    material*: MaterialData
     transform*: Mat4 = Unit4
     instanceTransforms*: seq[Mat4]
     applyMeshTransformToInstances*: bool = true # if true, the transform attribute for the shader will apply the instance transform AND the mesh transform, to each instance
@@ -38,6 +38,19 @@
     dirtyAttributes: seq[string]
   Mesh* = ref MeshObject
 
+func material*(mesh: MeshObject): MaterialData =
+  mesh.material
+
+func `material=`*(mesh: var MeshObject, material: MaterialData) =
+  for name, theType in material.theType.meshAttributes:
+    if mesh.vertexData.contains(name):
+      assert mesh.vertexData[name].theType == theType, &"{material.theType} expected mesh attribute '{name}' to be '{theType}' but it is {mesh.vertexData[name].theType}"
+    elif mesh.instanceData.contains(name):
+      assert mesh.instanceData[name].theType == theType, &"{material.theType} expected mesh attribute '{name}' to be '{theType}' but it is {mesh.instanceData[name].theType}"
+    else:
+      assert false, &"Mesh '{mesh.name}' is missing required mesh attribute '{name}: {theType}' for {material.theType}"
+  mesh.material = material
+
 func instanceCount*(mesh: MeshObject): int =
   mesh.instanceTransforms.len
 
@@ -52,9 +65,9 @@
 
 func `$`*(mesh: MeshObject): string =
   if mesh.indexType == None:
-    &"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})"
+    &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, material: {mesh.material})"
   else:
-    &"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})"
+    &"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}, material: {mesh.material})"
 func `$`*(mesh: Mesh): string =
   $mesh[]
 
@@ -79,7 +92,7 @@
 
 proc initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) =
   assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
-  mesh.vertexData[attribute] = newDataList(thetype=getDataType[T]())
+  mesh.vertexData[attribute] = initDataList(thetype=getDataType[T]())
   mesh.vertexData[attribute].setLen(mesh.vertexCount)
   mesh.vertexData[attribute].setValues(value)
 proc initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: T) =
@@ -88,13 +101,16 @@
   initVertexAttribute(mesh=mesh, attribute=attribute, value=default(T))
 proc initVertexAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) =
   assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
-  mesh.vertexData[attribute] = newDataList(thetype=datatype)
+  mesh.vertexData[attribute] = initDataList(thetype=datatype)
   mesh.vertexData[attribute].setLen(mesh.vertexCount)
+proc initVertexAttribute*(mesh: var MeshObject, attribute: string, data: DataList) =
+  assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
+  mesh.vertexData[attribute] = data
 
 
 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]())
+  mesh.instanceData[attribute] = initDataList(thetype=getDataType[T]())
   mesh.instanceData[attribute].setLen(mesh.instanceCount)
   mesh.instanceData[attribute].setValues(value)
 proc initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: T) =
@@ -103,8 +119,11 @@
   initInstanceAttribute(mesh=mesh, attribute=attribute, value=default(T))
 proc initInstanceAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) =
   assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
-  mesh.instanceData[attribute] = newDataList(thetype=datatype)
+  mesh.instanceData[attribute] = initDataList(thetype=datatype)
   mesh.instanceData[attribute].setLen(mesh.instanceCount)
+proc initInstanceAttribute*(mesh: var MeshObject, attribute: string, data: DataList) =
+  assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
+  mesh.instanceData[attribute] = data
 
 proc newMesh*(
   positions: openArray[Vec3f],
@@ -139,13 +158,14 @@
     vertexCount: positions.len,
     instanceTransforms: @instanceTransforms,
     transform: transform,
-    materials: @[material],
   )
 
   result[].initVertexAttribute("position", positions.toSeq)
   if colors.len > 0: result[].initVertexAttribute("color", colors.toSeq)
   if uvs.len > 0: result[].initVertexAttribute("uv", uvs.toSeq)
 
+  `material=`(result[], material)
+
   # assert all indices are valid
   for i in indices:
     assert int(i[0]) < result[].vertexCount
@@ -249,6 +269,20 @@
 template `[]`*(mesh: Mesh, attribute: string, i: int, t: typedesc): untyped =
   getAttribute[t](mesh[], attribute, i)
 
+proc updateAttributeData[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: DataList) =
+  if mesh.vertexData.contains(attribute):
+    assert data.len == mesh.vertexCount
+    assert data.theType == mesh.vertexData[attribute].theType
+    mesh.vertexData[attribute] = data
+  elif mesh.instanceData.contains(attribute):
+    assert data.len == mesh.instanceCount
+    assert data.theType == mesh.instanceData[attribute].theType
+    mesh.instanceData[attribute] = data
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+  if not mesh.dirtyAttributes.contains(attribute):
+    mesh.dirtyAttributes.add attribute
+
 proc updateAttributeData[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) =
   if mesh.vertexData.contains(attribute):
     assert data.len == mesh.vertexCount
@@ -273,6 +307,11 @@
   if not mesh.dirtyAttributes.contains(attribute):
     mesh.dirtyAttributes.add attribute
 
+proc `[]=`*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: DataList) =
+  updateAttributeData[T](mesh, attribute, data)
+proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: DataList) =
+  updateAttributeData[t](mesh[], attribute, data)
+
 proc `[]=`*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) =
   updateAttributeData[T](mesh, attribute, data)
 proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) =
@@ -288,29 +327,6 @@
 proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, i: int, value: T) =
   updateAttributeData[T](mesh[], attribute, i, value)
 
-proc appendAttributeData*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) =
-  if mesh.vertexData.contains(attribute):
-    appendValues(mesh.vertexData[attribute], data)
-  elif mesh.instanceData.contains(attribute):
-    appendValues(mesh.instanceData[attribute], data)
-  else:
-    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
-  if not mesh.dirtyAttributes.contains(attribute):
-    mesh.dirtyAttributes.add attribute
-
-# currently only used for loading from files, shouls
-proc appendAttributeData*(mesh: var MeshObject, attribute: string, data: DataList) =
-  if mesh.vertexData.contains(attribute):
-    assert data.thetype == mesh.vertexData[attribute].thetype
-    appendValues(mesh.vertexData[attribute], data)
-  elif mesh.instanceData.contains(attribute):
-    assert data.thetype == mesh.instanceData[attribute].thetype
-    appendValues(mesh.instanceData[attribute], data)
-  else:
-    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
-  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)
@@ -376,11 +392,11 @@
   result = MeshObject(
     vertexCount: mesh.indicesCount,
     indexType: None,
-    materials: mesh.materials,
     transform: mesh.transform,
     instanceTransforms: mesh.instanceTransforms,
     visible: mesh.visible,
   )
+  `material=`(result, mesh.material)
   for attribute, datalist in mesh.vertexData.pairs:
     result.initVertexAttribute(attribute, datalist.theType)
   for attribute, datalist in mesh.instanceData.pairs:
@@ -421,9 +437,9 @@
     instanceTransforms: @[Unit4F32],
     indexType: Small,
     smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]],
-    materials: @[DEFAULT_MATERIAL],
     name: &"rect-{instanceCounter}",
   )
+  `material=`(result[], DEFAULT_MATERIAL)
   inc instanceCounter
 
   let
@@ -440,9 +456,9 @@
   result = Mesh(
     vertexCount: 3,
     instanceTransforms: @[Unit4F32],
-    materials: @[DEFAULT_MATERIAL],
     name: &"tri-{instanceCounter}",
   )
+  `material=`(result[], DEFAULT_MATERIAL)
   inc instanceCounter
   let
     half_w = width / 2
@@ -458,9 +474,9 @@
     vertexCount: 3 + nSegments,
     instanceTransforms: @[Unit4F32],
     indexType: Small,
-    materials: @[DEFAULT_MATERIAL],
     name: &"circle-{instanceCounter}",
   )
+  `material=`(result[], DEFAULT_MATERIAL)
   inc instanceCounter
 
   let
--- a/src/semicongine/renderer.nim	Thu Oct 12 14:54:01 2023 +0700
+++ b/src/semicongine/renderer.nim	Sat Oct 21 01:05:34 2023 +0700
@@ -44,7 +44,7 @@
     scenedata: Table[Scene, SceneData]
     emptyTexture: VulkanTexture
 
-proc initRenderer*(device: Device, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true): Renderer =
+proc initRenderer*(device: Device, shaders: openArray[(MaterialType, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true): Renderer =
   assert device.vk.valid
   
   result.device = device
@@ -61,8 +61,8 @@
 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:
-      if scene.usesMaterial(materialName):
+    for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines:
+      if scene.usesMaterial(materialType):
         for input in pipeline.inputs:
           if found.contains(input.name):
             assert input.name == found[input.name].name, &"{input.name}: {input.name} != {found[input.name].name}"
@@ -75,19 +75,19 @@
 
 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:
-      if scene.usesMaterial(materialName):
+    for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines:
+      if scene.usesMaterial(materialType):
         result.add pipeline.samplers
 
-func materialCompatibleWithPipeline(scene: Scene, material: MaterialData, pipeline: Pipeline): (bool, string) =
+func materialCompatibleWithPipeline(scene: Scene, materialType: MaterialType, pipeline: Pipeline): (bool, string) =
   for uniform in pipeline.uniforms:
     if scene.shaderGlobals.contains(uniform.name):
       if scene.shaderGlobals[uniform.name].theType != uniform.theType:
         return (true, &"shader uniform needs type {uniform.theType} but scene global is of type {scene.shaderGlobals[uniform.name].theType}")
     else:
       var foundMatch = true
-      for name, constant in material.values.pairs:
-        if name == uniform.name and constant.theType == uniform.theType:
+      for name, theType in materialType.attributes.pairs:
+        if name == uniform.name and theType == uniform.theType:
           foundMatch = true
           break
       if not foundMatch:
@@ -98,7 +98,7 @@
         return (true, &"shader sampler '{sampler.name}' needs type {sampler.theType} but scene global is of type {scene.shaderGlobals[sampler.name].theType}")
     else:
       var foundMatch = true
-      for name, value in material.textures:
+      for name in materialType.attributes.keys:
         if name == sampler.name:
           foundMatch = true
           break
@@ -120,34 +120,34 @@
     if input.perInstance and not mesh[].instanceAttributes.contains(input.name):
       return (true, &"Shader input '{input.name}' expected to be per instance attribute, but mesh has no such instance attribute (available are: {mesh[].instanceAttributes})")
 
-  var pipelineCompatabilities = mesh.materials.mapIt(materialCompatibleWithPipeline(scene, it, pipeline))
-  if pipelineCompatabilities.filterIt(not it[0]).len == 0:
-    return (true, pipelineCompatabilities.mapIt(it[1]).join(" / "))
+  let pipelineCompatability = scene.materialCompatibleWithPipeline(mesh.material.theType, pipeline)
+  if pipelineCompatability[0]:
+    return (true, pipelineCompatability[1])
   return (false, "")
 
 func checkSceneIntegrity(renderer: Renderer, scene: Scene) =
+  # TODO: this and the sub-functions can likely be simplified a ton
   if scene.meshes.len == 0:
     return
 
   var foundRenderableObject = false
-  var shaderTypes: seq[string]
+  var materialTypes: seq[MaterialType]
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines:
-      shaderTypes.add materialName
+    for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines:
+      materialTypes.add materialType
       for mesh in scene.meshes:
-        if mesh.materials.anyIt(it.name == materialName):
+        if mesh.material.theType == materialType:
           foundRenderableObject = true
           let (error, message) = scene.meshCompatibleWithPipeline(mesh, pipeline)
           if error:
-            raise newException(Exception, &"Mesh '{mesh}' not compatible with assigned pipeline ({materialName}) because: {message}")
+            raise newException(Exception, &"Mesh '{mesh}' not compatible with assigned pipeline ({materialType}) because: {message}")
 
   if not foundRenderableObject:
-    var materialTypes: seq[string]
+    var matTypes: seq[string]
     for mesh in scene.meshes:
-      for material in mesh.materials:
-        if not materialTypes.contains(material.name):
-            materialTypes.add material.name
-    raise newException(Exception, &"Scene '{scene.name}' has been added but materials are not compatible with any registered shader: Materials in scene: {materialTypes}, registered shader-materialtypes: {shaderTypes}")
+      if not matTypes.contains(mesh.material.name):
+          matTypes.add mesh.material.name
+    raise newException(Exception, &"Scene '{scene.name}' has been added but materials are not compatible with any registered shader: Materials in scene: {matTypes}, registered shader-materialtypes: {materialTypes}")
 
 proc setupDrawableBuffers*(renderer: var Renderer, scene: var Scene) =
   assert not (scene in renderer.scenedata)
@@ -159,16 +159,16 @@
   var scenedata = SceneData()
 
   for mesh in scene.meshes:
-    for material in mesh.materials:
-      if not scenedata.materials.contains(material):
-        scenedata.materials.add material
-        for textureName, texture in material.textures.pairs:
-          if scene.shaderGlobals.contains(textureName) and scene.shaderGlobals[textureName].theType == TextureType:
-            warn &"Ignoring material texture '{textureName}' as scene-global textures with the same name have been defined"
+    if not scenedata.materials.contains(mesh.material):
+      scenedata.materials.add mesh.material
+      for name, value in mesh.material.attributes.pairs:
+        if value.theType == TextureType:
+          if scene.shaderGlobals.contains(name) and scene.shaderGlobals[name].theType == TextureType:
+            warn &"Ignoring material texture '{name}' as scene-global textures with the same name have been defined"
           else:
-            if not scenedata.textures.hasKey(textureName):
-              scenedata.textures[textureName] = @[]
-            scenedata.textures[textureName].add renderer.device.uploadTexture(texture)
+            if not scenedata.textures.hasKey(name):
+              scenedata.textures[name] = @[]
+            scenedata.textures[name].add renderer.device.uploadTexture(getValue[Texture](value, 0))
 
   for name, value in scene.shaderGlobals.pairs:
     if value.theType == TextureType:
@@ -253,8 +253,8 @@
     # 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:
-        if scene.usesMaterial(materialName):
+      for (materialType, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines:
+        if scene.usesMaterial(materialType):
           offsets[pipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]()
           for attribute in pipeline.inputs:
             offsets[pipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)])
@@ -286,8 +286,8 @@
 
   # setup uniforms and samplers
   for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialName, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines:
-      if scene.usesMaterial(materialName):
+    for (materialType, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines:
+      if scene.usesMaterial(materialType):
         var uniformBufferSize = 0
         for uniform in pipeline.uniforms:
           uniformBufferSize += uniform.size
@@ -361,9 +361,9 @@
 
   # loop over all used shaders/pipelines
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines:
+    for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines:
       if (
-        scene.usesMaterial(materialName) and
+        scene.usesMaterial(materialType) and
         renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and
         renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0
       ):
@@ -383,7 +383,7 @@
             foundValue = true
           else:
             for mat in renderer.scenedata[scene].materials:
-              for name, materialConstant in mat.values.pairs:
+              for name, materialConstant in mat.attributes.pairs:
                 if uniform.name == name:
                   value = materialConstant
                   foundValue = true
@@ -427,12 +427,12 @@
   debug "  Index buffer: ", renderer.scenedata[scene].indexBuffer
 
   for i in 0 ..< renderer.renderPass.subpasses.len:
-    for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines:
-      if scene.usesMaterial(materialName):
-        debug &"Start pipeline for '{materialName}'"
+    for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines:
+      if scene.usesMaterial(materialType):
+        debug &"Start pipeline for '{materialType}'"
         commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.vk)
         commandBuffer.vkCmdBindDescriptorSets(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[pipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil)
-        for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].materials.anyIt(it.name == materialName)):
+        for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].material.theType == materialType):
           drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer, pipeline.vk)
 
     if i < renderer.renderPass.subpasses.len - 1:
--- a/src/semicongine/resources/mesh.nim	Thu Oct 12 14:54:01 2023 +0700
+++ b/src/semicongine/resources/mesh.nim	Sat Oct 21 01:05:34 2023 +0700
@@ -2,7 +2,6 @@
 import std/json
 import std/logging
 import std/tables
-import std/sequtils
 import std/strformat
 import std/streams
 
@@ -94,7 +93,7 @@
   copyMem(dstPointer, addr mainBuffer[bufferOffset], result.len)
 
 proc getAccessorData(root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): DataList =
-  result = newDataList(thetype=accessor.getGPUType("??"))
+  result = initDataList(thetype=accessor.getGPUType("??"))
   result.setLen(accessor["count"].getInt())
 
   let bufferView = root["bufferViews"][accessor["bufferView"].getInt()]
@@ -150,124 +149,72 @@
       result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
 
 
-proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8], materialIndex: uint16): MaterialData =
-  result = MaterialData(name: materialNode["name"].getStr(), index: materialIndex)
+proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8]): MaterialData =
+  result = MaterialData(name: materialNode["name"].getStr())
   let pbr = materialNode["pbrMetallicRoughness"]
 
   # color
-  result.values["baseColorFactor"] = newDataList(thetype=Vec4F32)
+  result.attributes["baseColorFactor"] = initDataList(thetype=Vec4F32)
   if pbr.hasKey("baseColorFactor"):
-    setValue(result.values["baseColorFactor"], @[newVec4f(
+    setValue(result.attributes["baseColorFactor"], @[newVec4f(
       pbr["baseColorFactor"][0].getFloat(),
       pbr["baseColorFactor"][1].getFloat(),
       pbr["baseColorFactor"][2].getFloat(),
       pbr["baseColorFactor"][3].getFloat(),
     )])
   else:
-    setValue(result.values["baseColorFactor"], @[newVec4f(1, 1, 1, 1)])
+    setValue(result.attributes["baseColorFactor"], @[newVec4f(1, 1, 1, 1)])
 
   # pbr material values
   for factor in ["metallicFactor", "roughnessFactor"]:
-    result.values[factor] = newDataList(thetype=Float32)
+    result.attributes[factor] = initDataList(thetype=Float32)
     if pbr.hasKey(factor):
-      setValue(result.values[factor], @[float32(pbr[factor].getFloat())])
+      setValue(result.attributes[factor], @[float32(pbr[factor].getFloat())])
     else:
-      setValue(result.values[factor], @[0.5'f32])
+      setValue(result.attributes[factor], @[0.5'f32])
 
   # pbr material textures
   for texture in ["baseColorTexture", "metallicRoughnessTexture"]:
+    result.attributes[texture] = initDataList(thetype=TextureType)
+    result.attributes[texture & "Index"] = initDataList(thetype=UInt8)
     if pbr.hasKey(texture):
-      result.textures[texture] = loadTexture(root, pbr[texture]["index"].getInt(), mainBuffer)
-      result.values[texture & "Index"] = newDataList(thetype=UInt8)
-      setValue(result.values[texture & "Index"], @[pbr[texture].getOrDefault("texCoord").getInt(0).uint8])
+      setValue(result.attributes[texture], @[loadTexture(root, pbr[texture]["index"].getInt(), mainBuffer)])
+      setValue(result.attributes[texture & "Index"], @[pbr[texture].getOrDefault("texCoord").getInt(0).uint8])
     else:
-      result.textures[texture] = EMPTY_TEXTURE
-      result.values[texture & "Index"] = newDataList(thetype=UInt8)
-      setValue(result.values[texture & "Index"], @[0'u8])
+      setValue(result.attributes[texture & "Index"], @[EMPTY_TEXTURE])
+      setValue(result.attributes[texture & "Index"], @[0'u8])
 
   # generic material textures
   for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]:
+    result.attributes[texture] = initDataList(thetype=TextureType)
+    result.attributes[texture & "Index"] = initDataList(thetype=UInt8)
     if materialNode.hasKey(texture):
-      result.textures[texture] = loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)
-      result.values[texture & "Index"] = newDataList(thetype=UInt8)
-      setValue(result.values[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8])
+      setValue(result.attributes[texture], @[loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)])
+      setValue(result.attributes[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8])
     else:
-      result.textures[texture] = EMPTY_TEXTURE
-      result.values[texture & "Index"] = newDataList(thetype=UInt8)
-      setValue(result.values[texture & "Index"], @[0'u8])
+      setValue(result.attributes[texture], @[EMPTY_TEXTURE])
+      setValue(result.attributes[texture & "Index"], @[0'u8])
 
   # emissiv color
-  result.values["emissiveFactor"] = newDataList(thetype=Vec3F32)
+  result.attributes["emissiveFactor"] = initDataList(thetype=Vec3F32)
   if materialNode.hasKey("emissiveFactor"):
-    setValue(result.values["emissiveFactor"], @[newVec3f(
+    setValue(result.attributes["emissiveFactor"], @[newVec3f(
       materialNode["emissiveFactor"][0].getFloat(),
       materialNode["emissiveFactor"][1].getFloat(),
       materialNode["emissiveFactor"][2].getFloat(),
     )])
   else:
-    setValue(result.values["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)])
+    setValue(result.attributes["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)])
 
-proc addPrimitive(mesh: Mesh, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) =
+proc loadMesh(meshname: string, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]): Mesh =
   if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4:
     raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode")
 
-  var vertexCount = 0
-  for attribute, accessor in primitiveNode["attributes"].pairs:
-    let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer)
-    mesh[].appendAttributeData(attribute.toLowerAscii, data)
-    assert data.len == vertexCount or vertexCount == 0
-    vertexCount = data.len
-  mesh.vertexCount += vertexCount
-
-  var materialId = 0'u16
-  if primitiveNode.hasKey("material"):
-    materialId = uint16(primitiveNode["material"].getInt())
-  mesh[].appendAttributeData("materialIndex", newSeqWith(vertexCount, materialId))
-  if "materials" in root and int(materialId) < root["materials"].len:
-    let material = loadMaterial(root, root["materials"][int(materialId)], mainBuffer, materialId)
-    # FIX: materialIndex is designed to support multiple different materials per mesh (as it is per vertex),
-    # but or current mesh/rendering implementation is only designed for a single material
-    # currently this is usually handled by adding the values as shader globals
-    # TODO: this is bad
-    if not mesh[].materials.contains(material):
-      mesh[].materials.add material
-  else:
-    if not mesh[].materials.contains(DEFAULT_MATERIAL):
-      mesh[].materials.add DEFAULT_MATERIAL
-
-  if primitiveNode.hasKey("indices"):
-    assert mesh[].indexType != None
-    let data = root.getAccessorData(root["accessors"][primitiveNode["indices"].getInt()], mainBuffer)
-    let baseIndex = mesh[].vertexCount - vertexCount
-    var tri: seq[int]
-    case data.thetype
-      of UInt16:
-        for entry in getValues[uint16](data)[]:
-          tri.add int(entry) + baseIndex
-          if tri.len == 3:
-            # FYI gltf uses counter-clockwise indexing
-            mesh[].appendIndicesData(tri[0], tri[1], tri[2])
-            tri.setLen(0)
-      of UInt32:
-        for entry in getValues[uint32](data)[]:
-          tri.add int(entry) + baseIndex
-          if tri.len == 3:
-            # FYI gltf uses counter-clockwise indexing
-            mesh[].appendIndicesData(tri[0], tri[1], tri[2])
-            tri.setLen(0)
-      else:
-        raise newException(Exception, &"Unsupported index data type: {data.thetype}")
-
-# TODO: use one mesh per primitive?? right now we are merging primitives... check addPrimitive below
-proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: seq[uint8]): Mesh =
-
-  # check if and how we use indexes
-  var indexCount = 0
   var indexType = None
-  let indexed = meshNode["primitives"][0].hasKey("indices")
+  let indexed = primitiveNode.hasKey("indices")
   if indexed:
-    for primitive in meshNode["primitives"]:
-      indexCount += root["accessors"][primitive["indices"].getInt()]["count"].getInt()
+    # TODO: Tiny indices
+    var indexCount = root["accessors"][primitiveNode["indices"].getInt()]["count"].getInt()
     if indexCount < int(high(uint16)):
       indexType = Small
     else:
@@ -276,37 +223,53 @@
   result = Mesh(
     instanceTransforms: @[Unit4F32],
     indexType: indexType,
-    name: meshNode["name"].getStr()
+    name: meshname,
+    vertexCount: 0,
   )
 
-  # check we have the same attributes for all primitives
-  let attributes = meshNode["primitives"][0]["attributes"].keys.toSeq
-  for primitive in meshNode["primitives"]:
-    assert primitive["attributes"].keys.toSeq == attributes
+  for attribute, accessor in primitiveNode["attributes"].pairs:
+    let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer)
+    if result.vertexCount == 0:
+      result.vertexCount = data.len
+    assert data.len == result.vertexCount
+    result[].initVertexAttribute(attribute.toLowerAscii, data)
 
-  # prepare mesh attributes
-  for attribute, accessor in meshNode["primitives"][0]["attributes"].pairs:
-    result[].initVertexAttribute(attribute.toLowerAscii, root["accessors"][accessor.getInt()].getGPUType(attribute))
-  result[].initVertexAttribute("materialIndex", UInt16)
+  if primitiveNode.hasKey("material"):
+    let materialId = primitiveNode["material"].getInt()
+    result[].material = loadMaterial(root, root["materials"][materialId], mainBuffer)
+  else:
+    result[].material = DEFAULT_MATERIAL
 
-  # add all mesh data
-  for primitive in meshNode["primitives"]:
-    result.addPrimitive(root, primitive, mainBuffer)
+  if primitiveNode.hasKey("indices"):
+    assert result[].indexType != None
+    let data = root.getAccessorData(root["accessors"][primitiveNode["indices"].getInt()], mainBuffer)
+    var tri: seq[int]
+    case data.thetype
+      of UInt16:
+        for entry in getValues[uint16](data)[]:
+          tri.add int(entry)
+          if tri.len == 3:
+            # FYI gltf uses counter-clockwise indexing
+            result[].appendIndicesData(tri[0], tri[1], tri[2])
+            tri.setLen(0)
+      of UInt32:
+        for entry in getValues[uint32](data)[]:
+          tri.add int(entry)
+          if tri.len == 3:
+            # FYI gltf uses counter-clockwise indexing
+            result[].appendIndicesData(tri[0], tri[1], tri[2])
+            tri.setLen(0)
+      else:
+        raise newException(Exception, &"Unsupported index data type: {data.thetype}")
   transform[Vec3f](result[], "position", scale(1, -1, 1))
 
-  var maxMaterialIndex = 0
-  for material in result[].materials:
-    maxMaterialIndex = max(int(material.index), maxMaterialIndex)
-  var materials = result[].materials
-  result[].materials = newSeqWith(maxMaterialIndex + 1, DEFAULT_MATERIAL)
-  for material in materials:
-    result[].materials[material.index] = material
-
 proc loadNode(root: JsonNode, node: JsonNode, mainBuffer: var seq[uint8]): MeshTree =
   result = MeshTree()
   # mesh
   if node.hasKey("mesh"):
-    result.mesh = loadMesh(root, root["meshes"][node["mesh"].getInt()], mainBuffer)
+    let mesh = root["meshes"][node["mesh"].getInt()]
+    for primitive in mesh["primitives"]:
+      result.children.add MeshTree(mesh: loadMesh(mesh["name"].getStr(), root, primitive, mainBuffer))
 
   # transformation
   if node.hasKey("matrix"):
--- a/src/semicongine/scene.nim	Thu Oct 12 14:54:01 2023 +0700
+++ b/src/semicongine/scene.nim	Sat Oct 21 01:05:34 2023 +0700
@@ -5,6 +5,7 @@
 
 import ./core
 import ./mesh
+import ./material
 
 type
   Scene* = object
@@ -44,13 +45,13 @@
 
 proc addShaderGlobal*[T](scene: var Scene, name: string, data: T) =
   assert not scene.loaded, &"Scene {scene.name} has already been loaded, cannot add shader values"
-  scene.shaderGlobals[name] = newDataList(thetype=getDataType[T]())
+  scene.shaderGlobals[name] = initDataList(thetype=getDataType[T]())
   setValues(scene.shaderGlobals[name], @[data])
   scene.dirtyShaderGlobals.add name
 
 proc addShaderGlobalArray*[T](scene: var Scene, name: string, data: openArray[T]) =
   assert not scene.loaded, &"Scene {scene.name} has already been loaded, cannot add shader values"
-  scene.shaderGlobals[name] = newDataList(data)
+  scene.shaderGlobals[name] = initDataList(data)
   scene.dirtyShaderGlobals.add name
 
 func getShaderGlobal*[T](scene: Scene, name: string): T =
@@ -81,5 +82,5 @@
 func `==`*(a, b: Scene): bool =
   a.name == b.name
 
-func usesMaterial*(scene: Scene, materialName: string): bool =
-  return scene.meshes.anyIt(it.materials.anyIt(it.name == materialName))
+func usesMaterial*(scene: Scene, materialType: MaterialType): bool =
+  return scene.meshes.anyIt(it.material.theType == materialType)
--- a/src/semicongine/text.nim	Thu Oct 12 14:54:01 2023 +0700
+++ b/src/semicongine/text.nim	Sat Oct 21 01:05:34 2023 +0700
@@ -27,7 +27,11 @@
   TRANSFORM_ATTRIB = "transform"
   POSITION_ATTRIB = SHADER_ATTRIB_PREFIX & "position"
   UV_ATTRIB = SHADER_ATTRIB_PREFIX & "uv"
-  TEXT_MATERIAL* = "default-text"
+  TEXT_MATERIAL_TYPE* = MaterialType(
+    name: "default-text-material-type",
+    meshAttributes: {TRANSFORM_ATTRIB: Mat4F32, POSITION_ATTRIB: Vec3F32, UV_ATTRIB: Vec2F32}.toTable,
+    attributes: {"fontAtlas": TextureType}.toTable,
+  )
   TEXT_SHADER* = createShaderConfiguration(
     inputs=[
       attr[Mat4](TRANSFORM_ATTRIB, memoryPerformanceHint=PreferFastWrite, perInstance=true),
@@ -110,10 +114,10 @@
   inc instanceCounter
   result.mesh[].renameAttribute("position", POSITION_ATTRIB)
   result.mesh[].renameAttribute("uv", UV_ATTRIB)
-  result.mesh.materials = @[MaterialData(
-    name: TEXT_MATERIAL,
-    textures: {"fontAtlas": font.fontAtlas}.toTable,
-  )]
+  result.mesh.material = MaterialData(
+    name: font.name & " text",
+    attributes: {"fontAtlas": initDataList(@[font.fontAtlas])}.toTable,
+  )
 
   result.updateMesh()
 
--- a/src/semicongine/vulkan/renderpass.nim	Thu Oct 12 14:54:01 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Sat Oct 21 01:05:34 2023 +0700
@@ -1,6 +1,7 @@
 import std/options
 
 import ../core
+import ../material
 import ./device
 import ./physicaldevice
 import ./pipeline
@@ -14,7 +15,7 @@
     flags: VkSubpassDescriptionFlags
     outputs: seq[VkAttachmentReference]
     depthStencil: Option[VkAttachmentReference]
-    pipelines*: seq[(string, Pipeline)]
+    pipelines*: seq[(MaterialType, Pipeline)]
   RenderPass* = object
     vk*: VkRenderPass
     device*: Device
@@ -61,7 +62,7 @@
 
 proc simpleForwardRenderPass*(
   device: Device,
-  shaders: openArray[(string, ShaderConfiguration)],
+  shaders: openArray[(MaterialType, ShaderConfiguration)],
   inFlightFrames=2,
   clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]),
   backFaceCulling=true,
@@ -70,7 +71,7 @@
   {.warning: "Need to implement material -> shader compatability" .}
   
   assert device.vk.valid
-  for (material, shaderconfig) in shaders:
+  for (_, 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:
-    result.subpasses[0].pipelines.add (material, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling=backFaceCulling))
+  for (materialtype, shaderconfig) in shaders:
+    result.subpasses[0].pipelines.add (materialtype, 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 material, pipeline in renderPass.subpasses[i].pipelines.mitems:
+    for _, pipeline in renderPass.subpasses[i].pipelines.mitems:
       pipeline.destroy()
   renderPass.subpasses = @[]