changeset 653:ac7dfbd56cc6

add: textures now support in shader via scene data, also: improved config handling a bit, more to come
author Sam <sam@basx.dev>
date Sat, 06 May 2023 23:06:33 +0700
parents 43240986f76d
children 7973a17f76e6
files src/semicongine.nim src/semicongine/config.nim src/semicongine/engine.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/vulkan/descriptor.nim src/semicongine/vulkan/pipeline.nim tests/test_vulkan_wrapper.nim
diffstat 8 files changed, 49 insertions(+), 147 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine.nim	Sat May 06 01:37:33 2023 +0700
+++ b/src/semicongine.nim	Sat May 06 23:06:33 2023 +0700
@@ -1,6 +1,8 @@
+import semicongine/buildconfig
+export buildconfig
+
 import semicongine/audio
 import semicongine/color
-import semicongine/config
 import semicongine/engine
 import semicongine/entity
 import semicongine/events
@@ -8,12 +10,12 @@
 import semicongine/math
 import semicongine/mesh
 import semicongine/renderer
+import semicongine/settings
 import semicongine/platform/window
 import semicongine/vulkan
 
 export audio
 export color
-export config
 export engine
 export entity
 export events
@@ -21,5 +23,6 @@
 export math
 export mesh
 export renderer
+export settings
 export window
 export vulkan
--- a/src/semicongine/config.nim	Sat May 06 01:37:33 2023 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,119 +0,0 @@
-import std/logging
-import std/parsecfg
-import std/strutils
-import std/sequtils
-import std/parseutils
-import std/strformat
-import std/tables
-import std/os
-
-
-# build configuration
-# =====================
-
-# compile-time defines, usefull for build-dependent settings
-# can be overriden with compiler flags, e.g. -d:Foo=42 -d:Bar=false
-# pramas: {.intdefine.} {.strdefine.} {.booldefine.}
-
-# root of where config files will be searched
-# must be relative (to the directory of the binary)
-const DEBUG* = not defined(release)
-const CONFIGROOT {.strdefine.}: string = "."
-assert not isAbsolute(CONFIGROOT)
-
-const CONFIGEXTENSION {.strdefine.}: string = "ini"
-
-# by default enable hot-reload of runtime-configuration only in debug builds
-const CONFIGHOTRELOAD {.booldefine.}: bool = DEBUG
-
-# milliseconds to wait between checks for config hotreload
-const CONFIGHOTRELOADINTERVAL {.intdefine.}: int = 1000
-
-
-when CONFIGHOTRELOAD:
-  var configUpdates: Channel[(string, Config)]
-  configUpdates.open()
-
-
-# runtime configuration
-# =====================
-# namespace is the path from the CONFIGROOT to the according config file without the file extension
-# a config file must always have the extension CONFIGEXTENSION
-# a fully qualified config identifier can be in the form {namespace}.{section}.{key}
-# {key} and {section} may not contain dots
-
-# a "namespace" is the path from the config root to an *.CONFIGEXTENSION file, without the file extension
-# config is a namespace <-> config mapping
-var theConfig: Table[string, Config]
-
-proc configRoot(): string =
-  joinPath(absolutePath(getAppDir()), CONFIGROOT)
-
-proc getFile(namespace: string): string =
-  joinPath(configRoot(), namespace & "." & CONFIGEXTENSION)
-
-iterator walkConfigNamespaces(): string =
-  for file in walkDirRec(dir=configRoot(), relative=true, checkDir=true):
-    if file.endsWith("." & CONFIGEXTENSION):
-      yield file[0 ..< ^(CONFIGEXTENSION.len + 1)]
-
-proc loadAllConfig(): Table[string, Config] =
-  for ns in walkConfigNamespaces():
-    result[ns] = ns.getFile().loadConfig()
-
-proc configStr(key: string, section="", namespace = ""): string =
-  var ns = namespace
-  if ns == "":
-    ns = "config"
-  when CONFIGHOTRELOAD:
-    while configUpdates.peek() > 0:
-      let (updatedNamespace, updatedConfig) = configUpdates.recv()
-      theConfig[updatedNamespace] = updatedConfig
-  if not theConfig.hasKey(ns):
-    raise newException(Exception, &"Namespace {ns} not found, available namespaces are {theConfig.keys().toSeq}")
-  theConfig[ns].getSectionValue(section, key)
-
-proc config*[T: int|float|string](key: string, section: string, namespace = "config"): T =
-  when T is int:
-    let value = configStr(key, section, namespace)
-    if parseInt(value, result) == 0:
-      raise newException(Exception, &"Unable to parse int from config {namespace}.{section}.{key}: {value}")
-  elif T is float:
-    let value = configStr(key, section, namespace)
-    if parseFloat(value, result) == 0:
-      raise newException(Exception, &"Unable to parse float from config {namespace}.{section}.{key}: {value}")
-  else:
-    result = configStr(key, section, namespace)
-
-proc config*[T: int|float|string](identifier: string): T =
-  # identifier can be in the form:
-  # {key}
-  # {section}.{key}
-  # {namespace}.{section}.{key}
-  let parts = identifier.rsplit(".", maxsplit=2)
-  if parts.len == 1: result = config[T](parts[0], "")
-  elif parts.len == 2: result = config[T](parts[1], parts[0])
-  elif parts.len == 3: result = config[T](parts[2], parts[1], parts[0])
-
-theConfig = loadAllConfig()
-
-when CONFIGHOTRELOAD == true:
-  import std/times
-
-  proc configFileWatchdog() {.thread.} =
-    var configModTimes: Table[string, Time]
-    while true:
-      for namespace in walkConfigNamespaces():
-        if not (namespace in configModTimes):
-          configModTimes[namespace] = Time()
-        let lastMod = namespace.getFile().getLastModificationTime()
-        if lastMod != configModTimes[namespace]:
-          configUpdates.send((namespace, namespace.getFile().loadConfig()))
-      sleep CONFIGHOTRELOADINTERVAL
-  var thethread: Thread[void]
-  createThread(thethread, configFileWatchdog)
-
-if DEBUG:
-  setLogFilter(lvlAll)
-else:
-  setLogFilter(lvlWarn)
--- a/src/semicongine/engine.nim	Sat May 06 01:37:33 2023 +0700
+++ b/src/semicongine/engine.nim	Sat May 06 23:06:33 2023 +0700
@@ -1,4 +1,6 @@
 import std/sequtils
+import std/strutils
+import std/logging
 import std/os
 
 import ./platform/window
@@ -13,7 +15,7 @@
 import ./entity
 import ./renderer
 import ./events
-import ./config
+import ./buildconfig
 import ./math
 import ./audio
 
@@ -64,6 +66,9 @@
   resizeHandler: proc(engine: var Engine) = nil,
   eventHandler: proc(engine: var Engine, event: Event) = nil
 ): Engine =
+  echo "Set log level to ", ENGINE_LOGLEVEL
+  setLogFilter(ENGINE_LOGLEVEL)
+
   result.state = Starting
   result.exitHandler = exitHandler
   result.resizeHandler = resizeHandler
--- a/src/semicongine/mesh.nim	Sat May 06 01:37:33 2023 +0700
+++ b/src/semicongine/mesh.nim	Sat May 06 23:06:33 2023 +0700
@@ -140,6 +140,11 @@
   mesh.data[attribute] = DataList(thetype: getDataType[T]())
   setValues(mesh.data[attribute], data)
 
+proc updateMeshData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) =
+  assert attribute in mesh.data
+  mesh.changedAttributes.add attribute
+  setValues(mesh.data[attribute], data)
+
 proc updateMeshData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, i: uint32, value: T) =
   assert attribute in mesh.data
   mesh.changedAttributes.add attribute
--- a/src/semicongine/renderer.nim	Sat May 06 01:37:33 2023 +0700
+++ b/src/semicongine/renderer.nim	Sat May 06 23:06:33 2023 +0700
@@ -1,6 +1,7 @@
 import std/options
 import std/tables
 import std/strformat
+import std/strutils
 import std/logging
 
 import ./vulkan/api
@@ -18,6 +19,7 @@
 import ./mesh
 import ./gpu_data
 import ./math
+import ./buildconfig
 
 type
   SceneData = object
@@ -26,7 +28,7 @@
     indexBuffer*: Buffer
     uniformBuffers*: seq[Buffer] # one per frame-in-flight
     images*: seq[Image] # used to back texturees
-    textures*: seq[Table[string, Texture]] # per frame-in-flight
+    textures*: Table[string, Texture] # per frame-in-flight
     attributeLocation*: Table[string, MemoryPerformanceHint]
     attributeBindingNumber*: Table[string, int]
     transformAttribute: string # name of attribute that is used for per-instance mesh transformation
@@ -155,29 +157,26 @@
     data.drawables[mesh] = drawable
 
   # setup uniforms and textures
-  for i in 0 ..< renderer.renderPass.subpasses.len:
-    var subpass = renderer.renderPass.subpasses[i]
+  for subpass_i in 0 ..< renderer.renderPass.subpasses.len:
+    var subpass = renderer.renderPass.subpasses[subpass_i]
     for pipeline in subpass.pipelines.mitems:
       var uniformBufferSize = 0'u64
       for uniform in pipeline.uniforms:
         uniformBufferSize += uniform.thetype.size
       if uniformBufferSize > 0:
-        for i in 0 ..< renderer.swapchain.inFlightFrames:
+        for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
           data.uniformBuffers.add renderer.device.createBuffer(
             size=uniformBufferSize,
             usage=[VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT],
             requireMappable=true,
             preferVRAM=true,
           )
-      for i in 0 ..< renderer.swapchain.inFlightFrames:
-        var textures: Table[string, Texture]
-        # todo: get textures from scene, currently only 32 bit images supported
-        for name, image in scene.textures.pairs:
-          textures[name] = renderer.device.createTexture(image.width, image.height, 4, addr image.imagedata[0][0])
-        data.textures.add textures
-      # need a separate descriptor for each frame in flight
+
+      for name, image in scene.textures.pairs:
+        data.textures[name] = renderer.device.createTexture(image.width, image.height, 4, addr image.imagedata[0][0])
       pipeline.setupDescriptors(data.uniformBuffers, data.textures, inFlightFrames=renderer.swapchain.inFlightFrames)
-      pipeline.descriptorSets[i].writeDescriptorSet()
+      for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
+        pipeline.descriptorSets[frame_i].writeDescriptorSet()
 
   renderer.scenedata[scene] = data
 
@@ -251,6 +250,7 @@
     for pipeline in subpass.pipelines:
       var mpipeline = pipeline
       commandBuffer.vkCmdBindPipeline(subpass.pipelineBindPoint, mpipeline.vk)
+      echo "############## ", mpipeline.descriptorSets[renderer.swapchain.currentInFlight]
       commandBuffer.vkCmdBindDescriptorSets(subpass.pipelineBindPoint, mpipeline.layout, 0, 1, addr(mpipeline.descriptorSets[renderer.swapchain.currentInFlight].vk), 0, nil)
 
       debug "Scene buffers:"
--- a/src/semicongine/vulkan/descriptor.nim	Sat May 06 01:37:33 2023 +0700
+++ b/src/semicongine/vulkan/descriptor.nim	Sat May 06 23:06:33 2023 +0700
@@ -170,4 +170,7 @@
           pImageInfo: addr imageInfos[^1],
         )
     inc i
+  echo "wrote descriptors:"
+  for w in descriptorSetWrites:
+    echo "  ", w
   descriptorSet.layout.device.vk.vkUpdateDescriptorSets(uint32(descriptorSetWrites.len), descriptorSetWrites.toCPointer, 0, nil)
--- a/src/semicongine/vulkan/pipeline.nim	Sat May 06 01:37:33 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Sat May 06 23:06:33 2023 +0700
@@ -36,10 +36,9 @@
         uniformList[attribute.name] = attribute
   result = uniformList.values.toSeq
 
-proc setupDescriptors*(pipeline: var Pipeline, buffers: seq[Buffer], textures: seq[Table[string, Texture]], inFlightFrames: int) =
+proc setupDescriptors*(pipeline: var Pipeline, buffers: seq[Buffer], textures: Table[string, Texture], inFlightFrames: int) =
   assert pipeline.vk.valid
   assert buffers.len == 0 or buffers.len == inFlightFrames
-  assert textures.len == 0 or textures.len == inFlightFrames
   assert pipeline.descriptorSets.len > 0
 
   for i in 0 ..< inFlightFrames:
@@ -54,10 +53,10 @@
         descriptor.size = size
         offset += size
       elif descriptor.thetype == ImageSampler:
-        if not (descriptor.name in textures[i]):
+        if not (descriptor.name in textures):
           raise newException(Exception, "Missing shader texture in scene: " & descriptor.name)
-        descriptor.imageview = textures[i][descriptor.name].imageView
-        descriptor.sampler = textures[i][descriptor.name].sampler
+        descriptor.imageview = textures[descriptor.name].imageView
+        descriptor.sampler = textures[descriptor.name].sampler
 
 proc createPipeline*(device: Device, renderPass: VkRenderPass, vertexCode: ShaderCode, fragmentCode: ShaderCode, inFlightFrames: int, subpass = 0'u32): Pipeline =
   assert renderPass.valid
--- a/tests/test_vulkan_wrapper.nim	Sat May 06 01:37:33 2023 +0700
+++ b/tests/test_vulkan_wrapper.nim	Sat May 06 23:06:33 2023 +0700
@@ -100,11 +100,16 @@
   var r = rect(color="ff0000")
   var t = tri(color="0000ff")
   var c = circle(color="00ff00")
-  setInstanceData[Vec3f](r, "translate", @[newVec3f(0.5, -0.3)])
-  setInstanceData[Vec3f](t, "translate", @[newVec3f(0.3,  0.3)])
-  setInstanceData[Vec3f](c, "translate", @[newVec3f(-0.3,  0.1)])
+  r.setInstanceData("translate", @[newVec3f(0.5, -0.3)])
+  t.setInstanceData("translate", @[newVec3f(0.3,  0.3)])
+  c.setInstanceData("translate", @[newVec3f(-0.3,  0.1)])
   result = newEntity("root", t, r, c)
 
+proc scene_flag(): Entity =
+  var r = rect(color="ff0000")
+  r.updateMeshData("color", @[newVec3f(0, 0), newVec3f(1, 0), newVec3f(1, 1), newVec3f(0, 1)])
+  result = newEntity("root", r)
+
 proc main() =
   var engine = initEngine("Test")
 
@@ -142,16 +147,17 @@
   var scenes = [
     newScene("simple", scene_simple()),
     newScene("different mesh types", scene_different_mesh_types()),
-    newScene("primitives", scene_primitives())
+    newScene("primitives", scene_primitives()),
+    newScene("flag", scene_flag()),
   ]
   for scene in scenes.mitems:
     scene.addShaderGlobal("time", 0.0'f32)
-    let (R, W) = ([0'u8, 0'u8, 0'u8, 0'u8], [0'u8, 0'u8, 0'u8, 0'u8])
+    let (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8])
     scene.addTexture("my_little_texture", TextureImage(width: 5, height: 5, imagedata: @[
       R, R, R, R, R,
-      R, R, R, R, R,
-      R, R, R, R, R,
-      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,
     ]))
     engine.addScene(scene, vertexInput)