changeset 776:002d9c576756

did: preparations to refactor material system, still tons to do
author Sam <sam@basx.dev>
date Sun, 23 Jul 2023 19:53:10 +0700
parents b4b3143e3626
children 754835bf175e
files src/semicongine.nim src/semicongine/core/buildconfig.nim src/semicongine/engine.nim src/semicongine/material.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/vulkan/descriptor.nim src/semicongine/vulkan/image.nim tests/test_materials.nim tests/test_vulkan_wrapper.nim
diffstat 12 files changed, 164 insertions(+), 94 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -7,6 +7,7 @@
 import semicongine/collision
 import semicongine/scene
 import semicongine/events
+import semicongine/material
 import semicongine/mesh
 import semicongine/renderer
 import semicongine/resources
@@ -21,6 +22,7 @@
 export collision
 export scene
 export events
+export material
 export mesh
 export renderer
 export resources
--- a/src/semicongine/core/buildconfig.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine/core/buildconfig.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -50,7 +50,7 @@
 const CONFIGHOTRELOADINTERVAL* {.intdefine.}: int = 1000
 
 # log level
-const LOGLEVEL {.strdefine.}: string = (when DEBUG: "lvlAll" else: "lvlWarn")
+const LOGLEVEL {.strdefine.}: string = (when DEBUG: "lvlWarn" else: "lvlWarn")
 const ENGINE_LOGLEVEL* = parseEnum[Level](LOGLEVEL)
 
 # resource bundleing settings, need to be configured per project
--- a/src/semicongine/engine.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine/engine.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -106,10 +106,10 @@
     engine.renderer.get.destroy()
   engine.renderer = some(engine.device.initRenderer(renderPass))
 
-proc addScene*(engine: var Engine, scene: Scene, vertexInput: seq[ShaderAttribute], samplers: seq[ShaderAttribute], transformAttribute="transform") =
+proc addScene*(engine: var Engine, scene: Scene, vertexInput: seq[ShaderAttribute], samplers: seq[ShaderAttribute], transformAttribute="transform", materialIndexAttribute="materialIndex") =
   assert transformAttribute == "" or transformAttribute in map(vertexInput, proc(a: ShaderAttribute): string = a.name)
   assert engine.renderer.isSome
-  engine.renderer.get.setupDrawableBuffers(scene, vertexInput, samplers, transformAttribute=transformAttribute)
+  engine.renderer.get.setupDrawableBuffers(scene, vertexInput, samplers, transformAttribute=transformAttribute, materialIndexAttribute=materialIndexAttribute)
 
 proc renderScene*(engine: var Engine, scene: var Scene) =
   assert engine.state == Running
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/semicongine/material.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -0,0 +1,12 @@
+import std/tables
+
+import ./core
+
+type
+  Material* = ref object
+    name*: string
+    index*: int
+    constants*: Table[string, DataValue]
+    textures*: Table[string, Texture]
+
+func `$`*(mat: Material): string = mat.name
--- a/src/semicongine/mesh.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine/mesh.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -8,6 +8,7 @@
 import ./core
 import ./scene
 import ./collision
+import ./material
 
 type
   MeshIndexType* = enum
@@ -18,6 +19,7 @@
   Mesh* = ref object of Component
     instanceCount*: uint32
     instanceTransforms*: seq[Mat4] # this should not reside in data["transform"], as we will use data["transform"] to store the final transformation matrix (as derived from the scene-tree)
+    materials*: seq[string]
     dirtyInstanceTransforms: bool
     data: Table[string, DataList]
     changedAttributes: seq[string]
--- a/src/semicongine/renderer.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine/renderer.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -1,4 +1,6 @@
 import std/options
+import std/sequtils
+import std/enumerate
 import std/tables
 import std/strformat
 import std/strutils
@@ -17,6 +19,7 @@
 
 import ./scene
 import ./mesh
+import ./material
 
 type
   SceneData = object
@@ -28,6 +31,7 @@
     attributeLocation*: Table[string, MemoryPerformanceHint]
     attributeBindingNumber*: Table[string, int]
     transformAttribute: string # name of attribute that is used for per-instance mesh transformation
+    materialIndexAttribute: string # name of attribute that is used for material selection
     entityTransformationCache: Table[Mesh, Mat4] # remembers last transformation, avoid to send GPU-updates if no changes
     descriptorPool*: DescriptorPool
     descriptorSets*: Table[VkPipeline, seq[DescriptorSet]]
@@ -52,12 +56,12 @@
     raise newException(Exception, "Unable to create swapchain")
   result.swapchain = swapchain.get()
 
-proc setupDrawableBuffers*(renderer: var Renderer, scene: Scene, inputs: seq[ShaderAttribute], samplers: seq[ShaderAttribute], transformAttribute="transform") =
+proc setupDrawableBuffers*(renderer: var Renderer, scene: Scene, inputs: seq[ShaderAttribute], samplers: seq[ShaderAttribute], transformAttribute="transform", materialIndexAttribute="materialIndex") =
   assert not (scene in renderer.scenedata)
   const VERTEX_ATTRIB_ALIGNMENT = 4 # used for buffer alignment
-  var data = SceneData()
+  var scenedata = SceneData()
 
-  # when mesh transformation are handled through the scenegraph-transformation, set it up here
+  # if mesh transformation are handled through the scenegraph-transformation, set it up here
   if transformattribute != "":
     var hasTransformAttribute = false
     for input in inputs:
@@ -66,7 +70,17 @@
         assert getDataType[Mat4]() == input.thetype
         hasTransformAttribute = true
     assert hasTransformAttribute
-    data.transformAttribute = transformAttribute
+    scenedata.transformAttribute = transformAttribute
+
+  # check if we have support for material indices, if required
+  if materialIndexAttribute != "":
+    var hasMaterialIndexAttribute = false
+    for input in inputs:
+      if input.name == materialIndexAttribute:
+        assert getDataType[uint16]() == input.thetype
+        hasMaterialIndexAttribute = true
+    assert hasMaterialIndexAttribute
+    scenedata.materialIndexAttribute = materialIndexAttribute
 
   # find all meshes, populate missing attribute values for shader
   var allMeshes: seq[Mesh]
@@ -74,8 +88,16 @@
     allMeshes.add mesh
     for inputAttr in inputs:
       if not mesh.hasDataFor(inputAttr.name):
+        warn(&"Mesh is missing data for shader attribute {inputAttr.name}, auto-filling with empty values")
         mesh.initData(inputAttr)
       assert mesh.dataType(inputAttr.name) == inputAttr.thetype, &"mesh attribute {inputAttr.name} has type {mesh.dataType(inputAttr.name)} but shader expects {inputAttr.thetype}"
+      if scenedata.materialIndexAttribute != "" and inputAttr.name == scenedata.materialIndexAttribute:
+        assert mesh.materials.len == getMeshData[uint16](mesh, scenedata.materialIndexAttribute)[].len
+        for i, material in enumerate(mesh.materials):
+          let matIndex = materialIndex(scene, material)
+          if matIndex < 0:
+            raise newException(Exception, &"Required material '{material}' not available in scene (available are: {scene.materials.toSeq})")
+          updateMeshData[uint16](mesh, scenedata.materialIndexAttribute, uint32(i), uint16(matIndex))
   
   # create index buffer if necessary
   var indicesBufferSize = 0'u64
@@ -91,7 +113,7 @@
         indicesBufferSize += indexAlignment - (indicesBufferSize mod indexAlignment)
       indicesBufferSize += mesh.indexDataSize
   if indicesBufferSize > 0:
-    data.indexBuffer = renderer.device.createBuffer(
+    scenedata.indexBuffer = renderer.device.createBuffer(
       size=indicesBufferSize,
       usage=[VK_BUFFER_USAGE_INDEX_BUFFER_BIT],
       requireMappable=false,
@@ -108,8 +130,8 @@
     perLocationOffsets[hint] = 0
     perLocationSizes[hint] = 0
   for attribute in inputs:
-    data.attributeLocation[attribute.name] = attribute.memoryPerformanceHint
-    data.attributeBindingNumber[attribute.name] = bindingNumber
+    scenedata.attributeLocation[attribute.name] = attribute.memoryPerformanceHint
+    scenedata.attributeBindingNumber[attribute.name] = bindingNumber
     inc bindingNumber
     # setup one buffer per attribute-location-type
     for mesh in allMeshes:
@@ -120,7 +142,7 @@
       perLocationSizes[attribute.memoryPerformanceHint] += mesh.dataSize(attribute.name)
   for memoryPerformanceHint, bufferSize in perLocationSizes.pairs:
     if bufferSize > 0:
-      data.vertexBuffers[memoryPerformanceHint] = renderer.device.createBuffer(
+      scenedata.vertexBuffers[memoryPerformanceHint] = renderer.device.createBuffer(
         size=bufferSize,
         usage=[VK_BUFFER_USAGE_VERTEX_BUFFER_BIT],
         requireMappable=memoryPerformanceHint==PreferFastWrite,
@@ -135,7 +157,7 @@
       offsets.add (attribute.name, attribute.memoryPerformanceHint, perLocationOffsets[attribute.memoryPerformanceHint])
       var (pdata, size) = mesh.getRawData(attribute.name)
       if pdata != nil: # no data
-        data.vertexBuffers[attribute.memoryPerformanceHint].setData(pdata, size, perLocationOffsets[attribute.memoryPerformanceHint])
+        scenedata.vertexBuffers[attribute.memoryPerformanceHint].setData(pdata, size, perLocationOffsets[attribute.memoryPerformanceHint])
         perLocationOffsets[attribute.memoryPerformanceHint] += size
         if perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0:
           perLocationOffsets[attribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT)
@@ -160,15 +182,15 @@
       drawable.indexBufferOffset = indexBufferOffset
       drawable.indexType = mesh.indexType
       var (pdata, size) = mesh.getRawIndexData()
-      data.indexBuffer.setData(pdata, size, indexBufferOffset)
+      scenedata.indexBuffer.setData(pdata, size, indexBufferOffset)
       indexBufferOffset += size
-    data.drawables[mesh] = drawable
+    scenedata.drawables[mesh] = drawable
 
-  for material in scene.getMaterials():
+  for material in scene.materials:
     for textureName, texture in material.textures.pairs:
-      if not data.textures.hasKey(textureName):
-        data.textures[textureName] = @[]
-      data.textures[textureName].add renderer.device.uploadTexture(texture)
+      if not scenedata.textures.hasKey(textureName):
+        scenedata.textures[textureName] = @[]
+      scenedata.textures[textureName].add renderer.device.uploadTexture(texture)
 
   # setup uniforms and samplers
   for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
@@ -177,9 +199,9 @@
       for uniform in pipeline.uniforms:
         uniformBufferSize += uniform.size
       if uniformBufferSize > 0:
-        data.uniformBuffers[pipeline.vk] = newSeq[Buffer]()
+        scenedata.uniformBuffers[pipeline.vk] = newSeq[Buffer]()
         for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
-          data.uniformBuffers[pipeline.vk].add renderer.device.createBuffer(
+          scenedata.uniformBuffers[pipeline.vk].add renderer.device.createBuffer(
             size=uniformBufferSize,
             usage=[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
             requireMappable=true,
@@ -191,20 +213,20 @@
         var samplercount = 0'u32
         for sampler in samplers:
           samplercount += (if sampler.arrayCount == 0: 1'u32 else: sampler.arrayCount)
-        poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, uint32(renderer.swapchain.inFlightFrames) * samplercount)
+        poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, uint32(renderer.swapchain.inFlightFrames) * samplercount * 2)
     
-      data.descriptorPool = renderer.device.createDescriptorSetPool(poolsizes)
+      scenedata.descriptorPool = renderer.device.createDescriptorSetPool(poolsizes)
   
-      data.descriptorSets[pipeline.vk] = pipeline.setupDescriptors(
-        data.descriptorPool,
-        data.uniformBuffers.getOrDefault(pipeline.vk, @[]),
-        data.textures,
+      scenedata.descriptorSets[pipeline.vk] = pipeline.setupDescriptors(
+        scenedata.descriptorPool,
+        scenedata.uniformBuffers.getOrDefault(pipeline.vk, @[]),
+        scenedata.textures,
         inFlightFrames=renderer.swapchain.inFlightFrames
       )
       for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
-        data.descriptorSets[pipeline.vk][frame_i].writeDescriptorSet()
+        scenedata.descriptorSets[pipeline.vk][frame_i].writeDescriptorSet()
 
-  renderer.scenedata[scene] = data
+  renderer.scenedata[scene] = scenedata
 
 proc refreshMeshAttributeData(sceneData: var SceneData, mesh: Mesh, attribute: string) =
   debug &"Refreshing data on mesh {mesh} for {attribute}"
@@ -311,20 +333,20 @@
   renderer.device.vk.valid
 
 proc destroy*(renderer: var Renderer) =
-  for data in renderer.scenedata.mvalues:
-    for buffer in data.vertexBuffers.mvalues:
+  for scenedata in renderer.scenedata.mvalues:
+    for buffer in scenedata.vertexBuffers.mvalues:
       assert buffer.vk.valid
       buffer.destroy()
-    if data.indexBuffer.vk.valid:
-      assert data.indexBuffer.vk.valid
-      data.indexBuffer.destroy()
-    for pipelineUniforms in data.uniformBuffers.mvalues:
+    if scenedata.indexBuffer.vk.valid:
+      assert scenedata.indexBuffer.vk.valid
+      scenedata.indexBuffer.destroy()
+    for pipelineUniforms in scenedata.uniformBuffers.mvalues:
       for buffer in pipelineUniforms.mitems:
         assert buffer.vk.valid
         buffer.destroy()
-    for textures in data.textures.mvalues:
+    for textures in scenedata.textures.mvalues:
       for texture in textures.mitems:
         texture.destroy()
-    data.descriptorPool.destroy()
+    scenedata.descriptorPool.destroy()
   renderer.renderPass.destroy()
   renderer.swapchain.destroy()
--- a/src/semicongine/resources/mesh.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine/resources/mesh.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -1,4 +1,5 @@
 import std/strutils
+import std/enumerate
 import std/json
 import std/logging
 import std/tables
@@ -8,6 +9,7 @@
 
 import ../scene
 import ../mesh
+import ../material
 import ../core
 
 import ./image
@@ -161,7 +163,7 @@
       else:
         raise newException(Exception, &"Unsupported index data type: {data.thetype}")
 
-proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: var seq[uint8]): Mesh =
+proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: var seq[uint8], materials: seq[Material]): Mesh =
   result = Mesh(instanceCount: 1, instanceTransforms: newSeqWith(1, Unit4F32))
 
   # check if and how we use indexes
@@ -193,7 +195,7 @@
 
   setInstanceData(result, "transform", newSeqWith(int(result.instanceCount), Unit4F32))
 
-proc loadNode(root: JsonNode, node: JsonNode, mainBuffer: var seq[uint8]): Entity =
+proc loadNode(root: JsonNode, node: JsonNode, mainBuffer: var seq[uint8], materials: seq[Material]): Entity =
   var name = "<Unknown>"
   if node.hasKey("name"):
     name = node["name"].getStr()
@@ -233,16 +235,16 @@
   # children
   if node.hasKey("children"):
     for childNode in node["children"]:
-      result.add loadNode(root, root["nodes"][childNode.getInt()], mainBuffer)
+      result.add loadNode(root, root["nodes"][childNode.getInt()], mainBuffer, materials)
 
   # mesh
   if node.hasKey("mesh"):
-    result["mesh"] = loadMesh(root, root["meshes"][node["mesh"].getInt()], mainBuffer)
+    result["mesh"] = loadMesh(root, root["meshes"][node["mesh"].getInt()], mainBuffer, materials)
 
-proc loadScene(root: JsonNode, scenenode: JsonNode, mainBuffer: var seq[uint8]): Scene =
+proc loadScene(root: JsonNode, scenenode: JsonNode, mainBuffer: var seq[uint8], materials: seq[Material]): Scene =
   var rootEntity = newEntity("<root>")
   for nodeId in scenenode["nodes"]:
-    var node = loadNode(root, root["nodes"][nodeId.getInt()], mainBuffer)
+    var node = loadNode(root, root["nodes"][nodeId.getInt()], mainBuffer, materials)
     node.transform = node.transform * scale3d(1'f32, -1'f32, 1'f32)
     rootEntity.add node
 
@@ -280,63 +282,63 @@
     if sampler.hasKey("wrapT"):
       result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
 
-proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: var seq[uint8]): Material =
-  result.name = materialNode.getStr("name")
+proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: var seq[uint8], materialIndex: int): Material =
+  result = Material(name: materialNode.getStr("name"), index: materialIndex)
 
   let pbr = materialNode["pbrMetallicRoughness"]
 
   # color
-  result.data["baseColorFactor"] = DataValue(thetype: Vec4F32)
+  result.constants["baseColorFactor"] = DataValue(thetype: Vec4F32)
   if pbr.hasKey("baseColorFactor"):
-    setValue(result.data["baseColorFactor"], newVec4f(
+    setValue(result.constants["baseColorFactor"], newVec4f(
       pbr["baseColorFactor"][0].getFloat(),
       pbr["baseColorFactor"][1].getFloat(),
       pbr["baseColorFactor"][2].getFloat(),
       pbr["baseColorFactor"][3].getFloat(),
     ))
   else:
-    setValue(result.data["baseColorFactor"], newVec4f(1, 1, 1, 1))
+    setValue(result.constants["baseColorFactor"], newVec4f(1, 1, 1, 1))
 
   # pbr material constants
   for factor in ["metallicFactor", "roughnessFactor"]:
-    result.data[factor] = DataValue(thetype: Float32)
+    result.constants[factor] = DataValue(thetype: Float32)
     if pbr.hasKey(factor):
-      setValue(result.data[factor], float32(pbr[factor].getFloat()))
+      setValue(result.constants[factor], float32(pbr[factor].getFloat()))
     else:
-      setValue(result.data[factor], 0.5'f32)
+      setValue(result.constants[factor], 0.5'f32)
 
   # pbr material textures
   for texture in ["baseColorTexture", "metallicRoughnessTexture"]:
     if pbr.hasKey(texture):
       result.textures[texture] = loadTexture(root, pbr[texture]["index"].getInt(), mainBuffer)
-      result.data[texture & "Index"] = DataValue(thetype: UInt8)
-      setValue(result.data[texture & "Index"], pbr[texture].getOrDefault("texCoord").getInt(0).uint8)
+      result.constants[texture & "Index"] = DataValue(thetype: UInt8)
+      setValue(result.constants[texture & "Index"], pbr[texture].getOrDefault("texCoord").getInt(0).uint8)
     else:
       result.textures[texture] = DEFAULTEXTURE
-      result.data[texture & "Index"] = DataValue(thetype: UInt8)
-      setValue(result.data[texture & "Index"], 0'u8)
+      result.constants[texture & "Index"] = DataValue(thetype: UInt8)
+      setValue(result.constants[texture & "Index"], 0'u8)
 
   # generic material textures
   for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]:
     if materialNode.hasKey(texture):
       result.textures[texture] = loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)
-      result.data[texture & "Index"] = DataValue(thetype: UInt8)
-      setValue(result.data[texture & "Index"], materialNode[texture].getOrDefault("texCoord").getInt(0).uint8)
+      result.constants[texture & "Index"] = DataValue(thetype: UInt8)
+      setValue(result.constants[texture & "Index"], materialNode[texture].getOrDefault("texCoord").getInt(0).uint8)
     else:
       result.textures[texture] = DEFAULTEXTURE
-      result.data[texture & "Index"] = DataValue(thetype: UInt8)
-      setValue(result.data[texture & "Index"], 0'u8)
+      result.constants[texture & "Index"] = DataValue(thetype: UInt8)
+      setValue(result.constants[texture & "Index"], 0'u8)
 
   # emissiv color
-  result.data["emissiveFactor"] = DataValue(thetype: Vec3F32)
+  result.constants["emissiveFactor"] = DataValue(thetype: Vec3F32)
   if materialNode.hasKey("emissiveFactor"):
-    setValue(result.data["emissiveFactor"], newVec3f(
+    setValue(result.constants["emissiveFactor"], newVec3f(
       materialNode["emissiveFactor"][0].getFloat(),
       materialNode["emissiveFactor"][1].getFloat(),
       materialNode["emissiveFactor"][2].getFloat(),
     ))
   else:
-    setValue(result.data["emissiveFactor"], newVec3f(1'f32, 1'f32, 1'f32))
+    setValue(result.constants["emissiveFactor"], newVec3f(1'f32, 1'f32, 1'f32))
 
 proc readglTF*(stream: Stream): seq[Scene] =
   var
@@ -368,8 +370,9 @@
   debug data.structuredContent.pretty
 
   for scene in data.structuredContent["scenes"]:
-    var scene = data.structuredContent.loadScene(scene, data.binaryBufferData)
-    for materialNode in data.structuredContent["materials"]:
-      scene.addMaterial loadMaterial(data.structuredContent, materialNode, data.binaryBufferData)
+    var materials: seq[Material]
+    for i, materialNode in enumerate(data.structuredContent["materials"]):
+      materials.add loadMaterial(data.structuredContent, materialNode, data.binaryBufferData, i)
+    var scene = data.structuredContent.loadScene(scene, data.binaryBufferData, materials)
 
     result.add scene
--- a/src/semicongine/scene.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine/scene.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -7,6 +7,7 @@
 import std/typetraits
 
 import ./core
+import ./material
 import ./animation
 
 type
@@ -14,12 +15,14 @@
     name*: string
     root*: Entity
     shaderGlobals*: Table[string, DataList]
-    materials: seq[Material]
+    materials: OrderedTable[string, Material]
 
+  #[
   Material* = object
     name*: string
     textures*: Table[string, Texture]
     data*: Table[string, DataValue]
+  ]#
 
   Component* = ref object of RootObj
     entity*: Entity
@@ -109,19 +112,25 @@
 func newScene*(name: string, root: Entity): Scene =
   Scene(name: name, root: root)
 
-func getMaterials*(scene: Scene): seq[Material] = scene.materials
+func addMaterial*(scene: var Scene, material: Material) =
+  assert not scene.materials.contains(material.name), &"Material with name '{material.name}' already exists in scene"
+  for name, value in material.constants.pairs:
+    scene.shaderGlobals[name] = newDataList(thetype=value.thetype)
 
-func addMaterial*(scene: var Scene, material: Material) =
-  if scene.materials.len > 0:
-    assert material.data.keys.toSeq.sorted() == scene.materials[0].data.keys.toSeq.sorted(), &"{material.data.keys.toSeq.sorted()} == {scene.materials[0].data.keys.toSeq.sorted()}"
-  else:
-    for name, value in material.data.pairs:
-      scene.shaderGlobals[name] = newDataList(thetype=value.thetype)
-
-  for name, value in material.data.pairs:
+  for name, value in material.constants.pairs:
     scene.shaderGlobals[name].appendValue(value)
 
-  scene.materials.add material
+  scene.materials[material.name] = material
+
+func materialIndex*(scene: Scene, materialName: string): int =
+  for name in scene.materials.keys:
+    if name == materialName:
+      return result
+    inc result 
+  return -1
+
+func materials*(scene: Scene): auto =
+  scene.materials.values.toSeq
 
 func hash*(scene: Scene): Hash =
   hash(scene.name)
--- a/src/semicongine/vulkan/descriptor.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine/vulkan/descriptor.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -106,7 +106,6 @@
   assert layout.device.vk.valid
   assert layout.vk.valid
 
-
   var layouts: seq[VkDescriptorSetLayout]
   var descriptorSets = newSeq[VkDescriptorSet](nframes)
   for i in 0 ..< nframes:
@@ -132,6 +131,9 @@
   var bufferInfos: seq[VkDescriptorBufferInfo]
 
   var i = bindingBase
+  # need to keep this sequence out of the loop, otherwise it will be 
+  # gc-ed before the final update call and pointers are invalid :(
+  var imgInfos: seq[seq[VkDescriptorImageInfo]]
   for descriptor in descriptorSet.layout.descriptors:
     if descriptor.thetype == Uniform:
       assert descriptor.buffer.vk.valid
@@ -149,6 +151,7 @@
           descriptorCount: descriptor.count,
           pBufferInfo: addr bufferInfos[^1],
         )
+      echo bufferInfos
     elif descriptor.thetype == ImageSampler:
       var imgInfo: seq[VkDescriptorImageInfo]
       for img_i in 0 ..< descriptor.count:
@@ -159,6 +162,7 @@
           imageView: descriptor.imageviews[img_i].vk,
           sampler: descriptor.samplers[img_i].vk,
         )
+      imgInfos.add imgInfo
       descriptorSetWrites.add VkWriteDescriptorSet(
           sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
           dstSet: descriptorSet.vk,
@@ -166,7 +170,8 @@
           dstArrayElement: 0,
           descriptorType: descriptor.vkType,
           descriptorCount: descriptor.count,
-          pImageInfo: addr imgInfo[0],
+          pImageInfo: imgInfos[^1].toCPointer,
         )
     inc i
+  echo descriptorSetWrites
   descriptorSet.layout.device.vk.vkUpdateDescriptorSets(uint32(descriptorSetWrites.len), descriptorSetWrites.toCPointer, 0, nil)
--- a/src/semicongine/vulkan/image.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/src/semicongine/vulkan/image.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -64,7 +64,7 @@
     preferAutoFlush=preferAutoFlush
   )
   image.memoryAllocated = true
-  debug "Allocating memory for image: ", image.width, "x", image.height, "x", image.depth, " bytes of type ", memoryType
+  debug "Allocating memory for image: ", image.width, "x", image.height, "x", image.depth, ", ", requirements.size, " bytes of type ", memoryType
   image.memory = image.device.allocate(requirements.size, memoryType)
   checkVkResult image.device.vk.vkBindImageMemory(image.vk, image.memory.vk, VkDeviceSize(0))
 
--- a/tests/test_materials.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/tests/test_materials.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -4,11 +4,12 @@
 import semicongine
 
 proc main() =
-  var scene = newScene("main", root=newEntity("rect", {"mesh": Component(rect())}))
+  var flag = rect()
+  var scene = newScene("main", root=newEntity("rect", {"mesh": Component(flag)}))
   let (RT, WT, PT) = (hexToColorAlpha("A51931").asPixel, hexToColorAlpha("F4F5F8").asPixel, hexToColorAlpha("2D2A4A").asPixel)
   let
     # image from memory
-    t1 = Image(width: 7, height: 5, imagedata: @[
+    thai = Image(width: 7, height: 5, imagedata: @[
       RT, RT, RT, RT, RT, RT, RT,
       WT, WT, WT, WT, WT, WT, WT,
       PT, PT, PT, PT, PT, PT, PT,
@@ -17,13 +18,19 @@
     ])
   let
     # image from file
-    t2 = loadImage("flag.png")
+    swiss = loadImage("flag.png")
 
   var sampler = DefaultSampler()
   sampler.magnification = VK_FILTER_NEAREST
   sampler.minification = VK_FILTER_NEAREST
-  scene.addMaterial(Material(name:"my material", textures: {"my_texture": Texture(image: t1, sampler: sampler)}.toTable))
-  scene.addMaterial(Material(name:"my material", textures: {"my_texture": Texture(image: t2, sampler: sampler)}.toTable))
+  scene.addMaterial(Material(name:"material1", textures: {
+    "swissflag": Texture(image: swiss, sampler: sampler),
+    "thaiflag": Texture(image: thai, sampler: sampler),
+  }.toTable))
+  scene.addMaterial(Material(name:"material2", textures: {
+    "swissflag": Texture(image: thai, sampler: sampler),
+    "thaiflag": Texture(image: swiss, sampler: sampler),
+  }.toTable))
   scene.addShaderGlobalArray("test2", @[0'f32, 0'f32])
 
   var engine = initEngine("Test materials")
@@ -35,7 +42,10 @@
     ]
     vertexOutput = @[attr[Vec2f]("uvout")]
     uniforms = @[attr[float32]("test2", arrayCount=2)]
-    samplers = @[attr[Sampler2DType]("my_texture", arrayCount=2)]
+    samplers = @[
+      attr[Sampler2DType]("swissflag", arrayCount=2),
+      attr[Sampler2DType]("thaiflag", arrayCount=2),
+    ]
     fragOutput = @[attr[Vec4f]("color")]
     vertexCode = compileGlslShader(
       stage=VK_SHADER_STAGE_VERTEX_BIT,
@@ -53,11 +63,11 @@
       outputs=fragOutput,
       main="""
 float d = sin(Uniforms.test2[0]) * 0.5 + 0.5;
-color = texture(my_texture[0], uvout) * (1 - d) + texture(my_texture[1], uvout) * d;
+color = texture(swissflag[1], uvout) * (1 - d) + texture(thaiflag[1], uvout) * d;
 """
     )
   engine.setRenderer(engine.gpuDevice.simpleForwardRenderPass(vertexCode, fragmentCode))
-  engine.addScene(scene, vertexInput, samplers, transformAttribute="")
+  engine.addScene(scene, vertexInput, samplers, transformAttribute="", materialIndexAttribute="")
   var t = cpuTime()
   while engine.updateInputs() == Running and not engine.keyIsDown(Escape):
     var d = float32(cpuTime() - t)
--- a/tests/test_vulkan_wrapper.nim	Sun Jul 09 17:40:46 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Sun Jul 23 19:53:10 2023 +0700
@@ -126,14 +126,19 @@
   for scene in scenes.mitems:
     scene.addShaderGlobal("time", 0.0'f32)
     let (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8])
-    scene.addMaterial(Material(name: "my_material", textures: {"my_little_texture": Texture(image: Image(width: 5, height: 5, imagedata: @[
-      R, R, R, R, R,
-      R, R, W, R, R,
-      R, W, W, W, R,
-      R, R, W, R, R,
-      R, R, R, R, R,
-    ]), sampler: sampler)}.toTable))
-    engine.addScene(scene, vertexInput, samplers, transformAttribute="")
+    scene.addMaterial(Material(
+      name: "my_material",
+      textures: {
+        "my_little_texture": Texture(image: Image(width: 5, height: 5, imagedata: @[
+        R, R, R, R, R,
+        R, R, W, R, R,
+        R, W, W, W, R,
+        R, R, W, R, R,
+        R, R, R, R, R,
+        ]), sampler: sampler)
+      }.toTable
+    ))
+    engine.addScene(scene, vertexInput, samplers, transformAttribute="", materialIndexAttribute="")
 
   # MAINLOOP
   echo "Setup successfull, start rendering"