changeset 266:fd1a95f433d1

add: better material loading system, still far from great
author Sam <sam@basx.dev>
date Sun, 28 May 2023 17:52:03 +0700
parents ad4f2b45410b
children a107d9ef1a1f
files src/semicongine/core/gpu_data.nim src/semicongine/core/imagetypes.nim src/semicongine/renderer.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/vulkan/pipeline.nim
diffstat 6 files changed, 160 insertions(+), 131 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/core/gpu_data.nim	Sat May 27 13:45:03 2023 +0700
+++ b/src/semicongine/core/gpu_data.nim	Sun May 28 17:52:03 2023 +0700
@@ -806,6 +806,54 @@
   of Mat4F64: value.mat4f64[].add data.mat4f64[]
   else: raise newException(Exception, &"Unsupported data type for GPU data:" )
 
+func appendValue*(value: var DataList, data: DataValue) =
+  assert value.thetype == data.thetype
+  value.len += 1
+  case value.thetype:
+  of Float32: value.float32[].add data.float32
+  of Float64: value.float64[].add data.float64
+  of Int8: value.int8[].add data.int8
+  of Int16: value.int16[].add data.int16
+  of Int32: value.int32[].add data.int32
+  of Int64: value.int64[].add data.int64
+  of UInt8: value.uint8[].add data.uint8
+  of UInt16: value.uint16[].add data.uint16
+  of UInt32: value.uint32[].add data.uint32
+  of UInt64: value.uint64[].add data.uint64
+  of Vec2I32: value.vec2i32[].add data.vec2i32
+  of Vec2I64: value.vec2i64[].add data.vec2i64
+  of Vec3I32: value.vec3i32[].add data.vec3i32
+  of Vec3I64: value.vec3i64[].add data.vec3i64
+  of Vec4I32: value.vec4i32[].add data.vec4i32
+  of Vec4I64: value.vec4i64[].add data.vec4i64
+  of Vec2U32: value.vec2u32[].add data.vec2u32
+  of Vec2U64: value.vec2u64[].add data.vec2u64
+  of Vec3U32: value.vec3u32[].add data.vec3u32
+  of Vec3U64: value.vec3u64[].add data.vec3u64
+  of Vec4U32: value.vec4u32[].add data.vec4u32
+  of Vec4U64: value.vec4u64[].add data.vec4u64
+  of Vec2F32: value.vec2f32[].add data.vec2f32
+  of Vec2F64: value.vec2f64[].add data.vec2f64
+  of Vec3F32: value.vec3f32[].add data.vec3f32
+  of Vec3F64: value.vec3f64[].add data.vec3f64
+  of Vec4F32: value.vec4f32[].add data.vec4f32
+  of Vec4F64: value.vec4f64[].add data.vec4f64
+  of Mat2F32: value.mat2f32[].add data.mat2f32
+  of Mat2F64: value.mat2f64[].add data.mat2f64
+  of Mat23F32: value.mat23f32[].add data.mat23f32
+  of Mat23F64: value.mat23f64[].add data.mat23f64
+  of Mat32F32: value.mat32f32[].add data.mat32f32
+  of Mat32F64: value.mat32f64[].add data.mat32f64
+  of Mat3F32: value.mat3f32[].add data.mat3f32
+  of Mat3F64: value.mat3f64[].add data.mat3f64
+  of Mat34F32: value.mat34f32[].add data.mat34f32
+  of Mat34F64: value.mat34f64[].add data.mat34f64
+  of Mat43F32: value.mat43f32[].add data.mat43f32
+  of Mat43F64: value.mat43f64[].add data.mat43f64
+  of Mat4F32: value.mat4f32[].add data.mat4f32
+  of Mat4F64: value.mat4f64[].add data.mat4f64
+  else: raise newException(Exception, &"Unsupported data type for GPU data:" )
+
 func setValue*[T: GPUType|int|uint|float](value: var DataList, i: uint32, data: T) =
   assert i < value.len
   when T is float32: value.float32[][i] = data
--- a/src/semicongine/core/imagetypes.nim	Sat May 27 13:45:03 2023 +0700
+++ b/src/semicongine/core/imagetypes.nim	Sun May 28 17:52:03 2023 +0700
@@ -15,6 +15,7 @@
 
   Image* = ref ImageObject
   Texture* = object
+    name*: string
     image*: Image
     sampler*: Sampler
 
--- a/src/semicongine/renderer.nim	Sat May 27 13:45:03 2023 +0700
+++ b/src/semicongine/renderer.nim	Sun May 28 17:52:03 2023 +0700
@@ -17,14 +17,13 @@
 
 import ./scene
 import ./mesh
-import ./text
 
 type
   SceneData = object
     drawables*: OrderedTable[Mesh, Drawable]
     vertexBuffers*: Table[MemoryPerformanceHint, Buffer]
     indexBuffer*: Buffer
-    uniformBuffers*: seq[Buffer] # one per frame-in-flight
+    uniformBuffers*: Table[VkPipeline, seq[Buffer]] # one per frame-in-flight
     textures*: Table[string, seq[VulkanTexture]] # per frame-in-flight
     attributeLocation*: Table[string, MemoryPerformanceHint]
     attributeBindingNumber*: Table[string, int]
@@ -164,6 +163,7 @@
       indexBufferOffset += size
     data.drawables[mesh] = drawable
 
+  #[
   # extract textures
   var sampler = DefaultSampler()
   sampler.magnification = VK_FILTER_NEAREST
@@ -174,11 +174,13 @@
       data.textures[textbox.font.name] = @[
         renderer.device.uploadTexture(Texture(image: textbox.font.fontAtlas, sampler: sampler))
       ]
+  ]#
 
-  for name, textures in scene.textures.pairs:
-    data.textures[name] = @[]
-    for texture in textures:
-      data.textures[name].add renderer.device.uploadTexture(texture)
+  for material in scene.getMaterials():
+    for textureName, texture in material.textures.pairs:
+      if not data.textures.hasKey(textureName):
+        data.textures[textureName] = @[]
+      data.textures[textureName].add renderer.device.uploadTexture(texture)
 
   # setup uniforms and samplers
   for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
@@ -187,8 +189,9 @@
       for uniform in pipeline.uniforms:
         uniformBufferSize += uniform.size
       if uniformBufferSize > 0:
+        data.uniformBuffers[pipeline.vk] = newSeq[Buffer]()
         for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
-          data.uniformBuffers.add renderer.device.createBuffer(
+          data.uniformBuffers[pipeline.vk].add renderer.device.createBuffer(
             size=uniformBufferSize,
             usage=[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
             requireMappable=true,
@@ -204,7 +207,7 @@
     
       data.descriptorPool = renderer.device.createDescriptorSetPool(poolsizes)
   
-      data.descriptorSets[pipeline.vk] = pipeline.setupDescriptors(data.descriptorPool, data.uniformBuffers, data.textures, inFlightFrames=renderer.swapchain.inFlightFrames)
+      data.descriptorSets[pipeline.vk] = pipeline.setupDescriptors(data.descriptorPool, data.uniformBuffers[pipeline.vk], data.textures, inFlightFrames=renderer.swapchain.inFlightFrames)
       for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
         data.descriptorSets[pipeline.vk][frame_i].writeDescriptorSet()
 
@@ -241,18 +244,19 @@
 proc updateUniformData*(renderer: var Renderer, scene: var Scene) =
   assert scene in renderer.scenedata
 
-  if renderer.scenedata[scene].uniformBuffers.len == 0:
-    return
-  assert renderer.scenedata[scene].uniformBuffers[renderer.swapchain.currentInFlight].vk.valid
-
   for i in 0 ..< renderer.renderPass.subpasses.len:
     for pipeline in renderer.renderPass.subpasses[i].pipelines.mitems:
-      var offset = 0'u64
-      for uniform in pipeline.uniforms:
-        assert uniform.thetype == scene.shaderGlobals[uniform.name].thetype
-        let (pdata, size) = scene.shaderGlobals[uniform.name].getRawData()
-        renderer.scenedata[scene].uniformBuffers[renderer.swapchain.currentInFlight].setData(pdata, size, offset)
-        offset += size
+      if renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0:
+        assert renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].vk.valid
+        var offset = 0'u64
+        for uniform in pipeline.uniforms:
+          if not scene.shaderGlobals.hasKey(uniform.name):
+            raise newException(Exception, &"Uniform '{uniform.name}' not found in scene shaderGlobals")
+          if uniform.thetype != scene.shaderGlobals[uniform.name].thetype:
+            raise newException(Exception, &"Uniform '{uniform.name}' has wrong type {uniform.thetype}, required is {scene.shaderGlobals[uniform.name].thetype}")
+          let (pdata, size) = scene.shaderGlobals[uniform.name].getRawData()
+          renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].setData(pdata, size, offset)
+          offset += size
 
 proc render*(renderer: var Renderer, scene: var Scene) =
   assert scene in renderer.scenedata
@@ -313,9 +317,10 @@
     if data.indexBuffer.vk.valid:
       assert data.indexBuffer.vk.valid
       data.indexBuffer.destroy()
-    for buffer in data.uniformBuffers.mitems:
-      assert buffer.vk.valid
-      buffer.destroy()
+    for pipelineUniforms in data.uniformBuffers.mvalues:
+      for buffer in pipelineUniforms.mitems:
+        assert buffer.vk.valid
+        buffer.destroy()
     for textures in data.textures.mvalues:
       for texture in textures.mitems:
         texture.destroy()
--- a/src/semicongine/resources/mesh.nim	Sat May 27 13:45:03 2023 +0700
+++ b/src/semicongine/resources/mesh.nim	Sun May 28 17:52:03 2023 +0700
@@ -1,5 +1,4 @@
 import std/strutils
-import std/options
 import std/json
 import std/logging
 import std/tables
@@ -13,6 +12,8 @@
 
 import ./image
 
+let DEFAULTEXTURE = Texture(image: newImage(1, 1, @[[255'u8, 255'u8, 255'u8, 255'u8]]), sampler: DefaultSampler())
+
 type
   glTFHeader = object
     magic: uint32
@@ -21,21 +22,6 @@
   glTFData = object
     structuredContent: JsonNode
     binaryBufferData: seq[uint8]
-  glTFMaterial = object
-    color: Vec4f
-    colorTexture: Option[Texture]
-    colorTextureIndex: uint32
-    metallic: float32
-    roughness: float32
-    metallicRoughnessTexture: Option[Texture]
-    metallicRoughnessTextureIndex: uint32
-    normalTexture: Option[Texture]
-    normalTextureIndex: uint32
-    occlusionTexture: Option[Texture]
-    occlusionTextureIndex: uint32
-    emissiveTexture: Option[Texture]
-    emissiveTextureIndex: uint32
-    emissiveFactor: Vec3f
 
 const
   JSON_CHUNK = 0x4E4F534A
@@ -287,40 +273,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]): glTFMaterial =
-  let defaultMaterial = glTFMaterial(color: newVec4f(1, 1, 1, 1))
-  result = defaultMaterial
+proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: var seq[uint8]): Material =
+  result.name = materialNode.getStr("name")
+
   let pbr = materialNode["pbrMetallicRoughness"]
+
+  # color
+  result.data["baseColorFactor"] = DataValue(thetype: Vec4F32)
   if pbr.hasKey("baseColorFactor"):
-    result.color[0] = pbr["baseColorFactor"][0].getFloat()
-    result.color[1] = pbr["baseColorFactor"][1].getFloat()
-    result.color[2] = pbr["baseColorFactor"][2].getFloat()
-    result.color[3] = pbr["baseColorFactor"][3].getFloat()
-  if pbr.hasKey("baseColorTexture"):
-    result.colorTexture = some(loadTexture(root, pbr["baseColorTexture"]["index"].getInt(), mainBuffer))
-    result.colorTextureIndex = pbr["baseColorTexture"].getOrDefault("texCoord").getInt(0).uint32
-  if pbr.hasKey("metallicRoughnessTexture"):
-    result.metallicRoughnessTexture = some(loadTexture(root, pbr["metallicRoughnessTexture"]["index"].getInt(), mainBuffer))
-    result.metallicRoughnessTextureIndex = pbr["metallicRoughnessTexture"].getOrDefault("texCoord").getInt().uint32
-  if pbr.hasKey("metallicFactor"):
-    result.metallic = pbr["metallicFactor"].getFloat()
-  if pbr.hasKey("roughnessFactor"):
-    result.roughness= pbr["roughnessFactor"].getFloat()
+    setValue(result.data["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))
+
+  # pbr material constants
+  for factor in ["metallicFactor", "roughnessFactor"]:
+    result.data[factor] = DataValue(thetype: Float32)
+    if pbr.hasKey(factor):
+      setValue(result.data[factor], float32(pbr[factor].getFloat()))
+    else:
+      setValue(result.data[factor], 0.5'f32)
 
-  if materialNode.hasKey("normalTexture"):
-    result.normalTexture = some(loadTexture(root, materialNode["normalTexture"]["index"].getInt(), mainBuffer))
-    result.metallicRoughnessTextureIndex = materialNode["normalTexture"].getOrDefault("texCoord").getInt().uint32
-  if materialNode.hasKey("occlusionTexture"):
-    result.occlusionTexture = some(loadTexture(root, materialNode["occlusionTexture"]["index"].getInt(), mainBuffer))
-    result.occlusionTextureIndex = materialNode["occlusionTexture"].getOrDefault("texCoord").getInt().uint32
-  if materialNode.hasKey("emissiveTexture"):
-    result.emissiveTexture = some(loadTexture(root, materialNode["emissiveTexture"]["index"].getInt(), mainBuffer))
-    result.occlusionTextureIndex = materialNode["emissiveTexture"].getOrDefault("texCoord").getInt().uint32
-  if materialNode.hasKey("roughnessFactor"):
-    result.roughness = materialNode["roughnessFactor"].getFloat()
+  # 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)
+    else:
+      result.textures[texture] = DEFAULTEXTURE
+      result.data[texture & "Index"] = DataValue(thetype: UInt8)
+      setValue(result.data[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)
+    else:
+      result.textures[texture] = DEFAULTEXTURE
+      result.data[texture & "Index"] = DataValue(thetype: UInt8)
+      setValue(result.data[texture & "Index"], 0'u8)
+
+  # emissiv color
+  result.data["emissiveFactor"] = DataValue(thetype: Vec3F32)
   if materialNode.hasKey("emissiveFactor"):
-    let em = materialNode["emissiveFactor"]
-    result.emissiveFactor = newVec3f(em[0].getFloat(), em[1].getFloat(), em[2].getFloat())
+    setValue(result.data["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))
 
 proc readglTF*(stream: Stream): seq[Scene] =
   var
@@ -353,60 +362,7 @@
 
   for scene in data.structuredContent["scenes"]:
     var scene = data.structuredContent.loadScene(scene, data.binaryBufferData)
-    var
-      color: seq[Vec4f]
-      colorTexture: seq[Texture]
-      colorTextureIndex: seq[uint32]
-      metallic: seq[float32]
-      roughness: seq[float32]
-      metallicRoughnessTexture: seq[Texture]
-      metallicRoughnessTextureIndex: seq[uint32]
-      normalTexture: seq[Texture]
-      normalTextureIndex: seq[uint32]
-      occlusionTexture: seq[Texture]
-      occlusionTextureIndex: seq[uint32]
-      emissiveTexture: seq[Texture]
-      emissiveTextureIndex: seq[uint32]
-      emissiveFactor: seq[Vec3f]
     for materialNode in data.structuredContent["materials"]:
-      let m = loadMaterial(data.structuredContent, materialNode, data.binaryBufferData)
-      color.add m.color
-      if not m.colorTexture.isSome:
-        colorTexture.add m.colorTexture.get
-        colorTextureIndex.add m.colorTextureIndex
-      metallic.add m.metallic
-      roughness.add m.roughness
-      if not m.metallicRoughnessTexture.isSome:
-        metallicRoughnessTexture.add m.metallicRoughnessTexture.get
-        metallicRoughnessTextureIndex.add m.metallicRoughnessTextureIndex
-      if not m.normalTexture.isSome:
-        normalTexture.add m.normalTexture.get
-        normalTextureIndex.add m.normalTextureIndex
-      if not m.occlusionTexture.isSome:
-        occlusionTexture.add m.occlusionTexture.get
-        occlusionTextureIndex.add m.occlusionTextureIndex
-      if not m.emissiveTexture.isSome:
-        emissiveTexture.add m.emissiveTexture.get
-        emissiveTextureIndex.add m.emissiveTextureIndex
-      emissiveFactor.add m.emissiveFactor
-
-    # material constants
-    if color.len > 0: scene.addShaderGlobalArray("material_color", color)
-    if colorTextureIndex.len > 0: scene.addShaderGlobalArray("material_color_texture_index", colorTextureIndex)
-    if metallic.len > 0: scene.addShaderGlobalArray("material_metallic", metallic)
-    if roughness.len > 0: scene.addShaderGlobalArray("material_roughness", roughness)
-    if metallicRoughnessTextureIndex.len > 0: scene.addShaderGlobalArray("material_metallic_roughness_texture_index", metallicRoughnessTextureIndex)
-    if normalTextureIndex.len > 0: scene.addShaderGlobalArray("material_normal_texture_index", normalTextureIndex)
-    if occlusionTextureIndex.len > 0: scene.addShaderGlobalArray("material_occlusion_texture_index", occlusionTextureIndex)
-    if emissiveTextureIndex.len > 0: scene.addShaderGlobalArray("material_emissive_texture_index", emissiveTextureIndex)
-    if emissiveFactor.len > 0: scene.addShaderGlobalArray("material_emissive_factor", emissiveFactor)
-
-    # texture
-    if colorTexture.len > 0: scene.addTextures("material_color_texture", colorTexture)
-    if metallicRoughnessTexture.len > 0: scene.addTextures("material_metallic_roughness_texture", metallicRoughnessTexture)
-    if normalTexture.len > 0: scene.addTextures("material_normal_texture", normalTexture)
-    if occlusionTexture.len > 0: scene.addTextures("material_occlusion_texture", occlusionTexture)
-    if emissiveTexture.len > 0: scene.addTextures("material_emissive_texture", emissiveTexture)
+      scene.addMaterial loadMaterial(data.structuredContent, materialNode, data.binaryBufferData)
 
     result.add scene
-
--- a/src/semicongine/scene.nim	Sat May 27 13:45:03 2023 +0700
+++ b/src/semicongine/scene.nim	Sun May 28 17:52:03 2023 +0700
@@ -1,4 +1,6 @@
 import std/strformat
+import std/sequtils
+import std/algorithm
 import std/strutils
 import std/tables
 import std/hashes
@@ -7,14 +9,19 @@
 import ./core
 
 type
-  Component* = ref object of RootObj
-    entity*: Entity
-
   Scene* = object
     name*: string
     root*: Entity
     shaderGlobals*: Table[string, DataList]
-    textures*: Table[string, seq[Texture]]
+    materials: seq[Material]
+
+  Material* = object
+    name*: string
+    textures*: Table[string, Texture]
+    data*: Table[string, DataValue]
+
+  Component* = ref object of RootObj
+    entity*: Entity
 
   Entity* = ref object of RootObj
     name*: string
@@ -50,15 +57,26 @@
 func setShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) =
   setValues[T](scene.shaderGlobals[name], value)
 
-func addTextures*(scene: var Scene, name: string, textures: seq[Texture], interpolation=VK_FILTER_LINEAR) =
-  scene.textures[name] = textures
-
-func addTexture*(scene: var Scene, name: string, texture: Texture) =
-  scene.textures[name] = @[texture]
+func appendShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) =
+  appendValues[T](scene.shaderGlobals[name], value)
 
 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) =
+  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:
+    scene.shaderGlobals[name].appendValue(value)
+
+  scene.materials.add material
+
 func hash*(scene: Scene): Hash =
   hash(scene.name)
 
--- a/src/semicongine/vulkan/pipeline.nim	Sat May 27 13:45:03 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Sun May 28 17:52:03 2023 +0700
@@ -1,4 +1,5 @@
 import std/tables
+import std/sequtils
 import std/strformat
 
 import ../core
@@ -49,7 +50,7 @@
         offset += size
       elif descriptor.thetype == ImageSampler:
         if not (descriptor.name in textures):
-          raise newException(Exception, "Missing shader texture in scene: " & descriptor.name)
+          raise newException(Exception, &"Missing shader texture in scene: {descriptor.name}, available are {textures.keys.toSeq}")
         if uint32(textures[descriptor.name].len) != descriptor.count:
           raise newException(Exception, &"Incorrect number of textures in array for {descriptor.name}: has {textures[descriptor.name].len} but needs {descriptor.count}")
         for t in textures[descriptor.name]: