changeset 662:9848403320f0

add: support for arrays of samplers
author Sam <sam@basx.dev>
date Mon, 08 May 2023 00:38:05 +0700
parents 9995702c34b2
children af9ab61aa87e
files src/semicongine/entity.nim src/semicongine/gpu_data.nim src/semicongine/renderer.nim src/semicongine/vulkan/descriptor.nim src/semicongine/vulkan/pipeline.nim src/semicongine/vulkan/shader.nim tests/test_materials.nim
diffstat 7 files changed, 79 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/entity.nim	Sun May 07 18:13:39 2023 +0700
+++ b/src/semicongine/entity.nim	Mon May 08 00:38:05 2023 +0700
@@ -15,7 +15,7 @@
     name*: string
     root*: Entity
     shaderGlobals*: Table[string, DataValue]
-    textures*: Table[string, TextureImage]
+    textures*: Table[string, seq[TextureImage]]
 
   Entity* = ref object of RootObj
     name*: string
@@ -42,8 +42,11 @@
 func setShaderGlobal*[T](scene: var Scene, name: string, value: T) =
   setValue[T](scene.shaderGlobals[name], value)
 
+func addTextures*(scene: var Scene, name: string, texture: seq[TextureImage]) =
+  scene.textures[name] = texture
+
 func addTexture*(scene: var Scene, name: string, texture: TextureImage) =
-  scene.textures[name] = texture
+  scene.textures[name] = @[texture]
 
 func newScene*(name: string, root: Entity): Scene =
   Scene(name: name, root: root)
--- a/src/semicongine/gpu_data.nim	Sun May 07 18:13:39 2023 +0700
+++ b/src/semicongine/gpu_data.nim	Mon May 08 00:38:05 2023 +0700
@@ -148,6 +148,7 @@
   ShaderAttribute* = object
     name*: string
     thetype*: DataType
+    arrayCount*: int
     perInstance*: bool
     memoryPerformanceHint*: MemoryPerformanceHint
 
@@ -216,7 +217,7 @@
 
 func size*(attribute: ShaderAttribute, perDescriptor=false): uint32 =
   if perDescriptor: attribute.thetype.size div attribute.thetype.numberOfVertexInputAttributeDescriptors
-  else:      attribute.thetype.size
+  else: attribute.thetype.size
 
 func size*(thetype: seq[ShaderAttribute]): uint32 =
   for attribute in thetype:
@@ -285,12 +286,14 @@
 func attr*[T: GPUType](
   name: string,
   perInstance=false,
+  arrayCount=0,
   memoryPerformanceHint=PreferFastRead,
 ): auto =
   ShaderAttribute(
     name: name,
     thetype: getDataType[T](),
     perInstance: perInstance,
+    arrayCount: arrayCount,
     memoryPerformanceHint: memoryPerformanceHint,
   )
 
@@ -844,9 +847,10 @@
     return @[]
   var i = 0'u32
   for attribute in group:
-      result.add &"layout(location = {i}) in {attribute.thetype.glslType} {attribute.name};"
-      for j in 0 ..< attribute.thetype.numberOfVertexInputAttributeDescriptors:
-        i += attribute.thetype.nLocationSlots
+    assert attribute.arrayCount == 0, "arrays not yet supported for shader vertex attributes"
+    result.add &"layout(location = {i}) in {attribute.thetype.glslType} {attribute.name};"
+    for j in 0 ..< attribute.thetype.numberOfVertexInputAttributeDescriptors:
+      i += attribute.thetype.nLocationSlots
 
 func glslUniforms*(group: seq[ShaderAttribute], blockName="Uniforms", binding: int): seq[string] =
   if group.len == 0:
@@ -854,6 +858,7 @@
   # currently only a single uniform block supported, therefore binding = 0
   result.add(&"layout(binding = {binding}) uniform T{blockName} {{")
   for attribute in group:
+    assert attribute.arrayCount == 0, "arrays not yet supported for uniforms"
     result.add(&"    {attribute.thetype.glslType} {attribute.name};")
   result.add(&"}} {blockName};")
 
@@ -862,7 +867,10 @@
     return @[]
   var thebinding = basebinding
   for attribute in group:
-    result.add(&"layout(binding = {thebinding}) uniform {attribute.thetype.glslType} {attribute.name};")
+    var arrayDecl = ""
+    if attribute.arrayCount > 0:
+      arrayDecl = &"[{attribute.arrayCount}]"
+    result.add(&"layout(binding = {thebinding}) uniform {attribute.thetype.glslType} {attribute.name}{arrayDecl};")
     inc thebinding
 
 func glslOutput*(group: seq[ShaderAttribute]): seq[string] =
@@ -870,5 +878,6 @@
     return @[]
   var i = 0'u32
   for attribute in group:
+    assert attribute.arrayCount == 0, "arrays not yet supported for outputs"
     result.add &"layout(location = {i}) out {attribute.thetype.glslType} {attribute.name};"
     i += 1
--- a/src/semicongine/renderer.nim	Sun May 07 18:13:39 2023 +0700
+++ b/src/semicongine/renderer.nim	Mon May 08 00:38:05 2023 +0700
@@ -28,7 +28,7 @@
     indexBuffer*: Buffer
     uniformBuffers*: seq[Buffer] # one per frame-in-flight
     images*: seq[Image] # used to back texturees
-    textures*: Table[string, Texture] # per frame-in-flight
+    textures*: Table[string, seq[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
@@ -172,8 +172,10 @@
             preferVRAM=true,
           )
 
-      for name, image in scene.textures.pairs:
-        data.textures[name] = renderer.device.createTexture(image.width, image.height, 4, addr image.imagedata[0][0], image.interpolation)
+      for name, images in scene.textures.pairs:
+        data.textures[name] = @[]
+        for image in images:
+          data.textures[name].add renderer.device.createTexture(image.width, image.height, 4, addr image.imagedata[0][0], image.interpolation)
       pipeline.setupDescriptors(data.uniformBuffers, data.textures, inFlightFrames=renderer.swapchain.inFlightFrames)
       for frame_i in 0 ..< renderer.swapchain.inFlightFrames:
         pipeline.descriptorSets[frame_i].writeDescriptorSet()
@@ -294,7 +296,8 @@
     for buffer in data.uniformBuffers.mitems:
       assert buffer.vk.valid
       buffer.destroy()
-    for texture in data.textures.mvalues:
-      texture.destroy()
+    for textures in data.textures.mvalues:
+      for texture in textures.mitems:
+        texture.destroy()
   renderer.renderPass.destroy()
   renderer.swapchain.destroy()
--- a/src/semicongine/vulkan/descriptor.nim	Sun May 07 18:13:39 2023 +0700
+++ b/src/semicongine/vulkan/descriptor.nim	Mon May 08 00:38:05 2023 +0700
@@ -21,8 +21,8 @@
       offset*: uint64
       size*: uint64
     of ImageSampler:
-      imageview*: ImageView
-      sampler*: Sampler
+      imageviews*: seq[ImageView]
+      samplers*: seq[Sampler]
   DescriptorSet* = object # "instance" of a DescriptorSetLayout
     vk*: VkDescriptorSet
     layout*: DescriptorSetLayout
@@ -132,7 +132,6 @@
 
   var descriptorSetWrites: seq[VkWriteDescriptorSet]
   var bufferInfos: seq[VkDescriptorBufferInfo]
-  var imageInfos: seq[VkDescriptorImageInfo]
 
   var i = bindingBase
   for descriptor in descriptorSet.layout.descriptors:
@@ -153,13 +152,15 @@
           pBufferInfo: addr bufferInfos[^1],
         )
     elif descriptor.thetype == ImageSampler:
-      assert descriptor.imageview.vk.valid
-      assert descriptor.sampler.vk.valid
-      imageInfos.add VkDescriptorImageInfo(
-        imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-        imageView: descriptor.imageview.vk,
-        sampler: descriptor.sampler.vk,
-      )
+      var imgInfo: seq[VkDescriptorImageInfo]
+      for img_i in 0 ..< descriptor.count:
+        assert descriptor.imageviews[img_i].vk.valid
+        assert descriptor.samplers[img_i].vk.valid
+        imgInfo.add VkDescriptorImageInfo(
+          imageLayout: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+          imageView: descriptor.imageviews[img_i].vk,
+          sampler: descriptor.samplers[img_i].vk,
+        )
       descriptorSetWrites.add VkWriteDescriptorSet(
           sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
           dstSet: descriptorSet.vk,
@@ -167,7 +168,7 @@
           dstArrayElement: 0,
           descriptorType: descriptor.vkType,
           descriptorCount: descriptor.count,
-          pImageInfo: addr imageInfos[^1],
+          pImageInfo: addr imgInfo[0],
         )
     inc i
   descriptorSet.layout.device.vk.vkUpdateDescriptorSets(uint32(descriptorSetWrites.len), descriptorSetWrites.toCPointer, 0, nil)
--- a/src/semicongine/vulkan/pipeline.nim	Sun May 07 18:13:39 2023 +0700
+++ b/src/semicongine/vulkan/pipeline.nim	Mon May 08 00:38:05 2023 +0700
@@ -1,4 +1,5 @@
 import std/tables
+import std/strformat
 import std/sequtils
 
 import ./api
@@ -36,7 +37,7 @@
         uniformList[attribute.name] = attribute
   result = uniformList.values.toSeq
 
-proc setupDescriptors*(pipeline: var Pipeline, buffers: seq[Buffer], textures: Table[string, Texture], inFlightFrames: int) =
+proc setupDescriptors*(pipeline: var Pipeline, buffers: seq[Buffer], textures: Table[string, seq[Texture]], inFlightFrames: int) =
   assert pipeline.vk.valid
   assert buffers.len == 0 or buffers.len == inFlightFrames # need to guard against this in case we have no uniforms, then we also create no buffers
   assert pipeline.descriptorSets.len > 0
@@ -54,8 +55,11 @@
       elif descriptor.thetype == ImageSampler:
         if not (descriptor.name in textures):
           raise newException(Exception, "Missing shader texture in scene: " & descriptor.name)
-        descriptor.imageview = textures[descriptor.name].imageView
-        descriptor.sampler = textures[descriptor.name].sampler
+        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]:
+          descriptor.imageviews.add t.imageView
+          descriptor.samplers.add t.sampler
 
 proc createPipeline*(device: Device, renderPass: VkRenderPass, vertexCode: ShaderCode, fragmentCode: ShaderCode, inFlightFrames: int, subpass = 0'u32): Pipeline =
   assert renderPass.valid
@@ -76,6 +80,8 @@
   var descriptors: seq[Descriptor]
 
   if vertexCode.uniforms.len > 0:
+    for uniform in vertexCode.uniforms:
+      assert uniform.arrayCount == 0, "arrays not yet supported for uniforms"
     descriptors.add Descriptor(
       name: "Uniforms",
       thetype: Uniform,
@@ -87,7 +93,7 @@
     descriptors.add Descriptor(
       name: sampler.name,
       thetype: ImageSampler,
-      count: 1,
+      count: (if sampler.arrayCount == 0: 1 else: sampler.arrayCount),
       stages: @[VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT],
       itemsize: 0,
     )
--- a/src/semicongine/vulkan/shader.nim	Sun May 07 18:13:39 2023 +0700
+++ b/src/semicongine/vulkan/shader.nim	Mon May 08 00:38:05 2023 +0700
@@ -21,9 +21,9 @@
 
 type
   ShaderCode* = object # compiled shader code with some meta data
+    binary: seq[uint32]
     stage: VkShaderStageFlagBits
     entrypoint: string
-    binary: seq[uint32]
     inputs*: seq[ShaderAttribute]
     uniforms*: seq[ShaderAttribute]
     samplers*: seq[ShaderAttribute]
--- a/tests/test_materials.nim	Sun May 07 18:13:39 2023 +0700
+++ b/tests/test_materials.nim	Mon May 08 00:38:05 2023 +0700
@@ -1,16 +1,28 @@
+import std/times
+
 import semicongine
 
 proc main() =
   var scene = newScene("main", root=newEntity("rect", rect()))
   let (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8])
   let (RT, WT, PT) = (hexToColorAlpha("A51931").asPixel, hexToColorAlpha("F4F5F8").asPixel, hexToColorAlpha("2D2A4A").asPixel)
-  scene.addTexture("my_texture", TextureImage(width: 13, height: 5, imagedata: @[
-    R, R, R, R, R, W, RT, RT, RT, RT, RT, RT, RT,
-    R, R, W, R, R, W, WT, WT, WT, WT, WT, WT, WT,
-    R, W, W, W, R, W, PT, PT, PT, PT, PT, PT, PT,
-    R, R, W, R, R, W, WT, WT, WT, WT, WT, WT, WT,
-    R, R, R, R, R, W, RT, RT, RT, RT, RT, RT, RT,
-  ]))
+  let
+    t1 = TextureImage(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,
+    ])
+    t2 = TextureImage(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,
+      WT, WT, WT, WT, WT, WT, WT,
+      RT, RT, RT, RT, RT, RT, RT,
+    ])
+  scene.addTextures("my_texture", @[t1, t2])
+  scene.addShaderGlobal("time", 0'f32)
   var m: Mesh = Mesh(scene.root.components[0])
 
   var engine = initEngine("Test materials")
@@ -20,11 +32,13 @@
       attr[Vec2f]("uv", memoryPerformanceHint=PreferFastRead),
     ]
     vertexOutput = @[attr[Vec2f]("uvout")]
-    samplers = @[attr[Sampler2DType]("my_texture")]
+    uniforms = @[attr[float32]("time")]
+    samplers = @[attr[Sampler2DType]("my_texture", arrayCount=2)]
     fragOutput = @[attr[Vec4f]("color")]
     vertexCode = compileGlslShader(
       stage=VK_SHADER_STAGE_VERTEX_BIT,
       inputs=vertexInput,
+      uniforms=uniforms,
       samplers=samplers,
       outputs=vertexOutput,
       main="""gl_Position = vec4(position, 1.0); uvout = uv;"""
@@ -32,13 +46,19 @@
     fragmentCode = compileGlslShader(
       stage=VK_SHADER_STAGE_FRAGMENT_BIT,
       inputs=vertexOutput,
+      uniforms=uniforms,
       samplers=samplers,
       outputs=fragOutput,
-      main="color = texture(my_texture, uvout);"
+      main="""
+float d = sin(Uniforms.time * 0.5) * 0.5 + 0.5;
+color = texture(my_texture[0], uvout) * (1 - d) + texture(my_texture[1], uvout) * d;
+"""
     )
   engine.setRenderer(engine.gpuDevice.simpleForwardRenderPass(vertexCode, fragmentCode))
   engine.addScene(scene, vertexInput)
+  var t = cpuTime()
   while engine.updateInputs() == Running and not engine.keyIsDown(Escape):
+    setShaderGlobal(scene, "time", float32(cpuTime() - t))
     engine.renderScene(scene)
   engine.destroy()