Mercurial > games > semicongine
diff static_utils.nim @ 1161:dbca0528c714 compiletime-tests
add: more stuff
author | sam <sam@basx.dev> |
---|---|
date | Wed, 19 Jun 2024 13:50:18 +0700 |
parents | 836dc1eda5e3 |
children | 46fae89cffb0 |
line wrap: on
line diff
--- a/static_utils.nim Wed Jun 19 09:17:24 2024 +0700 +++ b/static_utils.nim Wed Jun 19 13:50:18 2024 +0700 @@ -1,12 +1,18 @@ import std/macros +import std/strformat import std/typetraits +import semicongine/core/utils +import semicongine/core/imagetypes import semicongine/core/vector import semicongine/core/matrix import semicongine/core/vulkanapi +import semicongine/vulkan/buffer template VertexAttribute* {.pragma.} template InstanceAttribute* {.pragma.} +template DescriptorAttribute* {.pragma.} + type SupportedGPUType* = float32 | float64 | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 | TVec2[int32] | TVec2[int64] | TVec3[int32] | TVec3[int64] | TVec4[int32] | TVec4[int64] | TVec2[uint32] | TVec2[uint64] | TVec3[uint32] | TVec3[uint64] | TVec4[uint32] | TVec4[uint64] | TVec2[float32] | TVec2[float64] | TVec3[float32] | TVec3[float64] | TVec4[float32] | TVec4[float64] | TMat2[float32] | TMat2[float64] | TMat23[float32] | TMat23[float64] | TMat32[float32] | TMat32[float64] | TMat3[float32] | TMat3[float64] | TMat34[float32] | TMat34[float64] | TMat43[float32] | TMat43[float64] | TMat4[float32] | TMat4[float64] @@ -61,15 +67,9 @@ {.error: "getElementType can only be used with seq or array".} genericParams(typeof(field)).get(0) -proc isVertexAttribute[T](value: T): bool {.compileTime.} = - hasCustomPragma(T, VertexAttribute) - -proc isInstanceAttribute[T](value: T): bool {.compileTime.} = - hasCustomPragma(T, InstanceAttribute) - -template ForAttributeFields*(inputData: typed, fieldname, valuename, isinstancename, body: untyped): untyped = +template ForVertexDataFields*(inputData: typed, fieldname, valuename, isinstancename, body: untyped): untyped = for theFieldname, value in fieldPairs(inputData): - when isVertexAttribute(value) or isInstanceAttribute(value): + when hasCustomPragma(value, VertexAttribute) or hasCustomPragma(value, InstanceAttribute): when not typeof(value) is seq: {.error: "field '" & theFieldname & "' needs to be a seq".} when not typeof(value) is SupportedGPUType: @@ -80,6 +80,29 @@ let `isinstancename` {.inject.} = value.isInstanceAttribute() body +template ForDescriptorFields*(inputData: typed, fieldname, valuename, typename, countname, body: untyped): untyped = + for theFieldname, value in fieldPairs(inputData): + when hasCustomPragma(value, DescriptorAttribute): + when not ( + typeof(value) is SupportedGPUType + or (typeof(value) is array and elementType(value) is SupportedGPUType) + or typeof(value) is Texture + ): + {.error: "field '" & theFieldname & "' needs to be a SupportedGPUType or an array of SupportedGPUType".} + block: + let `fieldname` {.inject.} = theFieldname + let `valuename` {.inject.} = default(getElementType(value)) + + # TODO + let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER + + when typeof(value) is array: + let `countname` {.inject.} = genericParams(typeof(value)).get(0) + else: + let `countname` {.inject.} = 1 + body + func NumberOfVertexInputAttributeDescriptors[T: SupportedGPUType](value: T): uint32 = when T is TMat2[float32] or T is TMat2[float64] or T is TMat23[float32] or T is TMat23[float64]: 2 @@ -107,42 +130,83 @@ return 1 type - Renderable = object - buffers: seq[VkBuffer] - offsets: seq[VkDeviceSize] + IndexType = enum + None, UInt8, UInt16, UInt32 + RenderBuffers = object + deviceBuffers: seq[Buffer] # for fast reads + hostVisibleBuffers: seq[Buffer] # for fast writes + Renderable[TMesh, TInstance] = object + vertexBuffers: seq[VkBuffer] + bufferOffsets: seq[VkDeviceSize] instanceCount: uint32 - case indexType: VkIndexType - of VK_INDEX_TYPE_NONE_KHR: + case indexType: IndexType + of None: vertexCount: uint32 - of VK_INDEX_TYPE_UINT8_EXT, VK_INDEX_TYPE_UINT16, VK_INDEX_TYPE_UINT32: + else: indexBuffer: VkBuffer indexCount: uint32 indexBufferOffset: VkDeviceSize - Pipeline = object + Pipeline[TShaderInputs] = object pipeline: VkPipeline layout: VkPipelineLayout descriptorSets: array[2, seq[VkDescriptorSet]] - ShaderSet[ShaderInputType, ShaderDescriptorType] = object + ShaderSet[TShaderInputs] = object vertexShader: VkShaderModule fragmentShader: VkShaderModule - # TODO: I think this needs more fields? +converter toVkIndexType(indexType: IndexType): VkIndexType = + case indexType: + of None: VK_INDEX_TYPE_NONE_KHR + of UInt8: VK_INDEX_TYPE_UINT8_EXT + of UInt16: VK_INDEX_TYPE_UINT16 + of UInt32: VK_INDEX_TYPE_UINT32 -proc CreatePipeline*[ShaderInputType, ShaderDescriptorType]( + +proc CreatePipeline*[TShaderInputs]( device: VkDevice, renderPass: VkRenderPass, - shaderSet: ShaderSet[ShaderInputType, ShaderDescriptorType], + shaderSet: ShaderSet[TShaderInputs], topology: VkPrimitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, polygonMode: VkPolygonMode = VK_POLYGON_MODE_FILL, cullMode: VkCullModeFlagBits = VK_CULL_MODE_BACK_BIT, frontFace: VkFrontFace = VK_FRONT_FACE_CLOCKWISE, -): Pipeline = +): Pipeline[TShaderInputs] = # assumptions/limitations: # - we are only using vertex and fragment shaders (2 stages) # - we only support one subpass # CONTINUE HERE, WITH PIPELINE LAYOUT!!!! - # Rely on ShaderDescriptorType - checkVkResult vkCreatePipelineLayout(device.vk, addr(pipelineLayoutInfo), nil, addr(result.layout)) + # Rely on TShaderInputs + + var layoutbindings: seq[VkDescriptorSetLayoutBinding] + let descriptors = [ + (VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), # more than 1 for arrays + (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1), + ] + var descriptorBindingNumber = 0'u32 + ForDescriptorFields(default(TShaderInputs), fieldname, value, descriptorCount): + layoutbindings.add VkDescriptorSetLayoutBinding( + binding: descriptorBindingNumber, + descriptorType: descriptorType, + descriptorCount: descriptorCount, + stageFlags: VK_SHADER_STAGE_ALL_GRAPHICS, + pImmutableSamplers: nil, + ) + inc descriptorBindingNumber + var layoutCreateInfo = VkDescriptorSetLayoutCreateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, + bindingCount: uint32(layoutbindings.len), + pBindings: layoutbindings.ToCPointer + ) + var descriptorSetLayout: VkDescriptorSetLayout + checkVkResult vkCreateDescriptorSetLayout(device.vk, addr(layoutCreateInfo), nil, addr(descriptorSetLayout)) + let pipelineLayoutInfo = VkPipelineLayoutCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + setLayoutCount: 1, + pSetLayouts: addr(descriptorSetLayout), + # pushConstantRangeCount: uint32(pushConstants.len), + # pPushConstantRanges: pushConstants.ToCPointer, + ) + checkVkResult vkCreatePipelineLayout(device, addr(pipelineLayoutInfo), nil, addr(result.layout)) let stages = [ VkPipelineShaderStageCreateInfo( @@ -163,7 +227,7 @@ attributes: var seq[VkVertexInputAttributeDescription] var inputBindingNumber = 0'u32 var inputLocationNumber = 0'u32 - ForAttributeFields(default(ShaderInputType), fieldname, value, isInstanceAttr): + ForVertexDataFields(default(TShaderInputs), fieldname, value, isInstanceAttr): bindings.add VkVertexInputBindingDescription( binding: inputBindingNumber, stride: sizeof(value).uint32, @@ -270,42 +334,118 @@ addr(result.pipeline) ) +proc CreateRenderable[TMesh, TInstance]( + mesh: TMesh, + instance: TInstance, + buffers: RenderBuffers, +): Renderable[TMesh, TInstance] = + result.indexType = None -proc Render*(renderable: Renderable, commandBuffer: VkCommandBuffer, pipeline: Pipeline, frameInFlight: int) = - assert 0 <= frameInFlight and frameInFlight < 2 +proc Bind(pipeline: Pipeline, commandBuffer: VkCommandBuffer, currentFrameInFlight: int) = commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline) commandBuffer.vkCmdBindDescriptorSets( VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.layout, 0, - pipeline.descriptorSets[frameInFlight].len, - pipeline.descriptorSets[frameInFlight], + pipeline.descriptorSets[currentFrameInFlight].len, + pipeline.descriptorSets[currentFrameInFlight], 0, nil, ) + +proc AssertCompatible(TShaderInputs, TMesh, TInstance, TGlobals: typedesc) = + # assert seq-fields of TMesh|TInstance == seq-fields of TShaderInputs + # assert normal fields of TMesh|Globals == normal fields of TShaderDescriptors + for inputName, inputValue in default(TShaderInputs).fieldPairs: + echo "checking shader input '" & inputName & "'" + var foundField = false + when hasCustomPragma(inputValue, VertexAttribute): + echo " is vertex attribute" + for meshName, meshValue in default(TMesh).fieldPairs: + when meshName == inputName: + assert foundField == false, "Shader input '" & TShaderInputs.name & "." & inputName & "' has been found more than once" + assert getElementType(meshValue) is typeof(inputValue), "Shader input " & TShaderInputs.name & "." & inputName & " is of type '" & typeof(inputValue).name & "' but mesh attribute is of type '" & getElementType(meshValue).name & "'" + foundField = true + assert foundField, "Shader input '" & TShaderInputs.name & "." & inputName & ": " & typeof(inputValue).name & "' not found in '" & TMesh.name & "'" + elif hasCustomPragma(inputValue, InstanceAttribute): + echo " is instance attribute" + for instanceName, instanceValue in default(TInstance).fieldPairs: + when instanceName == inputName: + assert foundField == false, "Shader input '" & TShaderInputs.name & "." & inputName & "' has been found more than once" + assert getElementType(instanceValue) is typeof(inputValue), "Shader input " & TShaderInputs.name & "." & inputName & " is of type '" & typeof(inputValue).name & "' but instance attribute is of type '" & getElementType(instanceValue).name & "'" + foundField = true + assert foundField, "Shader input '" & TShaderInputs.name & "." & inputName & ": " & typeof(inputValue).name & "' not found in '" & TInstance.name & "'" + elif hasCustomPragma(inputValue, DescriptorAttribute): + echo " is descriptor attribute" + for meshName, meshValue in default(TMesh).fieldPairs: + when meshName == inputName: + assert foundField == false, "Shader input '" & TShaderInputs.name & "." & inputName & "' has been found more than once" + assert typeof(meshValue) is typeof(inputValue), "Shader input " & TShaderInputs.name & "." & inputName & " is of type '" & typeof(inputValue).name & "' but mesh attribute is of type '" & getElementType(meshValue).name & "'" + foundField = true + for globalName, globalValue in default(TGlobals).fieldPairs: + when globalName == inputName: + assert foundField == false, "Shader input '" & TShaderInputs.name & "." & inputName & "' has been found more than once" + assert typeof(globalValue) is typeof(inputValue), "Shader input " & TShaderInputs.name & "." & inputName & " is of type '" & typeof(inputValue).name & "' but global attribute is of type '" & typeof(globalValue).name & "'" + foundField = true + assert foundField, "Shader input '" & TShaderInputs.name & "." & inputName & ": " & typeof(inputValue).name & "' not found in '" & TMesh.name & "|" & TGlobals.name & "'" + echo " found" + + +proc Render[TShaderInputs, TMesh, TInstance, TGlobals]( + pipeline: Pipeline[TShaderInputs], + renderable: Renderable[TMesh, TInstance], + globals: TGlobals, + commandBuffer: VkCommandBuffer, +) = + static: + AssertCompatible(TShaderInputs, TMesh, TInstance, TGlobals) commandBuffer.vkCmdBindVertexBuffers( firstBinding = 0'u32, - bindingCount = uint32(renderable.buffers.len), - pBuffers = renderable.buffers.ToCPointer(), - pOffsets = renderable.offsets.ToCPointer() + bindingCount = uint32(renderable.vertexBuffers.len), + pBuffers = renderable.vertexBuffers.ToCPointer(), + pOffsets = renderable.bufferOffsets.ToCPointer() ) - if renderable.indexType != VK_INDEX_TYPE_NONE_KHR: + if renderable.indexType != None: commandBuffer.vkCmdBindIndexBuffer( renderable.indexBuffer, renderable.indexBufferOffset, - IndexType, + renderable.indexType, ) commandBuffer.vkCmdDrawIndexed( - indexCount = drawable.indexCount, - instanceCount = drawable.instanceCount, + indexCount = renderable.indexCount, + instanceCount = renderable.instanceCount, firstIndex = 0, vertexOffset = 0, firstInstance = 0 ) else: commandBuffer.vkCmdDraw( - vertexCount = drawable.vertexCount, - instanceCount = drawable.instanceCount, + vertexCount = renderable.vertexCount, + instanceCount = renderable.instanceCount, firstVertex = 0, firstInstance = 0 ) + +when isMainModule: + type + MeshA = object + position: seq[Vec3f] + transparency: float + InstanceA = object + transform: seq[Mat4] + position: seq[Vec3f] + Globals = object + color: Vec4f + + ShaderInputsA = object + position {.VertexAttribute.}: Vec3f + transform {.InstanceAttribute.}: Mat4 + color {.DescriptorAttribute.}: Vec4f + + var p: Pipeline[ShaderInputsA] + var r: Renderable[MeshA, InstanceA] + var g: Globals + var s: ShaderSet[ShaderInputsA] + + var p1 = CreatePipeline(device = VkDevice(0), renderPass = VkRenderPass(0), shaderSet = s) + Render(p, r, g, VkCommandBuffer(0))