Mercurial > games > semicongine
changeset 367:7ef01f1841b3
did: improve material system a ton, more to come
author | Sam <sam@basx.dev> |
---|---|
date | Sat, 21 Oct 2023 01:05:34 +0700 |
parents | 857cd931d24b |
children | 51ee41c1d8ed |
files | src/semicongine/core/dynamic_arrays.nim src/semicongine/engine.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/text.nim src/semicongine/vulkan/renderpass.nim |
diffstat | 8 files changed, 239 insertions(+), 256 deletions(-) [+] |
line wrap: on
line diff
--- a/src/semicongine/core/dynamic_arrays.nim Thu Oct 12 14:55:28 2023 +0700 +++ b/src/semicongine/core/dynamic_arrays.nim Sat Oct 21 01:05:34 2023 +0700 @@ -151,6 +151,54 @@ of Mat4F64: return a.mat4f64 == b.mat4f64 of TextureType: a.texture == b.texture +proc setLen*(value: var DataList, len: int) = + value.len = len + case value.theType + of Float32: value.float32[].setLen(len) + of Float64: value.float64[].setLen(len) + of Int8: value.int8[].setLen(len) + of Int16: value.int16[].setLen(len) + of Int32: value.int32[].setLen(len) + of Int64: value.int64[].setLen(len) + of UInt8: value.uint8[].setLen(len) + of UInt16: value.uint16[].setLen(len) + of UInt32: value.uint32[].setLen(len) + of UInt64: value.uint64[].setLen(len) + of Vec2I32: value.vec2i32[].setLen(len) + of Vec2I64: value.vec2i64[].setLen(len) + of Vec3I32: value.vec3i32[].setLen(len) + of Vec3I64: value.vec3i64[].setLen(len) + of Vec4I32: value.vec4i32[].setLen(len) + of Vec4I64: value.vec4i64[].setLen(len) + of Vec2U32: value.vec2u32[].setLen(len) + of Vec2U64: value.vec2u64[].setLen(len) + of Vec3U32: value.vec3u32[].setLen(len) + of Vec3U64: value.vec3u64[].setLen(len) + of Vec4U32: value.vec4u32[].setLen(len) + of Vec4U64: value.vec4u64[].setLen(len) + of Vec2F32: value.vec2f32[].setLen(len) + of Vec2F64: value.vec2f64[].setLen(len) + of Vec3F32: value.vec3f32[].setLen(len) + of Vec3F64: value.vec3f64[].setLen(len) + of Vec4F32: value.vec4f32[].setLen(len) + of Vec4F64: value.vec4f64[].setLen(len) + of Mat2F32: value.mat2f32[].setLen(len) + of Mat2F64: value.mat2f64[].setLen(len) + of Mat23F32: value.mat23f32[].setLen(len) + of Mat23F64: value.mat23f64[].setLen(len) + of Mat32F32: value.mat32f32[].setLen(len) + of Mat32F64: value.mat32f64[].setLen(len) + of Mat3F32: value.mat3f32[].setLen(len) + of Mat3F64: value.mat3f64[].setLen(len) + of Mat34F32: value.mat34f32[].setLen(len) + of Mat34F64: value.mat34f64[].setLen(len) + of Mat43F32: value.mat43f32[].setLen(len) + of Mat43F64: value.mat43f64[].setLen(len) + of Mat4F32: value.mat4f32[].setLen(len) + of Mat4F64: value.mat4f64[].setLen(len) + of TextureType: discard + + proc setValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) = value.setLen(data.len) when T is float32: value.float32[] = data @@ -204,7 +252,7 @@ elif T is Texture: value.texture[] = data else: {. error: "Virtual datatype has no values" .} -func newDataList*(theType: DataType): DataList = +proc initDataList*(theType: DataType, len=1): DataList = result = DataList(theType: theType) case result.theType of Float32: result.float32 = new seq[float32] @@ -250,19 +298,15 @@ of Mat4F32: result.mat4f32 = new seq[TMat4[float32]] of Mat4F64: result.mat4f64 = new seq[TMat4[float64]] of TextureType: result.texture = new seq[Texture] - -proc newDataList*[T: GPUType](len=0): DataList = - result = newDataList(getDataType[T]()) - if len > 0: - result.setLen(len) + result.setLen(len) -proc newDataList*[T: GPUType](data: openArray[T]): DataList = - result = newDataList(getDataType[T]()) - result.setValues(@data) +proc initDataList*[T: GPUType](len=1): DataList = + result = initDataList(getDataType[T]()) + result.setLen(len) -proc toGPUValue*[T: GPUType](value: seq[T]): DataList = - result = newDataList[T](value.len) - result.setValue(value) +proc initDataList*[T: GPUType](data: openArray[T]): DataList = + result = initDataList(getDataType[T]()) + result.setValues(@data) func getValues*[T: GPUType|int|uint|float](value: DataList): ref seq[T] = when T is float32: value.float32 @@ -417,53 +461,6 @@ of Mat4F64: result[0] = value.mat4f64[].toCPointer of TextureType: result[0] = nil -proc setLen*(value: var DataList, len: int) = - value.len = len - case value.theType - of Float32: value.float32[].setLen(len) - of Float64: value.float64[].setLen(len) - of Int8: value.int8[].setLen(len) - of Int16: value.int16[].setLen(len) - of Int32: value.int32[].setLen(len) - of Int64: value.int64[].setLen(len) - of UInt8: value.uint8[].setLen(len) - of UInt16: value.uint16[].setLen(len) - of UInt32: value.uint32[].setLen(len) - of UInt64: value.uint64[].setLen(len) - of Vec2I32: value.vec2i32[].setLen(len) - of Vec2I64: value.vec2i64[].setLen(len) - of Vec3I32: value.vec3i32[].setLen(len) - of Vec3I64: value.vec3i64[].setLen(len) - of Vec4I32: value.vec4i32[].setLen(len) - of Vec4I64: value.vec4i64[].setLen(len) - of Vec2U32: value.vec2u32[].setLen(len) - of Vec2U64: value.vec2u64[].setLen(len) - of Vec3U32: value.vec3u32[].setLen(len) - of Vec3U64: value.vec3u64[].setLen(len) - of Vec4U32: value.vec4u32[].setLen(len) - of Vec4U64: value.vec4u64[].setLen(len) - of Vec2F32: value.vec2f32[].setLen(len) - of Vec2F64: value.vec2f64[].setLen(len) - of Vec3F32: value.vec3f32[].setLen(len) - of Vec3F64: value.vec3f64[].setLen(len) - of Vec4F32: value.vec4f32[].setLen(len) - of Vec4F64: value.vec4f64[].setLen(len) - of Mat2F32: value.mat2f32[].setLen(len) - of Mat2F64: value.mat2f64[].setLen(len) - of Mat23F32: value.mat23f32[].setLen(len) - of Mat23F64: value.mat23f64[].setLen(len) - of Mat32F32: value.mat32f32[].setLen(len) - of Mat32F64: value.mat32f64[].setLen(len) - of Mat3F32: value.mat3f32[].setLen(len) - of Mat3F64: value.mat3f64[].setLen(len) - of Mat34F32: value.mat34f32[].setLen(len) - of Mat34F64: value.mat34f64[].setLen(len) - of Mat43F32: value.mat43f32[].setLen(len) - of Mat43F64: value.mat43f64[].setLen(len) - of Mat4F32: value.mat4f32[].setLen(len) - of Mat4F64: value.mat4f64[].setLen(len) - of TextureType: discard - proc appendValues*[T: GPUType|int|uint|float](value: var DataList, data: seq[T]) = value.len += data.len when T is float32: value.float32[].add data @@ -718,7 +715,7 @@ of TextureType: a.texture[i] = b.texture[j] proc copy*(datalist: DataList): DataList = - result = newDataList(datalist.theType) + result = initDataList(datalist.theType) result.appendValues(datalist) func `$`*(list: DataList): string =
--- a/src/semicongine/engine.nim Thu Oct 12 14:55:28 2023 +0700 +++ b/src/semicongine/engine.nim Sat Oct 21 01:05:34 2023 +0700 @@ -11,6 +11,7 @@ import ./vulkan/shader import ./scene +import ./material import ./renderer import ./events import ./audio @@ -102,11 +103,11 @@ ) startMixerThread() -proc initRenderer*(engine: var Engine, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true) = +proc initRenderer*(engine: var Engine, shaders: openArray[(MaterialType, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true) = assert not engine.renderer.isSome var allShaders = @shaders - allShaders.add (TEXT_MATERIAL, TEXT_SHADER) + allShaders.add (TEXT_MATERIAL_TYPE, TEXT_SHADER) engine.renderer = some(engine.device.initRenderer(shaders=allShaders, clearColor=clearColor, backFaceCulling=backFaceCulling)) proc initRenderer*(engine: var Engine, clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) =
--- a/src/semicongine/mesh.nim Thu Oct 12 14:55:28 2023 +0700 +++ b/src/semicongine/mesh.nim Sat Oct 21 01:05:34 2023 +0700 @@ -27,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[MaterialData] + material*: 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 @@ -38,6 +38,19 @@ dirtyAttributes: seq[string] Mesh* = ref MeshObject +func material*(mesh: MeshObject): MaterialData = + mesh.material + +func `material=`*(mesh: var MeshObject, material: MaterialData) = + for name, theType in material.theType.meshAttributes: + if mesh.vertexData.contains(name): + assert mesh.vertexData[name].theType == theType, &"{material.theType} expected mesh attribute '{name}' to be '{theType}' but it is {mesh.vertexData[name].theType}" + elif mesh.instanceData.contains(name): + assert mesh.instanceData[name].theType == theType, &"{material.theType} expected mesh attribute '{name}' to be '{theType}' but it is {mesh.instanceData[name].theType}" + else: + assert false, &"Mesh '{mesh.name}' is missing required mesh attribute '{name}: {theType}' for {material.theType}" + mesh.material = material + func instanceCount*(mesh: MeshObject): int = mesh.instanceTransforms.len @@ -52,9 +65,9 @@ func `$`*(mesh: MeshObject): string = if mesh.indexType == None: - &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, materials: {mesh.materials})" + &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, material: {mesh.material})" else: - &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, indexCount: {mesh.indicesCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, materials: {mesh.materials})" + &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, indexCount: {mesh.indicesCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, material: {mesh.material})" func `$`*(mesh: Mesh): string = $mesh[] @@ -79,7 +92,7 @@ proc initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) = assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute) - mesh.vertexData[attribute] = newDataList(thetype=getDataType[T]()) + mesh.vertexData[attribute] = initDataList(thetype=getDataType[T]()) mesh.vertexData[attribute].setLen(mesh.vertexCount) mesh.vertexData[attribute].setValues(value) proc initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: T) = @@ -88,13 +101,16 @@ initVertexAttribute(mesh=mesh, attribute=attribute, value=default(T)) proc initVertexAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) = assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute) - mesh.vertexData[attribute] = newDataList(thetype=datatype) + mesh.vertexData[attribute] = initDataList(thetype=datatype) mesh.vertexData[attribute].setLen(mesh.vertexCount) +proc initVertexAttribute*(mesh: var MeshObject, attribute: string, data: DataList) = + assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute) + mesh.vertexData[attribute] = data proc initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) = assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute) - mesh.instanceData[attribute] = newDataList(thetype=getDataType[T]()) + mesh.instanceData[attribute] = initDataList(thetype=getDataType[T]()) mesh.instanceData[attribute].setLen(mesh.instanceCount) mesh.instanceData[attribute].setValues(value) proc initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: T) = @@ -103,8 +119,11 @@ initInstanceAttribute(mesh=mesh, attribute=attribute, value=default(T)) proc initInstanceAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) = assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute) - mesh.instanceData[attribute] = newDataList(thetype=datatype) + mesh.instanceData[attribute] = initDataList(thetype=datatype) mesh.instanceData[attribute].setLen(mesh.instanceCount) +proc initInstanceAttribute*(mesh: var MeshObject, attribute: string, data: DataList) = + assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute) + mesh.instanceData[attribute] = data proc newMesh*( positions: openArray[Vec3f], @@ -139,13 +158,14 @@ vertexCount: positions.len, instanceTransforms: @instanceTransforms, transform: transform, - materials: @[material], ) result[].initVertexAttribute("position", positions.toSeq) if colors.len > 0: result[].initVertexAttribute("color", colors.toSeq) if uvs.len > 0: result[].initVertexAttribute("uv", uvs.toSeq) + `material=`(result[], material) + # assert all indices are valid for i in indices: assert int(i[0]) < result[].vertexCount @@ -249,6 +269,20 @@ template `[]`*(mesh: Mesh, attribute: string, i: int, t: typedesc): untyped = getAttribute[t](mesh[], attribute, i) +proc updateAttributeData[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: DataList) = + if mesh.vertexData.contains(attribute): + assert data.len == mesh.vertexCount + assert data.theType == mesh.vertexData[attribute].theType + mesh.vertexData[attribute] = data + elif mesh.instanceData.contains(attribute): + assert data.len == mesh.instanceCount + assert data.theType == mesh.instanceData[attribute].theType + mesh.instanceData[attribute] = data + else: + raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") + if not mesh.dirtyAttributes.contains(attribute): + mesh.dirtyAttributes.add attribute + proc updateAttributeData[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) = if mesh.vertexData.contains(attribute): assert data.len == mesh.vertexCount @@ -273,6 +307,11 @@ if not mesh.dirtyAttributes.contains(attribute): mesh.dirtyAttributes.add attribute +proc `[]=`*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: DataList) = + updateAttributeData[T](mesh, attribute, data) +proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: DataList) = + updateAttributeData[t](mesh[], attribute, data) + proc `[]=`*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) = updateAttributeData[T](mesh, attribute, data) proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) = @@ -288,29 +327,6 @@ proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, i: int, value: T) = updateAttributeData[T](mesh[], attribute, i, value) -proc appendAttributeData*[T: GPUType|int|uint|float](mesh: var MeshObject, 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}") - if not mesh.dirtyAttributes.contains(attribute): - mesh.dirtyAttributes.add attribute - -# currently only used for loading from files, shouls -proc appendAttributeData*(mesh: var MeshObject, 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}") - if not mesh.dirtyAttributes.contains(attribute): - mesh.dirtyAttributes.add attribute - proc removeAttribute*(mesh: var MeshObject, attribute: string) = if mesh.vertexData.contains(attribute): mesh.vertexData.del(attribute) @@ -376,11 +392,11 @@ result = MeshObject( vertexCount: mesh.indicesCount, indexType: None, - materials: mesh.materials, transform: mesh.transform, instanceTransforms: mesh.instanceTransforms, visible: mesh.visible, ) + `material=`(result, mesh.material) for attribute, datalist in mesh.vertexData.pairs: result.initVertexAttribute(attribute, datalist.theType) for attribute, datalist in mesh.instanceData.pairs: @@ -421,9 +437,9 @@ instanceTransforms: @[Unit4F32], indexType: Small, smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]], - materials: @[DEFAULT_MATERIAL], name: &"rect-{instanceCounter}", ) + `material=`(result[], DEFAULT_MATERIAL) inc instanceCounter let @@ -440,9 +456,9 @@ result = Mesh( vertexCount: 3, instanceTransforms: @[Unit4F32], - materials: @[DEFAULT_MATERIAL], name: &"tri-{instanceCounter}", ) + `material=`(result[], DEFAULT_MATERIAL) inc instanceCounter let half_w = width / 2 @@ -458,9 +474,9 @@ vertexCount: 3 + nSegments, instanceTransforms: @[Unit4F32], indexType: Small, - materials: @[DEFAULT_MATERIAL], name: &"circle-{instanceCounter}", ) + `material=`(result[], DEFAULT_MATERIAL) inc instanceCounter let
--- a/src/semicongine/renderer.nim Thu Oct 12 14:55:28 2023 +0700 +++ b/src/semicongine/renderer.nim Sat Oct 21 01:05:34 2023 +0700 @@ -44,7 +44,7 @@ scenedata: Table[Scene, SceneData] emptyTexture: VulkanTexture -proc initRenderer*(device: Device, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true): Renderer = +proc initRenderer*(device: Device, shaders: openArray[(MaterialType, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true): Renderer = assert device.vk.valid result.device = device @@ -61,8 +61,8 @@ func inputs(renderer: Renderer, scene: Scene): seq[ShaderAttribute] = var found: Table[string, ShaderAttribute] for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: - if scene.usesMaterial(materialName): + for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: + if scene.usesMaterial(materialType): for input in pipeline.inputs: if found.contains(input.name): assert input.name == found[input.name].name, &"{input.name}: {input.name} != {found[input.name].name}" @@ -75,19 +75,19 @@ func samplers(renderer: Renderer, scene: Scene): seq[ShaderAttribute] = for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: - if scene.usesMaterial(materialName): + for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: + if scene.usesMaterial(materialType): result.add pipeline.samplers -func materialCompatibleWithPipeline(scene: Scene, material: MaterialData, pipeline: Pipeline): (bool, string) = +func materialCompatibleWithPipeline(scene: Scene, materialType: MaterialType, 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.values.pairs: - if name == uniform.name and constant.theType == uniform.theType: + for name, theType in materialType.attributes.pairs: + if name == uniform.name and theType == uniform.theType: foundMatch = true break if not foundMatch: @@ -98,7 +98,7 @@ return (true, &"shader sampler '{sampler.name}' needs type {sampler.theType} but scene global is of type {scene.shaderGlobals[sampler.name].theType}") else: var foundMatch = true - for name, value in material.textures: + for name in materialType.attributes.keys: if name == sampler.name: foundMatch = true break @@ -120,34 +120,34 @@ if input.perInstance and not mesh[].instanceAttributes.contains(input.name): return (true, &"Shader input '{input.name}' expected to be per instance attribute, but mesh has no such instance attribute (available are: {mesh[].instanceAttributes})") - var pipelineCompatabilities = mesh.materials.mapIt(materialCompatibleWithPipeline(scene, it, pipeline)) - if pipelineCompatabilities.filterIt(not it[0]).len == 0: - return (true, pipelineCompatabilities.mapIt(it[1]).join(" / ")) + let pipelineCompatability = scene.materialCompatibleWithPipeline(mesh.material.theType, pipeline) + if pipelineCompatability[0]: + return (true, pipelineCompatability[1]) return (false, "") func checkSceneIntegrity(renderer: Renderer, scene: Scene) = + # TODO: this and the sub-functions can likely be simplified a ton if scene.meshes.len == 0: return var foundRenderableObject = false - var shaderTypes: seq[string] + var materialTypes: seq[MaterialType] for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: - shaderTypes.add materialName + for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: + materialTypes.add materialType for mesh in scene.meshes: - if mesh.materials.anyIt(it.name == materialName): + if mesh.material.theType == materialType: foundRenderableObject = true let (error, message) = scene.meshCompatibleWithPipeline(mesh, pipeline) if error: - raise newException(Exception, &"Mesh '{mesh}' not compatible with assigned pipeline ({materialName}) because: {message}") + raise newException(Exception, &"Mesh '{mesh}' not compatible with assigned pipeline ({materialType}) because: {message}") if not foundRenderableObject: - var materialTypes: seq[string] + var matTypes: seq[string] for mesh in scene.meshes: - for material in mesh.materials: - if not materialTypes.contains(material.name): - materialTypes.add material.name - raise newException(Exception, &"Scene '{scene.name}' has been added but materials are not compatible with any registered shader: Materials in scene: {materialTypes}, registered shader-materialtypes: {shaderTypes}") + if not matTypes.contains(mesh.material.name): + matTypes.add mesh.material.name + raise newException(Exception, &"Scene '{scene.name}' has been added but materials are not compatible with any registered shader: Materials in scene: {matTypes}, registered shader-materialtypes: {materialTypes}") proc setupDrawableBuffers*(renderer: var Renderer, scene: var Scene) = assert not (scene in renderer.scenedata) @@ -159,16 +159,16 @@ var scenedata = SceneData() for mesh in scene.meshes: - for material in mesh.materials: - 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 == TextureType: - warn &"Ignoring material texture '{textureName}' as scene-global textures with the same name have been defined" + if not scenedata.materials.contains(mesh.material): + scenedata.materials.add mesh.material + for name, value in mesh.material.attributes.pairs: + if value.theType == TextureType: + if scene.shaderGlobals.contains(name) and scene.shaderGlobals[name].theType == TextureType: + warn &"Ignoring material texture '{name}' as scene-global textures with the same name have been defined" else: - if not scenedata.textures.hasKey(textureName): - scenedata.textures[textureName] = @[] - scenedata.textures[textureName].add renderer.device.uploadTexture(texture) + if not scenedata.textures.hasKey(name): + scenedata.textures[name] = @[] + scenedata.textures[name].add renderer.device.uploadTexture(getValue[Texture](value, 0)) for name, value in scene.shaderGlobals.pairs: if value.theType == TextureType: @@ -253,8 +253,8 @@ # fill offsets per pipeline (as sequence corresponds to shader input binding) var offsets: Table[VkPipeline, seq[(string, MemoryPerformanceHint, int)]] for subpass_i in 0 ..< renderer.renderPass.subpasses.len: - for (materialName, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines: - if scene.usesMaterial(materialName): + for (materialType, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines: + if scene.usesMaterial(materialType): offsets[pipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]() for attribute in pipeline.inputs: offsets[pipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)]) @@ -286,8 +286,8 @@ # setup uniforms and samplers for subpass_i in 0 ..< renderer.renderPass.subpasses.len: - for (materialName, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines: - if scene.usesMaterial(materialName): + for (materialType, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines: + if scene.usesMaterial(materialType): var uniformBufferSize = 0 for uniform in pipeline.uniforms: uniformBufferSize += uniform.size @@ -361,9 +361,9 @@ # loop over all used shaders/pipelines for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: + for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: if ( - scene.usesMaterial(materialName) and + scene.usesMaterial(materialType) and renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0 ): @@ -383,7 +383,7 @@ foundValue = true else: for mat in renderer.scenedata[scene].materials: - for name, materialConstant in mat.values.pairs: + for name, materialConstant in mat.attributes.pairs: if uniform.name == name: value = materialConstant foundValue = true @@ -427,12 +427,12 @@ debug " Index buffer: ", renderer.scenedata[scene].indexBuffer for i in 0 ..< renderer.renderPass.subpasses.len: - for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: - if scene.usesMaterial(materialName): - debug &"Start pipeline for '{materialName}'" + for (materialType, pipeline) in renderer.renderPass.subpasses[i].pipelines: + if scene.usesMaterial(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, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].materials.anyIt(it.name == materialName)): + for (drawable, mesh) in renderer.scenedata[scene].drawables.filterIt(it[1].visible and it[1].material.theType == materialType): drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer, pipeline.vk) if i < renderer.renderPass.subpasses.len - 1:
--- a/src/semicongine/resources/mesh.nim Thu Oct 12 14:55:28 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Sat Oct 21 01:05:34 2023 +0700 @@ -2,7 +2,6 @@ import std/json import std/logging import std/tables -import std/sequtils import std/strformat import std/streams @@ -94,7 +93,7 @@ copyMem(dstPointer, addr mainBuffer[bufferOffset], result.len) proc getAccessorData(root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): DataList = - result = newDataList(thetype=accessor.getGPUType("??")) + result = initDataList(thetype=accessor.getGPUType("??")) result.setLen(accessor["count"].getInt()) let bufferView = root["bufferViews"][accessor["bufferView"].getInt()] @@ -150,124 +149,72 @@ result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] -proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8], materialIndex: uint16): MaterialData = - result = MaterialData(name: materialNode["name"].getStr(), index: materialIndex) +proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8]): MaterialData = + result = MaterialData(name: materialNode["name"].getStr()) let pbr = materialNode["pbrMetallicRoughness"] # color - result.values["baseColorFactor"] = newDataList(thetype=Vec4F32) + result.attributes["baseColorFactor"] = initDataList(thetype=Vec4F32) if pbr.hasKey("baseColorFactor"): - setValue(result.values["baseColorFactor"], @[newVec4f( + setValue(result.attributes["baseColorFactor"], @[newVec4f( pbr["baseColorFactor"][0].getFloat(), pbr["baseColorFactor"][1].getFloat(), pbr["baseColorFactor"][2].getFloat(), pbr["baseColorFactor"][3].getFloat(), )]) else: - setValue(result.values["baseColorFactor"], @[newVec4f(1, 1, 1, 1)]) + setValue(result.attributes["baseColorFactor"], @[newVec4f(1, 1, 1, 1)]) # pbr material values for factor in ["metallicFactor", "roughnessFactor"]: - result.values[factor] = newDataList(thetype=Float32) + result.attributes[factor] = initDataList(thetype=Float32) if pbr.hasKey(factor): - setValue(result.values[factor], @[float32(pbr[factor].getFloat())]) + setValue(result.attributes[factor], @[float32(pbr[factor].getFloat())]) else: - setValue(result.values[factor], @[0.5'f32]) + setValue(result.attributes[factor], @[0.5'f32]) # pbr material textures for texture in ["baseColorTexture", "metallicRoughnessTexture"]: + result.attributes[texture] = initDataList(thetype=TextureType) + result.attributes[texture & "Index"] = initDataList(thetype=UInt8) if pbr.hasKey(texture): - result.textures[texture] = loadTexture(root, pbr[texture]["index"].getInt(), mainBuffer) - result.values[texture & "Index"] = newDataList(thetype=UInt8) - setValue(result.values[texture & "Index"], @[pbr[texture].getOrDefault("texCoord").getInt(0).uint8]) + setValue(result.attributes[texture], @[loadTexture(root, pbr[texture]["index"].getInt(), mainBuffer)]) + setValue(result.attributes[texture & "Index"], @[pbr[texture].getOrDefault("texCoord").getInt(0).uint8]) else: - result.textures[texture] = EMPTY_TEXTURE - result.values[texture & "Index"] = newDataList(thetype=UInt8) - setValue(result.values[texture & "Index"], @[0'u8]) + setValue(result.attributes[texture & "Index"], @[EMPTY_TEXTURE]) + setValue(result.attributes[texture & "Index"], @[0'u8]) # generic material textures for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]: + result.attributes[texture] = initDataList(thetype=TextureType) + result.attributes[texture & "Index"] = initDataList(thetype=UInt8) if materialNode.hasKey(texture): - result.textures[texture] = loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer) - result.values[texture & "Index"] = newDataList(thetype=UInt8) - setValue(result.values[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8]) + setValue(result.attributes[texture], @[loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)]) + setValue(result.attributes[texture & "Index"], @[materialNode[texture].getOrDefault("texCoord").getInt(0).uint8]) else: - result.textures[texture] = EMPTY_TEXTURE - result.values[texture & "Index"] = newDataList(thetype=UInt8) - setValue(result.values[texture & "Index"], @[0'u8]) + setValue(result.attributes[texture], @[EMPTY_TEXTURE]) + setValue(result.attributes[texture & "Index"], @[0'u8]) # emissiv color - result.values["emissiveFactor"] = newDataList(thetype=Vec3F32) + result.attributes["emissiveFactor"] = initDataList(thetype=Vec3F32) if materialNode.hasKey("emissiveFactor"): - setValue(result.values["emissiveFactor"], @[newVec3f( + setValue(result.attributes["emissiveFactor"], @[newVec3f( materialNode["emissiveFactor"][0].getFloat(), materialNode["emissiveFactor"][1].getFloat(), materialNode["emissiveFactor"][2].getFloat(), )]) else: - setValue(result.values["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)]) + setValue(result.attributes["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)]) -proc addPrimitive(mesh: Mesh, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) = +proc loadMesh(meshname: string, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]): Mesh = if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4: raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode") - var vertexCount = 0 - for attribute, accessor in primitiveNode["attributes"].pairs: - let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer) - mesh[].appendAttributeData(attribute.toLowerAscii, data) - assert data.len == vertexCount or vertexCount == 0 - vertexCount = data.len - mesh.vertexCount += vertexCount - - var materialId = 0'u16 - if primitiveNode.hasKey("material"): - materialId = uint16(primitiveNode["material"].getInt()) - mesh[].appendAttributeData("materialIndex", newSeqWith(vertexCount, materialId)) - if "materials" in root and int(materialId) < root["materials"].len: - let material = loadMaterial(root, root["materials"][int(materialId)], mainBuffer, materialId) - # FIX: materialIndex is designed to support multiple different materials per mesh (as it is per vertex), - # but or current mesh/rendering implementation is only designed for a single material - # currently this is usually handled by adding the values as shader globals - # TODO: this is bad - if not mesh[].materials.contains(material): - mesh[].materials.add material - else: - if not mesh[].materials.contains(DEFAULT_MATERIAL): - mesh[].materials.add DEFAULT_MATERIAL - - if primitiveNode.hasKey("indices"): - assert mesh[].indexType != None - let data = root.getAccessorData(root["accessors"][primitiveNode["indices"].getInt()], mainBuffer) - let baseIndex = mesh[].vertexCount - vertexCount - var tri: seq[int] - case data.thetype - of UInt16: - for entry in getValues[uint16](data)[]: - tri.add int(entry) + baseIndex - if tri.len == 3: - # FYI gltf uses counter-clockwise indexing - mesh[].appendIndicesData(tri[0], tri[1], tri[2]) - tri.setLen(0) - of UInt32: - for entry in getValues[uint32](data)[]: - tri.add int(entry) + baseIndex - if tri.len == 3: - # FYI gltf uses counter-clockwise indexing - mesh[].appendIndicesData(tri[0], tri[1], tri[2]) - tri.setLen(0) - else: - raise newException(Exception, &"Unsupported index data type: {data.thetype}") - -# TODO: use one mesh per primitive?? right now we are merging primitives... check addPrimitive below -proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: seq[uint8]): Mesh = - - # check if and how we use indexes - var indexCount = 0 var indexType = None - let indexed = meshNode["primitives"][0].hasKey("indices") + let indexed = primitiveNode.hasKey("indices") if indexed: - for primitive in meshNode["primitives"]: - indexCount += root["accessors"][primitive["indices"].getInt()]["count"].getInt() + # TODO: Tiny indices + var indexCount = root["accessors"][primitiveNode["indices"].getInt()]["count"].getInt() if indexCount < int(high(uint16)): indexType = Small else: @@ -276,37 +223,53 @@ result = Mesh( instanceTransforms: @[Unit4F32], indexType: indexType, - name: meshNode["name"].getStr() + name: meshname, + vertexCount: 0, ) - # check we have the same attributes for all primitives - let attributes = meshNode["primitives"][0]["attributes"].keys.toSeq - for primitive in meshNode["primitives"]: - assert primitive["attributes"].keys.toSeq == attributes + for attribute, accessor in primitiveNode["attributes"].pairs: + let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer) + if result.vertexCount == 0: + result.vertexCount = data.len + assert data.len == result.vertexCount + result[].initVertexAttribute(attribute.toLowerAscii, data) - # prepare mesh attributes - for attribute, accessor in meshNode["primitives"][0]["attributes"].pairs: - result[].initVertexAttribute(attribute.toLowerAscii, root["accessors"][accessor.getInt()].getGPUType(attribute)) - result[].initVertexAttribute("materialIndex", UInt16) + if primitiveNode.hasKey("material"): + let materialId = primitiveNode["material"].getInt() + result[].material = loadMaterial(root, root["materials"][materialId], mainBuffer) + else: + result[].material = DEFAULT_MATERIAL - # add all mesh data - for primitive in meshNode["primitives"]: - result.addPrimitive(root, primitive, mainBuffer) + if primitiveNode.hasKey("indices"): + assert result[].indexType != None + let data = root.getAccessorData(root["accessors"][primitiveNode["indices"].getInt()], mainBuffer) + var tri: seq[int] + case data.thetype + of UInt16: + for entry in getValues[uint16](data)[]: + tri.add int(entry) + if tri.len == 3: + # FYI gltf uses counter-clockwise indexing + result[].appendIndicesData(tri[0], tri[1], tri[2]) + tri.setLen(0) + of UInt32: + for entry in getValues[uint32](data)[]: + tri.add int(entry) + if tri.len == 3: + # FYI gltf uses counter-clockwise indexing + result[].appendIndicesData(tri[0], tri[1], tri[2]) + tri.setLen(0) + else: + raise newException(Exception, &"Unsupported index data type: {data.thetype}") transform[Vec3f](result[], "position", scale(1, -1, 1)) - var maxMaterialIndex = 0 - for material in result[].materials: - maxMaterialIndex = max(int(material.index), maxMaterialIndex) - var materials = result[].materials - result[].materials = newSeqWith(maxMaterialIndex + 1, DEFAULT_MATERIAL) - for material in materials: - result[].materials[material.index] = material - proc loadNode(root: JsonNode, node: JsonNode, mainBuffer: var seq[uint8]): MeshTree = result = MeshTree() # mesh if node.hasKey("mesh"): - result.mesh = loadMesh(root, root["meshes"][node["mesh"].getInt()], mainBuffer) + let mesh = root["meshes"][node["mesh"].getInt()] + for primitive in mesh["primitives"]: + result.children.add MeshTree(mesh: loadMesh(mesh["name"].getStr(), root, primitive, mainBuffer)) # transformation if node.hasKey("matrix"):
--- a/src/semicongine/scene.nim Thu Oct 12 14:55:28 2023 +0700 +++ b/src/semicongine/scene.nim Sat Oct 21 01:05:34 2023 +0700 @@ -5,6 +5,7 @@ import ./core import ./mesh +import ./material type Scene* = object @@ -44,13 +45,13 @@ proc addShaderGlobal*[T](scene: var Scene, name: string, data: T) = assert not scene.loaded, &"Scene {scene.name} has already been loaded, cannot add shader values" - scene.shaderGlobals[name] = newDataList(thetype=getDataType[T]()) + scene.shaderGlobals[name] = initDataList(thetype=getDataType[T]()) setValues(scene.shaderGlobals[name], @[data]) scene.dirtyShaderGlobals.add name proc addShaderGlobalArray*[T](scene: var Scene, name: string, data: openArray[T]) = assert not scene.loaded, &"Scene {scene.name} has already been loaded, cannot add shader values" - scene.shaderGlobals[name] = newDataList(data) + scene.shaderGlobals[name] = initDataList(data) scene.dirtyShaderGlobals.add name func getShaderGlobal*[T](scene: Scene, name: string): T = @@ -81,5 +82,5 @@ func `==`*(a, b: Scene): bool = a.name == b.name -func usesMaterial*(scene: Scene, materialName: string): bool = - return scene.meshes.anyIt(it.materials.anyIt(it.name == materialName)) +func usesMaterial*(scene: Scene, materialType: MaterialType): bool = + return scene.meshes.anyIt(it.material.theType == materialType)
--- a/src/semicongine/text.nim Thu Oct 12 14:55:28 2023 +0700 +++ b/src/semicongine/text.nim Sat Oct 21 01:05:34 2023 +0700 @@ -27,7 +27,11 @@ TRANSFORM_ATTRIB = "transform" POSITION_ATTRIB = SHADER_ATTRIB_PREFIX & "position" UV_ATTRIB = SHADER_ATTRIB_PREFIX & "uv" - TEXT_MATERIAL* = "default-text" + TEXT_MATERIAL_TYPE* = MaterialType( + name: "default-text-material-type", + meshAttributes: {TRANSFORM_ATTRIB: Mat4F32, POSITION_ATTRIB: Vec3F32, UV_ATTRIB: Vec2F32}.toTable, + attributes: {"fontAtlas": TextureType}.toTable, + ) TEXT_SHADER* = createShaderConfiguration( inputs=[ attr[Mat4](TRANSFORM_ATTRIB, memoryPerformanceHint=PreferFastWrite, perInstance=true), @@ -110,10 +114,10 @@ inc instanceCounter result.mesh[].renameAttribute("position", POSITION_ATTRIB) result.mesh[].renameAttribute("uv", UV_ATTRIB) - result.mesh.materials = @[MaterialData( - name: TEXT_MATERIAL, - textures: {"fontAtlas": font.fontAtlas}.toTable, - )] + result.mesh.material = MaterialData( + name: font.name & " text", + attributes: {"fontAtlas": initDataList(@[font.fontAtlas])}.toTable, + ) result.updateMesh()
--- a/src/semicongine/vulkan/renderpass.nim Thu Oct 12 14:55:28 2023 +0700 +++ b/src/semicongine/vulkan/renderpass.nim Sat Oct 21 01:05:34 2023 +0700 @@ -1,6 +1,7 @@ import std/options import ../core +import ../material import ./device import ./physicaldevice import ./pipeline @@ -14,7 +15,7 @@ flags: VkSubpassDescriptionFlags outputs: seq[VkAttachmentReference] depthStencil: Option[VkAttachmentReference] - pipelines*: seq[(string, Pipeline)] + pipelines*: seq[(MaterialType, Pipeline)] RenderPass* = object vk*: VkRenderPass device*: Device @@ -61,7 +62,7 @@ proc simpleForwardRenderPass*( device: Device, - shaders: openArray[(string, ShaderConfiguration)], + shaders: openArray[(MaterialType, ShaderConfiguration)], inFlightFrames=2, clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true, @@ -70,7 +71,7 @@ {.warning: "Need to implement material -> shader compatability" .} assert device.vk.valid - for (material, shaderconfig) in shaders: + for (_, shaderconfig) in shaders: assert shaderconfig.outputs.len == 1 var attachments = @[VkAttachmentDescription( @@ -100,8 +101,8 @@ dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT], )] result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies) - for (material, shaderconfig) in shaders: - result.subpasses[0].pipelines.add (material, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling=backFaceCulling)) + for (materialtype, shaderconfig) in shaders: + result.subpasses[0].pipelines.add (materialtype, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling=backFaceCulling)) proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer) = @@ -160,6 +161,6 @@ renderPass.device.vk.vkDestroyRenderPass(renderPass.vk, nil) renderPass.vk.reset for i in 0 ..< renderPass.subpasses.len: - for material, pipeline in renderPass.subpasses[i].pipelines.mitems: + for _, pipeline in renderPass.subpasses[i].pipelines.mitems: pipeline.destroy() renderPass.subpasses = @[]