changeset 366:857cd931d24b

add: function-based animations, preparing-refactring for better material system, hashable dynamic arrays
author Sam <sam@basx.dev>
date Thu, 12 Oct 2023 14:55:28 +0700
parents a6bcee717532
children 7ef01f1841b3
files config.nims src/semicongine.nim src/semicongine/animation.nim src/semicongine/core/buildconfig.nim src/semicongine/core/dynamic_arrays.nim src/semicongine/core/gpu_types.nim src/semicongine/core/imagetypes.nim src/semicongine/engine.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources/mesh.nim src/semicongine/text.nim src/semicongine/vulkan/renderpass.nim
diffstat 13 files changed, 142 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/config.nims	Wed Oct 04 22:02:23 2023 +0700
+++ b/config.nims	Thu Oct 12 14:55:28 2023 +0700
@@ -14,6 +14,7 @@
 task build, "build":
   switch("d", "BUNDLETYPE=" & BUNDLETYPE)
   switch("d", "RESOURCEROOT=" & RESOURCEROOT)
+  switch("d", "nimPreviewHashRef")
   switch("mm", "orc")
   switch("experimental", "strictEffects")
   switch("threads", "on")
--- a/src/semicongine.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -10,7 +10,9 @@
 import semicongine/collision
 import semicongine/scene
 import semicongine/events
+import semicongine/material
 import semicongine/mesh
+import semicongine/noise
 import semicongine/renderer
 import semicongine/resources
 import semicongine/settings
@@ -24,7 +26,9 @@
 export collision
 export scene
 export events
+export material
 export mesh
+export noise
 export renderer
 export resources
 export settings
--- a/src/semicongine/animation.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/animation.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -1,7 +1,7 @@
+import std/sugar
 import std/tables
 import std/math
 import std/sequtils
-import std/strformat
 import std/algorithm
 
 import ./core/matrix
@@ -17,7 +17,7 @@
     Expo
     Sine
     Circ
-  AnimationTime = 0'f32 .. 1'f32
+  AnimationTime* = 0'f32 .. 1'f32
   Direction* = enum
     Forward
     Backward
@@ -28,7 +28,7 @@
     easeIn: Ease
     easeOut: Ease
   Animation*[T] = object
-    keyframes*: seq[Keyframe[T]]
+    animationFunction: (t: AnimationTime) -> T
     duration: float32
     direction: Direction
     iterations: int
@@ -50,9 +50,6 @@
 func easeSine(x: float32): float32 = 1'f32 - cos((x * PI) / 2'f32)
 func easeCirc(x: float32): float32 = 1'f32 - sqrt(1'f32 - pow(x, 2'f32))
 
-func `$`*(animation: Animation): string =
-  &"{animation.keyframes.len} keyframes, {animation.duration}s"
-
 const EASEFUNC_MAP = {
     None: easeConst,
     Linear: easeLinear,
@@ -96,44 +93,58 @@
     assert kf.timestamp > last, "Succeding keyframes must have increasing timestamps"
     last = kf.timestamp
 
+  let theKeyframes = keyframes.toSeq
+
+  proc animationFunc(t: AnimationTime): T =
+    var i = 0
+    while i < theKeyframes.len - 1:
+      if theKeyframes[i].timestamp > t:
+        break
+      inc i
+
+    let
+      keyFrameDist = theKeyframes[i].timestamp - theKeyframes[i - 1].timestamp
+      timestampDist = t - theKeyframes[i - 1].timestamp
+      x = timestampDist / keyFrameDist
+
+    let value = theKeyframes[i - 1].interpol(x)
+    return theKeyframes[i].value * value + theKeyframes[i - 1].value * (1 - value)
+
   Animation[T](
-    keyframes: keyframes.toSeq,
+    animationFunction: animationFunc,
     duration: duration,
     direction: direction,
     iterations: iterations
   )
 
-func valueAt[T](animation: Animation[T], timestamp: AnimationTime): T =
-  var i = 0
-  while i < animation.keyframes.len - 1:
-    if animation.keyframes[i].timestamp > timestamp:
-      break
-    inc i
-
-  let
-    keyFrameDist = animation.keyframes[i].timestamp - animation.keyframes[i - 1].timestamp
-    timestampDist = timestamp - animation.keyframes[i - 1].timestamp
-    x = timestampDist / keyFrameDist
-
-  let newX = animation.keyframes[i - 1].interpol(x)
-  return animation.keyframes[i].value * newX + animation.keyframes[i - 1].value * (1 - newX)
+func newAnimation*[T](fun: (t: AnimationTime) -> T, duration: float32, direction=Forward, iterations=1): Animation[T] =
+  Animation[T](
+    animationFunction: fun,
+    duration: duration,
+    direction: direction,
+    iterations: iterations
+  )
 
 func resetPlayer*(player: var AnimationPlayer) =
   player.currentTime = 0
   player.currentDirection = if player.animation.direction == Backward: -1 else : 1
   player.currentIteration = player.animation.iterations
 
+
 func newAnimationPlayer*[T](animation: Animation[T]): AnimationPlayer[T] =
-  result = AnimationPlayer[T]( animation: animation, playing: false,)
+  result = AnimationPlayer[T](animation: animation, playing: false)
   result.resetPlayer()
 
+func newAnimationPlayer*[T](value: T = default(T)): AnimationPlayer[T] =
+  newAnimationPlayer[T](newAnimation[T]((t: AnimationTime) => value, 0))
+
 func start*(player: var AnimationPlayer) =
   player.playing = true
 
 func stop*(player: var AnimationPlayer) =
   player.playing = false
 
-func advance*[T](player: var AnimationPlayer[T], dt: float32): T =
+proc advance*[T](player: var AnimationPlayer[T], dt: float32): T =
   # TODO: check this function, not 100% correct I think
   if player.playing:
     player.currentTime += float32(player.currentDirection) * dt
@@ -152,5 +163,5 @@
             player.currentDirection = -player.currentDirection
             player.currentTime += float32(player.currentDirection) * dt * 2'f32
 
-  player.currentValue = valueAt(player.animation, (abs(player.currentTime) / player.animation.duration) mod high(AnimationTime))
+  player.currentValue = player.animation.animationFunction((abs(player.currentTime) / player.animation.duration) mod high(AnimationTime))
   return player.currentValue
--- a/src/semicongine/core/buildconfig.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/core/buildconfig.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -15,10 +15,12 @@
 # checks required build options:
 static:
   assert compileOption("threads"), ENGINENAME & " requires --threads=on"
+  assert defined(nimPreviewHashRef), ENGINENAME & " requires -d:nimPreviewHashRef"
 
   if defined(release):
     assert compileOption("app", "gui"), ENGINENAME & " requires --app=gui for release builds"
 
+
   if defined(linux):
     assert defined(VK_USE_PLATFORM_XLIB_KHR), ENGINENAME & " requires --d:VK_USE_PLATFORM_XLIB_KHR for linux builds"
   elif defined(windows):
--- a/src/semicongine/core/dynamic_arrays.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/core/dynamic_arrays.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -1,3 +1,5 @@
+import std/hashes
+
 import ./gpu_types
 import ./vector
 import ./matrix
@@ -50,11 +52,57 @@
     of Mat43F64: mat43f64: ref seq[TMat43[float64]]
     of Mat4F32: mat4f32: ref seq[TMat4[float32]]
     of Mat4F64: mat4f64: ref seq[TMat4[float64]]
-    of Sampler2D: texture: ref seq[Texture]
+    of TextureType: texture: ref seq[Texture]
 
 func size*(value: DataList): int =
   value.theType.size * value.len
 
+func hash*(value: DataList): Hash =
+  case value.theType
+    of Float32: hash(value.float32)
+    of Float64: hash(value.float64)
+    of Int8: hash(value.int8)
+    of Int16: hash(value.int16)
+    of Int32: hash(value.int32)
+    of Int64: hash(value.int64)
+    of UInt8: hash(value.uint8)
+    of UInt16: hash(value.uint16)
+    of UInt32: hash(value.uint32)
+    of UInt64: hash(value.uint64)
+    of Vec2I32: hash(value.vec2i32)
+    of Vec2I64: hash(value.vec2i64)
+    of Vec3I32: hash(value.vec3i32)
+    of Vec3I64: hash(value.vec3i64)
+    of Vec4I32: hash(value.vec4i32)
+    of Vec4I64: hash(value.vec4i64)
+    of Vec2U32: hash(value.vec2u32)
+    of Vec2U64: hash(value.vec2u64)
+    of Vec3U32: hash(value.vec3u32)
+    of Vec3U64: hash(value.vec3u64)
+    of Vec4U32: hash(value.vec4u32)
+    of Vec4U64: hash(value.vec4u64)
+    of Vec2F32: hash(value.vec2f32)
+    of Vec2F64: hash(value.vec2f64)
+    of Vec3F32: hash(value.vec3f32)
+    of Vec3F64: hash(value.vec3f64)
+    of Vec4F32: hash(value.vec4f32)
+    of Vec4F64: hash(value.vec4f64)
+    of Mat2F32: hash(value.mat2f32)
+    of Mat2F64: hash(value.mat2f64)
+    of Mat23F32: hash(value.mat23f32)
+    of Mat23F64: hash(value.mat23f64)
+    of Mat32F32: hash(value.mat32f32)
+    of Mat32F64: hash(value.mat32f64)
+    of Mat3F32: hash(value.mat3f32)
+    of Mat3F64: hash(value.mat3f64)
+    of Mat34F32: hash(value.mat34f32)
+    of Mat34F64: hash(value.mat34f64)
+    of Mat43F32: hash(value.mat43f32)
+    of Mat43F64: hash(value.mat43f64)
+    of Mat4F32: hash(value.mat4f32)
+    of Mat4F64: hash(value.mat4f64)
+    of TextureType: hash(value.texture)
+
 func `==`*(a, b: DataList): bool =
   if a.theType != b.theType:
     return false
@@ -101,7 +149,7 @@
     of Mat43F64: return a.mat43f64 == b.mat43f64
     of Mat4F32: return a.mat4f32 == b.mat4f32
     of Mat4F64: return a.mat4f64 == b.mat4f64
-    of Sampler2D: a.texture == b.texture
+    of TextureType: a.texture == b.texture
 
 proc setValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) =
   value.setLen(data.len)
@@ -201,7 +249,7 @@
     of Mat43F64: result.mat43f64 = new seq[TMat43[float64]]
     of Mat4F32: result.mat4f32 = new seq[TMat4[float32]]
     of Mat4F64: result.mat4f64 = new seq[TMat4[float64]]
-    of Sampler2D: result.texture = new seq[Texture]
+    of TextureType: result.texture = new seq[Texture]
 
 proc newDataList*[T: GPUType](len=0): DataList =
   result = newDataList(getDataType[T]())
@@ -367,7 +415,7 @@
     of Mat43F64: result[0] = value.mat43f64[].toCPointer
     of Mat4F32: result[0] = value.mat4f32[].toCPointer
     of Mat4F64: result[0] = value.mat4f64[].toCPointer
-    of Sampler2D: result[0] = nil
+    of TextureType: result[0] = nil
 
 proc setLen*(value: var DataList, len: int) =
   value.len = len
@@ -414,7 +462,7 @@
     of Mat43F64: value.mat43f64[].setLen(len)
     of Mat4F32: value.mat4f32[].setLen(len)
     of Mat4F64: value.mat4f64[].setLen(len)
-    of Sampler2D: discard
+    of TextureType: discard
 
 proc appendValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) =
   value.len += data.len
@@ -515,7 +563,7 @@
   of Mat43F64: value.mat43f64[].add data.mat43f64[]
   of Mat4F32: value.mat4f32[].add data.mat4f32[]
   of Mat4F64: value.mat4f64[].add data.mat4f64[]
-  of Sampler2D: value.texture[].add data.texture[]
+  of TextureType: value.texture[].add data.texture[]
 
 proc setValue*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) =
   when T is float32: value.float32[] = data
@@ -667,7 +715,7 @@
     of Mat43F64: a.mat43f64[i] = b.mat43f64[j]
     of Mat4F32: a.mat4f32[i] = b.mat4f32[j]
     of Mat4F64: a.mat4f64[i] = b.mat4f64[j]
-    of Sampler2D: a.texture[i] = b.texture[j]
+    of TextureType: a.texture[i] = b.texture[j]
 
 proc copy*(datalist: DataList): DataList =
   result = newDataList(datalist.theType)
@@ -717,4 +765,4 @@
     of Mat43F64: $list.mat43f64[]
     of Mat4F32: $list.mat4f32[]
     of Mat4F64: $list.mat4f64[]
-    of Sampler2D: $list.texture[]
+    of TextureType: $list.texture[]
--- a/src/semicongine/core/gpu_types.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/core/gpu_types.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -51,7 +51,7 @@
     Mat43F64
     Mat4F32
     Mat4F64
-    Sampler2D
+    TextureType
   MemoryPerformanceHint* = enum
     PreferFastRead, PreferFastWrite
   ShaderAttribute* = object
@@ -123,7 +123,7 @@
     of Mat43F64: 92
     of Mat4F32: 64
     of Mat4F64: 128
-    of Sampler2D: 0
+    of TextureType: 0
 
 func size*(attribute: ShaderAttribute, perDescriptor=false): int =
   if perDescriptor:
@@ -187,7 +187,7 @@
   elif T is TMat43[float64]: Mat43F64
   elif T is TMat4[float32]: Mat4F32
   elif T is TMat4[float64]: Mat4F64
-  elif T is Texture: Sampler2D
+  elif T is Texture: TextureType
   else:
     static:
       raise newException(Exception, &"Unsupported data type for GPU data: {name(T)}" )
@@ -309,7 +309,7 @@
     of Mat43F64: 2
     of Mat4F32: 1
     of Mat4F64: 2
-    of Sampler2D: 1
+    of TextureType: 1
 
 func glslType*(theType: DataType): string =
   # todo: likely not correct as we would need to enable some 
@@ -353,7 +353,7 @@
     of Mat43F64: "dmat43"
     of Mat4F32: "mat4"
     of Mat4F64: "dmat4"
-    of Sampler2D: "sampler2D"
+    of TextureType: "sampler2D"
 
 func glslInput*(group: openArray[ShaderAttribute]): seq[string] =
   if group.len == 0:
--- a/src/semicongine/core/imagetypes.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/core/imagetypes.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -1,4 +1,5 @@
 import ./vulkanapi
+import ./vector
 
 type
   Pixel* = array[4, uint8]
@@ -18,6 +19,9 @@
     image*: Image
     sampler*: Sampler
 
+converter toRGBA*(p: Pixel): Vec4f =
+  newVec4f(float32(p[0]) / 255'f32, float32(p[1]) / 255'f32, float32(p[2]) / 255'f32, float32(p[3]) / 255'f32)
+
 proc `[]`*(image: Image, x, y: int): Pixel =
   assert x < image.width
   assert y < image.height
--- a/src/semicongine/engine.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/engine.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -1,4 +1,3 @@
-import std/tables
 import std/options
 import std/logging
 import std/os
--- a/src/semicongine/mesh.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/mesh.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -9,8 +9,9 @@
 
 import ./core
 import ./collision
+import ./material
 
-var instanceCounter = 0
+var instanceCounter* = 0
 
 type
   MeshIndexType* = enum
@@ -26,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[Material]
+    materials*: seq[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
@@ -36,15 +37,6 @@
     instanceData: Table[string, DataList]
     dirtyAttributes: seq[string]
   Mesh* = ref MeshObject
-  Material* = object
-    name*: string
-    constants*: Table[string, DataList]
-    textures*: Table[string, Texture]
-    index*: uint16 # optional, may be used to index into uniform arrays in shader
-
-let DEFAULT_MATERIAL* = Material(
-  name: "default material"
-)
 
 func instanceCount*(mesh: MeshObject): int =
   mesh.instanceTransforms.len
@@ -66,15 +58,6 @@
 func `$`*(mesh: Mesh): string =
   $mesh[]
 
-proc `$`*(material: Material): string =
-  var constants: seq[string]
-  for key, value in material.constants.pairs:
-    constants.add &"{key}: {value}"
-  var textures: seq[string]
-  for key in material.textures.keys:
-    textures.add &"{key}"
-  return &"""{material.name} | Values: {constants.join(", ")} | Textures: {textures.join(", ")}"""
-
 func vertexAttributes*(mesh: MeshObject): seq[string] =
   mesh.vertexData.keys.toSeq
 
@@ -84,9 +67,6 @@
 func attributes*(mesh: MeshObject): seq[string] =
   mesh.vertexAttributes & mesh.instanceAttributes
 
-func hash*(material: Material): Hash =
-  hash(material.name)
-
 func hash*(mesh: Mesh): Hash =
   hash(cast[ptr MeshObject](mesh))
 
@@ -133,7 +113,7 @@
   uvs: openArray[Vec2f]=[],
   transform: Mat4=Unit4F32,
   instanceTransforms: openArray[Mat4]=[Unit4F32],
-  material: Material=DEFAULT_MATERIAL,
+  material: MaterialData=DEFAULT_MATERIAL,
   autoResize=true,
   name: string=""
 ): Mesh =
@@ -189,7 +169,7 @@
   uvs: openArray[Vec2f]=[],
   transform: Mat4=Unit4F32,
   instanceTransforms: openArray[Mat4]=[Unit4F32],
-  material: Material=DEFAULT_MATERIAL,
+  material: MaterialData=DEFAULT_MATERIAL,
   name: string="",
 ): Mesh =
   newMesh(
--- a/src/semicongine/renderer.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/renderer.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -19,6 +19,7 @@
 
 import ./scene
 import ./mesh
+import ./material
 
 const TRANSFORMATTRIBUTE = "transform"
 const VERTEX_ATTRIB_ALIGNMENT = 4 # used for buffer alignment
@@ -34,7 +35,7 @@
     vertexBufferOffsets*: Table[(Mesh, string), int]
     descriptorPools*: Table[VkPipeline, DescriptorPool]
     descriptorSets*: Table[VkPipeline, seq[DescriptorSet]]
-    materials: seq[Material]
+    materials: seq[MaterialData]
   Renderer* = object
     device: Device
     surfaceFormat: VkSurfaceFormatKHR
@@ -78,14 +79,14 @@
       if scene.usesMaterial(materialName):
         result.add pipeline.samplers
 
-func materialCompatibleWithPipeline(scene: Scene, material: Material, pipeline: Pipeline): (bool, string) =
+func materialCompatibleWithPipeline(scene: Scene, material: MaterialData, 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.constants.pairs:
+      for name, constant in material.values.pairs:
         if name == uniform.name and constant.theType == uniform.theType:
           foundMatch = true
           break
@@ -162,7 +163,7 @@
       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 == Sampler2D:
+          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"
           else:
             if not scenedata.textures.hasKey(textureName):
@@ -170,7 +171,7 @@
             scenedata.textures[textureName].add renderer.device.uploadTexture(texture)
 
   for name, value in scene.shaderGlobals.pairs:
-    if value.theType == Sampler2D:
+    if value.theType == TextureType:
       assert not scenedata.textures.contains(name) # should be handled by the above code
       scenedata.textures[name] = @[]
       for texture in getValues[Texture](value)[]:
@@ -382,7 +383,7 @@
             foundValue = true
           else:
             for mat in renderer.scenedata[scene].materials:
-              for name, materialConstant in mat.constants.pairs:
+              for name, materialConstant in mat.values.pairs:
                 if uniform.name == name:
                   value = materialConstant
                   foundValue = true
--- a/src/semicongine/resources/mesh.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/resources/mesh.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -7,6 +7,7 @@
 import std/streams
 
 import ../mesh
+import ../material
 import ../core
 
 import ./image
@@ -149,62 +150,62 @@
       result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
 
 
-proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8], materialIndex: uint16): Material =
-  result = Material(name: materialNode["name"].getStr(), index: materialIndex)
+proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8], materialIndex: uint16): MaterialData =
+  result = MaterialData(name: materialNode["name"].getStr(), index: materialIndex)
   let pbr = materialNode["pbrMetallicRoughness"]
 
   # color
-  result.constants["baseColorFactor"] = newDataList(thetype=Vec4F32)
+  result.values["baseColorFactor"] = newDataList(thetype=Vec4F32)
   if pbr.hasKey("baseColorFactor"):
-    setValue(result.constants["baseColorFactor"], @[newVec4f(
+    setValue(result.values["baseColorFactor"], @[newVec4f(
       pbr["baseColorFactor"][0].getFloat(),
       pbr["baseColorFactor"][1].getFloat(),
       pbr["baseColorFactor"][2].getFloat(),
       pbr["baseColorFactor"][3].getFloat(),
     )])
   else:
-    setValue(result.constants["baseColorFactor"], @[newVec4f(1, 1, 1, 1)])
+    setValue(result.values["baseColorFactor"], @[newVec4f(1, 1, 1, 1)])
 
-  # pbr material constants
+  # pbr material values
   for factor in ["metallicFactor", "roughnessFactor"]:
-    result.constants[factor] = newDataList(thetype=Float32)
+    result.values[factor] = newDataList(thetype=Float32)
     if pbr.hasKey(factor):
-      setValue(result.constants[factor], @[float32(pbr[factor].getFloat())])
+      setValue(result.values[factor], @[float32(pbr[factor].getFloat())])
     else:
-      setValue(result.constants[factor], @[0.5'f32])
+      setValue(result.values[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.constants[texture & "Index"] = newDataList(thetype=UInt8)
-      setValue(result.constants[texture & "Index"], @[pbr[texture].getOrDefault("texCoord").getInt(0).uint8])
+      result.values[texture & "Index"] = newDataList(thetype=UInt8)
+      setValue(result.values[texture & "Index"], @[pbr[texture].getOrDefault("texCoord").getInt(0).uint8])
     else:
       result.textures[texture] = EMPTY_TEXTURE
-      result.constants[texture & "Index"] = newDataList(thetype=UInt8)
-      setValue(result.constants[texture & "Index"], @[0'u8])
+      result.values[texture & "Index"] = newDataList(thetype=UInt8)
+      setValue(result.values[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.constants[texture & "Index"] = newDataList(thetype=UInt8)
-      setValue(result.constants[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8])
+      result.values[texture & "Index"] = newDataList(thetype=UInt8)
+      setValue(result.values[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8])
     else:
       result.textures[texture] = EMPTY_TEXTURE
-      result.constants[texture & "Index"] = newDataList(thetype=UInt8)
-      setValue(result.constants[texture & "Index"], @[0'u8])
+      result.values[texture & "Index"] = newDataList(thetype=UInt8)
+      setValue(result.values[texture & "Index"], @[0'u8])
 
   # emissiv color
-  result.constants["emissiveFactor"] = newDataList(thetype=Vec3F32)
+  result.values["emissiveFactor"] = newDataList(thetype=Vec3F32)
   if materialNode.hasKey("emissiveFactor"):
-    setValue(result.constants["emissiveFactor"], @[newVec3f(
+    setValue(result.values["emissiveFactor"], @[newVec3f(
       materialNode["emissiveFactor"][0].getFloat(),
       materialNode["emissiveFactor"][1].getFloat(),
       materialNode["emissiveFactor"][2].getFloat(),
     )])
   else:
-    setValue(result.constants["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)])
+    setValue(result.values["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)])
 
 proc addPrimitive(mesh: Mesh, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) =
   if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4:
--- a/src/semicongine/text.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/text.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -4,6 +4,7 @@
 
 import ./core
 import ./mesh
+import ./material
 import ./vulkan/shader
 
 const SHADER_ATTRIB_PREFIX = "semicon_text_"
@@ -109,7 +110,7 @@
   inc instanceCounter
   result.mesh[].renameAttribute("position", POSITION_ATTRIB)
   result.mesh[].renameAttribute("uv", UV_ATTRIB)
-  result.mesh.materials = @[Material(
+  result.mesh.materials = @[MaterialData(
     name: TEXT_MATERIAL,
     textures: {"fontAtlas": font.fontAtlas}.toTable,
   )]
--- a/src/semicongine/vulkan/renderpass.nim	Wed Oct 04 22:02:23 2023 +0700
+++ b/src/semicongine/vulkan/renderpass.nim	Thu Oct 12 14:55:28 2023 +0700
@@ -1,5 +1,4 @@
 import std/options
-import std/tables
 
 import ../core
 import ./device