Mercurial > games > semicongine
changeset 32:9edca5dc4e93
add: working implementation of uniforms
author | Sam <sam@basx.dev> |
---|---|
date | Sat, 14 Jan 2023 23:34:50 +0700 |
parents | 0996104ad066 |
children | 94c38e4b5782 |
files | examples/alotof_triangles.nim examples/hello_triangle.nim src/zamikongine/buffer.nim src/zamikongine/descriptor.nim src/zamikongine/engine.nim src/zamikongine/glsl_helpers.nim src/zamikongine/shader.nim src/zamikongine/thing.nim src/zamikongine/vertex.nim tests/test_matrix.nim |
diffstat | 10 files changed, 308 insertions(+), 126 deletions(-) [+] |
line wrap: on
line diff
--- a/examples/alotof_triangles.nim Sat Jan 14 14:15:50 2023 +0700 +++ b/examples/alotof_triangles.nim Sat Jan 14 23:34:50 2023 +0700 @@ -1,21 +1,26 @@ import std/times +import std/strutils import std/math import std/random +import std/enumerate import zamikongine/engine import zamikongine/math/vector import zamikongine/math/matrix import zamikongine/vertex +import zamikongine/descriptor import zamikongine/mesh import zamikongine/thing import zamikongine/shader type VertexDataA = object - position11: VertexAttribute[Vec2[float32]] - color22: VertexAttribute[Vec3[float32]] + position11: PositionAttribute[Vec2[float32]] + color22: ColorAttribute[Vec3[float32]] + Uniforms = object + dt: Descriptor[float32] -proc globalUpdate(engine: var Engine, dt: Duration) = +proc globalUpdate(engine: var Engine, dt: float32) = discard proc randomtransform(): Mat33[float32] = @@ -41,14 +46,14 @@ let randomcolor1 = Vec3([float32(rand(1)), float32(rand(1)), float32(rand(1))]) let transform1 = randomtransform() randommesh.vertexData = VertexDataA( - position11: VertexAttribute[Vec2[float32]]( + position11: PositionAttribute[Vec2[float32]]( data: @[ Vec2[float32](transform1 * baseTriangle[0]), Vec2[float32](transform1 * baseTriangle[1]), Vec2[float32](transform1 * baseTriangle[2]), ] ), - color22: VertexAttribute[Vec3[float32]]( + color22: ColorAttribute[Vec3[float32]]( data: @[randomcolor1, randomcolor1, randomcolor1] ) ) @@ -57,14 +62,14 @@ let transform2 = randomtransform() var randomindexedmesh = new IndexedMesh[VertexDataA, uint16] randomindexedmesh.vertexData = VertexDataA( - position11: VertexAttribute[Vec2[float32]]( + position11: PositionAttribute[Vec2[float32]]( data: @[ Vec2[float32](transform2 * baseTriangle[0]), Vec2[float32](transform2 * baseTriangle[1]), Vec2[float32](transform2 * baseTriangle[2]), ] ), - color22: VertexAttribute[Vec3[float32]]( + color22: ColorAttribute[Vec3[float32]]( data: @[randomcolor2, randomcolor2, randomcolor2] ) ) @@ -74,11 +79,20 @@ childthing.parts.add randomindexedmesh scene.children.add childthing - var pipeline = setupPipeline[VertexDataA, float32, float32, uint16]( + const vertexShader = generateVertexShaderCode[VertexDataA, Uniforms]() + const fragmentShader = generateFragmentShaderCode[VertexDataA]() + static: + echo "--------------" + for (i, line) in enumerate(vertexShader.splitLines()): + echo $(i + 1) & " " & line + echo "--------------" + echo fragmentShader + echo "--------------" + var pipeline = setupPipeline[VertexDataA, float32, uint16]( myengine, scene, - generateVertexShaderCode[VertexDataA]("main", "position11", "color22"), - generateFragmentShaderCode[VertexDataA]("main"), + vertexShader, + fragmentShader ) myengine.run(pipeline, globalUpdate) pipeline.trash()
--- a/examples/hello_triangle.nim Sat Jan 14 14:15:50 2023 +0700 +++ b/examples/hello_triangle.nim Sat Jan 14 23:34:50 2023 +0700 @@ -1,8 +1,12 @@ import std/times +import std/strutils +import std/enumerate import zamikongine/engine import zamikongine/math/vector +import zamikongine/math/matrix import zamikongine/vertex +import zamikongine/descriptor import zamikongine/mesh import zamikongine/thing import zamikongine/shader @@ -11,16 +15,27 @@ type # define type of vertex VertexDataA = object - position: VertexAttribute[Vec2[float32]] - color: VertexAttribute[Vec3[float32]] - UniformType = float32 + position: PositionAttribute[Vec2[float32]] + color: ColorAttribute[Vec3[float32]] + Uniforms = object + mat: Descriptor[Mat44[float32]] + dt: Descriptor[float32] + +var pipeline: RenderPipeline[VertexDataA, Uniforms] -proc globalUpdate(engine: var Engine, dt: Duration) = - # var t = float32(dt.inNanoseconds) / 1_000_000_000'f32 - # for buffer in engine.vulkan.uniformBuffers: - # buffer.updateData(t) - - echo dt +var pos = 0'f32; +var uniforms = Uniforms( + mat: Descriptor[Mat44[float32]](value: Unit44f32), + dt: Descriptor[float32](value: 0'f32), +) +var scaledir = 1'f32 +proc globalUpdate(engine: var Engine, dt: float32) = + uniforms.mat.value = uniforms.mat.value * scale3d(1 + scaledir * dt, 1 + scaledir * dt, 0'f32) + if uniforms.mat.value[0, 0] > 2'f32 or uniforms.mat.value[0, 0] < 0.5'f32: + scaledir = - scaledir + for buffer in pipeline.uniformBuffers: + buffer.updateData(uniforms) + echo uniforms.mat.value # vertex data (types must match the above VertexAttributes) const @@ -41,8 +56,8 @@ # build a mesh var trianglemesh = new Mesh[VertexDataA] trianglemesh.vertexData = VertexDataA( - position: VertexAttribute[Vec2[float32]](data: triangle_pos), - color: VertexAttribute[Vec3[float32]](data: triangle_color), + position: PositionAttribute[Vec2[float32]](data: triangle_pos), + color: ColorAttribute[Vec3[float32]](data: triangle_color), ) # build a single-object scene graph var triangle = new Thing @@ -50,11 +65,26 @@ triangle.parts.add trianglemesh # upload data, prepare shaders, etc - var pipeline = setupPipeline[VertexDataA, UniformType, float32, uint16]( + const vertexShader = generateVertexShaderCode[VertexDataA, Uniforms]( + # have 1 at: + # [2][0] [0][3] + # "out_position = vec4(in_position[0] + uniforms.mat[0][0], in_position[1] + uniforms.mat[0][0], 0, 1);" + "out_position = uniforms.mat * vec4(in_position, 0, 1);" + # "out_position = vec4(in_position, 0, 1);" + ) + const fragmentShader = generateFragmentShaderCode[VertexDataA]() + static: + echo "--------------" + for (i, line) in enumerate(vertexShader.splitLines()): + echo $(i + 1) & " " & line + echo "--------------" + echo fragmentShader + echo "--------------" + pipeline = setupPipeline[VertexDataA, Uniforms, uint16]( myengine, triangle, - generateVertexShaderCode[VertexDataA]("main", "position", "color"), - generateFragmentShaderCode[VertexDataA]("main"), + vertexShader, + fragmentShader ) # show something myengine.run(pipeline, globalUpdate)
--- a/src/zamikongine/buffer.nim Sat Jan 14 14:15:50 2023 +0700 +++ b/src/zamikongine/buffer.nim Sat Jan 14 23:34:50 2023 +0700 @@ -84,7 +84,7 @@ body vkUnmapMemory(buffer.device, buffer.memory) -# note: does not work with seq +# note: does not work with seq, because of sizeof proc updateData*[T](buffer: Buffer, data: var T) = if buffer.persistentMapping: copyMem(buffer.mapped, addr(data), sizeof(T))
--- a/src/zamikongine/descriptor.nim Sat Jan 14 14:15:50 2023 +0700 +++ b/src/zamikongine/descriptor.nim Sat Jan 14 23:34:50 2023 +0700 @@ -1,9 +1,17 @@ +import std/strutils +import std/unicode +import std/strformat +import std/typetraits + import ./vulkan import ./vulkan_helpers import ./math/vector import ./math/matrix import ./buffer +import ./glsl_helpers +# TODO: check for alignment in uniform blocks +# type DescriptorType = SomeNumber|Vec|Mat Descriptor*[T:DescriptorType] = object @@ -37,3 +45,20 @@ persistentMapping=true, ) result[i] = buffer + +template getDescriptorType*(v: Descriptor): auto = get(genericParams(typeof(v)), 0) + +func generateGLSLUniformDeclarations*[Uniforms](binding: int = 0): string {.compileTime.} = + var stmtList: seq[string] + + let uniformTypeName = name(Uniforms).toUpper() + let uniformInstanceName = name(Uniforms).toLower() + stmtList.add(&"layout(binding = {binding}) uniform {uniformTypeName} {{") + for fieldname, value in Uniforms().fieldPairs: + when typeof(value) is Descriptor: + let glsltype = getGLSLType[getDescriptorType(value)]() + let n = fieldname + stmtList.add(&" {glsltype} {n};") + stmtList.add(&"}} {uniformInstanceName};") + + return stmtList.join("\n")
--- a/src/zamikongine/engine.nim Sat Jan 14 14:15:50 2023 +0700 +++ b/src/zamikongine/engine.nim Sat Jan 14 23:34:50 2023 +0700 @@ -24,6 +24,10 @@ const VULKAN_VERSION = VK_MAKE_API_VERSION(0'u32, 1'u32, 2'u32, 0'u32) +const ENGINE_NAME = "zamkongine" +const ENGINE_VERSION = "0.1" +const BUILD_VERSION = ENGINE_VERSION & '-' & gorge("git log -1 --format=format:'%H'") +echo "Engine: " & ENGINE_NAME & " " & BUILD_VERSION type Device = object @@ -37,15 +41,17 @@ swapchain: VkSwapchainKHR images: seq[VkImage] imageviews: seq[VkImageView] - RenderPipeline[T] = object + RenderPipeline*[VertexType, Uniforms] = object device*: VkDevice - shaders*: seq[ShaderProgram[T]] + shaders*: seq[ShaderProgram[VertexType, Uniforms]] layout*: VkPipelineLayout pipeline*: VkPipeline - uniformLayout*: VkDescriptorSetLayout vertexBuffers*: seq[(seq[Buffer], uint32)] indexedVertexBuffers*: seq[(seq[Buffer], Buffer, uint32, VkIndexType)] + descriptorSetLayout*: VkDescriptorSetLayout uniformBuffers*: array[MAX_FRAMES_IN_FLIGHT, Buffer] + descriptorPool*: VkDescriptorPool + descriptors: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSet] QueueFamily = object properties*: VkQueueFamilyProperties hasSurfaceSupport*: bool @@ -250,11 +256,11 @@ ) checkVkResult device.vkCreateRenderPass(addr(renderPassCreateInfo), nil, addr(result)) -proc initRenderPipeline[VertextType, T](device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[T] = +proc initRenderPipeline[VertexType, Uniforms](device: VkDevice, frameDimension: VkExtent2D, renderPass: VkRenderPass, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, Uniforms] = # load shaders result.device = device - result.shaders.add(initShaderProgram[T](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader)) - result.shaders.add(initShaderProgram[T](device, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader)) + result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_VERTEX_BIT, vertexShader)) + result.shaders.add(initShaderProgram[VertexType, Uniforms](device, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader)) var # define which parts can be dynamic (pipeline is fixed after setup) @@ -264,8 +270,8 @@ dynamicStateCount: uint32(dynamicStates.len), pDynamicStates: addr(dynamicStates[0]), ) - vertexbindings = generateInputVertexBinding[VertextType]() - attributebindings = generateInputAttributeBinding[VertextType]() + vertexbindings = generateInputVertexBinding[VertexType]() + attributebindings = generateInputAttributeBinding[VertexType]() # define input data format vertexInputInfo = VkPipelineVertexInputStateCreateInfo( @@ -336,13 +342,13 @@ blendConstants: [0.0'f, 0.0'f, 0.0'f, 0.0'f], ) - result.uniformLayout = device.createUniformDescriptorLayout(VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), 0) + result.descriptorSetLayout = device.createUniformDescriptorLayout(VkShaderStageFlags(VK_SHADER_STAGE_VERTEX_BIT), 0) var # "globals" that go into the shader, uniforms etc. pipelineLayoutInfo = VkPipelineLayoutCreateInfo( sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, setLayoutCount: 1, - pSetLayouts: addr(result.uniformLayout), + pSetLayouts: addr(result.descriptorSetLayout), pushConstantRangeCount: 0, pPushConstantRanges: nil, ) @@ -495,9 +501,9 @@ ) = result.vulkan.device.device.setupSyncPrimitives() -proc setupPipeline*[VertexType, UniformType, T: object, IndexType: uint16|uint32](engine: var Engine, scenedata: ref Thing, vertexShader, fragmentShader: static string): RenderPipeline[T] = +proc setupPipeline*[VertexType, UniformType: object, IndexType: uint16|uint32](engine: var Engine, scenedata: ref Thing, vertexShader, fragmentShader: static string): RenderPipeline[VertexType, UniformType] = engine.currentscenedata = scenedata - result = initRenderPipeline[VertexType, T]( + result = initRenderPipeline[VertexType, UniformType]( engine.vulkan.device.device, engine.vulkan.frameDimension, engine.vulkan.renderPass, @@ -510,7 +516,7 @@ allmeshes.add(mesh[]) if allmeshes.len > 0: var ubermesh = createUberMesh(allmeshes) - result.vertexBuffers.add createVertexBuffers(ubermesh, engine.vulkan.device.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue) + result.vertexBuffers.add createVertexBuffers(ubermesh, result.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue) # vertex buffers with indexes var allindexedmeshes: seq[IndexedMesh[VertexType, IndexType]] @@ -518,18 +524,62 @@ allindexedmeshes.add(mesh[]) if allindexedmeshes.len > 0: var indexedubermesh = createUberMesh(allindexedmeshes) - result.indexedVertexBuffers.add createIndexedVertexBuffers(indexedubermesh, engine.vulkan.device.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue) + result.indexedVertexBuffers.add createIndexedVertexBuffers(indexedubermesh, result.device, engine.vulkan.device.physicalDevice.device, engine.vulkan.commandPool, engine.vulkan.device.graphicsQueue) # uniform buffers result.uniformBuffers = createUniformBuffers[MAX_FRAMES_IN_FLIGHT, UniformType]( - engine.vulkan.device.device, + result.device, engine.vulkan.device.physicalDevice.device ) + var + poolSize = VkDescriptorPoolSize( + `type`: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + descriptorCount: uint32(MAX_FRAMES_IN_FLIGHT), + ) + poolInfo = VkDescriptorPoolCreateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + poolSizeCount: 1, + pPoolSizes: addr(poolSize), + maxSets: uint32(MAX_FRAMES_IN_FLIGHT), + ) + checkVkResult vkCreateDescriptorPool(result.device, addr(poolInfo), nil, addr(result.descriptorPool)) -proc runPipeline(commandBuffer: VkCommandBuffer, pipeline: RenderPipeline) = + var layouts: array[MAX_FRAMES_IN_FLIGHT, VkDescriptorSetLayout] + for i in 0 ..< MAX_FRAMES_IN_FLIGHT: + layouts[i] = result.descriptorSetLayout + var allocInfo = VkDescriptorSetAllocateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + descriptorPool: result.descriptorPool, + descriptorSetCount: uint32(MAX_FRAMES_IN_FLIGHT), + pSetLayouts: addr(layouts[0]), + ) + + checkVkResult vkAllocateDescriptorSets(result.device, addr(allocInfo), addr(result.descriptors[0])) + + var bufferInfos: array[MAX_FRAMES_IN_FLIGHT, array[1, VkDescriptorBufferInfo]] # because we use only one Uniform atm + for i in 0 ..< MAX_FRAMES_IN_FLIGHT: + bufferInfos[i][0] = VkDescriptorBufferInfo( + buffer: result.uniformBuffers[i].vkBuffer, + offset: VkDeviceSize(0), + range: VkDeviceSize(sizeof(UniformType)), + ) + var descriptorWrite = VkWriteDescriptorSet( + sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + dstSet: result.descriptors[i], + dstBinding: 0, + dstArrayElement: 0, + descriptorType: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + descriptorCount: 1, + pBufferInfo: cast[ptr ptr VkDescriptorBufferInfo](addr(bufferInfos[i][0])), + ) + vkUpdateDescriptorSets(result.device, 1, addr(descriptorWrite), 0, nil) + + +proc runPipeline(commandBuffer: VkCommandBuffer, pipeline: var RenderPipeline, currentFrame: int) = vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline) + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, 1, addr(pipeline.descriptors[currentFrame]), 0, nil) for (vertexBufferSet, vertexCount) in pipeline.vertexBuffers: var vertexBuffers: seq[VkBuffer] @@ -550,10 +600,10 @@ offsets.add VkDeviceSize(0) vkCmdBindVertexBuffers(commandBuffer, firstBinding=0'u32, bindingCount=2'u32, pBuffers=addr(vertexBuffers[0]), pOffsets=addr(offsets[0])) - vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType); - vkCmdDrawIndexed(commandBuffer, indicesCount, 1, 0, 0, 0); + vkCmdBindIndexBuffer(commandBuffer, indexBuffer.vkBuffer, VkDeviceSize(0), indexType) + vkCmdDrawIndexed(commandBuffer, indicesCount, 1, 0, 0, 0) -proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: RenderPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D) = +proc recordCommandBuffer(renderPass: VkRenderPass, pipeline: var RenderPipeline, commandBuffer: VkCommandBuffer, framebuffer: VkFramebuffer, frameDimension: VkExtent2D, currentFrame: int) = var beginInfo = VkCommandBufferBeginInfo( sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, @@ -588,11 +638,11 @@ vkCmdBeginRenderPass(commandBuffer, addr(renderPassInfo), VK_SUBPASS_CONTENTS_INLINE) vkCmdSetViewport(commandBuffer, firstViewport=0, viewportCount=1, addr(viewport)) vkCmdSetScissor(commandBuffer, firstScissor=0, scissorCount=1, addr(scissor)) - runPipeline(commandBuffer, pipeline) + runPipeline(commandBuffer, pipeline, currentFrame) vkCmdEndRenderPass(commandBuffer) checkVkResult vkEndCommandBuffer(commandBuffer) -proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: RenderPipeline) = +proc drawFrame(window: NativeWindow, vulkan: var Vulkan, currentFrame: int, resized: bool, pipeline: var RenderPipeline) = checkVkResult vkWaitForFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame]), VK_TRUE, high(uint64)) var bufferImageIndex: uint32 let nextImageResult = vkAcquireNextImageKHR( @@ -611,7 +661,7 @@ checkVkResult vkResetFences(vulkan.device.device, 1, addr(vulkan.inFlightFences[currentFrame])) checkVkResult vkResetCommandBuffer(vulkan.commandBuffers[currentFrame], VkCommandBufferResetFlags(0)) - vulkan.renderPass.recordCommandBuffer(pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension) + vulkan.renderPass.recordCommandBuffer(pipeline, vulkan.commandBuffers[currentFrame], vulkan.framebuffers[bufferImageIndex], vulkan.frameDimension, currentFrame) var waitSemaphores = [vulkan.imageAvailableSemaphores[currentFrame]] waitStages = [VkPipelineStageFlags(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)] @@ -644,7 +694,7 @@ (vulkan.swapchain, vulkan.framebuffers) = vulkan.recreateSwapchain() -proc run*(engine: var Engine, pipeline: RenderPipeline, globalUpdate: proc(engine: var Engine, dt: Duration)) = +proc run*(engine: var Engine, pipeline: var RenderPipeline, globalUpdate: proc(engine: var Engine, dt: float32)) = var killed = false currentFrame = 0 @@ -670,7 +720,7 @@ # game logic update let now = getTime() - dt = now - lastUpdate + dt = float32(float64((now - lastUpdate).inNanoseconds) / 1_000_000_000'f64) lastUpdate = now engine.globalUpdate(dt) for entity in allEntities(engine.currentscenedata): @@ -679,11 +729,12 @@ # submit frame for drawing engine.window.drawFrame(engine.vulkan, currentFrame, resized, pipeline) resized = false - currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT; + currentFrame = (currentFrame + 1) mod MAX_FRAMES_IN_FLIGHT checkVkResult vkDeviceWaitIdle(engine.vulkan.device.device) proc trash*(pipeline: var RenderPipeline) = - vkDestroyDescriptorSetLayout(pipeline.device, pipeline.uniformLayout, nil); + vkDestroyDescriptorPool(pipeline.device, pipeline.descriptorPool, nil) + vkDestroyDescriptorSetLayout(pipeline.device, pipeline.descriptorSetLayout, nil) vkDestroyPipeline(pipeline.device, pipeline.pipeline, nil) vkDestroyPipelineLayout(pipeline.device, pipeline.layout, nil) for shader in pipeline.shaders:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/zamikongine/glsl_helpers.nim Sat Jan 14 23:34:50 2023 +0700 @@ -0,0 +1,67 @@ +import ./math/vector +import ./math/matrix + +func getGLSLType*[T](): 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.. + when T is uint8: "uint" + elif T is int8: "int" + elif T is uint16: "uint" + elif T is int16: "int" + elif T is uint32: "uint" + elif T is int32: "int" + elif T is uint64: "uint" + elif T is int64: "int" + elif T is float32: "float" + elif T is float64: "double" + + elif T is Vec2[uint8]: "uvec2" + elif T is Vec2[int8]: "ivec2" + elif T is Vec2[uint16]: "uvec2" + elif T is Vec2[int16]: "ivec2" + elif T is Vec2[uint32]: "uvec2" + elif T is Vec2[int32]: "ivec2" + elif T is Vec2[uint64]: "uvec2" + elif T is Vec2[int64]: "ivec2" + elif T is Vec2[float32]: "vec2" + elif T is Vec2[float64]: "dvec2" + + elif T is Vec3[uint8]: "uvec3" + elif T is Vec3[int8]: "ivec3" + elif T is Vec3[uint16]: "uvec3" + elif T is Vec3[int16]: "ivec3" + elif T is Vec3[uint32]: "uvec3" + elif T is Vec3[int32]: "ivec3" + elif T is Vec3[uint64]: "uvec3" + elif T is Vec3[int64]: "ivec3" + elif T is Vec3[float32]: "vec3" + elif T is Vec3[float64]: "dvec3" + + elif T is Vec4[uint8]: "uvec4" + elif T is Vec4[int8]: "ivec4" + elif T is Vec4[uint16]: "uvec4" + elif T is Vec4[int16]: "ivec4" + elif T is Vec4[uint32]: "uvec4" + elif T is Vec4[int32]: "ivec4" + elif T is Vec4[uint64]: "uvec4" + elif T is Vec4[int64]: "ivec4" + elif T is Vec4[float32]: "vec4" + elif T is Vec4[float64]: "dvec4" + + elif T is Mat22[float32]: "mat2" + elif T is Mat23[float32]: "mat32" + elif T is Mat32[float32]: "mat23" + elif T is Mat33[float32]: "mat3" + elif T is Mat34[float32]: "mat43" + elif T is Mat43[float32]: "mat34" + elif T is Mat44[float32]: "mat4" + + elif T is Mat22[float64]: "dmat2" + elif T is Mat23[float64]: "dmat32" + elif T is Mat32[float64]: "dmat23" + elif T is Mat33[float64]: "dmat3" + elif T is Mat34[float64]: "dmat43" + elif T is Mat43[float64]: "dmat34" + elif T is Mat44[float64]: "dmat4"
--- a/src/zamikongine/shader.nim Sat Jan 14 14:15:50 2023 +0700 +++ b/src/zamikongine/shader.nim Sat Jan 14 23:34:50 2023 +0700 @@ -5,14 +5,16 @@ import ./vulkan_helpers +import ./glsl_helpers import ./vulkan import ./vertex +import ./descriptor import ./math/vector type AllowedUniformType = SomeNumber|Vec UniformSlot *[T:AllowedUniformType] = object - ShaderProgram*[Uniforms] = object + ShaderProgram*[VertexType, Uniforms] = object entryPoint*: string programType*: VkShaderStageFlagBits shader*: VkPipelineShaderStageCreateInfo @@ -58,7 +60,7 @@ ) i += 4 -proc initShaderProgram*[T](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string="main"): ShaderProgram[T] = +proc initShaderProgram*[VertexType, Uniforms](device: VkDevice, programType: static VkShaderStageFlagBits, shader: static string, entryPoint: static string="main"): ShaderProgram[VertexType, Uniforms] = result.entryPoint = entryPoint result.programType = programType @@ -79,31 +81,63 @@ pName: cstring(result.entryPoint), # entry point for shader ) -func generateVertexShaderCode*[VertexType](entryPoint, positionAttrName, colorAttrName: static string): string {.compileTime.} = +func generateVertexShaderCode*[VertexType, Uniforms]( + shaderBody: static string = "", + entryPoint: static string = "main", + glslVersion: static string = "450" +): string {.compileTime.} = var lines: seq[string] - lines.add "#version 450" - # lines.add "layout(binding = 0) uniform UniformBufferObject { float dt; } ubo;" - lines.add generateGLSLDeclarations[VertexType]() + lines.add "#version " & glslVersion + lines.add "layout(row_major) uniform;" + lines.add generateGLSLUniformDeclarations[Uniforms]() + lines.add generateGLSLVertexDeclarations[VertexType]() lines.add "layout(location = 0) out vec3 fragColor;" lines.add "void " & entryPoint & "() {" + var hasPosition = 0 + var hasColor = 0 for name, value in VertexType().fieldPairs: - when typeof(value) is VertexAttribute and name == positionAttrName: - # lines.add " vec2 tmp = " & name & " * ubo.dt;" - lines.add " vec2 tmp = " & name & ";" - lines.add " gl_Position = vec4(tmp, 0.0, 1.0);" - when typeof(value) is VertexAttribute and name == colorAttrName: - lines.add " fragColor = " & name & ";" + when typeof(value) is PositionAttribute: + let glsltype = getGLSLType[getAttributeType(value)]() + lines.add &" {glsltype} in_position = " & name & ";" + if getAttributeType(value) is Vec2: + lines.add " vec4 out_position = vec4(in_position, 0.0, 1.0);" + elif getAttributeType(value) is Vec3: + lines.add " vec4 out_position = vec4(in_position, 1.0);" + elif getAttributeType(value) is Vec4: + lines.add " vec4 out_position = in_position;" + hasPosition += 1 + when typeof(value) is ColorAttribute: + let glsltype = getGLSLType[getAttributeType(value)]() + lines.add &" {glsltype} in_color = " & name & ";" + lines.add &" {glsltype} out_color = in_color;"; + hasColor += 1 + + lines.add shaderBody + lines.add " gl_Position = out_position;" + lines.add " fragColor = out_color;" lines.add "}" + if hasPosition != 1: + raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type PositionAttribute (has {hasPosition})") + if hasColor != 1: + raise newException(Exception, fmt"VertexType needs to have exactly one attribute of type ColorAttribute (has {hasColor})") return lines.join("\n") -func generateFragmentShaderCode*[VertexType](entryPoint: static string): string {.compileTime.} = +func generateFragmentShaderCode*[VertexType]( + shaderBody: static string = "", + entryPoint: static string = "main", + glslVersion: static string = "450" +): string {.compileTime.} = var lines: seq[string] - lines.add "#version 450" + lines.add "#version " & glslVersion + lines.add "layout(row_major) uniform;" lines.add "layout(location = 0) in vec3 fragColor;" lines.add "layout(location = 0) out vec4 outColor;" lines.add "void " & entryPoint & "() {" - lines.add " outColor = vec4(fragColor, 1.0);" + lines.add " vec3 in_color = fragColor;" + lines.add " vec3 out_color = in_color;" + lines.add shaderBody + lines.add " outColor = vec4(out_color, 1.0);" lines.add "}" return lines.join("\n")
--- a/src/zamikongine/thing.nim Sat Jan 14 14:15:50 2023 +0700 +++ b/src/zamikongine/thing.nim Sat Jan 14 23:34:50 2023 +0700 @@ -10,7 +10,7 @@ children*: seq[ref Thing] parts*: seq[ref Part] -method update*(thing: ref Thing, dt: Duration) {.base.} = discard +method update*(thing: ref Thing, dt: float32) {.base.} = discard iterator partsOfType*[T: ref Part](root: ref Thing): T = var queue = @[root]
--- a/src/zamikongine/vertex.nim Sat Jan 14 14:15:50 2023 +0700 +++ b/src/zamikongine/vertex.nim Sat Jan 14 23:34:50 2023 +0700 @@ -4,17 +4,26 @@ import std/typetraits import ./math/vector +import ./math/matrix import ./vulkan +import ./glsl_helpers type VertexAttributeType = SomeNumber|Vec - VertexAttribute*[T:VertexAttributeType] = object + AttributePurpose* = enum + Unknown, Position Color + GenericAttribute*[T:VertexAttributeType] = object + data*: seq[T] + PositionAttribute*[T:VertexAttributeType] = object data*: seq[T] + ColorAttribute*[T:VertexAttributeType] = object + data*: seq[T] + VertexAttribute* = GenericAttribute|PositionAttribute|ColorAttribute -template rawAttributeType(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0) +template getAttributeType*(v: VertexAttribute): auto = get(genericParams(typeof(v)), 0) func datasize*(attribute: VertexAttribute): uint64 = - uint64(sizeof(rawAttributeType(attribute))) * uint64(attribute.data.len) + uint64(sizeof(getAttributeType(attribute))) * uint64(attribute.data.len) # from https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap15.html func nLocationSlots[T: VertexAttributeType](): int = @@ -66,54 +75,6 @@ elif T is Vec4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT elif T is Vec4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT -func getGLSLType[T: VertexAttributeType](): 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.. - when T is uint8: "uint" - elif T is int8: "int" - elif T is uint16: "uint" - elif T is int16: "int" - elif T is uint32: "uint" - elif T is int32: "int" - elif T is uint64: "uint" - elif T is int64: "int" - elif T is float32: "float" - elif T is float64: "double" - - elif T is Vec2[uint8]: "uvec2" - elif T is Vec2[int8]: "ivec2" - elif T is Vec2[uint16]: "uvec2" - elif T is Vec2[int16]: "ivec2" - elif T is Vec2[uint32]: "uvec2" - elif T is Vec2[int32]: "ivec2" - elif T is Vec2[uint64]: "uvec2" - elif T is Vec2[int64]: "ivec2" - elif T is Vec2[float32]: "vec2" - elif T is Vec2[float64]: "dvec2" - - elif T is Vec3[uint8]: "uvec3" - elif T is Vec3[int8]: "ivec3" - elif T is Vec3[uint16]: "uvec3" - elif T is Vec3[int16]: "ivec3" - elif T is Vec3[uint32]: "uvec3" - elif T is Vec3[int32]: "ivec3" - elif T is Vec3[uint64]: "uvec3" - elif T is Vec3[int64]: "ivec3" - elif T is Vec3[float32]: "vec3" - elif T is Vec3[float64]: "dvec3" - - elif T is Vec4[uint8]: "uvec4" - elif T is Vec4[int8]: "ivec4" - elif T is Vec4[uint16]: "uvec4" - elif T is Vec4[int16]: "ivec4" - elif T is Vec4[uint32]: "uvec4" - elif T is Vec4[int32]: "ivec4" - elif T is Vec4[uint64]: "uvec4" - elif T is Vec4[int64]: "ivec4" - elif T is Vec4[float32]: "vec4" - elif T is Vec4[float64]: "dvec4" func VertexCount*[T](t: T): uint32 = @@ -124,15 +85,15 @@ else: assert result == uint32(value.data.len) -func generateGLSLDeclarations*[T](): string = +func generateGLSLVertexDeclarations*[T](): string = var stmtList: seq[string] var i = 0 for name, value in T().fieldPairs: when typeof(value) is VertexAttribute: - let glsltype = getGLSLType[rawAttributeType(value)]() + let glsltype = getGLSLType[getAttributeType(value)]() let n = name stmtList.add(&"layout(location = {i}) in {glsltype} {n};") - i += nLocationSlots[rawAttributeType(value)]() + i += nLocationSlots[getAttributeType(value)]() return stmtList.join("\n") @@ -144,7 +105,7 @@ result.add( VkVertexInputBindingDescription( binding: uint32(binding), - stride: uint32(sizeof(rawAttributeType(value))), + stride: uint32(sizeof(getAttributeType(value))), inputRate: VK_VERTEX_INPUT_RATE_VERTEX, # VK_VERTEX_INPUT_RATE_INSTANCE for instances ) ) @@ -160,9 +121,9 @@ VkVertexInputAttributeDescription( binding: uint32(binding), location: uint32(location), - format: getVkFormat[rawAttributeType(value)](), + format: getVkFormat[getAttributeType(value)](), offset: 0, ) ) - location += nLocationSlots[rawAttributeType(value)]() + location += nLocationSlots[getAttributeType(value)]() binding += 1