# HG changeset patch # User Sam # Date 1683481085 -25200 # Node ID 9848403320f01c97e56c26b58ccc2902f3671ca9 # Parent 9995702c34b2eae1b9c0bc94944bac80053ef705 add: support for arrays of samplers diff -r 9995702c34b2 -r 9848403320f0 src/semicongine/entity.nim --- 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) diff -r 9995702c34b2 -r 9848403320f0 src/semicongine/gpu_data.nim --- 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 diff -r 9995702c34b2 -r 9848403320f0 src/semicongine/renderer.nim --- 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() diff -r 9995702c34b2 -r 9848403320f0 src/semicongine/vulkan/descriptor.nim --- 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) diff -r 9995702c34b2 -r 9848403320f0 src/semicongine/vulkan/pipeline.nim --- 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, ) diff -r 9995702c34b2 -r 9848403320f0 src/semicongine/vulkan/shader.nim --- 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] diff -r 9995702c34b2 -r 9848403320f0 tests/test_materials.nim --- 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()