# HG changeset patch # User Sam # Date 1693737269 -25200 # Node ID d65b62812d34a6e34b8489c1d882570e75480977 # Parent 43432dad4797350f8d1e4cfa44a9033c6bb4bf19 did: undid using meshes as values, ref is much better, fix a few things, fix a few huge performance issues diff -r 43432dad4797 -r d65b62812d34 src/semicongine/engine.nim --- a/src/semicongine/engine.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/src/semicongine/engine.nim Sun Sep 03 17:34:29 2023 +0700 @@ -130,12 +130,15 @@ proc updateInputs*(engine: var Engine): EngineState = assert engine.state in [Starting, Running] + # reset input states engine.input.keyWasPressed = {} engine.input.keyWasReleased = {} engine.input.mouseWasPressed = {} engine.input.mouseWasReleased = {} engine.input.mouseWheel = 0 engine.input.mouseMove = newVec2f() + engine.input.windowWasResized = false + if engine.state == Starting: engine.input.windowWasResized = true var mpos = engine.window.getMousePosition() diff -r 43432dad4797 -r d65b62812d34 src/semicongine/mesh.nim --- a/src/semicongine/mesh.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/src/semicongine/mesh.nim Sun Sep 03 17:34:29 2023 +0700 @@ -16,7 +16,7 @@ Tiny # up to 2^8 vertices # TODO: need to check and enable support for this Small # up to 2^16 vertices Big # up to 2^32 vertices - Mesh* = object + MeshObject* = object vertexCount*: int case indexType*: MeshIndexType of None: discard @@ -30,6 +30,7 @@ vertexData: Table[string, DataList] instanceData: Table[string, DataList] dirtyAttributes: seq[string] + Mesh* = ref MeshObject Material* = object name*: string constants*: Table[string, DataList] @@ -39,8 +40,10 @@ name: "empty material" ) +func `$`*(mesh: MeshObject): string = + &"Mesh(vertexCount: {mesh.vertexCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})" func `$`*(mesh: Mesh): string = - &"Mesh(vertexCount: {mesh.vertexCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})" + $mesh[] proc `$`*(material: Material): string = var constants: seq[string] @@ -51,19 +54,22 @@ textures.add &"{key}" return &"""{material.name} | Values: {constants.join(", ")} | Textures: {textures.join(", ")}""" -func vertexAttributes*(mesh: Mesh): seq[string] = +func vertexAttributes*(mesh: MeshObject): seq[string] = mesh.vertexData.keys.toSeq -func instanceAttributes*(mesh: Mesh): seq[string] = +func instanceAttributes*(mesh: MeshObject): seq[string] = mesh.instanceData.keys.toSeq -func attributes*(mesh: Mesh): seq[string] = +func attributes*(mesh: MeshObject): seq[string] = mesh.vertexAttributes & mesh.instanceAttributes func hash*(material: Material): Hash = hash(material.name) -func instanceCount*(mesh: Mesh): int = +func hash*(mesh: Mesh): Hash = + hash(cast[ptr MeshObject](mesh)) + +func instanceCount*(mesh: MeshObject): int = mesh.instanceTransforms.len converter toVulkan*(indexType: MeshIndexType): VkIndexType = @@ -73,7 +79,7 @@ of Small: VK_INDEX_TYPE_UINT16 of Big: VK_INDEX_TYPE_UINT32 -func indicesCount*(mesh: Mesh): int = +func indicesCount*(mesh: MeshObject): int = ( case mesh.indexType of None: 0 @@ -82,35 +88,35 @@ of Big: mesh.bigIndices.len ) * 3 -func initVertexAttribute*[T](mesh: var Mesh, attribute: string, value: seq[T]) = +func initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) = assert not mesh.vertexData.contains(attribute) mesh.vertexData[attribute] = newDataList(thetype=getDataType[T]()) mesh.vertexData[attribute].initData(mesh.vertexCount) mesh.vertexData[attribute].setValues(value) -func initVertexAttribute*[T](mesh: var Mesh, attribute: string, value: T) = +func initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: T) = initVertexAttribute(mesh, attribute, newSeqWith(mesh.vertexCount, value)) -func initVertexAttribute*[T](mesh: var Mesh, attribute: string) = +func initVertexAttribute*[T](mesh: var MeshObject, attribute: string) = initVertexAttribute(mesh=mesh, attribute=attribute, value=default(T)) -func initVertexAttribute*(mesh: var Mesh, attribute: string, datatype: DataType) = +func initVertexAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) = assert not mesh.vertexData.contains(attribute) mesh.vertexData[attribute] = newDataList(thetype=datatype) mesh.vertexData[attribute].initData(mesh.vertexCount) -func initInstanceAttribute*[T](mesh: var Mesh, attribute: string, value: seq[T]) = +func initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) = assert not mesh.instanceData.contains(attribute) mesh.instanceData[attribute] = newDataList(thetype=getDataType[T]()) mesh.instanceData[attribute].initData(mesh.instanceCount) mesh.instanceData[attribute].setValues(value) -func initInstanceAttribute*[T](mesh: var Mesh, attribute: string, value: T) = +func initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: T) = initInstanceAttribute(mesh, attribute, newSeqWith(mesh.instanceCount, value)) -func initInstanceAttribute*[T](mesh: var Mesh, attribute: string) = +func initInstanceAttribute*[T](mesh: var MeshObject, attribute: string) = initInstanceAttribute(mesh=mesh, attribute=attribute, value=default(T)) -func initInstanceAttribute*(mesh: var Mesh, attribute: string, datatype: DataType) = +func initInstanceAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) = assert not mesh.instanceData.contains(attribute) mesh.instanceData[attribute] = newDataList(thetype=datatype) mesh.instanceData[attribute].initData(mesh.instanceCount) -func newMesh*( +proc newMesh*( positions: openArray[Vec3f], indices: openArray[array[3, uint32|uint16|uint8]], colors: openArray[Vec4f]=[], @@ -140,28 +146,28 @@ material: material, ) - result.initVertexAttribute("position", positions.toSeq) - if colors.len > 0: result.initVertexAttribute("color", colors.toSeq) - if uvs.len > 0: result.initVertexAttribute("uv", uvs.toSeq) + result[].initVertexAttribute("position", positions.toSeq) + if colors.len > 0: result[].initVertexAttribute("color", colors.toSeq) + if uvs.len > 0: result[].initVertexAttribute("uv", uvs.toSeq) # assert all indices are valid for i in indices: - assert int(i[0]) < result.vertexCount - assert int(i[1]) < result.vertexCount - assert int(i[2]) < result.vertexCount + assert int(i[0]) < result[].vertexCount + assert int(i[1]) < result[].vertexCount + assert int(i[2]) < result[].vertexCount # cast index values to appropiate type - if result.indexType == Tiny and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support + if result[].indexType == Tiny and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support for i, tri in enumerate(indices): - result.tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])] - elif result.indexType == Small and uint32(positions.len) < uint32(high(uint16)): + result[].tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])] + elif result[].indexType == Small and uint32(positions.len) < uint32(high(uint16)): for i, tri in enumerate(indices): - result.smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])] - elif result.indexType == Big: + result[].smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])] + elif result[].indexType == Big: for i, tri in enumerate(indices): - result.bigIndices.add [uint32(tri[0]), uint32(tri[1]), uint32(tri[2])] + result[].bigIndices.add [uint32(tri[0]), uint32(tri[1]), uint32(tri[2])] -func newMesh*( +proc newMesh*( positions: openArray[Vec3f], colors: openArray[Vec4f]=[], uvs: openArray[Vec2f]=[], @@ -179,7 +185,7 @@ material=material, ) -func attributeSize*(mesh: Mesh, attribute: string): int = +func attributeSize*(mesh: MeshObject, attribute: string): int = if mesh.vertexData.contains(attribute): mesh.vertexData[attribute].size elif mesh.instanceData.contains(attribute): @@ -187,7 +193,7 @@ else: 0 -func attributeType*(mesh: Mesh, attribute: string): DataType = +func attributeType*(mesh: MeshObject, attribute: string): DataType = if mesh.vertexData.contains(attribute): mesh.vertexData[attribute].theType elif mesh.instanceData.contains(attribute): @@ -195,7 +201,7 @@ else: raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") -func indexSize*(mesh: Mesh): int = +func indexSize*(mesh: MeshObject): int = case mesh.indexType of None: 0 of Tiny: mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0)) @@ -205,14 +211,14 @@ func rawData[T: seq](value: T): (pointer, int) = (pointer(addr(value[0])), sizeof(get(genericParams(typeof(value)), 0)) * value.len) -func getRawIndexData*(mesh: Mesh): (pointer, int) = +func getRawIndexData*(mesh: MeshObject): (pointer, int) = case mesh.indexType: of None: raise newException(Exception, "Trying to get index data for non-indexed mesh") of Tiny: rawData(mesh.tinyIndices) of Small: rawData(mesh.smallIndices) of Big: rawData(mesh.bigIndices) -func getRawData*(mesh: var Mesh, attribute: string): (pointer, int) = +func getRawData*(mesh: var MeshObject, attribute: string): (pointer, int) = if mesh.vertexData.contains(attribute): mesh.vertexData[attribute].getRawData() elif mesh.instanceData.contains(attribute): @@ -220,7 +226,7 @@ else: raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") -proc getAttribute*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string): seq[T] = +proc getAttribute*[T: GPUType|int|uint|float](mesh: MeshObject, attribute: string): seq[T] = if mesh.vertexData.contains(attribute): getValues[T](mesh.vertexData[attribute]) elif mesh.instanceData.contains(attribute): @@ -228,7 +234,7 @@ else: raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") -proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) = +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 setValues(mesh.vertexData[attribute], data) @@ -237,9 +243,10 @@ setValues(mesh.instanceData[attribute], data) else: raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") - mesh.dirtyAttributes.add attribute + if not mesh.dirtyAttributes.contains(attribute): + mesh.dirtyAttributes.add attribute -proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, i: int, value: T) = +proc updateAttributeData*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, i: int, value: T) = if mesh.vertexData.contains(attribute): assert i < mesh.vertexData[attribute].len setValue(mesh.vertexData[attribute], i, value) @@ -248,19 +255,21 @@ setValue(mesh.instanceData[attribute], i, value) else: raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") - mesh.dirtyAttributes.add attribute + if not mesh.dirtyAttributes.contains(attribute): + mesh.dirtyAttributes.add attribute -proc appendAttributeData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) = +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}") - mesh.dirtyAttributes.add attribute + if not mesh.dirtyAttributes.contains(attribute): + mesh.dirtyAttributes.add attribute # currently only used for loading from files, shouls -proc appendAttributeData*(mesh: var Mesh, attribute: string, data: DataList) = +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) @@ -269,28 +278,29 @@ appendValues(mesh.instanceData[attribute], data) else: raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") - mesh.dirtyAttributes.add attribute + if not mesh.dirtyAttributes.contains(attribute): + mesh.dirtyAttributes.add attribute -proc appendIndicesData*(mesh: var Mesh, v1, v2, v3: int) = +proc appendIndicesData*(mesh: var MeshObject, v1, v2, v3: int) = case mesh.indexType of None: raise newException(Exception, "Mesh does not support indexed data") of Tiny: mesh.tinyIndices.add([uint8(v1), uint8(v2), uint8(v3)]) of Small: mesh.smallIndices.add([uint16(v1), uint16(v2), uint16(v3)]) of Big: mesh.bigIndices.add([uint32(v1), uint32(v2), uint32(v3)]) -proc updateInstanceTransforms*(mesh: var Mesh, attribute: string) = +proc updateInstanceTransforms*(mesh: var MeshObject, attribute: string) = let currentTransforms = mesh.instanceTransforms.mapIt(mesh.transform * it) if currentTransforms != mesh.transformCache: mesh.updateAttributeData(attribute, currentTransforms) mesh.transformCache = currentTransforms -func dirtyAttributes*(mesh: Mesh): seq[string] = +func dirtyAttributes*(mesh: MeshObject): seq[string] = mesh.dirtyAttributes -proc clearDirtyAttributes*(mesh: var Mesh) = - mesh.dirtyAttributes = @[] +proc clearDirtyAttributes*(mesh: var MeshObject) = + mesh.dirtyAttributes.reset -proc transform*[T: GPUType](mesh: Mesh, attribute: string, transform: Mat4) = +proc transform*[T: GPUType](mesh: MeshObject, attribute: string, transform: Mat4) = if mesh.vertexData.contains(attribute): for i in 0 ..< mesh.vertexData[attribute].len: setValue(mesh.vertexData[attribute], i, transform * getValue[T](mesh.vertexData[attribute], i)) @@ -300,7 +310,7 @@ else: raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}") -func rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = +proc rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = result = Mesh( vertexCount: 4, instanceTransforms: @[Unit4F32], @@ -314,21 +324,21 @@ pos = @[newVec3f(-half_w, -half_h), newVec3f( half_w, -half_h), newVec3f( half_w, half_h), newVec3f(-half_w, half_h)] c = hexToColorAlpha(color) - result.initVertexAttribute("position", pos) - result.initVertexAttribute("color", @[c, c, c, c]) - result.initVertexAttribute("uv", @[newVec2f(0, 0), newVec2f(1, 0), newVec2f(1, 1), newVec2f(0, 1)]) + result[].initVertexAttribute("position", pos) + result[].initVertexAttribute("color", @[c, c, c, c]) + result[].initVertexAttribute("uv", @[newVec2f(0, 0), newVec2f(1, 0), newVec2f(1, 1), newVec2f(0, 1)]) -func tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = +proc tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = result = Mesh(vertexCount: 3, instanceTransforms: @[Unit4F32]) let half_w = width / 2 half_h = height / 2 colorVec = hexToColorAlpha(color) - result.initVertexAttribute("position", @[newVec3f(0, -half_h), newVec3f( half_w, half_h), newVec3f(-half_w, half_h)]) - result.initVertexAttribute("color", @[colorVec, colorVec, colorVec]) + result[].initVertexAttribute("position", @[newVec3f(0, -half_h), newVec3f( half_w, half_h), newVec3f(-half_w, half_h)]) + result[].initVertexAttribute("color", @[colorVec, colorVec, colorVec]) -func circle*(width=1'f32, height=1'f32, nSegments=12, color="ffffffff"): Mesh = +proc circle*(width=1'f32, height=1'f32, nSegments=12, color="ffffffff"): Mesh = assert nSegments >= 3 result = Mesh(vertexCount: 3 + nSegments, instanceTransforms: @[Unit4F32], indexType: Small) @@ -343,11 +353,11 @@ for i in 0 .. nSegments: pos.add newVec3f(cos(float32(i) * step) * half_w, sin(float32(i) * step) * half_h) col.add c - result.smallIndices.add [uint16(0), uint16(i + 1), uint16(i + 2)] + result[].smallIndices.add [uint16(0), uint16(i + 1), uint16(i + 2)] - result.initVertexAttribute("position", pos) - result.initVertexAttribute("color", col) + result[].initVertexAttribute("position", pos) + result[].initVertexAttribute("color", col) -func getCollisionPoints*(mesh: Mesh, positionAttribute="position"): seq[Vec3f] = +func getCollisionPoints*(mesh: MeshObject, positionAttribute="position"): seq[Vec3f] = for p in getAttribute[Vec3f](mesh, positionAttribute): result.add mesh.transform * p diff -r 43432dad4797 -r d65b62812d34 src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/src/semicongine/renderer.nim Sun Sep 03 17:34:29 2023 +0700 @@ -3,7 +3,6 @@ import std/strformat import std/sequtils import std/logging -import std/enumerate import ./core import ./vulkan/buffer @@ -25,13 +24,13 @@ type SceneData = object - drawables*: seq[tuple[drawable: Drawable, meshIndex: int]] + drawables*: seq[tuple[drawable: Drawable, mesh: Mesh]] vertexBuffers*: Table[MemoryPerformanceHint, Buffer] indexBuffer*: Buffer uniformBuffers*: Table[VkPipeline, seq[Buffer]] # one per frame-in-flight textures*: Table[string, seq[VulkanTexture]] # per frame-in-flight attributeLocation*: Table[string, MemoryPerformanceHint] - vertexBufferOffsets*: Table[(int, string), int] + vertexBufferOffsets*: Table[(Mesh, string), int] descriptorPools*: Table[VkPipeline, DescriptorPool] descriptorSets*: Table[VkPipeline, seq[DescriptorSet]] materials: seq[Material] @@ -106,11 +105,11 @@ for input in pipeline.inputs: if input.name == TRANSFORMATTRIBUTE: # will be populated automatically continue - if not (input.name in mesh.attributes): + if not (input.name in mesh[].attributes): return (true, &"Shader input '{input.name}' is not available for mesh '{mesh}'") - if input.theType != mesh.attributeType(input.name): - return (true, &"Shader input '{input.name}' expects type {input.theType}, but mesh '{mesh}' has {mesh.attributeType(input.name)}") - if input.perInstance != mesh.instanceAttributes.contains(input.name): + if input.theType != mesh[].attributeType(input.name): + return (true, &"Shader input '{input.name}' expects type {input.theType}, but mesh '{mesh}' has {mesh[].attributeType(input.name)}") + if input.perInstance != mesh[].instanceAttributes.contains(input.name): return (true, &"Shader input '{input.name}' expects to be per instance, but mesh '{mesh}' has is not as instance attribute") return materialCompatibleWithPipeline(scene, mesh.material, pipeline) @@ -159,20 +158,20 @@ for mesh in scene.meshes.mitems: for inputAttr in inputs: if inputAttr.name == TRANSFORMATTRIBUTE: - mesh.initInstanceAttribute(inputAttr.name, inputAttr.thetype) - elif not mesh.attributes.contains(inputAttr.name): + mesh[].initInstanceAttribute(inputAttr.name, inputAttr.thetype) + elif not mesh[].attributes.contains(inputAttr.name): warn(&"Mesh is missing data for shader attribute {inputAttr.name}, auto-filling with empty values") if inputAttr.perInstance: - mesh.initInstanceAttribute(inputAttr.name, inputAttr.thetype) + mesh[].initInstanceAttribute(inputAttr.name, inputAttr.thetype) else: - mesh.initVertexAttribute(inputAttr.name, inputAttr.thetype) - assert mesh.attributeType(inputAttr.name) == inputAttr.thetype, &"mesh attribute {inputAttr.name} has type {mesh.attributeType(inputAttr.name)} but shader expects {inputAttr.thetype}" + mesh[].initVertexAttribute(inputAttr.name, inputAttr.thetype) + 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 for mesh in scene.meshes: - if mesh.indexType != MeshIndexType.None: - let indexAlignment = case mesh.indexType + if mesh[].indexType != MeshIndexType.None: + let indexAlignment = case mesh[].indexType of MeshIndexType.None: 0 of Tiny: 1 of Small: 2 @@ -180,7 +179,7 @@ # index value alignment required by Vulkan if indicesBufferSize mod indexAlignment != 0: indicesBufferSize += indexAlignment - (indicesBufferSize mod indexAlignment) - indicesBufferSize += mesh.indexSize + indicesBufferSize += mesh[].indexSize if indicesBufferSize > 0: scenedata.indexBuffer = renderer.device.createBuffer( size=indicesBufferSize, @@ -202,7 +201,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.attributeSize(attribute.name) + perLocationSizes[attribute.memoryPerformanceHint] += mesh[].attributeSize(attribute.name) # create vertex buffers for memoryPerformanceHint, bufferSize in perLocationSizes.pairs: @@ -220,10 +219,10 @@ for hint in MemoryPerformanceHint: perLocationOffsets[hint] = 0 - for (meshIndex, mesh) in enumerate(scene.meshes.mitems): + for mesh in scene.meshes: for attribute in inputs: - scenedata.vertexBufferOffsets[(meshIndex, attribute.name)] = perLocationOffsets[attribute.memoryPerformanceHint] - let size = mesh.getRawData(attribute.name)[1] + scenedata.vertexBufferOffsets[(mesh, attribute.name)] = perLocationOffsets[attribute.memoryPerformanceHint] + let size = mesh[].getRawData(attribute.name)[1] perLocationOffsets[attribute.memoryPerformanceHint] += size if perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT != 0: perLocationOffsets[attribute.memoryPerformanceHint] += VERTEX_ATTRIB_ALIGNMENT - (perLocationOffsets[attribute.memoryPerformanceHint] mod VERTEX_ATTRIB_ALIGNMENT) @@ -235,14 +234,14 @@ if scene.usesMaterial(materialName): offsets[pipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]() for attribute in pipeline.inputs: - offsets[pipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(meshIndex, attribute.name)]) + offsets[pipeline.vk].add (attribute.name, attribute.memoryPerformanceHint, scenedata.vertexBufferOffsets[(mesh, attribute.name)]) # create drawables let indexed = mesh.indexType != MeshIndexType.None var drawable = Drawable( - elementCount: if indexed: mesh.indicesCount else: mesh.vertexCount, + elementCount: if indexed: mesh[].indicesCount else: mesh[].vertexCount, bufferOffsets: offsets, - instanceCount: mesh.instanceCount, + instanceCount: mesh[].instanceCount, indexed: indexed, ) if indexed: @@ -256,10 +255,10 @@ indexBufferOffset += indexAlignment - (indexBufferOffset mod indexAlignment) drawable.indexBufferOffset = indexBufferOffset drawable.indexType = mesh.indexType - var (pdata, size) = mesh.getRawIndexData() + var (pdata, size) = mesh[].getRawIndexData() scenedata.indexBuffer.setData(pdata, size, indexBufferOffset) indexBufferOffset += size - scenedata.drawables.add (drawable, meshIndex) + scenedata.drawables.add (drawable, mesh) # setup uniforms and samplers for subpass_i in 0 ..< renderer.renderPass.subpasses.len: @@ -299,39 +298,58 @@ renderer.scenedata[scene] = scenedata -proc refreshMeshAttributeData(renderer: Renderer, scene: var Scene, drawable: Drawable, meshIndex: int, attribute: string) = - debug &"Refreshing data on mesh {scene.meshes[meshIndex]} for {attribute}" +proc refreshMeshAttributeData(renderer: Renderer, scene: var Scene, drawable: Drawable, mesh: Mesh, attribute: string) = + debug &"Refreshing data on mesh mesh for {attribute}" # ignore attributes that are not used in this shader if not (attribute in renderer.scenedata[scene].attributeLocation): return - let (pdata, size) = scene.meshes[meshIndex].getRawData(attribute) + let (pdata, size) = mesh[].getRawData(attribute) let memoryPerformanceHint = renderer.scenedata[scene].attributeLocation[attribute] - renderer.scenedata[scene].vertexBuffers[memoryPerformanceHint].setData(pdata, size, renderer.scenedata[scene].vertexBufferOffsets[(meshIndex, attribute)]) + renderer.scenedata[scene].vertexBuffers[memoryPerformanceHint].setData(pdata, size, renderer.scenedata[scene].vertexBufferOffsets[(mesh, attribute)]) proc updateMeshData*(renderer: var Renderer, scene: var Scene, forceAll=false) = assert scene in renderer.scenedata - for (drawable, meshIndex) in renderer.scenedata[scene].drawables.mitems: - if scene.meshes[meshIndex].attributes.contains(TRANSFORMATTRIBUTE): - scene.meshes[meshIndex].updateInstanceTransforms(TRANSFORMATTRIBUTE) - let attrs = (if forceAll: scene.meshes[meshIndex].attributes else: scene.meshes[meshIndex].dirtyAttributes) + for (drawable, mesh) in renderer.scenedata[scene].drawables.mitems: + if mesh[].attributes.contains(TRANSFORMATTRIBUTE): + mesh[].updateInstanceTransforms(TRANSFORMATTRIBUTE) + let attrs = (if forceAll: mesh[].attributes else: mesh[].dirtyAttributes) for attribute in attrs: - renderer.refreshMeshAttributeData(scene, drawable, meshIndex, attribute) + renderer.refreshMeshAttributeData(scene, drawable, mesh, attribute) debug &"Update mesh attribute {attribute}" - scene.meshes[meshIndex].clearDirtyAttributes() + mesh[].clearDirtyAttributes() -# TODO: only update if uniform values changed -proc updateUniformData*(renderer: var Renderer, scene: var Scene, forceAll=true) = +proc updateUniformData*(renderer: var Renderer, scene: var Scene, forceAll=false) = assert scene in renderer.scenedata + # TODO: maybe check for dirty materials too, but atm we copy materials into the + # renderers scenedata, so they will are immutable after initialization, would + # need to allow updates of materials too in order to make sense + + let dirty = scene.dirtyShaderGlobals + if not forceAll and dirty.len == 0: + return + + if forceAll: + debug "Update uniforms because 'forceAll' was given" + else: + debug &"Update uniforms because of dirty scene globals: {dirty}" # loop over all used shaders/pipelines for i in 0 ..< renderer.renderPass.subpasses.len: for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: - if scene.usesMaterial(materialName) and renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and renderer.scenedata[scene].uniformBuffers[pipeline.vk].len != 0: + if ( + scene.usesMaterial(materialName) 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 + if forceAll: + for buffer in renderer.scenedata[scene].uniformBuffers[pipeline.vk]: + assert buffer.vk.valid + var offset = 0 - # loop over all uniforms of the shader + # loop over all uniforms of the shader-pipeline for uniform in pipeline.uniforms: var foundValue = false var value: DataList @@ -349,10 +367,16 @@ if foundValue: break if not foundValue: raise newException(Exception, &"Uniform '{uniform.name}' not found in scene shaderGlobals or materials") - debug &"Update uniform {uniform.name}" + debug &" update uniform {uniform.name} with value: {value}" let (pdata, size) = value.getRawData() - renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].setData(pdata, size, offset) + if dirty.contains(uniform.name) or forceAll: # only update if necessary + if forceAll: # need to update all in-flight frames, when forced to do all + for buffer in renderer.scenedata[scene].uniformBuffers[pipeline.vk]: + buffer.setData(pdata, size, offset) + else: + renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].setData(pdata, size, offset) offset += size + scene.clearDirtyShaderGlobals() proc render*(renderer: var Renderer, scene: Scene) = assert scene in renderer.scenedata @@ -385,8 +409,8 @@ 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, meshIndex) in renderer.scenedata[scene].drawables: - if scene.meshes[meshIndex].material.name == materialName: + for (drawable, mesh) in renderer.scenedata[scene].drawables: + if mesh.material.name == materialName: drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer, pipeline.vk) if i < renderer.renderPass.subpasses.len - 1: diff -r 43432dad4797 -r d65b62812d34 src/semicongine/resources.nim --- a/src/semicongine/resources.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/src/semicongine/resources.nim Sun Sep 03 17:34:29 2023 +0700 @@ -133,9 +133,6 @@ let defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_=+[{]};:,<.>/? ".toRunes() loadResource_intern(path).readTrueType(name, defaultCharset, color, resolution) -proc loadFirstMesh*(path: string): Mesh = - loadResource_intern(path).readglTF()[0].children[0].mesh - proc loadMeshes*(path: string): seq[MeshTree] = loadResource_intern(path).readglTF() diff -r 43432dad4797 -r d65b62812d34 src/semicongine/resources/mesh.nim --- a/src/semicongine/resources/mesh.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Sun Sep 03 17:34:29 2023 +0700 @@ -20,7 +20,7 @@ structuredContent: JsonNode binaryBufferData: seq[uint8] MeshTree* = ref object - mesh*: Mesh + mesh*: MeshObject children*: seq[MeshTree] const @@ -210,7 +210,7 @@ setValue(result.constants["emissiveFactor"], @[newVec3f(1'f32, 1'f32, 1'f32)]) -proc addPrimitive(mesh: var Mesh, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) = +proc addPrimitive(mesh: var MeshObject, root: JsonNode, primitiveNode: JsonNode, mainBuffer: seq[uint8]) = if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4: raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode") @@ -253,7 +253,7 @@ 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 = +proc loadMesh(root: JsonNode, meshNode: JsonNode, mainBuffer: seq[uint8]): MeshObject = # check if and how we use indexes var indexCount = 0 @@ -267,7 +267,7 @@ else: indexType = Big - result = Mesh(instanceTransforms: @[Unit4F32], indexType: indexType) + result = MeshObject(instanceTransforms: @[Unit4F32], indexType: indexType) # check we have the same attributes for all primitives let attributes = meshNode["primitives"][0]["attributes"].keys.toSeq diff -r 43432dad4797 -r d65b62812d34 src/semicongine/scene.nim --- a/src/semicongine/scene.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/src/semicongine/scene.nim Sun Sep 03 17:34:29 2023 +0700 @@ -9,14 +9,29 @@ name*: string shaderGlobals*: Table[string, DataList] meshes*: seq[Mesh] + dirtyShaderGlobals: seq[string] + +proc add*(scene: var Scene, mesh: MeshObject) = + var tmp = new Mesh + tmp[] = mesh + scene.meshes.add tmp + +proc add*(scene: var Scene, mesh: Mesh) = + scene.meshes.add mesh + +# generic way to add objects that have a mesh-attribute +proc add*[T](scene: var Scene, obj: T) = + scene.meshes.add obj.mesh func addShaderGlobal*[T](scene: var Scene, name: string, data: T) = scene.shaderGlobals[name] = newDataList(thetype=getDataType[T]()) setValues(scene.shaderGlobals[name], @[data]) + scene.dirtyShaderGlobals.add name func addShaderGlobalArray*[T](scene: var Scene, name: string, data: seq[T]) = scene.shaderGlobals[name] = newDataList(thetype=getDataType[T]()) setValues(scene.shaderGlobals[name], data) + scene.dirtyShaderGlobals.add name func getShaderGlobal*[T](scene: Scene, name: string): T = getValues[T](scene.shaderGlobals[name])[0] @@ -26,12 +41,24 @@ func setShaderGlobal*[T](scene: var Scene, name: string, value: T) = setValues[T](scene.shaderGlobals[name], @[value]) + if not scene.dirtyShaderGlobals.contains(name): + scene.dirtyShaderGlobals.add name func setShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) = setValues[T](scene.shaderGlobals[name], value) + if not scene.dirtyShaderGlobals.contains(name): + scene.dirtyShaderGlobals.add name func appendShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) = appendValues[T](scene.shaderGlobals[name], value) + if not scene.dirtyShaderGlobals.contains(name): + scene.dirtyShaderGlobals.add name + +func dirtyShaderGlobals*(scene: Scene): seq[string] = + scene.dirtyShaderGlobals + +func clearDirtyShaderGlobals*(scene: var Scene) = + scene.dirtyShaderGlobals.reset func hash*(scene: Scene): Hash = hash(scene.name) diff -r 43432dad4797 -r d65b62812d34 src/semicongine/text.nim --- a/src/semicongine/text.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/src/semicongine/text.nim Sun Sep 03 17:34:29 2023 +0700 @@ -22,20 +22,15 @@ TEXT_MATERIAL* = "default-text" TEXT_SHADER* = createShaderConfiguration( inputs=[ - attr[Mat4]("transform", memoryPerformanceHint=PreferFastWrite), + attr[Mat4]("transform", memoryPerformanceHint=PreferFastWrite, perInstance=true), attr[Vec3f]("position", memoryPerformanceHint=PreferFastWrite), attr[Vec2f]("uv", memoryPerformanceHint=PreferFastWrite), ], intermediates=[attr[Vec2f]("uvFrag")], outputs=[attr[Vec4f]("color")], samplers=[attr[Sampler2DType]("fontAtlas")], - uniforms=[ - attr[Mat4]("perspective"), - ], - vertexCode="""gl_Position = vec4(position, 1.0) * (transform * Uniforms.perspective); uvFrag = uv;""", - # vertexCode="""gl_Position = vec4(position, 1.0);""", + vertexCode="""gl_Position = vec4(position, 1.0) * transform; uvFrag = uv;""", fragmentCode="""color = texture(fontAtlas, uvFrag);""", - # fragmentCode="""color = vec4(1, 0, 0, 1);""", ) proc updateMesh(textbox: var Textbox) = @@ -61,24 +56,24 @@ top = glyph.topOffset bottom = glyph.topOffset + glyph.dimension.y - 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[].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.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]) + 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 < textbox.text.len - 1: offsetX += textbox.font.kerning[(textbox.text[i], textbox.text[i + 1])] else: - 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()) + 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] = @@ -104,10 +99,7 @@ result.mesh = newMesh(positions = positions, indices = indices, uvs = uvs) result.mesh.material = Material( name: TEXT_MATERIAL, - # constants: {"glyphCoordinates": }.toTable, textures: {"fontAtlas": font.fontAtlas}.toTable, ) - # wrap the text mesh in a new entity to preserve the font-scaling - result.mesh.transform = scale(1 / font.resolution, 1 / font.resolution) result.updateMesh() diff -r 43432dad4797 -r d65b62812d34 src/semicongine/vulkan/swapchain.nim --- a/src/semicongine/vulkan/swapchain.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/src/semicongine/vulkan/swapchain.nim Sun Sep 03 17:34:29 2023 +0700 @@ -77,7 +77,6 @@ imageCount = max(imageCount, capabilities.minImageCount) if capabilities.maxImageCount != 0: imageCount = min(imageCount, capabilities.maxImageCount) - var createInfo = VkSwapchainCreateInfoKHR( sType: VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, surface: device.physicalDevice.surface, diff -r 43432dad4797 -r d65b62812d34 tests/test_collision.nim --- a/tests/test_collision.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/tests/test_collision.nim Sun Sep 03 17:34:29 2023 +0700 @@ -5,9 +5,9 @@ proc main() = var scene = Scene(name: "main") - scene.meshes.add rect(color="f00f") - scene.meshes.add rect() - scene.meshes.add circle(color="0f0f") + scene.add rect(color="f00f") + scene.add rect() + scene.add circle(color="0f0f") scene.meshes[1].transform = scale(0.8, 0.8) scene.meshes[2].transform = scale(0.1, 0.1) scene.addShaderGlobal("perspective", Unit4F32) diff -r 43432dad4797 -r d65b62812d34 tests/test_font.nim --- a/tests/test_font.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/tests/test_font.nim Sun Sep 03 17:34:29 2023 +0700 @@ -7,16 +7,19 @@ var font = loadFont("DejaVuSans.ttf", color=newVec4f(1, 0.5, 0.5, 1), resolution=20) var textbox = initTextbox(32, font, "".toRunes) - var scene = Scene(name: "main", meshes: @[textbox.mesh]) + var scene = Scene(name: "main") + scene.add textbox + textbox.mesh.transform = scale(0.01, 0.01) var engine = initEngine("Test fonts") engine.initRenderer() + scene.addShaderGlobal("perspective", Unit4F32) engine.addScene(scene) - scene.addShaderGlobal("perspective", Unit4F32) while engine.updateInputs() == Running and not engine.keyIsDown(Escape): if engine.windowWasResized(): var winSize = engine.getWindow().size scene.setShaderGlobal("perspective", orthoWindowAspect(winSize[1] / winSize[0])) + textbox.mesh.transform = scale(0.01 * (winSize[1] / winSize[0]), 0.01) for c in [Key.A, Key.B, Key.C, Key.D, Key.E, Key.F, Key.G, Key.H, Key.I, Key.J, Key.K, Key.L, Key.M, Key.N, Key.O, Key.P, Key.Q, Key.R, Key.S, Key.T, Key.U, Key.V, Key.W, Key.X, Key.Y, Key.Z]: if engine.keyWasPressed(c): if engine.keyIsDown(ShiftL) or engine.keyIsDown(ShiftR): diff -r 43432dad4797 -r d65b62812d34 tests/test_vulkan_wrapper.nim --- a/tests/test_vulkan_wrapper.nim Sat Sep 02 23:51:02 2023 +0700 +++ b/tests/test_vulkan_wrapper.nim Sun Sep 03 17:34:29 2023 +0700 @@ -168,7 +168,6 @@ attr[Vec4f]("outcolor"), ], outputs=[attr[Vec4f]("color")], - uniforms=[attr[float32]("time")], samplers=[ attr[Sampler2DType]("my_little_texture") ], @@ -202,7 +201,6 @@ ] for scene in scenes.mitems: - scene.addShaderGlobal("time", 0.0'f32) engine.addScene(scene) # MAINLOOP @@ -214,7 +212,6 @@ if engine.updateInputs() != Running or engine.keyIsDown(Escape): engine.destroy() return - setShaderGlobal(scene, "time", getShaderGlobal[float32](scene, "time") + 0.0005'f) engine.renderScene(scene) echo "Rendered ", engine.framesRendered, " frames" echo "Processed ", engine.eventsProcessed, " events"