Mercurial > games > semicongine
changeset 783:893ec0fbfd44
add: first, incomplete version of material use
author | Sam <sam@basx.dev> |
---|---|
date | Sat, 19 Aug 2023 01:10:42 +0700 |
parents | b6950ea89b37 |
children | fa39e67dded7 |
files | src/semicongine.nim src/semicongine/engine.nim src/semicongine/material.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/resources/mesh.nim src/semicongine/scene.nim src/semicongine/vulkan/pipeline.nim src/semicongine/vulkan/renderpass.nim tests/test_vulkan_wrapper.nim |
diffstat | 10 files changed, 271 insertions(+), 249 deletions(-) [+] |
line wrap: on
line diff
--- a/src/semicongine.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/src/semicongine.nim Sat Aug 19 01:10:42 2023 +0700 @@ -7,7 +7,6 @@ import semicongine/collision import semicongine/scene import semicongine/events -import semicongine/material import semicongine/mesh import semicongine/renderer import semicongine/resources @@ -22,7 +21,6 @@ export collision export scene export events -export material export mesh export renderer export resources
--- a/src/semicongine/engine.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/src/semicongine/engine.nim Sat Aug 19 01:10:42 2023 +0700 @@ -1,4 +1,4 @@ -import std/sequtils +import std/tables import std/options import std/logging import std/os @@ -9,10 +9,10 @@ import ./vulkan/instance import ./vulkan/device import ./vulkan/physicaldevice -import ./vulkan/renderpass import ./vulkan/shader import ./scene +import ./mesh import ./renderer import ./events import ./audio @@ -102,21 +102,14 @@ ) startMixerThread() -proc setRenderer*(engine: var Engine, renderPass: RenderPass) = - if engine.renderer.isSome: - engine.renderer.get.destroy() - engine.renderer = some(engine.device.initRenderer(renderPass)) +proc setRenderer*(engine: var Engine, shaders: Table[string, ShaderConfiguration], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) = -proc addScene*(engine: var Engine, scene: Scene, vertexInput: seq[ShaderAttribute], samplers: seq[ShaderAttribute], transformAttribute="transform", materialIndexAttribute="materialIndex") = - assert transformAttribute == "" or transformAttribute in map(vertexInput, proc(a: ShaderAttribute): string = a.name) - assert materialIndexAttribute == "" or materialIndexAttribute in map(vertexInput, proc(a: ShaderAttribute): string = a.name) + assert not engine.renderer.isSome + engine.renderer = some(engine.device.initRenderer(shaders=shaders, clearColor=clearColor)) + +proc addScene*(engine: var Engine, scene: Scene) = assert engine.renderer.isSome - # TODO: - # rethink when and how we set up those buffers - # scene should have no idea about shader inputs and samplers, but we need to use those in the setup - # idea: gather material-names -> get materials -> get shaders -> determine vertexInputs and samplers? - # also, be aware: need to support multiple pipelines/shaders - engine.renderer.get.setupDrawableBuffers(scene, vertexInput, samplers) + engine.renderer.get.setupDrawableBuffers(scene) proc renderScene*(engine: var Engine, scene: var Scene) = assert engine.state == Running
--- a/src/semicongine/material.nim Tue Aug 15 23:51:37 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -import std/tables -import std/strformat -import std/strutils - -import ./core - -type - Material* = ref object - name*: string - index*: int - constants*: Table[string, DataValue] - textures*: Table[string, Texture] - -proc `$`*(material: Material): string = - var constants: seq[string] - for key, value in material.constants.pairs: - constants.add &"{key}: {value}" - var textures: seq[string] - for key in material.textures.keys: - textures.add &"{key}" - return &"""{material.name} ({material.index}) | Values: {constants.join(", ")} | Textures: {textures.join(", ")}"""
--- a/src/semicongine/mesh.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/src/semicongine/mesh.nim Sat Aug 19 01:10:42 2023 +0700 @@ -1,8 +1,10 @@ -import std/math as nimmath +import std/hashes +import std/options import std/typetraits import std/tables +import std/strformat import std/enumerate -import std/strformat +import std/strutils import std/sequtils import ./core @@ -18,7 +20,7 @@ Mesh* = ref object of Component 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) - materials*: seq[string] + material*: Material dirtyInstanceTransforms: bool data: Table[string, DataList] changedAttributes: seq[string] @@ -27,6 +29,15 @@ of Tiny: tinyIndices: seq[array[3, uint8]] of Small: smallIndices: seq[array[3, uint16]] of Big: bigIndices: seq[array[3, uint32]] + 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) converter toVulkan*(indexType: MeshIndexType): VkIndexType = case indexType: @@ -52,6 +63,15 @@ method `$`*(mesh: Mesh): string = &"Mesh, vertexCount: {mesh.vertexCount}, vertexData: {mesh.data.keys().toSeq()}, indexType: {mesh.indexType}" +proc `$`*(material: Material): string = + var constants: seq[string] + for key, value in material.constants.pairs: + constants.add &"{key}: {value}" + var textures: seq[string] + for key in material.textures.keys: + textures.add &"{key}" + return &"""{material.name} ({material.index}) | Values: {constants.join(", ")} | Textures: {textures.join(", ")}""" + func prettyData*(mesh: Mesh): string = for attr, data in mesh.data.pairs: result &= &"{attr}: {data}\n" @@ -79,6 +99,7 @@ indices: openArray[array[3, uint32|int32|uint16|int16|int]], colors: openArray[Vec4f]=[], uvs: openArray[Vec2f]=[], + material: Material=nil, instanceCount=1'u32, autoResize=true ): auto = @@ -94,7 +115,13 @@ elif autoResize and uint32(positions.len) < uint32(high(uint16)): indexType = Small - result = Mesh(instanceCount: instanceCount, instanceTransforms: newSeqWith(int(instanceCount), Unit4F32), indexType: indexType) + result = Mesh( + instanceCount: instanceCount, + instanceTransforms: newSeqWith(int(instanceCount), Unit4F32), + indexType: indexType, + ) + result.material = material + setMeshData(result, "position", positions.toSeq) if colors.len > 0: setMeshData(result, "color", colors.toSeq) if uvs.len > 0: setMeshData(result, "uv", uvs.toSeq) @@ -122,8 +149,16 @@ colors: openArray[Vec4f]=[], uvs: openArray[Vec2f]=[], instanceCount=1'u32, + material: Material=nil, ): auto = - newMesh(positions, newSeq[array[3, int]](), colors, uvs, instanceCount) + newMesh( + positions=positions, + indices=newSeq[array[3, int]](), + colors=colors, + uvs=uvs, + material=material, + instanceCount=instanceCount, + ) func availableAttributes*(mesh: Mesh): seq[string] = mesh.data.keys.toSeq
--- a/src/semicongine/renderer.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/src/semicongine/renderer.nim Sat Aug 19 01:10:42 2023 +0700 @@ -1,25 +1,23 @@ import std/options -import std/sequtils import std/enumerate import std/tables import std/strformat -import std/strutils import std/logging import ./core import ./vulkan/buffer import ./vulkan/device import ./vulkan/drawable +import ./vulkan/physicaldevice import ./vulkan/pipeline -import ./vulkan/physicaldevice import ./vulkan/renderpass import ./vulkan/swapchain +import ./vulkan/shader import ./vulkan/descriptor import ./vulkan/image import ./scene import ./mesh -import ./material type SceneData = object @@ -32,6 +30,7 @@ attributeBindingNumber*: Table[string, int] transformAttribute: string # name of attribute that is used for per-instance mesh transformation 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 descriptorSets*: Table[VkPipeline, seq[DescriptorSet]] @@ -43,22 +42,36 @@ scenedata: Table[Scene, SceneData] -proc initRenderer*(device: Device, renderPass: RenderPass): Renderer = +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 - assert renderPass.vk.valid - + result.device = device - result.renderPass = renderPass + result.renderPass = device.simpleForwardRenderPass(shaders, clearColor=clearColor) result.surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat() # use last renderpass as output for swapchain let swapchain = device.createSwapchain(result.renderPass.vk, result.surfaceFormat, device.firstGraphicsQueue().get().family) if not swapchain.isSome: raise newException(Exception, "Unable to create swapchain") + result.swapchain = swapchain.get() -proc setupDrawableBuffers*(renderer: var Renderer, scene: Scene, inputs: seq[ShaderAttribute], samplers: seq[ShaderAttribute]) = +func inputs(renderer: Renderer): seq[ShaderAttribute] = + for i in 0 ..< renderer.renderPass.subpasses.len: + for pipeline in renderer.renderPass.subpasses[i].pipelines.values: + result.add pipeline.inputs + +func samplers(renderer: Renderer): seq[ShaderAttribute] = + for i in 0 ..< renderer.renderPass.subpasses.len: + for pipeline in renderer.renderPass.subpasses[i].pipelines.values: + result.add pipeline.samplers + +proc setupDrawableBuffers*(renderer: var Renderer, scene: Scene) = 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 var scenedata = SceneData() # if mesh transformation are handled through the scenegraph-transformation, set it up here @@ -67,7 +80,7 @@ for input in inputs: if input.name == scene.transformAttribute: assert input.perInstance == true, $input - assert getDataType[Mat4]() == input.thetype + assert getDataType[Mat4]() == input.thetype, $input hasTransformAttribute = true assert hasTransformAttribute scenedata.transformAttribute = scene.transformAttribute @@ -82,6 +95,10 @@ assert hasMaterialIndexAttribute scenedata.materialIndexAttribute = scene.materialIndexAttribute + for mesh in allComponentsOfType[Mesh](scene.root): + if mesh.material != nil and not scenedata.materials.contains(mesh.material): + scenedata.materials.add mesh.material + # find all meshes, populate missing attribute values for shader var allMeshes: seq[Mesh] for mesh in allComponentsOfType[Mesh](scene.root): @@ -92,13 +109,11 @@ 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.materials.len > 0, "Missing material specification for mesh. Either set the 'materials' attribute or pass the argument 'materialIndexAttribute=\"\"' when calling 'addScene'" - assert mesh.materials.len == getMeshData[uint16](mesh, scenedata.materialIndexAttribute)[].len - for i, material in enumerate(mesh.materials): - let matIndex = scene.materialIndex(material) - if matIndex < 0: - raise newException(Exception, &"Required material '{material}' not available in scene (available are: {scene.materials.toSeq})") - updateMeshData[uint16](mesh, scenedata.materialIndexAttribute, uint32(i), uint16(matIndex)) + 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)) # create index buffer if necessary var indicesBufferSize = 0'u64 @@ -187,7 +202,8 @@ indexBufferOffset += size scenedata.drawables[mesh] = drawable - for material in scene.materials: + # upload textures + for material in scenedata.materials: for textureName, texture in material.textures.pairs: if not scenedata.textures.hasKey(textureName): scenedata.textures[textureName] = @[] @@ -195,7 +211,7 @@ # setup uniforms and samplers for subpass_i in 0 ..< renderer.renderPass.subpasses.len: - for pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.mitems: + for material, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs: var uniformBufferSize = 0'u64 for uniform in pipeline.uniforms: uniformBufferSize += uniform.size @@ -269,7 +285,7 @@ assert scene in renderer.scenedata for i in 0 ..< renderer.renderPass.subpasses.len: - for pipeline in renderer.renderPass.subpasses[i].pipelines.mitems: + 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: assert renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].vk.valid var offset = 0'u64 @@ -301,18 +317,19 @@ commandBuffer = commandBufferResult.get() commandBuffer.beginRenderCommands(renderer.renderPass, renderer.swapchain.currentFramebuffer()) + debug "Scene buffers:" + for (location, buffer) in renderer.scenedata[scene].vertexBuffers.pairs: + debug " ", location, ": ", buffer + debug " Index buffer: ", renderer.scenedata[scene].indexBuffer + for i in 0 ..< renderer.renderPass.subpasses.len: - for pipeline in renderer.renderPass.subpasses[i].pipelines.mitems: + 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) - debug "Scene buffers:" - for (location, buffer) in renderer.scenedata[scene].vertexBuffers.pairs: - debug " ", location, ": ", buffer - debug " Index buffer: ", renderer.scenedata[scene].indexBuffer - for drawable in renderer.scenedata[scene].drawables.values: - drawable.draw(commandBuffer, vertexBuffers=renderer.scenedata[scene].vertexBuffers, indexBuffer=renderer.scenedata[scene].indexBuffer) + if 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)
--- a/src/semicongine/resources/mesh.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Sat Aug 19 01:10:42 2023 +0700 @@ -1,5 +1,4 @@ import std/strutils -import std/enumerate import std/json import std/logging import std/tables @@ -9,7 +8,6 @@ import ../scene import ../mesh -import ../material import ../core import ./image @@ -92,7 +90,7 @@ of Float32: return Vec4F32 else: raise newException(Exception, &"Unsupported data type: {componentType} {theType}") -proc getBufferViewData(bufferView: JsonNode, mainBuffer: var seq[uint8], baseBufferOffset=0): seq[uint8] = +proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8], baseBufferOffset=0): seq[uint8] = assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported" result = newSeq[uint8](bufferView["byteLength"].getInt()) @@ -127,6 +125,98 @@ else: copyMem(dstPointer, addr mainBuffer[bufferOffset], length) +proc loadImage(root: JsonNode, imageIndex: int, mainBuffer: seq[uint8]): Image = + if root["images"][imageIndex].hasKey("uri"): + raise newException(Exception, "Unsupported feature: Load images from external files") + + let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()] + let imgData = newStringStream(cast[string](getBufferViewData(bufferView, mainBuffer))) + + let imageType = root["images"][imageIndex]["mimeType"].getStr() + case imageType + of "image/bmp": + result = readBMP(imgData) + of "image/png": + result = readPNG(imgData) + else: + raise newException(Exception, "Unsupported feature: Load image of type " & imageType) + +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()] + if sampler.hasKey("magFilter"): + result.sampler.magnification = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()] + if sampler.hasKey("minFilter"): + result.sampler.minification = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()] + if sampler.hasKey("wrapS"): + result.sampler.wrapModeS = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] + if sampler.hasKey("wrapT"): + result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] + + +proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: seq[uint8], materialIndex: uint16): Material = + result = Material(name: materialNode["name"].getStr(), index: materialIndex) + + let pbr = materialNode["pbrMetallicRoughness"] + + # color + result.constants["baseColorFactor"] = DataValue(thetype: Vec4F32) + if pbr.hasKey("baseColorFactor"): + setValue(result.constants["baseColorFactor"], newVec4f( + pbr["baseColorFactor"][0].getFloat(), + pbr["baseColorFactor"][1].getFloat(), + pbr["baseColorFactor"][2].getFloat(), + pbr["baseColorFactor"][3].getFloat(), + )) + else: + setValue(result.constants["baseColorFactor"], newVec4f(1, 1, 1, 1)) + + # pbr material constants + for factor in ["metallicFactor", "roughnessFactor"]: + result.constants[factor] = DataValue(thetype: Float32) + if pbr.hasKey(factor): + setValue(result.constants[factor], float32(pbr[factor].getFloat())) + else: + setValue(result.constants[factor], 0.5'f32) + + # pbr material textures + for texture in ["baseColorTexture", "metallicRoughnessTexture"]: + if pbr.hasKey(texture): + result.textures[texture] = loadTexture(root, pbr[texture]["index"].getInt(), mainBuffer) + 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.constants[texture & "Index"] = DataValue(thetype: UInt8) + setValue(result.constants[texture & "Index"], 0'u8) + + # generic material textures + for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]: + if materialNode.hasKey(texture): + result.textures[texture] = loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer) + 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.constants[texture & "Index"] = DataValue(thetype: UInt8) + setValue(result.constants[texture & "Index"], 0'u8) + + # emissiv color + result.constants["emissiveFactor"] = DataValue(thetype: Vec3F32) + if materialNode.hasKey("emissiveFactor"): + setValue(result.constants["emissiveFactor"], newVec3f( + materialNode["emissiveFactor"][0].getFloat(), + materialNode["emissiveFactor"][1].getFloat(), + materialNode["emissiveFactor"][2].getFloat(), + )) + else: + setValue(result.constants["emissiveFactor"], newVec3f(1'f32, 1'f32, 1'f32)) + + proc addPrimitive(mesh: var Mesh, 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") @@ -141,7 +231,10 @@ if primitiveNode.hasKey("material"): materialId = uint16(primitiveNode["material"].getInt()) mesh.appendMeshData("materialIndex", newSeqWith[uint8](int(vertexCount), materialId)) - mesh.materials.add newSeqWith[string](int(vertexCount), root["materials"][int(materialId)]["name"].getStr()) + 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") + mesh.material = material if primitiveNode.hasKey("indices"): assert mesh.indexType != None @@ -253,95 +346,6 @@ newScene(scenenode["name"].getStr(), rootEntity) -proc loadImage(root: JsonNode, imageIndex: int, mainBuffer: var seq[uint8]): Image = - if root["images"][imageIndex].hasKey("uri"): - raise newException(Exception, "Unsupported feature: Load images from external files") - - let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()] - let imgData = newStringStream(cast[string](getBufferViewData(bufferView, mainBuffer))) - - let imageType = root["images"][imageIndex]["mimeType"].getStr() - case imageType - of "image/bmp": - result = readBMP(imgData) - of "image/png": - result = readPNG(imgData) - else: - raise newException(Exception, "Unsupported feature: Load image of type " & imageType) - -proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: var 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()] - if sampler.hasKey("magFilter"): - result.sampler.magnification = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()] - if sampler.hasKey("minFilter"): - result.sampler.minification = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()] - if sampler.hasKey("wrapS"): - result.sampler.wrapModeS = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] - if sampler.hasKey("wrapT"): - result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()] - -proc loadMaterial(root: JsonNode, materialNode: JsonNode, mainBuffer: var seq[uint8], materialIndex: int): Material = - result = Material(name: materialNode["name"].getStr(), index: materialIndex) - - let pbr = materialNode["pbrMetallicRoughness"] - - # color - result.constants["baseColorFactor"] = DataValue(thetype: Vec4F32) - if pbr.hasKey("baseColorFactor"): - setValue(result.constants["baseColorFactor"], newVec4f( - pbr["baseColorFactor"][0].getFloat(), - pbr["baseColorFactor"][1].getFloat(), - pbr["baseColorFactor"][2].getFloat(), - pbr["baseColorFactor"][3].getFloat(), - )) - else: - setValue(result.constants["baseColorFactor"], newVec4f(1, 1, 1, 1)) - - # pbr material constants - for factor in ["metallicFactor", "roughnessFactor"]: - result.constants[factor] = DataValue(thetype: Float32) - if pbr.hasKey(factor): - setValue(result.constants[factor], float32(pbr[factor].getFloat())) - else: - setValue(result.constants[factor], 0.5'f32) - - # pbr material textures - for texture in ["baseColorTexture", "metallicRoughnessTexture"]: - if pbr.hasKey(texture): - result.textures[texture] = loadTexture(root, pbr[texture]["index"].getInt(), mainBuffer) - 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.constants[texture & "Index"] = DataValue(thetype: UInt8) - setValue(result.constants[texture & "Index"], 0'u8) - - # generic material textures - for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]: - if materialNode.hasKey(texture): - result.textures[texture] = loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer) - 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.constants[texture & "Index"] = DataValue(thetype: UInt8) - setValue(result.constants[texture & "Index"], 0'u8) - - # emissiv color - result.constants["emissiveFactor"] = DataValue(thetype: Vec3F32) - if materialNode.hasKey("emissiveFactor"): - setValue(result.constants["emissiveFactor"], newVec3f( - materialNode["emissiveFactor"][0].getFloat(), - materialNode["emissiveFactor"][1].getFloat(), - materialNode["emissiveFactor"][2].getFloat(), - )) - else: - setValue(result.constants["emissiveFactor"], newVec3f(1'f32, 1'f32, 1'f32)) proc readglTF*(stream: Stream): seq[Scene] = var @@ -373,9 +377,4 @@ debug "Loading mesh: ", data.structuredContent.pretty for scenedata in data.structuredContent["scenes"]: - var scene = data.structuredContent.loadScene(scenedata, data.binaryBufferData) - for i, materialNode in enumerate(data.structuredContent["materials"]): - let material = loadMaterial(data.structuredContent, materialNode, data.binaryBufferData, i) - scene.addMaterial material - - result.add scene + result.add data.structuredContent.loadScene(scenedata, data.binaryBufferData)
--- a/src/semicongine/scene.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/src/semicongine/scene.nim Sat Aug 19 01:10:42 2023 +0700 @@ -1,12 +1,10 @@ import std/strformat -import std/sequtils import std/strutils import std/tables import std/hashes import std/typetraits import ./core -import ./material import ./animation type @@ -14,7 +12,6 @@ name*: string root*: Entity shaderGlobals*: Table[string, DataList] - materials: OrderedTable[string, Material] transformAttribute*: string = "transform" materialIndexAttribute*: string = "materialIndex" @@ -103,28 +100,8 @@ func appendShaderGlobalArray*[T](scene: var Scene, name: string, value: seq[T]) = appendValues[T](scene.shaderGlobals[name], value) -func newScene*(name: string, root: Entity): Scene = - Scene(name: name, root: root) - -func addMaterial*(scene: var Scene, material: Material) = - assert not scene.materials.contains(material.name), &"Material with name '{material.name}' already exists in scene" - for name, value in material.constants.pairs: - scene.shaderGlobals[name] = newDataList(thetype=value.thetype) - - for name, value in material.constants.pairs: - scene.shaderGlobals[name].appendValue(value) - - scene.materials[material.name] = material - -func materialIndex*(scene: Scene, materialName: string): int = - for name in scene.materials.keys: - if name == materialName: - return result - inc result - return -1 - -func materials*(scene: Scene): auto = - scene.materials.values.toSeq +func newScene*(name: string, root: Entity, transformAttribute="transform", materialIndexAttribute="materialIndex"): Scene = + Scene(name: name, root: root, transformAttribute: transformAttribute, materialIndexAttribute: materialIndexAttribute) func hash*(scene: Scene): Hash = hash(scene.name)
--- a/src/semicongine/vulkan/pipeline.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/src/semicongine/vulkan/pipeline.nim Sat Aug 19 01:10:42 2023 +0700 @@ -18,12 +18,16 @@ shaderModules*: (ShaderModule, ShaderModule) descriptorSetLayout*: DescriptorSetLayout -func inputs*(pipeline: Pipeline): seq[ShaderAttribute] = pipeline.shaderConfiguration.inputs +func inputs*(pipeline: Pipeline): seq[ShaderAttribute] = + pipeline.shaderConfiguration.inputs func uniforms*(pipeline: Pipeline): seq[ShaderAttribute] = pipeline.shaderConfiguration.uniforms -proc setupDescriptors*(pipeline: var Pipeline, descriptorPool: DescriptorPool, buffers: seq[Buffer], textures: Table[string, seq[VulkanTexture]], inFlightFrames: int): seq[DescriptorSet] = +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] = 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 @@ -54,17 +58,18 @@ result.device = device result.shaderModules = device.createShaderModules(shaderConfiguration) + result.shaderConfiguration = shaderConfiguration var descriptors: seq[Descriptor] - if shaderConfiguration.uniforms.len > 0: + if result.shaderConfiguration.uniforms.len > 0: descriptors.add Descriptor( name: "Uniforms", thetype: Uniform, count: 1, stages: @[VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT], - size: shaderConfiguration.uniforms.size(), + size: result.shaderConfiguration.uniforms.size(), ) - for sampler in shaderConfiguration.samplers: + for sampler in result.shaderConfiguration.samplers: descriptors.add Descriptor( name: sampler.name, thetype: ImageSampler, @@ -93,7 +98,7 @@ var bindings: seq[VkVertexInputBindingDescription] attributes: seq[VkVertexInputAttributeDescription] - vertexInputInfo = shaderConfiguration.getVertexInputInfo(bindings, attributes) + vertexInputInfo = result.shaderConfiguration.getVertexInputInfo(bindings, attributes) inputAssembly = VkPipelineInputAssemblyStateCreateInfo( sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, topology: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
--- a/src/semicongine/vulkan/renderpass.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/src/semicongine/vulkan/renderpass.nim Sat Aug 19 01:10:42 2023 +0700 @@ -1,4 +1,5 @@ import std/options +import std/tables import ../core import ./device @@ -12,12 +13,9 @@ clearColor*: Vec4f pipelineBindPoint*: VkPipelineBindPoint flags: VkSubpassDescriptionFlags - inputs: seq[VkAttachmentReference] outputs: seq[VkAttachmentReference] - resolvers: seq[VkAttachmentReference] depthStencil: Option[VkAttachmentReference] - preserves: seq[uint32] - pipelines*: seq[Pipeline] + pipelines*: Table[string, Pipeline] RenderPass* = object vk*: VkRenderPass device*: Device @@ -39,14 +37,14 @@ subpassesList.add VkSubpassDescription( flags: subpass.flags, pipelineBindPoint: subpass.pipelineBindPoint, - inputAttachmentCount: uint32(subpass.inputs.len), - pInputAttachments: subpass.inputs.toCPointer, + inputAttachmentCount: 0, + pInputAttachments: nil, colorAttachmentCount: uint32(subpass.outputs.len), pColorAttachments: subpass.outputs.toCPointer, - pResolveAttachments: subpass.resolvers.toCPointer, + pResolveAttachments: nil, pDepthStencilAttachment: if subpass.depthStencil.isSome: addr(subpass.depthStencil.get) else: nil, - preserveAttachmentCount: uint32(subpass.preserves.len), - pPreserveAttachments: subpass.preserves.toCPointer, + preserveAttachmentCount: 0, + pPreserveAttachments: nil, ) var createInfo = VkRenderPassCreateInfo( @@ -64,19 +62,19 @@ proc simpleForwardRenderPass*( device: Device, - shaderConfiguration: ShaderConfiguration, + shaders: Table[string, ShaderConfiguration], inFlightFrames=2, - format=VK_FORMAT_UNDEFINED , clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]) ): RenderPass = + # TODO: check wether materials are compatible with the assigned shaders + {.warning: "Need to implement material -> shader compatability" .} + assert device.vk.valid - assert shaderConfiguration.outputs.len == 1 - var theformat = format - if theformat == VK_FORMAT_UNDEFINED: - theformat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat().format + for shaderconfig in shaders.values: + assert shaderconfig.outputs.len == 1 var attachments = @[VkAttachmentDescription( - format: theformat, + format: device.physicalDevice.getSurfaceFormats().filterSurfaceFormat().format, samples: VK_SAMPLE_COUNT_1_BIT, loadOp: VK_ATTACHMENT_LOAD_OP_CLEAR, storeOp: VK_ATTACHMENT_STORE_OP_STORE, @@ -102,7 +100,9 @@ dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT], )] result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies) - result.subpasses[0].pipelines.add device.createPipeline(result.vk, shaderConfiguration, inFlightFrames, 0) + for material, shaderconfig in shaders.pairs: + result.subpasses[0].pipelines[material] = device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0) + proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer) = assert commandBuffer.valid @@ -159,7 +159,7 @@ assert renderPass.vk.valid renderPass.device.vk.vkDestroyRenderPass(renderPass.vk, nil) renderPass.vk.reset - for subpass in renderPass.subpasses.mitems: - for pipeline in subpass.pipelines.mitems: + for i in 0 ..< renderPass.subpasses.len: + for pipeline in renderPass.subpasses[i].pipelines.mvalues: pipeline.destroy() renderPass.subpasses = @[]
--- a/tests/test_vulkan_wrapper.nim Tue Aug 15 23:51:37 2023 +0700 +++ b/tests/test_vulkan_wrapper.nim Sat Aug 19 01:10:42 2023 +0700 @@ -2,37 +2,64 @@ import semicongine + +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( + materialType: "my_material", + textures: { + "my_little_texture": Texture(image: Image(width: 5, height: 5, imagedata: @[ + R, R, R, R, R, + R, R, W, R, R, + R, W, W, W, R, + R, R, W, R, R, + R, R, R, R, R, + ]), sampler: sampler) + }.toTable + ) + proc scene_different_mesh_types(): Entity = result = newEntity("root", [], newEntity("triangle1", {"mesh": Component(newMesh( 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)], + material=mat, ))}), newEntity("triangle1b", {"mesh": Component(newMesh( positions=[newVec3f(0.0, -0.4), newVec3f(0.4, 0.4), newVec3f(-0.4, 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)], + material=mat, ))}), newEntity("triangle2a", {"mesh": Component(newMesh( 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]] + indices=[[0'u16, 2'u16, 1'u16]], + material=mat, ))}), 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]] + indices=[[0'u16, 2'u16, 1'u16]], + material=mat, ))}), 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 + autoResize=false, + material=mat, ))}), 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 + autoResize=false, + material=mat, ))}), ) for mesh in allComponentsOfType[Mesh](result): @@ -42,22 +69,26 @@ var mymesh1 = newMesh( positions=[newVec3f(0.0, -0.3), newVec3f(0.3, 0.3), newVec3f(-0.3, 0.3)], 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)], + material=mat, ) var mymesh2 = newMesh( 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)], + material=mat, ) var mymesh3 = newMesh( positions=[newVec3f(0.0, -0.6), newVec3f(0.6, 0.6), newVec3f(-0.6, 0.6)], 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, 1'u32, 2'u32]], - autoResize=false + autoResize=false, + material=mat, ) var mymesh4 = newMesh( positions=[newVec3f(0.0, -0.8), newVec3f(0.8, 0.8), newVec3f(-0.8, 0.8)], colors=[newVec4f(0.0, 0.0, 1.0, 1), newVec4f(0.0, 0.0, 1.0, 1), newVec4f(0.0, 0.0, 1.0, 1)], indices=[[0'u16, 1'u16, 2'u16]], - instanceCount=2 + instanceCount=2, + material=mat, ) mymesh1.setInstanceData("translate", @[newVec3f(0.3, 0.0)]) mymesh2.setInstanceData("translate", @[newVec3f(0.0, 0.3)]) @@ -69,14 +100,18 @@ var r = rect(color="ff0000") var t = tri(color="0000ff") var c = circle(color="00ff00") + t.material = mat + t.material = mat + c.material = mat r.setInstanceData("translate", @[newVec3f(0.5, -0.3)]) t.setInstanceData("translate", @[newVec3f(0.3, 0.3)]) c.setInstanceData("translate", @[newVec3f(-0.3, 0.1)]) - result = newEntity("root", {"mesh1": Component(t), "mesh1": Component(r), "mesh1": Component(c)}) + result = newEntity("root", {"mesh1": Component(t), "mesh2": Component(r), "mesh3": Component(c)}) proc scene_flag(): Entity = var r = rect(color="ff0000") + r.material = mat r.updateMeshData("color", @[newVec4f(0, 0), newVec4f(1, 0), newVec4f(1, 1), newVec4f(0, 1)]) result = newEntity("root", {"mesh": Component(r)}) @@ -98,35 +133,19 @@ vertexCode="""gl_Position = vec4(position + translate, 1.0); outcolor = color;""", fragmentCode="color = texture(my_little_texture, outcolor.xy) * 0.5 + outcolor * 0.5;", ) - var renderPass = engine.gpuDevice.simpleForwardRenderPass(shaderConfiguration) - engine.setRenderer(renderPass) + engine.setRenderer({"my_material": shaderConfiguration}.toTable) # INIT SCENES var scenes = [ - newScene("simple", scene_simple()), - newScene("different mesh types", scene_different_mesh_types()), - newScene("primitives", scene_primitives()), - newScene("flag", scene_flag()), + 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=""), ] - var sampler = DefaultSampler() - sampler.magnification = VK_FILTER_NEAREST - sampler.minification = VK_FILTER_NEAREST + for scene in scenes.mitems: scene.addShaderGlobal("time", 0.0'f32) - let (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8]) - scene.addMaterial(Material( - name: "my_material", - textures: { - "my_little_texture": Texture(image: Image(width: 5, height: 5, imagedata: @[ - R, R, R, R, R, - R, R, W, R, R, - R, W, W, W, R, - R, R, W, R, R, - R, R, R, R, R, - ]), sampler: sampler) - }.toTable - )) - engine.addScene(scene, vertexInput, samplers, transformAttribute="", materialIndexAttribute="") + engine.addScene(scene) # MAINLOOP echo "Setup successfull, start rendering"