Mercurial > games > semicongine
changeset 329:69e18f69713b
add: scene/shader compatability check, fix collision code to work with new APIs
author | Sam <sam@basx.dev> |
---|---|
date | Tue, 29 Aug 2023 00:01:13 +0700 |
parents | 8d0ffcacc7e3 |
children | 04531bec3583 |
files | src/semicongine/collision.nim src/semicongine/core/gpu_data.nim src/semicongine/mesh.nim src/semicongine/renderer.nim src/semicongine/scene.nim src/semicongine/vulkan/pipeline.nim tests/test_collision.nim tests/test_vulkan_wrapper.nim |
diffstat | 8 files changed, 229 insertions(+), 165 deletions(-) [+] |
line wrap: on
line diff
--- a/src/semicongine/collision.nim Fri Aug 25 01:14:04 2023 +0700 +++ b/src/semicongine/collision.nim Tue Aug 29 00:01:13 2023 +0700 @@ -1,42 +1,44 @@ -import std/sequtils - import ./core const MAX_COLLISON_DETECTION_ITERATIONS = 20 const MAX_COLLISON_POINT_CALCULATION_ITERATIONS = 20 type - HitBox* = object - transform*: Mat4 - HitSphere* = object - transform*: Mat4 - radius*: float32 + ColliderType* = enum + Box, Sphere + Collider* = object + transform*: Mat4 = Unit4F32 + case theType*: ColliderType + of Box: discard + of Sphere: radius*: float32 func between(value, b1, b2: float32): bool = min(b1, b2) <= value and value <= max(b1, b2) -func contains*(hitbox: HitBox, x: Vec3f): bool = +func contains*(collider: Collider, x: Vec3f): bool = # from https://math.stackexchange.com/questions/1472049/check-if-a-point-is-inside-a-rectangular-shaped-area-3d - let - t = hitbox.transform - P1 = t * newVec3f(0, 0, 0) # origin - P2 = t * Z - P4 = t * X - P5 = t * Y - u = (P1 - P4).cross(P1 - P5) - v = (P1 - P2).cross(P1 - P5) - w = (P1 - P2).cross(P1 - P4) - uP1 = u.dot(P1) - uP2 = u.dot(P2) - vP1 = v.dot(P1) - vP4 = v.dot(P4) - wP1 = w.dot(P1) - wP5 = w.dot(P5) - ux = u.dot(x) - vx = v.dot(x) - wx = w.dot(x) - - result = ux.between(uP1, uP2) and vx.between(vP1, vP4) and wx.between(wP1, wP5) + case collider.theType: + of Box: + let + P1 = collider.transform * newVec3f(0, 0, 0) # origin + P2 = collider.transform * Z + P4 = collider.transform * X + P5 = collider.transform * Y + u = (P1 - P4).cross(P1 - P5) + v = (P1 - P2).cross(P1 - P5) + w = (P1 - P2).cross(P1 - P4) + uP1 = u.dot(P1) + uP2 = u.dot(P2) + vP1 = v.dot(P1) + vP4 = v.dot(P4) + wP1 = w.dot(P1) + wP5 = w.dot(P5) + ux = u.dot(x) + vx = v.dot(x) + wx = w.dot(x) + ux.between(uP1, uP2) and vx.between(vP1, vP4) and wx.between(wP1, wP5) + of Sphere: + (collider.transform * x).length < (collider.transform * newVec3f()).length # implementation of GJK, based on https://blog.winter.dev/2020/gjk-algorithm/ @@ -51,12 +53,7 @@ maxDist = dist result = p -func findFurthestPoint(hitsphere: HitSphere, direction: Vec3f): Vec3f = - let directionNormalizedToSphere = ((direction / direction.length) * hitsphere.radius) - return hitsphere.transform * directionNormalizedToSphere - -func findFurthestPoint(hitbox: HitBox, direction: Vec3f): Vec3f = - let transform = hitbox.transform +func findFurthestPoint(transform: Mat4, direction: Vec3f): Vec3f = return findFurthestPoint( [ transform * newVec3f(0, 0, 0), @@ -70,8 +67,15 @@ ], direction ) +func findFurthestPoint(collider: Collider, direction: Vec3f): Vec3f = + case collider.theType + of Sphere: + let directionNormalizedToSphere = ((direction / direction.length) * collider.radius) + collider.transform * directionNormalizedToSphere + of Box: + findFurthestPoint(collider.transform, direction) -func supportPoint[A, B](a: A, b: B, direction: Vec3f): Vec3f = +func supportPoint(a, b: Collider, direction: Vec3f): Vec3f = a.findFurthestPoint(direction) - b.findFurthestPoint(-direction) func sameDirection(direction: Vec3f, ao: Vec3f): bool = @@ -191,7 +195,7 @@ of 4: simplex.tetrahedron(direction) else: raise newException(Exception, "Error in simplex") -func collisionPoint3D[A, B](simplex: var seq[Vec3f], a: A, b: B): tuple[normal: Vec3f, penetrationDepth: float32] = +func collisionPoint3D(simplex: var seq[Vec3f], a, b: Collider): tuple[normal: Vec3f, penetrationDepth: float32] = var polytope = simplex faces = @[ @@ -263,7 +267,7 @@ result = (normal: minNormal, penetrationDepth: minDistance + 0.001'f32) -func collisionPoint2D*[A, B](polytopeIn: seq[Vec3f], a: A, b: B): tuple[normal: Vec2f, penetrationDepth: float32] = +func collisionPoint2D(polytopeIn: seq[Vec3f], a, b: Collider): tuple[normal: Vec2f, penetrationDepth: float32] = var polytope = polytopeIn minIndex = 0 @@ -302,7 +306,7 @@ result = (normal: minNormal, penetrationDepth: minDistance + 0.001'f32) -func intersects*[A, B](a: A, b: B): bool = +func intersects*(a, b: Collider): bool = var support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0.5786)) # just random initial vector simplex = newSeq[Vec3f]() @@ -321,7 +325,7 @@ direction[0] = 0.0001 inc n -func collision*[A, B](a: A, b: B): tuple[hasCollision: bool, normal: Vec3f, penetrationDepth: float32] = +func collision*(a, b: Collider): tuple[hasCollision: bool, normal: Vec3f, penetrationDepth: float32] = var support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0.5786)) # just random initial vector simplex = newSeq[Vec3f]() @@ -341,7 +345,7 @@ direction[0] = 0.0001 inc n -func collision2D*[A, B](a: A, b: B): tuple[hasCollision: bool, normal: Vec2f, penetrationDepth: float32] = +func collision2D*(a, b: Collider): tuple[hasCollision: bool, normal: Vec2f, penetrationDepth: float32] = var support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0)) # just random initial vector simplex = newSeq[Vec3f]() @@ -361,7 +365,7 @@ direction[0] = 0.0001 inc n -func calculateHitbox*(points: seq[Vec3f]): HitBox = +func calculateCollider*(points: seq[Vec3f], theType: ColliderType): Collider = var minX = high(float32) maxX = low(float32) @@ -369,6 +373,7 @@ maxY = low(float32) minZ = high(float32) maxZ = low(float32) + center: Vec3f for p in points: minX = min(minX, p.x) @@ -377,15 +382,17 @@ maxY = max(maxY, p.y) minZ = min(minZ, p.z) maxZ = max(maxz, p.z) + center = center + p + center = center / float32(points.len) let scaleX = (maxX - minX) scaleY = (maxY - minY) scaleZ = (maxZ - minZ) - HitBox(transform: translate(minX, minY, minZ) * scale(scaleX, scaleY, scaleZ)) + result = Collider(theType: theType, transform: translate(minX, minY, minZ) * scale(scaleX, scaleY, scaleZ)) -func calculateHitsphere*(points: seq[Vec3f]): HitSphere = - result = HitSphere() - for p in points: - result.radius = max(result.radius, p.length) + if theType == Sphere: + result.transform = translate(center) + for p in points: + result.radius = max(result.radius, (p - center).length)
--- a/src/semicongine/core/gpu_data.nim Fri Aug 25 01:14:04 2023 +0700 +++ b/src/semicongine/core/gpu_data.nim Tue Aug 29 00:01:13 2023 +0700 @@ -54,7 +54,7 @@ Mat4F64 Sampler2D DataValue* = object - case thetype*: DataType + case theType*: DataType of Float32: float32: float32 of Float64: float64: float64 of Int8: int8: int8 @@ -100,7 +100,7 @@ of Sampler2D: discard DataList* = object len*: int - case thetype*: DataType + case theType*: DataType of Float32: float32: ref seq[float32] of Float64: float64: ref seq[float64] of Int8: int8: ref seq[int8] @@ -148,14 +148,14 @@ PreferFastRead, PreferFastWrite ShaderAttribute* = object name*: string - thetype*: DataType + theType*: DataType arrayCount*: int perInstance*: bool noInterpolation: bool memoryPerformanceHint*: MemoryPerformanceHint func hash*(value: DataList): Hash = - case value.thetype + case value.theType of Float32: hash(cast[pointer](value.float32)) of Float64: hash(cast[pointer](value.float64)) of Int8: hash(cast[pointer](value.int8)) @@ -200,10 +200,10 @@ of Mat4F64: hash(cast[pointer](value.mat4f64)) of Sampler2D: raise newException(Exception, "hash not defined for Sampler2D") -func `==`*(a, b: DataList): bool = - if a.thetype != b.thetype: +func `==`*(a, b: DataList | DataValue): bool = + if a.theType != b.theType: return false - case a.thetype + case a.theType of Float32: return a.float32 == b.float32 of Float64: return a.float64 == b.float64 of Int8: return a.int8 == b.int8 @@ -258,15 +258,15 @@ if attr.perInstance == false: result.add attr -func numberOfVertexInputAttributeDescriptors*(thetype: DataType): int = - case thetype: +func numberOfVertexInputAttributeDescriptors*(theType: DataType): int = + case theType: of Mat2F32, Mat2F64, Mat23F32, Mat23F64: 2 of Mat32F32, Mat32F64, Mat3F32, Mat3F64, Mat34F32, Mat34F64: 3 of Mat43F32, Mat43F64, Mat4F32, Mat4F64: 4 else: 1 -func size*(thetype: DataType): int = - case thetype: +func size*(theType: DataType): int = + case theType: of Float32: 4 of Float64: 8 of Int8: 1 @@ -313,22 +313,22 @@ func size*(attribute: ShaderAttribute, perDescriptor=false): int = if perDescriptor: - attribute.thetype.size div attribute.thetype.numberOfVertexInputAttributeDescriptors + attribute.theType.size div attribute.theType.numberOfVertexInputAttributeDescriptors else: if attribute.arrayCount == 0: - attribute.thetype.size + attribute.theType.size else: - attribute.thetype.size * attribute.arrayCount + attribute.theType.size * attribute.arrayCount -func size*(thetype: seq[ShaderAttribute]): int = - for attribute in thetype: +func size*(theType: seq[ShaderAttribute]): int = + for attribute in theType: result += attribute.size func size*(value: DataValue): int = - value.thetype.size + value.theType.size func size*(value: DataList): int = - value.thetype.size * value.len + value.theType.size * value.len func getDataType*[T: GPUType|int|uint|float](): DataType = when T is float32: Float32 @@ -393,7 +393,7 @@ ): auto = ShaderAttribute( name: name, - thetype: getDataType[T](), + theType: getDataType[T](), perInstance: perInstance, arrayCount: arrayCount, noInterpolation: noInterpolation, @@ -504,12 +504,12 @@ else: {. error: "Virtual datatype has no values" .} func toGPUValue*[T: GPUType](value: T): DataValue = - result = DataValue(thetype: getDataType[T]()) + result = DataValue(theType: getDataType[T]()) result.setValue(value) -func newDataList*(thetype: DataType): DataList = - result = DataList(thetype: thetype) - case result.thetype +func newDataList*(theType: DataType): DataList = + result = DataList(theType: theType) + case result.theType of Float32: result.float32 = new seq[float32] of Float64: result.float64 = new seq[float64] of Int8: result.int8 = new seq[int8] @@ -615,8 +615,8 @@ else: {. error: "Virtual datatype has no values" .} func getRawData*(value: DataValue): (pointer, int) = - result[1] = value.thetype.size - case value.thetype + result[1] = value.theType.size + case value.theType of Float32: result[0] = addr value.float32 of Float64: result[0] = addr value.float64 of Int8: result[0] = addr value.int8 @@ -664,8 +664,8 @@ func getRawData*(value: DataList): (pointer, int) = if value.len == 0: return (nil, 0) - result[1] = value.thetype.size * value.len - case value.thetype + result[1] = value.theType.size * value.len + case value.theType of Float32: result[0] = addr value.float32[][0] of Float64: result[0] = addr value.float64[][0] of Int8: result[0] = addr value.int8[][0] @@ -712,7 +712,7 @@ func initData*(value: var DataList, len: int) = value.len = len - case value.thetype + case value.theType of Float32: value.float32[].setLen(len) of Float64: value.float64[].setLen(len) of Int8: value.int8[].setLen(len) @@ -861,9 +861,9 @@ else: {. error: "Virtual datatype has no values" .} func appendValues*(value: var DataList, data: DataList) = - assert value.thetype == data.thetype + assert value.theType == data.theType value.len += data.len - case value.thetype: + case value.theType: of Float32: value.float32[].add data.float32[] of Float64: value.float64[].add data.float64[] of Int8: value.int8[].add data.int8[] @@ -909,9 +909,9 @@ else: raise newException(Exception, &"Unsupported data type for GPU data:" ) func appendValue*(value: var DataList, data: DataValue) = - assert value.thetype == data.thetype + assert value.theType == data.theType, &"appendValue expected {value.theType} but got {data.theType}" value.len += 1 - case value.thetype: + case value.theType: of Float32: value.float32[].add data.float32 of Float64: value.float64[].add data.float64 of Int8: value.int8[].add data.int8 @@ -1053,11 +1053,11 @@ Mat4F64: VK_FORMAT_R64G64B64A64_SFLOAT, }.toTable -func getVkFormat*(thetype: DataType): VkFormat = - TYPEMAP[thetype] +func getVkFormat*(theType: DataType): VkFormat = + TYPEMAP[theType] # from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html -func nLocationSlots*(thetype: DataType): int = +func nLocationSlots*(theType: DataType): int = #[ single location: 16-bit scalar and vector types, and @@ -1066,7 +1066,7 @@ two locations 64-bit three- and four-component vectors ]# - case thetype: + case theType: of Float32: 1 of Float64: 1 of Int8: 1 @@ -1111,12 +1111,12 @@ of Mat4F64: 2 of Sampler2D: 1 -func glslType*(thetype: DataType): string = +func glslType*(theType: DataType): string = # todo: likely not correct as we would need to enable some # extensions somewhere (Vulkan/GLSL compiler?) to have # everything work as intended. Or maybe the GPU driver does # some automagic conversion stuf.. - case thetype: + case theType: of Float32: "float" of Float64: "double" of Int8, Int16, Int32, Int64: "int" @@ -1162,9 +1162,9 @@ for attribute in group: assert attribute.arrayCount == 0, "arrays not supported for shader vertex attributes" let flat = if attribute.noInterpolation: "flat " else: "" - result.add &"layout(location = {i}) {flat}in {attribute.thetype.glslType} {attribute.name};" - for j in 0 ..< attribute.thetype.numberOfVertexInputAttributeDescriptors: - i += attribute.thetype.nLocationSlots + result.add &"layout(location = {i}) {flat}in {attribute.theType.glslType} {attribute.name};" + for j in 0 ..< attribute.theType.numberOfVertexInputAttributeDescriptors: + i += attribute.theType.nLocationSlots func glslUniforms*(group: openArray[ShaderAttribute], blockName="Uniforms", binding: int): seq[string] = if group.len == 0: @@ -1175,7 +1175,7 @@ var arrayDecl = "" if attribute.arrayCount > 0: arrayDecl = &"[{attribute.arrayCount}]" - result.add(&" {attribute.thetype.glslType} {attribute.name}{arrayDecl};") + result.add(&" {attribute.theType.glslType} {attribute.name}{arrayDecl};") result.add(&"}} {blockName};") func glslSamplers*(group: openArray[ShaderAttribute], basebinding: int): seq[string] = @@ -1186,7 +1186,7 @@ var arrayDecl = "" if attribute.arrayCount > 0: arrayDecl = &"[{attribute.arrayCount}]" - result.add(&"layout(binding = {thebinding}) uniform {attribute.thetype.glslType} {attribute.name}{arrayDecl};") + result.add(&"layout(binding = {thebinding}) uniform {attribute.theType.glslType} {attribute.name}{arrayDecl};") inc thebinding func glslOutput*(group: openArray[ShaderAttribute]): seq[string] = @@ -1196,5 +1196,5 @@ for attribute in group: assert attribute.arrayCount == 0, "arrays not supported for outputs" let flat = if attribute.noInterpolation: "flat " else: "" - result.add &"layout(location = {i}) {flat}out {attribute.thetype.glslType} {attribute.name};" + result.add &"layout(location = {i}) {flat}out {attribute.theType.glslType} {attribute.name};" i += 1
--- a/src/semicongine/mesh.nim Fri Aug 25 01:14:04 2023 +0700 +++ b/src/semicongine/mesh.nim Tue Aug 29 00:01:13 2023 +0700 @@ -30,12 +30,17 @@ vertexData: Table[string, DataList] instanceData: Table[string, DataList] dirtyAttributes: seq[string] - Material* = ref object + Material* = object materialType*: string name*: string constants*: Table[string, DataValue] textures*: Table[string, Texture] +let EMPTY_MATERIAL = Material( + materialType: "EMPTY MATERIAL", + name: "empty material" +) + func `$`*(mesh: Mesh): string = &"Mesh(vertexCount: {mesh.vertexCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})" @@ -58,7 +63,7 @@ mesh.vertexAttributes & mesh.instanceAttributes func hash*(material: Material): Hash = - hash(cast[pointer](material)) + hash(material.name) func instanceCount*(mesh: Mesh): int = mesh.instanceTransforms.len @@ -114,7 +119,7 @@ uvs: openArray[Vec2f]=[], transform: Mat4=Unit4F32, instanceTransforms: openArray[Mat4]=[Unit4F32], - material: Material=nil, + material: Material=EMPTY_MATERIAL, autoResize=true, ): Mesh = assert colors.len == 0 or colors.len == positions.len @@ -164,7 +169,7 @@ uvs: openArray[Vec2f]=[], transform: Mat4=Unit4F32, instanceTransforms: openArray[Mat4]=[Unit4F32], - material: Material=nil, + material: Material=EMPTY_MATERIAL, ): Mesh = newMesh( positions=positions, @@ -347,4 +352,4 @@ func getCollisionPoints*(mesh: Mesh, positionAttribute="position"): seq[Vec3f] = for p in getAttribute[Vec3f](mesh, positionAttribute)[]: - result.add p + result.add mesh.transform * p
--- a/src/semicongine/renderer.nim Fri Aug 25 01:14:04 2023 +0700 +++ b/src/semicongine/renderer.nim Tue Aug 29 00:01:13 2023 +0700 @@ -46,6 +46,12 @@ func usesMaterialType(scene: Scene, materialType: string): bool = return scene.meshes.anyIt(it.material.materialType == materialType) +func getPipelineForMaterialtype(renderer: Renderer, materialType: string): Option[Pipeline] = + for i in 0 ..< renderer.renderPass.subpasses.len: + for pipelineMaterialType, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: + if pipelineMaterialType == materialType: + return some(pipeline) + 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 @@ -78,8 +84,65 @@ if scene.usesMaterialType(materialType): result.add pipeline.samplers +func materialCompatibleWithPipeline(scene: Scene, material: Material, pipeline: Pipeline): (bool, string) = + for uniform in pipeline.uniforms: + if scene.shaderGlobals.contains(uniform.name): + if scene.shaderGlobals[uniform.name].theType != uniform.theType: + return (false, &"shader uniform needs type {uniform.theType} but scene global is of type {scene.shaderGlobals[uniform.name].theType}") + else: + var foundMatch = false + for name, constant in material.constants.pairs: + if name == uniform.name and constant.theType == uniform.theType: + foundMatch = true + break + if not foundMatch: + return (false, "shader uniform '{uniform.name}' was not found in scene globals or scene materials") + for sampler in pipeline.samplers: + var foundMatch = false + for name, value in material.textures: + if name == sampler.name: + foundMatch = true + break + if not foundMatch: + return (false, "Required texture for shader sampler '{sampler.name}' was not found in scene materials") + + return (true, "") + +func meshCompatibleWithPipeline(scene: Scene, mesh: Mesh, pipeline: Pipeline): (bool, string) = + for input in pipeline.inputs: + if not (input.name in mesh.attributes): + return (false, &"Shader input '{input.name}' is not available for mesh '{mesh}'") + if input.theType != mesh.attributeType(input.name): + return (false, &"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 (false, &"Shader input '{input.name}' expects to be per instance, but mesh '{mesh}' has is not as instance attribute") + + return materialCompatibleWithPipeline(scene, mesh.material, pipeline) + +func checkSceneIntegrity(renderer: Renderer, scene: Scene) = + var foundRenderableObject = false + var shaderTypes: seq[string] + for i in 0 ..< renderer.renderPass.subpasses.len: + for materialType, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: + shaderTypes.add materialType + for mesh in scene.meshes: + if mesh.material.materialType == materialType: + foundRenderableObject = true + let (error, message) = scene.meshCompatibleWithPipeline(mesh, pipeline) + if error: + raise newException(Exception, &"Mesh '{mesh}' not compatible with assigned pipeline ({materialType}) because: {message}") + + if not foundRenderableObject: + var materialTypes: seq[string] + for mesh in scene.meshes: + if not materialTypes.contains(mesh.material.materialType): + materialTypes.add mesh.material.materialType + 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}") + proc setupDrawableBuffers*(renderer: var Renderer, scene: var Scene) = assert not (scene in renderer.scenedata) + renderer.checkSceneIntegrity(scene) + const VERTEX_ATTRIB_ALIGNMENT = 4 # used for buffer alignment let @@ -88,7 +151,7 @@ var scenedata = SceneData() for mesh in scene.meshes: - if mesh.material != nil and not scenedata.materials.contains(mesh.material): + if not scenedata.materials.contains(mesh.material): scenedata.materials.add mesh.material for textureName, texture in mesh.material.textures.pairs: if not scenedata.textures.hasKey(textureName): @@ -101,7 +164,6 @@ if inputAttr.name == TRANSFORMATTRIBUTE: mesh.initInstanceAttribute(inputAttr.name, inputAttr.thetype) elif inputAttr.name == MATERIALINDEXATTRIBUTE: - assert mesh.material != nil, "Missing material specification for mesh. Set material attribute on mesh" 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})") @@ -178,9 +240,10 @@ var offsets: Table[VkPipeline, seq[(string, MemoryPerformanceHint, int)]] for subpass_i in 0 ..< renderer.renderPass.subpasses.len: for materialType, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs: - 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)]) + if scene.usesMaterialType(materialType): + 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)]) # create drawables let indexed = mesh.indexType != MeshIndexType.None @@ -274,12 +337,19 @@ assert renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].vk.valid var offset = 0 for uniform in pipeline.uniforms: - if not scene.shaderGlobals.hasKey(uniform.name): - raise newException(Exception, &"Uniform '{uniform.name}' not found in scene shaderGlobals") - if uniform.thetype != scene.shaderGlobals[uniform.name].thetype: - raise newException(Exception, &"Uniform '{uniform.name}' has wrong type {uniform.thetype}, required is {scene.shaderGlobals[uniform.name].thetype}") - debug &"Update uniforms {uniform.name}" - let (pdata, size) = scene.shaderGlobals[uniform.name].getRawData() + var value = newDataList(thetype=uniform.thetype) + if scene.shaderGlobals.hasKey(uniform.name): + assert scene.shaderGlobals[uniform.name].thetype == value.thetype + value = scene.shaderGlobals[uniform.name] + else: + for mat in renderer.scenedata[scene].materials: + for name, materialConstant in mat.constants.pairs: + if uniform.name == name: + value.appendValue(materialConstant) + if value.len == 0: + raise newException(Exception, &"Uniform '{uniform.name}' not found in scene shaderGlobals or materials") + debug &"Update uniform {uniform.name}" + let (pdata, size) = value.getRawData() renderer.scenedata[scene].uniformBuffers[pipeline.vk][renderer.swapchain.currentInFlight].setData(pdata, size, offset) offset += size @@ -315,7 +385,7 @@ 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 != nil and scene.meshes[meshIndex].material.materialType == materialType: + if scene.meshes[meshIndex].material.materialType == 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/scene.nim Fri Aug 25 01:14:04 2023 +0700 +++ b/src/semicongine/scene.nim Tue Aug 29 00:01:13 2023 +0700 @@ -1,11 +1,7 @@ -import std/strformat -import std/strutils import std/tables import std/hashes -import std/typetraits import ./core -import ./animation import ./mesh type
--- a/src/semicongine/vulkan/pipeline.nim Fri Aug 25 01:14:04 2023 +0700 +++ b/src/semicongine/vulkan/pipeline.nim Tue Aug 29 00:01:13 2023 +0700 @@ -14,7 +14,7 @@ device*: Device vk*: VkPipeline layout*: VkPipelineLayout - shaderConfiguration: ShaderConfiguration + shaderConfiguration*: ShaderConfiguration shaderModules*: (ShaderModule, ShaderModule) descriptorSetLayout*: DescriptorSetLayout
--- a/tests/test_collision.nim Fri Aug 25 01:14:04 2023 +0700 +++ b/tests/test_collision.nim Tue Aug 29 00:01:13 2023 +0700 @@ -1,67 +1,54 @@ +import std/tables + import semicongine - proc main() = - var scene = newScene("main", root=newEntity("rect")) + var scene = Scene(name: "main") - var obj1 = newEntity("Obj1", { - "mesh": Component(rect(color="f00f")), - "hitbox": Component(HitBox(transform: translate3d(-0.5, -0.5, -0.5))) - }) - var obj2 = newEntity("Obj2", { - "mesh": Component(rect()), - "hitbox": Component(HitBox(transform: translate3d(-0.5, -0.5, -0.5))) - }) - var obj3 = newEntity("Obj3", { - "mesh": Component(circle(color="0f0f")), - "hitbox": Component(HitSphere(radius: 0.5)) - }) - - scene.root.add obj2 - scene.root.add obj1 - scene.root.add obj3 - obj1.transform = scale3d(0.8, 0.8) - obj3.transform = scale3d(0.1, 0.1) + scene.meshes.add rect(color="f00f") + scene.meshes.add rect() + scene.meshes.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) const - vertexInput = @[ - attr[Mat4]("transform", memoryPerformanceHint=PreferFastRead, perInstance=true), - attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead), - attr[Vec4f]("color", memoryPerformanceHint=PreferFastRead), - ] - intermediate = @[attr[Vec4f]("colorout"),] - uniforms = @[attr[Mat4]("perspective")] - fragOutput = @[attr[Vec4f]("fragcolor")] - (vertexCode, fragmentCode) = compileVertexFragmentShaderSet( - inputs=vertexInput, - intermediate=intermediate, - outputs=fragOutput, - uniforms=uniforms, + shaderConfiguration = createShaderConfiguration( + inputs=[ + attr[Mat4]("transform", memoryPerformanceHint=PreferFastRead, perInstance=true), + attr[Vec3f]("position", memoryPerformanceHint=PreferFastRead), + attr[Vec4f]("color", memoryPerformanceHint=PreferFastRead), + ], + intermediates=[attr[Vec4f]("colorout")], + uniforms=[attr[Mat4]("perspective")], + outputs=[attr[Vec4f]("fragcolor")], vertexCode="""gl_Position = vec4(position, 1.0) * (transform * Uniforms.perspective); colorout = color;""", fragmentCode="""fragcolor = colorout;""", ) var engine = initEngine("Test collisions") - engine.setRenderer(engine.gpuDevice.simpleForwardRenderPass(vertexCode, fragmentCode)) - engine.addScene(scene, vertexInput, @[], materialIndexAttribute="") - scene.addShaderGlobal("perspective", Unit4F32) + + engine.initRenderer({"": shaderConfiguration}.toTable) + engine.addScene(scene) 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])) - if engine.keyIsDown(A): obj1.transform = obj1.transform * translate3d(-0.001, 0, 0) - if engine.keyIsDown(D): obj1.transform = obj1.transform * translate3d( 0.001, 0, 0) - if engine.keyIsDown(W): obj1.transform = obj1.transform * translate3d( 0, -0.001, 0) - if engine.keyIsDown(S): obj1.transform = obj1.transform * translate3d( 0, 0.001, 0) - if engine.keyIsDown(Q): obj1.transform = obj1.transform * rotate3d(-0.001, Z) - if engine.keyIsDown(Key.E): obj1.transform = obj1.transform * rotate3d( 0.001, Z) + if engine.keyIsDown(A): scene.meshes[0].transform = scene.meshes[0].transform * translate(-0.001, 0, 0) + if engine.keyIsDown(D): scene.meshes[0].transform = scene.meshes[0].transform * translate( 0.001, 0, 0) + if engine.keyIsDown(W): scene.meshes[0].transform = scene.meshes[0].transform * translate( 0, -0.001, 0) + if engine.keyIsDown(S): scene.meshes[0].transform = scene.meshes[0].transform * translate( 0, 0.001, 0) + if engine.keyIsDown(Q): scene.meshes[0].transform = scene.meshes[0].transform * rotate(-0.001, Z) + if engine.keyIsDown(Key.E): scene.meshes[0].transform = scene.meshes[0].transform * rotate( 0.001, Z) - if engine.keyIsDown(Key.Z): obj2.transform = obj2.transform * rotate3d(-0.001, Z) - if engine.keyIsDown(Key.X): obj2.transform = obj2.transform * rotate3d( 0.001, Z) - if engine.keyIsDown(Key.C): obj2.transform = obj2.transform * translate3d(0, -0.001, 0) - if engine.keyIsDown(Key.V): obj2.transform = obj2.transform * translate3d(0, 0.001, 0) - echo intersects(obj1["hitbox", HitBox()], obj3["hitbox", HitSphere()]) + if engine.keyIsDown(Key.Z): scene.meshes[1].transform = scene.meshes[1].transform * rotate(-0.001, Z) + if engine.keyIsDown(Key.X): scene.meshes[1].transform = scene.meshes[1].transform * rotate( 0.001, Z) + if engine.keyIsDown(Key.C): scene.meshes[1].transform = scene.meshes[1].transform * translate(0, -0.001, 0) + if engine.keyIsDown(Key.V): scene.meshes[1].transform = scene.meshes[1].transform * translate(0, 0.001, 0) + let hitbox = Collider(theType: Box, transform: scene.meshes[0].transform * translate(-0.5, -0.5)) + let hitsphere = Collider(theType: Sphere, transform: scene.meshes[2].transform, radius: 0.5) + echo intersects(hitbox, hitsphere) engine.renderScene(scene) engine.destroy()
--- a/tests/test_vulkan_wrapper.nim Fri Aug 25 01:14:04 2023 +0700 +++ b/tests/test_vulkan_wrapper.nim Tue Aug 29 00:01:13 2023 +0700 @@ -13,7 +13,7 @@ (R, W) = ([255'u8, 0'u8, 0'u8, 255'u8], [255'u8, 255'u8, 255'u8, 255'u8]) mat = Material( name: "mat", - materialType: "my_material", + materialType: "textures_material", textures: { "my_little_texture": Texture(image: Image(width: 5, height: 5, imagedata: @[ R, R, R, R, R, @@ -26,7 +26,7 @@ ) mat2 = Material( name: "mat2", - materialType: "my_material", + materialType: "textures_material", textures: { "my_little_texture": Texture(image: Image(width: 5, height: 5, imagedata: @[ R, W, R, W, R, @@ -39,7 +39,7 @@ ) mat3 = Material( name: "mat3", - materialType: "my_special_material", + materialType: "plain", constants: { "color": toGPUValue(newVec4f(0, 1, 0, 1)) }.toTable @@ -192,8 +192,8 @@ fragmentCode="color = outcolor;", ) engine.initRenderer({ - "my_material": shaderConfiguration1, - "my_special_material": shaderConfiguration2, + "textures_material": shaderConfiguration1, + "plain": shaderConfiguration2, }.toTable) # INIT SCENES @@ -205,7 +205,6 @@ Scene(name: "multimaterial", meshes: scene_multi_material()), ] - scenes[4].addShaderGlobal("color", newVec4f(1, 0, 0, 1)) for scene in scenes.mitems: scene.addShaderGlobal("time", 0.0'f32) engine.addScene(scene)