changeset 1159:e7cbb13999e4 compiletime-tests

add: changes and static tools
author sam <sam@basx.dev>
date Mon, 17 Jun 2024 22:21:59 +0700
parents f32359ffd882
children 836dc1eda5e3
files semicongine/mesh.nim semicongine/renderer.nim static_utils.nim test2.nim
diffstat 4 files changed, 323 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/mesh.nim	Sat Jun 15 21:02:21 2024 +0700
+++ b/semicongine/mesh.nim	Mon Jun 17 22:21:59 2024 +0700
@@ -18,7 +18,6 @@
 type
   MeshIndexType* = enum
     None
-    Tiny  # up to 2^8 vertices # TODO: need to check and enable support for this
     Small # up to 2^16 vertices
     Big   # up to 2^32 vertices
   MeshObject* = object
@@ -26,7 +25,6 @@
     vertexCount*: int
     case indexType*: MeshIndexType
       of None: discard
-      of Tiny: tinyIndices*: seq[array[3, uint8]]
       of Small: smallIndices*: seq[array[3, uint16]]
       of Big: bigIndices*: seq[array[3, uint32]]
     material*: MaterialData
@@ -65,7 +63,6 @@
   (
     case mesh.indexType
     of None: 0
-    of Tiny: mesh.tinyIndices.len
     of Small: mesh.smallIndices.len
     of Big: mesh.bigIndices.len
   ) * 3
@@ -93,7 +90,6 @@
 converter ToVulkan*(indexType: MeshIndexType): VkIndexType =
   case indexType:
     of None: VK_INDEX_TYPE_NONE_KHR
-    of Tiny: VK_INDEX_TYPE_UINT8_EXT
     of Small: VK_INDEX_TYPE_UINT16
     of Big: VK_INDEX_TYPE_UINT32
 
@@ -154,9 +150,7 @@
   var indexType = None
   if indices.len > 0:
     indexType = Big
-    if autoResize and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support
-      indexType = Tiny
-    elif autoResize and uint32(positions.len) < uint32(high(uint16)):
+    if autoResize and uint32(positions.len) < uint32(high(uint16)):
       indexType = Small
 
   result = Mesh(
@@ -178,10 +172,7 @@
     assert int(i[2]) < result[].vertexCount
 
   # cast index values to appropiate type
-  if result[].indexType == Tiny and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support
-    for i, tri in enumerate(indices):
-      result[].tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])]
-  elif result[].indexType == Small and uint32(positions.len) < uint32(high(uint16)):
+  if result[].indexType == Small and uint32(positions.len) < uint32(high(uint16)):
     for i, tri in enumerate(indices):
       result[].smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])]
   elif result[].indexType == Big:
@@ -228,7 +219,6 @@
 func IndexSize*(mesh: MeshObject): uint64 =
   case mesh.indexType
     of None: 0'u64
-    of Tiny: uint64(mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0)))
     of Small: uint64(mesh.smallIndices.len * sizeof(get(genericParams(typeof(mesh.smallIndices)), 0)))
     of Big: uint64(mesh.bigIndices.len * sizeof(get(genericParams(typeof(mesh.bigIndices)), 0)))
 
@@ -241,7 +231,6 @@
 func GetRawIndexData*(mesh: MeshObject): (pointer, uint64) =
   case mesh.indexType:
     of None: raise newException(Exception, "Trying to get index data for non-indexed mesh")
-    of Tiny: rawData(mesh.tinyIndices)
     of Small: rawData(mesh.smallIndices)
     of Big: rawData(mesh.bigIndices)
 
@@ -352,7 +341,6 @@
 proc AppendIndicesData*(mesh: var MeshObject, v1, v2, v3: int) =
   case mesh.indexType
   of None: raise newException(Exception, "Mesh does not support indexed data")
-  of Tiny: mesh.tinyIndices.add([uint8(v1), uint8(v2), uint8(v3)])
   of Small: mesh.smallIndices.add([uint16(v1), uint16(v2), uint16(v3)])
   of Big: mesh.bigIndices.add([uint32(v1), uint32(v2), uint32(v3)])
 
@@ -424,13 +412,6 @@
     result.instanceData[attribute] = datalist.Copy()
   var i = 0
   case mesh.indexType
-  of Tiny:
-    for indices in mesh.tinyIndices:
-      for attribute, value in mesh.vertexData.pairs:
-        result.vertexData[attribute].AppendFrom(i, mesh.vertexData[attribute], int(indices[0]))
-        result.vertexData[attribute].AppendFrom(i + 1, mesh.vertexData[attribute], int(indices[1]))
-        result.vertexData[attribute].AppendFrom(i + 2, mesh.vertexData[attribute], int(indices[2]))
-      i += 3
   of Small:
     for indices in mesh.smallIndices:
       for attribute, value in mesh.vertexData.pairs:
@@ -586,10 +567,6 @@
   case a.indexType:
     of None:
       discard
-    of Tiny:
-      let offset = uint8(originalOffset)
-      for i in b.tinyIndices:
-        a.tinyIndices.add [i[0] + offset, i[1] + offset, i[2] + offset]
     of Small:
       let offset = uint16(originalOffset)
       for i in b.smallIndices:
--- a/semicongine/renderer.nim	Sat Jun 15 21:02:21 2024 +0700
+++ b/semicongine/renderer.nim	Mon Jun 17 22:21:59 2024 +0700
@@ -125,7 +125,6 @@
     if mesh[].indexType != MeshIndexType.None:
       let indexAlignment = case mesh[].indexType
         of MeshIndexType.None: 0'u64
-        of Tiny: 1'u64
         of Small: 2'u64
         of Big: 4'u64
       # index value alignment required by Vulkan
@@ -205,7 +204,6 @@
     if indexed:
       let indexAlignment = case mesh.indexType
         of MeshIndexType.None: 0'u64
-        of Tiny: 1'u64
         of Small: 2'u64
         of Big: 4'u64
       # index value alignment required by Vulkan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static_utils.nim	Mon Jun 17 22:21:59 2024 +0700
@@ -0,0 +1,311 @@
+import std/macros
+import std/typetraits
+
+import semicongine/core/vector
+import semicongine/core/matrix
+import semicongine/core/vulkanapi
+
+template VertexAttribute* {.pragma.}
+template InstanceAttribute* {.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]
+
+func VkType[T: SupportedGPUType](value: T): VkFormat =
+  when T is float32: VK_FORMAT_R32_SFLOAT
+  elif T is float64: VK_FORMAT_R64_SFLOAT
+  elif T is int8: VK_FORMAT_R8_SINT
+  elif T is int16: VK_FORMAT_R16_SINT
+  elif T is int32: VK_FORMAT_R32_SINT
+  elif T is int64: VK_FORMAT_R64_SINT
+  elif T is uint8: VK_FORMAT_R8_UINT
+  elif T is uint16: VK_FORMAT_R16_UINT
+  elif T is uint32: VK_FORMAT_R32_UINT
+  elif T is uint64: VK_FORMAT_R64_UINT
+  elif T is TVec2[int32]: VK_FORMAT_R32G32_SINT
+  elif T is TVec2[int64]: VK_FORMAT_R64G64_SINT
+  elif T is TVec3[int32]: VK_FORMAT_R32G32B32_SINT
+  elif T is TVec3[int64]: VK_FORMAT_R64G64B64_SINT
+  elif T is TVec4[int32]: VK_FORMAT_R32G32B32A32_SINT
+  elif T is TVec4[int64]: VK_FORMAT_R64G64B64A64_SINT
+  elif T is TVec2[uint32]: VK_FORMAT_R32G32_UINT
+  elif T is TVec2[uint64]: VK_FORMAT_R64G64_UINT
+  elif T is TVec3[uint32]: VK_FORMAT_R32G32B32_UINT
+  elif T is TVec3[uint64]: VK_FORMAT_R64G64B64_UINT
+  elif T is TVec4[uint32]: VK_FORMAT_R32G32B32A32_UINT
+  elif T is TVec4[uint64]: VK_FORMAT_R64G64B64A64_UINT
+  elif T is TVec2[float32]: VK_FORMAT_R32G32_SFLOAT
+  elif T is TVec2[float64]: VK_FORMAT_R64G64_SFLOAT
+  elif T is TVec3[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is TVec3[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is TVec4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
+  elif T is TVec4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
+  elif T is Mat2[float32]: VK_FORMAT_R32G32_SFLOAT
+  elif T is Mat2[float64]: VK_FORMAT_R64G64_SFLOAT
+  elif T is Mat23[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is Mat23[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is Mat32[float32]: VK_FORMAT_R32G32_SFLOAT
+  elif T is Mat32[float64]: VK_FORMAT_R64G64_SFLOAT
+  elif T is Mat3[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is Mat3[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is Mat34[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
+  elif T is Mat34[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
+  elif T is Mat43[float32]: VK_FORMAT_R32G32B32_SFLOAT
+  elif T is Mat43[float64]: VK_FORMAT_R64G64B64_SFLOAT
+  elif T is Mat4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT
+  elif T is Mat4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT
+  else: {.error: "Unsupported data type on GPU".}
+
+template getElementType(field: typed): untyped =
+  when not (typeof(field) is seq or typeof(field) is array):
+    {.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 =
+  for theFieldname, value in fieldPairs(inputData):
+    when isVertexAttribute(value) or isInstanceAttribute(value):
+      when not typeof(value) is seq:
+        {.error: "field '" & theFieldname & "' needs to be a seq".}
+      when not typeof(value) is SupportedGPUType:
+        {.error: "field '" & theFieldname & "' is not a supported GPU type".}
+      block:
+        let `fieldname` {.inject.} = theFieldname
+        let `valuename` {.inject.} = default(getElementType(value))
+        let `isinstancename` {.inject.} = value.isInstanceAttribute()
+        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
+  elif T is TMat32[float32] or T is TMat32[float64] or T is TMat3[float32] or T is TMat3[float64] or T is TMat34[float32] or T is TMat34[float64]:
+    3
+  elif T is TMat43[float32] or T is TMat43[float64] or T is TMat4[float32] or T is TMat4[float64]:
+    4
+  else:
+    1
+
+func NLocationSlots[T: SupportedGPUType](value: T): uint32 =
+  #[
+  single location:
+    16-bit scalar and vector types, and
+    32-bit scalar and vector types, and
+    64-bit scalar and 2-component vector types.
+  two locations
+    64-bit three- and four-component vectors
+  ]#
+  when typeof(value) is TVec3 and sizeof(getElementType(value)) == 8:
+    return 2
+  elif typeof(value) is TVec4 and sizeof(getElementType(value)) == 8:
+    return 2
+  else:
+    return 1
+
+
+type
+  Renderable[IndexType: static VkIndexType] = object
+    buffers: seq[VkBuffer]
+    offsets: seq[VkDeviceSize]
+    instanceCount: uint32
+    when IndexType == VK_INDEX_TYPE_NONE_KHR:
+      vertexCount: uint32
+    else:
+      indexBuffer: VkBuffer
+      indexCount: uint32
+      indexBufferOffset: VkDeviceSize
+  Pipeline = object
+    pipeline: VkPipeline
+    layout: VkPipelineLayout
+    descriptorSets: array[2, seq[VkDescriptorSet]]
+  ShaderSet[ShaderInputType, ShaderDescriptorType] = object
+    vertexShader: VkShaderModule
+    fragmentShader: VkShaderModule
+    # TODO: I think this needs more fields?
+
+proc CreatePipeline*[ShaderInputType, ShaderDescriptorType](
+  device: VkDevice,
+  renderPass: VkRenderPass,
+  shaderSet: ShaderSet[ShaderInputType, ShaderDescriptorType],
+  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 =
+  # 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))
+
+  let stages = [
+    VkPipelineShaderStageCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+      stage: VK_SHADER_STAGE_VERTEX_BIT,
+      module: shaderSet.vertexShader,
+      pName: "main",
+    ),
+    VkPipelineShaderStageCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+      stage: VK_SHADER_STAGE_FRAGMENT_BIT,
+      module: shaderSet.fragmentShader,
+      pName: "main",
+    ),
+  ]
+  let
+    bindings: var seq[VkVertexInputBindingDescription]
+    attributes: var seq[VkVertexInputAttributeDescription]
+  var inputBindingNumber = 0'u32
+  var inputLocationNumber = 0'u32
+  ForAttributeFields(default(ShaderInputType), fieldname, value, isInstanceAttr):
+    bindings.add VkVertexInputBindingDescription(
+      binding: inputBindingNumber,
+      stride: sizeof(value).uint32,
+      inputRate: if isInstanceAttr: VK_VERTEX_INPUT_RATE_INSTANCE else: VK_VERTEX_INPUT_RATE_VERTEX,
+    )
+    # allows to submit larger data structures like Mat44, for most other types will be 1
+    let perDescriptorSize = sizeof(value).uint32 div NumberOfVertexInputAttributeDescriptors(value)
+    for i in 0'u32 ..< NumberOfVertexInputAttributeDescriptors(value):
+      attributes.add VkVertexInputAttributeDescription(
+        binding: inputBindingNumber,
+        inputLocationNumber: inputLocationNumber,
+        format: VkType(value),
+        offset: i * perDescriptorSize,
+      )
+      inputLocationNumber += NLocationSlots(value)
+    inc inputBindingNumber
+
+  let
+    vertexInputInfo = VkPipelineVertexInputStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
+      vertexBindingDescriptionCount: uint32(bindings.len),
+      pVertexBindingDescriptions: bindings.ToCPointer,
+      vertexAttributeDescriptionCount: uint32(attributes.len),
+      pVertexAttributeDescriptions: attributes.ToCPointer,
+    )
+    inputAssembly = VkPipelineInputAssemblyStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
+      topology: topology,
+      primitiveRestartEnable: false,
+    )
+    viewportState = VkPipelineViewportStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
+      viewportCount: 1,
+      scissorCount: 1,
+    )
+    rasterizer = VkPipelineRasterizationStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
+      depthClampEnable: VK_FALSE,
+      rasterizerDiscardEnable: VK_FALSE,
+      polygonMode: polygonMode,
+      lineWidth: 1.0,
+      cullMode: toBits [cullMode],
+      frontFace: frontFace,
+      depthBiasEnable: VK_FALSE,
+      depthBiasConstantFactor: 0.0,
+      depthBiasClamp: 0.0,
+      depthBiasSlopeFactor: 0.0,
+    )
+    multisampling = VkPipelineMultisampleStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
+      sampleShadingEnable: VK_FALSE,
+      rasterizationSamples: VK_SAMPLE_COUNT_1_BIT,
+      minSampleShading: 1.0,
+      pSampleMask: nil,
+      alphaToCoverageEnable: VK_FALSE,
+      alphaToOneEnable: VK_FALSE,
+    )
+    colorBlendAttachment = VkPipelineColorBlendAttachmentState(
+      colorWriteMask: toBits [VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT, VK_COLOR_COMPONENT_A_BIT],
+      blendEnable: VK_TRUE,
+      srcColorBlendFactor: VK_BLEND_FACTOR_SRC_ALPHA,
+      dstColorBlendFactor: VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+      colorBlendOp: VK_BLEND_OP_ADD,
+      srcAlphaBlendFactor: VK_BLEND_FACTOR_ONE,
+      dstAlphaBlendFactor: VK_BLEND_FACTOR_ZERO,
+      alphaBlendOp: VK_BLEND_OP_ADD,
+    )
+    colorBlending = VkPipelineColorBlendStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
+      logicOpEnable: false,
+      attachmentCount: 1,
+      pAttachments: addr(colorBlendAttachment),
+    )
+    dynamicStates = [VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR]
+    dynamicState = VkPipelineDynamicStateCreateInfo(
+      sType: VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
+      dynamicStateCount: dynamicStates.len.uint32,
+      pDynamicStates: dynamicStates.ToCPointer,
+    )
+  let createInfo = VkGraphicsPipelineCreateInfo(
+    sType: VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
+    stageCount: 2,
+    pStages: addr(stages),
+    pVertexInputState: addr(vertexInputInfo),
+    pInputAssemblyState: addr(inputAssembly),
+    pViewportState: addr(viewportState),
+    pRasterizationState: addr(rasterizer),
+    pMultisampleState: addr(multisampling),
+    pDepthStencilState: nil,
+    pColorBlendState: addr(colorBlending),
+    pDynamicState: addr(dynamicState),
+    layout: result.layout,
+    renderPass: renderPass,
+    subpass: 0,
+    basePipelineHandle: VkPipeline(0),
+    basePipelineIndex: -1,
+  )
+  checkVkResult vkCreateGraphicsPipelines(
+    device,
+    VkPipelineCache(0),
+    1,
+    addr(createInfo),
+    nil,
+    addr(result.pipeline)
+  )
+
+
+proc Render*[IndexType: static VkIndexType](renderable: Renderable[IndexType], commandBuffer: VkCommandBuffer, pipeline: Pipeline, frameInFlight: int) =
+  assert 0 <= frameInFlight and frameInFlight < 2
+  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],
+    0,
+    nil,
+  )
+  commandBuffer.vkCmdBindVertexBuffers(
+    firstBinding = 0'u32,
+    bindingCount = uint32(renderable.buffers.len),
+    pBuffers = renderable.buffers.ToCPointer(),
+    pOffsets = renderable.offsets.ToCPointer()
+  )
+  when IndexType != VK_INDEX_TYPE_NONE_KHR:
+    commandBuffer.vkCmdBindIndexBuffer(
+      renderable.indexBuffer,
+      renderable.indexBufferOffset,
+      IndexType,
+    )
+    commandBuffer.vkCmdDrawIndexed(
+      indexCount = drawable.indexCount,
+      instanceCount = drawable.instanceCount,
+      firstIndex = 0,
+      vertexOffset = 0,
+      firstInstance = 0
+    )
+  else:
+    commandBuffer.vkCmdDraw(
+      vertexCount = drawable.vertexCount,
+      instanceCount = drawable.instanceCount,
+      firstVertex = 0,
+      firstInstance = 0
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test2.nim	Mon Jun 17 22:21:59 2024 +0700
@@ -0,0 +1,10 @@
+type
+  ShaderInputA = object
+    positions {.VertexAttribute.}: seq[Vec3f]
+    colors {.VertexAttribute.}: seq[Vec3f]
+    transforms {.InstanceAttribute.}: seq[Vec3f]
+    other: bool
+  Enemy = object
+    shaderData: ShaderInputA
+
+proc initEnemy()