# HG changeset patch # User Sam # Date 1692458646 -25200 # Node ID 9defff46da48e4dc0aba90680258a976648c701b # Parent 6dab370d175823f56c3343b3dc15bbe7b22b312d add: first complete working version of multiple materials and shaders per scene, yipie :) diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/core/gpu_data.nim --- a/src/semicongine/core/gpu_data.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/core/gpu_data.nim Sat Aug 19 22:24:06 2023 +0700 @@ -408,6 +408,10 @@ elif T is TMat4[float64]: value.mat4f64[] = data else: {. error: "Virtual datatype has no values" .} +func toGPUValue*[T: GPUType](value: T): DataValue = + result = DataValue(thetype: getDataType[T]()) + result.setValue(value) + func newDataList*(thetype: DataType): DataList = result = DataList(thetype: thetype) case result.thetype diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/core/imagetypes.nim --- a/src/semicongine/core/imagetypes.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/core/imagetypes.nim Sat Aug 19 22:24:06 2023 +0700 @@ -16,15 +16,12 @@ Texture* = object name*: string image*: Image - sampler*: Sampler - -proc DefaultSampler*(): Sampler = - Sampler( - magnification: VK_FILTER_LINEAR, - minification: VK_FILTER_LINEAR, - wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, - wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, - ) + sampler*: Sampler = Sampler( + magnification: VK_FILTER_LINEAR, + minification: VK_FILTER_LINEAR, + wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, + wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, + ) proc `[]`*(image: Image, x, y: uint32): Pixel = assert x < image.width @@ -54,3 +51,10 @@ for x in 0 ..< width: result[x, y] = fill +let EMPTYTEXTURE* = Texture(image: newImage(1, 1, @[[255'u8, 0'u8, 255'u8, 255'u8]]), sampler: Sampler( + magnification: VK_FILTER_NEAREST, + minification: VK_FILTER_NEAREST, + wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, + wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, + ) +) diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/engine.nim --- a/src/semicongine/engine.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/engine.nim Sat Aug 19 22:24:06 2023 +0700 @@ -12,7 +12,6 @@ import ./vulkan/shader import ./scene -import ./mesh import ./renderer import ./events import ./audio @@ -81,7 +80,8 @@ if debug: instanceExtensions.add "VK_EXT_debug_utils" enabledLayers.add "VK_LAYER_KHRONOS_validation" - putEnv("VK_LAYER_ENABLES", "VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT") + # putEnv("VK_LAYER_ENABLES", "VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT") + putEnv("VK_LAYER_ENABLES", "") if defined(linux) and DEBUG: enabledLayers.add "VK_LAYER_MESA_overlay" @@ -102,7 +102,7 @@ ) startMixerThread() -proc setRenderer*(engine: var Engine, shaders: Table[string, ShaderConfiguration], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) = +proc initRenderer*(engine: var Engine, shaders: Table[string, ShaderConfiguration], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) = assert not engine.renderer.isSome engine.renderer = some(engine.device.initRenderer(shaders=shaders, clearColor=clearColor)) diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/mesh.nim --- a/src/semicongine/mesh.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/mesh.nim Sat Aug 19 22:24:06 2023 +0700 @@ -18,26 +18,28 @@ Small # up to 2^16 vertices Big # up to 2^32 vertices Mesh* = ref object of Component + vertexCount*: uint32 instanceCount*: uint32 - instanceTransforms*: seq[Mat4] # this should not reside in data["transform"], as we will use data["transform"] to store the final transformation matrix (as derived from the scene-tree) + instanceTransforms*: seq[Mat4] # this should not reside in instanceData["transform"], as we will use instanceData["transform"] to store the final transformation matrix (as derived from the scene-tree) material*: Material - dirtyInstanceTransforms: bool - data: Table[string, DataList] - changedAttributes: seq[string] case indexType*: MeshIndexType of None: discard of Tiny: tinyIndices: seq[array[3, uint8]] of Small: smallIndices: seq[array[3, uint16]] of Big: bigIndices: seq[array[3, uint32]] + visible: bool = true + dirtyInstanceTransforms: bool + vertexData: Table[string, DataList] + instanceData: Table[string, DataList] + dirtyAttributes: seq[string] Material* = ref object materialType*: string name*: string - index*: uint16 constants*: Table[string, DataValue] textures*: Table[string, Texture] proc hash*(material: Material): Hash = - hash(material.name) + hash(cast[int64](material)) converter toVulkan*(indexType: MeshIndexType): VkIndexType = case indexType: @@ -46,11 +48,6 @@ of Small: VK_INDEX_TYPE_UINT16 of Big: VK_INDEX_TYPE_UINT32 -func vertexCount*(mesh: Mesh): uint32 = - result = 0'u32 - for list in mesh.data.values: - result = max(list.len, result) - func indicesCount*(mesh: Mesh): uint32 = ( case mesh.indexType @@ -61,7 +58,7 @@ ) * 3 method `$`*(mesh: Mesh): string = - &"Mesh, vertexCount: {mesh.vertexCount}, vertexData: {mesh.data.keys().toSeq()}, indexType: {mesh.indexType}" + &"Mesh, vertexCount: {mesh.vertexCount}, vertexData: {mesh.vertexData.keys().toSeq()}, indexType: {mesh.indexType}" proc `$`*(material: Material): string = var constants: seq[string] @@ -70,10 +67,10 @@ var textures: seq[string] for key in material.textures.keys: textures.add &"{key}" - return &"""{material.name} ({material.index}) | Values: {constants.join(", ")} | Textures: {textures.join(", ")}""" + return &"""{material.name} | Values: {constants.join(", ")} | Textures: {textures.join(", ")}""" func prettyData*(mesh: Mesh): string = - for attr, data in mesh.data.pairs: + for attr, data in mesh.vertexData.pairs: result &= &"{attr}: {data}\n" result &= (case mesh.indexType of None: "" @@ -82,17 +79,17 @@ of Big: &"indices: {mesh.bigIndices}") proc setMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = - assert not (attribute in mesh.data) - mesh.data[attribute] = newDataList(data) + assert not (attribute in mesh.vertexData) + mesh.vertexData[attribute] = newDataList(data) proc setMeshData*(mesh: Mesh, attribute: string, data: DataList) = - assert not (attribute in mesh.data) - mesh.data[attribute] = data + assert not (attribute in mesh.vertexData) + mesh.vertexData[attribute] = data proc setInstanceData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = assert uint32(data.len) == mesh.instanceCount - assert not (attribute in mesh.data) - mesh.data[attribute] = newDataList(data) + assert not (attribute in mesh.instanceData) + mesh.instanceData[attribute] = newDataList(data) func newMesh*( positions: openArray[Vec3f], @@ -119,6 +116,7 @@ instanceCount: instanceCount, instanceTransforms: newSeqWith(int(instanceCount), Unit4F32), indexType: indexType, + vertexCount: uint32(positions.len) ) result.material = material @@ -160,16 +158,29 @@ instanceCount=instanceCount, ) -func availableAttributes*(mesh: Mesh): seq[string] = - mesh.data.keys.toSeq +func vertexAttributes*(mesh: Mesh): seq[string] = + mesh.vertexData.keys.toSeq + +func instanceAttributes*(mesh: Mesh): seq[string] = + mesh.instanceData.keys.toSeq -func dataSize*(mesh: Mesh, attribute: string): uint32 = - mesh.data[attribute].size +func attributeSize*(mesh: Mesh, attribute: string): uint32 = + if mesh.vertexData.contains(attribute): + mesh.vertexData[attribute].size + elif mesh.instanceData.contains(attribute): + mesh.instanceData[attribute].size + else: + 0 -func dataType*(mesh: Mesh, attribute: string): DataType = - mesh.data[attribute].theType +func attributeType*(mesh: Mesh, attribute: string): DataType = + if mesh.vertexData.contains(attribute): + mesh.vertexData[attribute].theType + elif mesh.instanceData.contains(attribute): + mesh.instanceData[attribute].theType + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") -func indexDataSize*(mesh: Mesh): uint32 = +func indexSize*(mesh: Mesh): uint32 = case mesh.indexType of None: 0'u32 of Tiny: uint32(mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0))) @@ -186,57 +197,91 @@ of Small: rawData(mesh.smallIndices) of Big: rawData(mesh.bigIndices) -func hasDataFor*(mesh: Mesh, attribute: string): bool = - attribute in mesh.data +func hasAttribute*(mesh: Mesh, attribute: string): bool = + mesh.vertexData.contains(attribute) or mesh.instanceData.contains(attribute) func getRawData*(mesh: Mesh, attribute: string): (pointer, uint32) = - mesh.data[attribute].getRawData() + if mesh.vertexData.contains(attribute): + mesh.vertexData[attribute].getRawData() + elif mesh.instanceData.contains(attribute): + mesh.instanceData[attribute].getRawData() + else: + (nil, 0) proc getMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string): ref seq[T] = - assert attribute in mesh.data - getValues[T](mesh.data[attribute]) + if mesh.vertexData.contains(attribute): + getValues[T](mesh.vertexData[attribute]) + elif mesh.instanceData.contains(attribute): + getValues[T](mesh.instanceData[attribute]) + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") -proc initData*(mesh: Mesh, attribute: ShaderAttribute) = - assert not (attribute.name in mesh.data) - mesh.data[attribute.name] = newDataList(thetype=attribute.thetype) +proc initAttribute*(mesh: Mesh, attribute: ShaderAttribute) = if attribute.perInstance: - mesh.data[attribute.name].initData(mesh.instanceCount) + mesh.instanceData[attribute.name] = newDataList(thetype=attribute.thetype) + mesh.instanceData[attribute.name].initData(mesh.instanceCount) else: - mesh.data[attribute.name].initData(mesh.vertexCount) - -proc updateMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = - assert attribute in mesh.data - mesh.changedAttributes.add attribute - setValues(mesh.data[attribute], data) + mesh.vertexData[attribute.name] = newDataList(thetype=attribute.thetype) + mesh.vertexData[attribute.name].initData(mesh.vertexCount) -proc updateMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, i: uint32, value: T) = - assert attribute in mesh.data - mesh.changedAttributes.add attribute - setValue(mesh.data[attribute], i, value) +proc initAttribute*[T](mesh: Mesh, attribute: ShaderAttribute, value: T) = + if attribute.perInstance: + mesh.instanceData[attribute.name] = newDataList(thetype=attribute.thetype) + mesh.instanceData[attribute.name].initData(mesh.instanceCount) + mesh.instanceData[attribute.name].setValues(newSeqWith(int(mesh.instanceCount), value)) + else: + mesh.vertexData[attribute.name] = newDataList(thetype=attribute.thetype) + mesh.vertexData[attribute.name].initData(mesh.vertexCount) + mesh.instanceData[attribute.name].setValues(newSeqWith(int(mesh.vertexCount), value)) -proc appendMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = - assert attribute in mesh.data - mesh.changedAttributes.add attribute - appendValues(mesh.data[attribute], data) +proc updateAttributeData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = + if mesh.vertexData.contains(attribute): + setValues(mesh.vertexData[attribute], data) + elif mesh.instanceData.contains(attribute): + setValues(mesh.instanceData[attribute], data) + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") + mesh.dirtyAttributes.add attribute -# currently only used for loading from files, shouls -proc appendMeshData*(mesh: Mesh, attribute: string, data: DataList) = - assert attribute in mesh.data - assert data.thetype == mesh.data[attribute].thetype - mesh.changedAttributes.add attribute - appendValues(mesh.data[attribute], data) +proc updateAttributeData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, i: uint32, value: T) = + if mesh.vertexData.contains(attribute): + setValue(mesh.vertexData[attribute], i, value) + elif mesh.instanceData.contains(attribute): + setValue(mesh.instanceData[attribute], i, value) + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") + mesh.dirtyAttributes.add attribute proc updateInstanceData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = assert uint32(data.len) == mesh.instanceCount - assert attribute in mesh.data - mesh.changedAttributes.add attribute - setValues(mesh.data[attribute], data) + if mesh.vertexData.contains(attribute): + setValues(mesh.vertexData[attribute], data) + elif mesh.instanceData.contains(attribute): + setValues(mesh.instanceData[attribute], data) + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") + mesh.dirtyAttributes.add attribute -proc appendInstanceData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = - assert uint32(data.len) == mesh.instanceCount - assert attribute in mesh.data - mesh.changedAttributes.add attribute - appendValues(mesh.data[attribute], data) +proc appendAttributeData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = + if mesh.vertexData.contains(attribute): + appendValues(mesh.vertexData[attribute], data) + elif mesh.instanceData.contains(attribute): + appendValues(mesh.instanceData[attribute], data) + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") + mesh.dirtyAttributes.add attribute + +# currently only used for loading from files, shouls +proc appendAttributeData*(mesh: Mesh, attribute: string, data: DataList) = + if mesh.vertexData.contains(attribute): + assert data.thetype == mesh.vertexData[attribute].thetype + appendValues(mesh.vertexData[attribute], data) + elif mesh.instanceData.contains(attribute): + assert data.thetype == mesh.instanceData[attribute].thetype + appendValues(mesh.instanceData[attribute], data) + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") + mesh.dirtyAttributes.add attribute proc appendIndicesData*(mesh: Mesh, v1, v2, v3: uint32) = case mesh.indexType @@ -246,18 +291,24 @@ of Big: mesh.bigIndices.add([v1, v2, v3]) func hasDataChanged*(mesh: Mesh, attribute: string): bool = - attribute in mesh.changedAttributes + attribute in mesh.dirtyAttributes proc clearDataChanged*(mesh: Mesh) = - mesh.changedAttributes = @[] + mesh.dirtyAttributes = @[] proc transform*[T: GPUType](mesh: Mesh, attribute: string, transform: Mat4) = - assert attribute in mesh.data - for v in getValues[T](mesh.data[attribute])[].mitems: - v = transform * v + if mesh.vertexData.contains(attribute): + for v in getValues[T](mesh.vertexData[attribute])[].mitems: + v = transform * v + elif mesh.instanceData.contains(attribute): + for v in getValues[T](mesh.instanceData[attribute])[].mitems: + v = transform * v + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") func rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = result = Mesh( + vertexCount: 4, instanceCount: 1, indexType: Small, smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]], @@ -276,7 +327,7 @@ setInstanceData(result, "transform", @[Unit4F32]) func tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = - result = Mesh(instanceCount: 1, instanceTransforms: @[Unit4F32]) + result = Mesh(vertexCount: 3, instanceCount: 1, instanceTransforms: @[Unit4F32]) let half_w = width / 2 half_h = height / 2 @@ -287,7 +338,7 @@ func circle*(width=1'f32, height=1'f32, nSegments=12'u16, color="ffffffff"): Mesh = assert nSegments >= 3 - result = Mesh(instanceCount: 1, indexType: Small, instanceTransforms: @[Unit4F32]) + result = Mesh(vertexCount: 3 + nSegments, instanceCount: 1, indexType: Small, instanceTransforms: @[Unit4F32]) let half_w = width / 2 diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/renderer.nim Sat Aug 19 22:24:06 2023 +0700 @@ -1,8 +1,8 @@ import std/options -import std/enumerate import std/tables import std/strformat import std/logging +import std/sequtils import ./core import ./vulkan/buffer @@ -32,7 +32,7 @@ materialIndexAttribute: string # name of attribute that is used for material selection materials: seq[Material] entityTransformationCache: Table[Mesh, Mat4] # remembers last transformation, avoid to send GPU-updates if no changes - descriptorPool*: DescriptorPool + descriptorPools*: Table[VkPipeline, DescriptorPool] descriptorSets*: Table[VkPipeline, seq[DescriptorSet]] Renderer* = object device: Device @@ -40,7 +40,13 @@ renderPass: RenderPass swapchain: Swapchain scenedata: Table[Scene, SceneData] + emptyTexture: VulkanTexture +func usesMaterialType(scenedata: SceneData, materialType: string): bool = + for drawable in scenedata.drawables.values: + if drawable.mesh.material.materialType == materialType: + return true + return false proc initRenderer*(device: Device, shaders: Table[string, ShaderConfiguration], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): Renderer = assert device.vk.valid @@ -54,6 +60,7 @@ raise newException(Exception, "Unable to create swapchain") result.swapchain = swapchain.get() + result.emptyTexture = device.uploadTexture(EMPTYTEXTURE) func inputs(renderer: Renderer): seq[ShaderAttribute] = for i in 0 ..< renderer.renderPass.subpasses.len: @@ -69,9 +76,9 @@ assert not (scene in renderer.scenedata) const VERTEX_ATTRIB_ALIGNMENT = 4 # used for buffer alignment - # TODO: find all inputs and samplers from scene materials - let inputs = renderer.inputs - var samplers = renderer.samplers + let + inputs = renderer.inputs + samplers = renderer.samplers var scenedata = SceneData() # if mesh transformation are handled through the scenegraph-transformation, set it up here @@ -98,22 +105,26 @@ for mesh in allComponentsOfType[Mesh](scene.root): if mesh.material != nil and not scenedata.materials.contains(mesh.material): scenedata.materials.add mesh.material + for textureName, texture in mesh.material.textures.pairs: + if not scenedata.textures.hasKey(textureName): + scenedata.textures[textureName] = @[] + scenedata.textures[textureName].add renderer.device.uploadTexture(texture) # find all meshes, populate missing attribute values for shader var allMeshes: seq[Mesh] for mesh in allComponentsOfType[Mesh](scene.root): allMeshes.add mesh for inputAttr in inputs: - if not mesh.hasDataFor(inputAttr.name): - warn(&"Mesh is missing data for shader attribute {inputAttr.name}, auto-filling with empty values") - mesh.initData(inputAttr) - assert mesh.dataType(inputAttr.name) == inputAttr.thetype, &"mesh attribute {inputAttr.name} has type {mesh.dataType(inputAttr.name)} but shader expects {inputAttr.thetype}" if scenedata.materialIndexAttribute != "" and inputAttr.name == scenedata.materialIndexAttribute: assert mesh.material != nil, "Missing material specification for mesh. Either set the 'materials' attribute or pass the argument 'materialIndexAttribute=\"\"' when calling 'addScene'" let matIndex = scenedata.materials.find(mesh.material) if matIndex < 0: raise newException(Exception, &"Required material '{mesh.material}' not available in scene (available are: {scenedata.materials})") - updateMeshData[uint16](mesh, scenedata.materialIndexAttribute, 0, uint16(matIndex)) + mesh.initAttribute(inputAttr, uint16(matIndex)) + elif not mesh.hasAttribute(inputAttr.name): + warn(&"Mesh is missing data for shader attribute {inputAttr.name}, auto-filling with empty values") + mesh.initAttribute(inputAttr) + assert mesh.attributeType(inputAttr.name) == inputAttr.thetype, &"mesh attribute {inputAttr.name} has type {mesh.attributeType(inputAttr.name)} but shader expects {inputAttr.thetype}" # create index buffer if necessary var indicesBufferSize = 0'u64 @@ -127,7 +138,7 @@ # index value alignment required by Vulkan if indicesBufferSize mod indexAlignment != 0: indicesBufferSize += indexAlignment - (indicesBufferSize mod indexAlignment) - indicesBufferSize += mesh.indexDataSize + indicesBufferSize += mesh.indexSize if indicesBufferSize > 0: scenedata.indexBuffer = renderer.device.createBuffer( size=indicesBufferSize, @@ -155,7 +166,7 @@ # we need to expand the buffer size as well, therefore considering alignment already here as well if perLocationSizes[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0: perLocationSizes[attribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationSizes[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT) - perLocationSizes[attribute.memoryPerformanceHint] += mesh.dataSize(attribute.name) + perLocationSizes[attribute.memoryPerformanceHint] += mesh.attributeSize(attribute.name) for memoryPerformanceHint, bufferSize in perLocationSizes.pairs: if bufferSize > 0: scenedata.vertexBuffers[memoryPerformanceHint] = renderer.device.createBuffer( @@ -202,13 +213,6 @@ indexBufferOffset += size scenedata.drawables[mesh] = drawable - # upload textures - for material in scenedata.materials: - for textureName, texture in material.textures.pairs: - if not scenedata.textures.hasKey(textureName): - scenedata.textures[textureName] = @[] - scenedata.textures[textureName].add renderer.device.uploadTexture(texture) - # setup uniforms and samplers for subpass_i in 0 ..< renderer.renderPass.subpasses.len: for material, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs: @@ -232,13 +236,14 @@ samplercount += (if sampler.arrayCount == 0: 1'u32 else: sampler.arrayCount) poolsizes.add (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, uint32(renderer.swapchain.inFlightFrames) * samplercount * 2) - scenedata.descriptorPool = renderer.device.createDescriptorSetPool(poolsizes) + scenedata.descriptorPools[pipeline.vk] = renderer.device.createDescriptorSetPool(poolsizes) scenedata.descriptorSets[pipeline.vk] = pipeline.setupDescriptors( - scenedata.descriptorPool, + scenedata.descriptorPools[pipeline.vk], scenedata.uniformBuffers.getOrDefault(pipeline.vk, @[]), scenedata.textures, - inFlightFrames=renderer.swapchain.inFlightFrames + inFlightFrames=renderer.swapchain.inFlightFrames, + emptyTexture=renderer.emptyTexture, ) for frame_i in 0 ..< renderer.swapchain.inFlightFrames: scenedata.descriptorSets[pipeline.vk][frame_i].writeDescriptorSet() @@ -253,7 +258,6 @@ var (pdata, size) = mesh.getRawData(attribute) let memoryPerformanceHint = sceneData.attributeLocation[attribute] let bindingNumber = sceneData.attributeBindingNumber[attribute] - sceneData.vertexBuffers[memoryPerformanceHint].setData(pdata, size, sceneData.drawables[mesh].bufferOffsets[bindingNumber][2]) proc updateMeshData*(renderer: var Renderer, scene: Scene) = @@ -267,26 +271,33 @@ var updatedTransform = newSeq[Mat4](int(mesh.instanceCount)) for i in 0 ..< mesh.instanceCount: updatedTransform[i] = transform * mesh.getInstanceTransform(i) + debug &"Update mesh transformation" mesh.updateInstanceData(renderer.scenedata[scene].transformAttribute, updatedTransform) renderer.scenedata[scene].entityTransformationCache[mesh] = transform # update any changed mesh attributes - for attribute in mesh.availableAttributes(): + for attribute in mesh.vertexAttributes: if mesh.hasDataChanged(attribute): renderer.scenedata[scene].refreshMeshAttributeData(mesh, attribute) + debug &"Update mesh vertex attribute {attribute}" + for attribute in mesh.instanceAttributes: + if mesh.hasDataChanged(attribute): + renderer.scenedata[scene].refreshMeshAttributeData(mesh, attribute) + debug &"Update mesh instance attribute {attribute}" var m = mesh m.clearDataChanged() proc updateAnimations*(renderer: var Renderer, scene: var Scene, dt: float32) = for animation in allComponentsOfType[EntityAnimation](scene.root): + debug &"Update animation {animation}" animation.update(dt) proc updateUniformData*(renderer: var Renderer, scene: var Scene) = assert scene in renderer.scenedata for i in 0 ..< renderer.renderPass.subpasses.len: - for material, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: - if renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0: + for materialType, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: + if renderer.scenedata[scene].usesMaterialType(materialType) and renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and 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: @@ -294,6 +305,7 @@ 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}") + debug &"Update uniforms {uniform.name}" let (pdata, size) = scene.shaderGlobals[uniform.name].getRawData() renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].setData(pdata, size, offset) offset += size @@ -324,12 +336,14 @@ for i in 0 ..< renderer.renderPass.subpasses.len: for materialType, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: - commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.vk) - commandBuffer.vkCmdBindDescriptorSets(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[pipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil) + if renderer.scenedata[scene].usesMaterialType(materialType): + debug &"Start pipeline for {materialType}" + commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.vk) + commandBuffer.vkCmdBindDescriptorSets(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.layout, 0, 1, addr(renderer.scenedata[scene].descriptorSets[pipeline.vk][renderer.swapchain.currentInFlight].vk), 0, nil) - for drawable in renderer.scenedata[scene].drawables.values: - if drawable.mesh.material.materialType == materialType: - drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer) + for drawable in renderer.scenedata[scene].drawables.values: + if drawable.mesh.material != nil and drawable.mesh.material.materialType == materialType: + drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer) if i < renderer.renderPass.subpasses.len - 1: commandBuffer.vkCmdNextSubpass(VK_SUBPASS_CONTENTS_INLINE) @@ -365,6 +379,8 @@ for textures in scenedata.textures.mvalues: for texture in textures.mitems: texture.destroy() - scenedata.descriptorPool.destroy() + for descriptorPool in scenedata.descriptorPools.mvalues: + descriptorPool.destroy() + renderer.emptyTexture.destroy() renderer.renderPass.destroy() renderer.swapchain.destroy() diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/resources/mesh.nim --- a/src/semicongine/resources/mesh.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Sat Aug 19 22:24:06 2023 +0700 @@ -12,14 +12,6 @@ import ./image -let DEFAULTSAMPLER = Sampler( - magnification: VK_FILTER_NEAREST, - minification: VK_FILTER_NEAREST, - wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, - wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, - ) -let DEFAULTEXTURE = Texture(image: newImage(1, 1, @[[255'u8, 255'u8, 255'u8, 255'u8]]), sampler: DEFAULTSAMPLER) - type glTFHeader = object magic: uint32 @@ -144,7 +136,6 @@ proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: seq[uint8]): Texture = let textureNode = root["textures"][textureIndex] result.image = loadImage(root, textureNode["source"].getInt(), mainBuffer) - result.sampler = DefaultSampler() if textureNode.hasKey("sampler"): let sampler = root["samplers"][textureNode["sampler"].getInt()] @@ -159,7 +150,7 @@ proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8], materialIndex: uint16): Material = - result = Material(name: materialNode["name"].getStr(), index: materialIndex) + result = Material(name: materialNode["name"].getStr()) let pbr = materialNode["pbrMetallicRoughness"] @@ -190,7 +181,7 @@ result.constants[texture & "Index"] = DataValue(thetype: UInt8) setValue(result.constants[texture & "Index"], pbr[texture].getOrDefault("texCoord").getInt(0).uint8) else: - result.textures[texture] = DEFAULTEXTURE + result.textures[texture] = EMPTYTEXTURE result.constants[texture & "Index"] = DataValue(thetype: UInt8) setValue(result.constants[texture & "Index"], 0'u8) @@ -201,7 +192,7 @@ result.constants[texture & "Index"] = DataValue(thetype: UInt8) setValue(result.constants[texture & "Index"], materialNode[texture].getOrDefault("texCoord").getInt(0).uint8) else: - result.textures[texture] = DEFAULTEXTURE + result.textures[texture] = EMPTYTEXTURE result.constants[texture & "Index"] = DataValue(thetype: UInt8) setValue(result.constants[texture & "Index"], 0'u8) @@ -224,13 +215,13 @@ var vertexCount = 0'u32 for attribute, accessor in primitiveNode["attributes"].pairs: let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer) - mesh.appendMeshData(attribute.toLowerAscii, data) + mesh.appendAttributeData(attribute.toLowerAscii, data) vertexCount = data.len var materialId = 0'u16 if primitiveNode.hasKey("material"): materialId = uint16(primitiveNode["material"].getInt()) - mesh.appendMeshData("materialIndex", newSeqWith[uint8](int(vertexCount), materialId)) + mesh.appendAttributeData("materialIndex", newSeqWith[uint8](int(vertexCount), materialId)) let material = loadMaterial(root, root["materials"][int(materialId)], mainBuffer, materialId) # if mesh.material != nil and mesh.material[] != material[]: # raise newException(Exception, &"Only one material per mesh supported at the moment") diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/text.nim --- a/src/semicongine/text.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/text.nim Sat Aug 19 22:24:06 2023 +0700 @@ -44,24 +44,24 @@ top = glyph.topOffset bottom = glyph.topOffset + glyph.dimension.y - textbox.mesh.updateMeshData("position", vertexOffset + 0, newVec3f(left - centerX, bottom + centerY)) - textbox.mesh.updateMeshData("position", vertexOffset + 1, newVec3f(left - centerX, top + centerY)) - textbox.mesh.updateMeshData("position", vertexOffset + 2, newVec3f(right - centerX, top + centerY)) - textbox.mesh.updateMeshData("position", vertexOffset + 3, newVec3f(right - centerX, bottom + centerY)) + textbox.mesh.updateAttributeData("position", vertexOffset + 0, newVec3f(left - centerX, bottom + centerY)) + textbox.mesh.updateAttributeData("position", vertexOffset + 1, newVec3f(left - centerX, top + centerY)) + textbox.mesh.updateAttributeData("position", vertexOffset + 2, newVec3f(right - centerX, top + centerY)) + textbox.mesh.updateAttributeData("position", vertexOffset + 3, newVec3f(right - centerX, bottom + centerY)) - textbox.mesh.updateMeshData("uv", vertexOffset + 0, glyph.uvs[0]) - textbox.mesh.updateMeshData("uv", vertexOffset + 1, glyph.uvs[1]) - textbox.mesh.updateMeshData("uv", vertexOffset + 2, glyph.uvs[2]) - textbox.mesh.updateMeshData("uv", vertexOffset + 3, glyph.uvs[3]) + textbox.mesh.updateAttributeData("uv", vertexOffset + 0, glyph.uvs[0]) + textbox.mesh.updateAttributeData("uv", vertexOffset + 1, glyph.uvs[1]) + textbox.mesh.updateAttributeData("uv", vertexOffset + 2, glyph.uvs[2]) + textbox.mesh.updateAttributeData("uv", vertexOffset + 3, glyph.uvs[3]) offsetX += glyph.advance if i < uint32(textbox.text.len - 1): offsetX += textbox.font.kerning[(textbox.text[i], textbox.text[i + 1])] else: - textbox.mesh.updateMeshData("position", vertexOffset + 0, newVec3f()) - textbox.mesh.updateMeshData("position", vertexOffset + 1, newVec3f()) - textbox.mesh.updateMeshData("position", vertexOffset + 2, newVec3f()) - textbox.mesh.updateMeshData("position", vertexOffset + 3, newVec3f()) + textbox.mesh.updateAttributeData("position", vertexOffset + 0, newVec3f()) + textbox.mesh.updateAttributeData("position", vertexOffset + 1, newVec3f()) + textbox.mesh.updateAttributeData("position", vertexOffset + 2, newVec3f()) + textbox.mesh.updateAttributeData("position", vertexOffset + 3, newVec3f()) func text*(textbox: Textbox): seq[Rune] = diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/vulkan/memory.nim --- a/src/semicongine/vulkan/memory.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/vulkan/memory.nim Sat Aug 19 22:24:06 2023 +0700 @@ -73,7 +73,7 @@ size: size, memoryType: memoryType, canMap: VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT in memoryType.flags, - needsFlushing: not (VK_MEMORY_PROPERTY_HOST_COHERENT_BIT in result.memoryType.flags), + needsFlushing: not (VK_MEMORY_PROPERTY_HOST_COHERENT_BIT in memoryType.flags), ) var allocationInfo = VkMemoryAllocateInfo( diff -r 6dab370d1758 -r 9defff46da48 src/semicongine/vulkan/pipeline.nim --- a/src/semicongine/vulkan/pipeline.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/src/semicongine/vulkan/pipeline.nim Sat Aug 19 22:24:06 2023 +0700 @@ -27,7 +27,7 @@ func samplers*(pipeline: Pipeline): seq[ShaderAttribute] = pipeline.shaderConfiguration.samplers -proc setupDescriptors*(pipeline: Pipeline, descriptorPool: DescriptorPool, buffers: seq[Buffer], textures: Table[string, seq[VulkanTexture]], inFlightFrames: int): seq[DescriptorSet] = +proc setupDescriptors*(pipeline: Pipeline, descriptorPool: DescriptorPool, buffers: seq[Buffer], textures: var Table[string, seq[VulkanTexture]], inFlightFrames: int, emptyTexture: VulkanTexture): seq[DescriptorSet] = 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 @@ -46,11 +46,14 @@ elif descriptor.thetype == ImageSampler: if not (descriptor.name in textures): 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]: - descriptor.imageviews.add t.imageView - descriptor.samplers.add t.sampler + + for textureIndex in 0 ..< int(descriptor.count): + if textureIndex < textures[descriptor.name].len: + descriptor.imageviews.add textures[descriptor.name][textureIndex].imageView + descriptor.samplers.add textures[descriptor.name][textureIndex].sampler + else: + descriptor.imageviews.add emptyTexture.imageView + descriptor.samplers.add emptyTexture.sampler proc createPipeline*(device: Device, renderPass: VkRenderPass, shaderConfiguration: ShaderConfiguration, inFlightFrames: int, subpass = 0'u32): Pipeline = assert renderPass.valid diff -r 6dab370d1758 -r 9defff46da48 tests/test_vulkan_wrapper.nim --- a/tests/test_vulkan_wrapper.nim Sat Aug 19 01:10:42 2023 +0700 +++ b/tests/test_vulkan_wrapper.nim Sat Aug 19 22:24:06 2023 +0700 @@ -3,14 +3,16 @@ import semicongine -let sampler = Sampler( +let + sampler = Sampler( magnification: VK_FILTER_NEAREST, minification: VK_FILTER_NEAREST, wrapModeS: VK_SAMPLER_ADDRESS_MODE_REPEAT, wrapModeT: VK_SAMPLER_ADDRESS_MODE_REPEAT, ) -let (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8]) -let mat = Material( + (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8]) + mat = Material( + name: "mat", materialType: "my_material", textures: { "my_little_texture": Texture(image: Image(width: 5, height: 5, imagedata: @[ @@ -22,6 +24,26 @@ ]), sampler: sampler) }.toTable ) + mat2 = Material( + name: "mat2", + materialType: "my_material", + textures: { + "my_little_texture": Texture(image: Image(width: 5, height: 5, imagedata: @[ + R, W, R, W, R, + W, R, W, R, W, + R, W, R, W, R, + W, R, W, R, W, + R, W, R, W, R, + ]), sampler: sampler) + }.toTable + ) + mat3 = Material( + name: "mat3", + materialType: "my_special_material", + constants: { + "colors": toGPUValue(newVec4f(0.5, 0.5, 0)) + }.toTable + ) proc scene_different_mesh_types(): Entity = result = newEntity("root", [], @@ -39,31 +61,35 @@ positions=[newVec3f(0.0, 0.5), newVec3f(0.5, -0.5), newVec3f(-0.5, -0.5)], colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)], indices=[[0'u16, 2'u16, 1'u16]], - material=mat, + material=mat2, ))}), newEntity("triangle2b", {"mesh": Component(newMesh( positions=[newVec3f(0.0, 0.4), newVec3f(0.4, -0.4), newVec3f(-0.4, -0.4)], colors=[newVec4f(1.0, 0.0, 0.0, 1), newVec4f(0.0, 1.0, 0.0, 1), newVec4f(0.0, 0.0, 1.0, 1)], indices=[[0'u16, 2'u16, 1'u16]], - material=mat, + material=mat2, ))}), newEntity("triangle3a", {"mesh": Component(newMesh( positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)], colors=[newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1)], indices=[[0'u32, 2'u32, 1'u32]], autoResize=false, - material=mat, + material=mat2, ))}), newEntity("triangle3b", {"mesh": Component(newMesh( positions=[newVec3f(0.4, 0.5), newVec3f(0.9, -0.3), newVec3f(0.0, -0.3)], colors=[newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1), newVec4f(1.0, 1.0, 0.0, 1)], indices=[[0'u32, 2'u32, 1'u32]], autoResize=false, - material=mat, + material=mat2, ))}), ) for mesh in allComponentsOfType[Mesh](result): mesh.setInstanceData("translate", @[newVec3f()]) + result[0]["mesh", Mesh()].updateInstanceData("translate", @[newVec3f(-0.6, -0.6)]) + result[1]["mesh", Mesh()].updateInstanceData("translate", @[newVec3f(-0.6, 0.6)]) + result[2]["mesh", Mesh()].updateInstanceData("translate", @[newVec3f(0.6, -0.6)]) + result[3]["mesh", Mesh()].updateInstanceData("translate", @[newVec3f(0.6, 0.6)]) proc scene_simple(): Entity = var mymesh1 = newMesh( @@ -90,17 +116,17 @@ instanceCount=2, material=mat, ) - mymesh1.setInstanceData("translate", @[newVec3f(0.3, 0.0)]) - mymesh2.setInstanceData("translate", @[newVec3f(0.0, 0.3)]) - mymesh3.setInstanceData("translate", @[newVec3f(-0.3, 0.0)]) - mymesh4.setInstanceData("translate", @[newVec3f(0.0, -0.3), newVec3f(0.0, 0.5)]) + mymesh1.setInstanceData("translate", @[newVec3f( 0.4, 0.4)]) + mymesh2.setInstanceData("translate", @[newVec3f( 0.4, -0.4)]) + mymesh3.setInstanceData("translate", @[newVec3f(-0.4, -0.4)]) + mymesh4.setInstanceData("translate", @[newVec3f(-0.4, 0.4), newVec3f(0.0, 0.0)]) result = newEntity("root", [], newEntity("triangle", {"mesh1": Component(mymesh4), "mesh2": Component(mymesh3), "mesh3": Component(mymesh2), "mesh4": Component(mymesh1)})) proc scene_primitives(): Entity = var r = rect(color="ff0000") var t = tri(color="0000ff") var c = circle(color="00ff00") - t.material = mat + r.material = mat t.material = mat c.material = mat @@ -110,11 +136,20 @@ result = newEntity("root", {"mesh1": Component(t), "mesh2": Component(r), "mesh3": Component(c)}) proc scene_flag(): Entity = - var r = rect(color="ff0000") + var r = rect(color="ffffff") r.material = mat - r.updateMeshData("color", @[newVec4f(0, 0), newVec4f(1, 0), newVec4f(1, 1), newVec4f(0, 1)]) result = newEntity("root", {"mesh": Component(r)}) +proc scene_multi_material(): Entity = + var + r1 = rect(color="ffffff") + r2 = rect(color="000000") + r1.material = mat + r2.material = mat3 + r1.setInstanceData("translate", @[newVec3f(-0.5)]) + r2.setInstanceData("translate", @[newVec3f(+0.5)]) + result = newEntity("root", {"mesh1": Component(r1), "mesh2": Component(r2)}) + proc main() = var engine = initEngine("Test") @@ -124,23 +159,32 @@ inputs=[ attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead), attr[Vec4f]("color", memoryPerformanceHint=PreferFastWrite), - attr[Vec3f]("translate", perInstance=true) + attr[Vec3f]("translate", perInstance=true), + attr[uint16]("materialIndex", perInstance=true), ], - intermediates=[attr[Vec4f]("outcolor")], + intermediates=[ + attr[Vec4f]("outcolor"), + attr[uint16]("materialIndexOut", noInterpolation=true), + ], outputs=[attr[Vec4f]("color")], uniforms=[attr[float32]("time")], - samplers=[attr[Sampler2DType]("my_little_texture")], - vertexCode="""gl_Position = vec4(position + translate, 1.0); outcolor = color;""", - fragmentCode="color = texture(my_little_texture, outcolor.xy) * 0.5 + outcolor * 0.5;", + samplers=[ + attr[Sampler2DType]("my_little_texture", arrayCount=2) + ], + vertexCode="""gl_Position = vec4(position + translate, 1.0); outcolor = color; materialIndexOut = materialIndex;""", + fragmentCode="color = texture(my_little_texture[materialIndexOut], outcolor.xy) * 0.5 + outcolor * 0.5;", ) - engine.setRenderer({"my_material": shaderConfiguration}.toTable) + engine.initRenderer({ + "my_material": shaderConfiguration, + "my_special_material": shaderConfiguration, + }.toTable) # INIT SCENES var scenes = [ - newScene("simple", scene_simple(), transformAttribute="", materialIndexAttribute=""), - newScene("different mesh types", scene_different_mesh_types(), transformAttribute="", materialIndexAttribute=""), - newScene("primitives", scene_primitives(), transformAttribute="", materialIndexAttribute=""), - newScene("flag", scene_flag(), transformAttribute="", materialIndexAttribute=""), + newScene("simple", scene_simple(), transformAttribute=""), + newScene("different mesh types", scene_different_mesh_types(), transformAttribute=""), + newScene("primitives", scene_primitives(), transformAttribute=""), + newScene("flag", scene_multi_material(), transformAttribute=""), ] for scene in scenes.mitems: