# HG changeset patch # User sam # Date 1719398796 -25200 # Node ID dc6e1660831dc914c2ff90a94f389440f24eb004 # Parent 58694b30b9cb89993f1ea2ab3f176466f7b1d36f# Parent 2addc5f6804f4e8454ee1667ea72fa8cab4fe822 merge diff -r 2addc5f6804f -r dc6e1660831d semicongine/core/imagetypes.nim --- a/semicongine/core/imagetypes.nim Wed Jun 26 05:14:30 2024 +0700 +++ b/semicongine/core/imagetypes.nim Wed Jun 26 17:46:36 2024 +0700 @@ -8,11 +8,12 @@ RGBAPixel* = array[4, uint8] GrayPixel* = uint8 Pixel* = RGBAPixel or GrayPixel - ImageObject*[T: Pixel] = object + # ImageObject*[T: Pixel] = object + Image*[T: Pixel] = object width*: uint32 height*: uint32 imagedata*: seq[T] - Image*[T: Pixel] = ref ImageObject[T] + # Image*[T: Pixel] = ref ImageObject[T] Sampler* = object magnification*: VkFilter = VK_FILTER_LINEAR @@ -81,7 +82,6 @@ assert width > 0 and height > 0 assert imagedata.len.uint32 == width * height or imagedata.len == 0 - result = new Image[T] result.imagedata = (if imagedata.len == 0: newSeq[T](width * height) else: @imagedata) assert width * height == result.imagedata.len.uint32 diff -r 2addc5f6804f -r dc6e1660831d semicongine/mesh.nim --- a/semicongine/mesh.nim Wed Jun 26 05:14:30 2024 +0700 +++ b/semicongine/mesh.nim Wed Jun 26 17:46:36 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: diff -r 2addc5f6804f -r dc6e1660831d semicongine/renderer.nim --- a/semicongine/renderer.nim Wed Jun 26 05:14:30 2024 +0700 +++ b/semicongine/renderer.nim Wed Jun 26 17:46:36 2024 +0700 @@ -132,7 +132,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 @@ -212,7 +211,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 diff -r 2addc5f6804f -r dc6e1660831d semicongine/vulkan/descriptor.nim --- a/semicongine/vulkan/descriptor.nim Wed Jun 26 05:14:30 2024 +0700 +++ b/semicongine/vulkan/descriptor.nim Wed Jun 26 17:46:36 2024 +0700 @@ -106,7 +106,6 @@ assert layout.vk.Valid var layouts: seq[VkDescriptorSetLayout] - var descriptorSets = newSeq[VkDescriptorSet](nframes) for i in 0 ..< nframes: layouts.add layout.vk var allocInfo = VkDescriptorSetAllocateInfo( @@ -116,6 +115,7 @@ pSetLayouts: layouts.ToCPointer, ) + var descriptorSets = newSeq[VkDescriptorSet](nframes) checkVkResult vkAllocateDescriptorSets(pool.device.vk, addr(allocInfo), descriptorSets.ToCPointer) for descriptorSet in descriptorSets: result.add DescriptorSet(vk: descriptorSet, layout: layout) diff -r 2addc5f6804f -r dc6e1660831d semicongine/vulkan/renderpass.nim diff -r 2addc5f6804f -r dc6e1660831d static_utils.nim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static_utils.nim Wed Jun 26 17:46:36 2024 +0700 @@ -0,0 +1,762 @@ +import std/os +import std/enumerate +import std/hashes +import std/macros +import std/strformat +import std/strutils +import std/sequtils +import std/typetraits as tt + +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 Pass* {.pragma.} +template PassFlat* {.pragma.} +template ShaderOutput* {.pragma.} + +const INFLIGHTFRAMES = 2'u32 +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] + ShaderObject*[TShader] = object + vertexShader: VkShaderModule + fragmentShader: VkShaderModule + +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 TMat2[float32]: VK_FORMAT_R32G32_SFLOAT + elif T is TMat2[float64]: VK_FORMAT_R64G64_SFLOAT + elif T is TMat23[float32]: VK_FORMAT_R32G32B32_SFLOAT + elif T is TMat23[float64]: VK_FORMAT_R64G64B64_SFLOAT + elif T is TMat32[float32]: VK_FORMAT_R32G32_SFLOAT + elif T is TMat32[float64]: VK_FORMAT_R64G64_SFLOAT + elif T is TMat3[float32]: VK_FORMAT_R32G32B32_SFLOAT + elif T is TMat3[float64]: VK_FORMAT_R64G64B64_SFLOAT + elif T is TMat34[float32]: VK_FORMAT_R32G32B32A32_SFLOAT + elif T is TMat34[float64]: VK_FORMAT_R64G64B64A64_SFLOAT + elif T is TMat43[float32]: VK_FORMAT_R32G32B32_SFLOAT + elif T is TMat43[float64]: VK_FORMAT_R64G64B64_SFLOAT + elif T is TMat4[float32]: VK_FORMAT_R32G32B32A32_SFLOAT + elif T is TMat4[float64]: VK_FORMAT_R64G64B64A64_SFLOAT + else: {.error: "Unsupported data type on GPU".} + +func GlslType[T: SupportedGPUType|Texture](value: T): string = + when T is float32: "float" + elif T is float64: "double" + elif T is int8 or T is int16 or T is int32 or T is int64: "int" + elif T is uint8 or T is uint16 or T is uint32 or T is uint64: "uint" + elif T is TVec2[int32]: "ivec2" + elif T is TVec2[int64]: "ivec2" + elif T is TVec3[int32]: "ivec3" + elif T is TVec3[int64]: "ivec3" + elif T is TVec4[int32]: "ivec4" + elif T is TVec4[int64]: "ivec4" + elif T is TVec2[uint32]: "uvec2" + elif T is TVec2[uint64]: "uvec2" + elif T is TVec3[uint32]: "uvec3" + elif T is TVec3[uint64]: "uvec3" + elif T is TVec4[uint32]: "uvec4" + elif T is TVec4[uint64]: "uvec4" + elif T is TVec2[float32]: "vec2" + elif T is TVec2[float64]: "dvec2" + elif T is TVec3[float32]: "vec3" + elif T is TVec3[float64]: "dvec3" + elif T is TVec4[float32]: "vec4" + elif T is TVec4[float64]: "dvec4" + elif T is TMat2[float32]: "mat2" + elif T is TMat2[float64]: "dmat2" + elif T is TMat23[float32]: "mat23" + elif T is TMat23[float64]: "dmat23" + elif T is TMat32[float32]: "mat32" + elif T is TMat32[float64]: "dmat32" + elif T is TMat3[float32]: "mat3" + elif T is TMat3[float64]: "dmat3" + elif T is TMat34[float32]: "mat34" + elif T is TMat34[float64]: "dmat34" + elif T is TMat43[float32]: "mat43" + elif T is TMat43[float64]: "dmat43" + elif T is TMat4[float32]: "mat4" + elif T is TMat4[float64]: "dmat4" + elif T is Texture: "sampler2D" + else: {.error: "Unsupported data type on GPU".} + +template ForVertexDataFields*(inputData: typed, fieldname, valuename, isinstancename, body: untyped): untyped = + for theFieldname, value in fieldPairs(inputData): + 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: + {.error: "field '" & theFieldname & "' is not a supported GPU type".} + block: + let `fieldname` {.inject.} = theFieldname + let `valuename` {.inject.} = value + let `isinstancename` {.inject.} = hasCustomPragma(value, InstanceAttribute) + body + +template ForDescriptorFields*(inputData: typed, typename, countname, body: untyped): untyped = + for theFieldname, value in fieldPairs(inputData): + when typeof(value) is Texture: + block: + let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER + let `countname` {.inject.} = 1'u32 + body + elif typeof(value) is object: + block: + let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + let `countname` {.inject.} = 1'u32 + body + elif typeof(value) is array: + when elementType(value) is Texture: + block: + let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER + let `countname` {.inject.} = uint32(typeof(value).len) + body + elif elementType(value) is object: + block: + let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + let `countname` {.inject.} = uint32(typeof(value).len) + body + +func NumberOfVertexInputAttributeDescriptors[T: SupportedGPUType|Texture](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|Texture](value: T): uint32 = + #[ + single location: + - any scalar + - any 16-bit vector + - any 32-bit vector + - any 64-bit vector that has max. 2 components + 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 T is TVec3[int64] or + T is TVec4[int64] or + T is TVec3[uint64] or + T is TVec4[uint64] or + T is TVec3[float64] or + T is TVec4[float64] or + T is TMat23[float64] or + T is TMat3[float64] or + T is TMat34[float64] or + T is TMat43[float64] or + T is TMat4[float64]: + return 2 + else: + return 1 + +type + 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: IndexType + of None: + vertexCount: uint32 + else: + indexBuffer: VkBuffer + indexCount: uint32 + indexBufferOffset: VkDeviceSize + Pipeline[TShader] = object + pipeline: VkPipeline + layout: VkPipelineLayout + descriptorSets: array[INFLIGHTFRAMES, VkDescriptorSet] + +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 compileGlslToSPIRV(stage: VkShaderStageFlagBits, shaderSource: string): seq[uint32] {.compileTime.} = + func stage2string(stage: VkShaderStageFlagBits): string {.compileTime.} = + case stage + of VK_SHADER_STAGE_VERTEX_BIT: "vert" + of VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT: "tesc" + of VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT: "tese" + of VK_SHADER_STAGE_GEOMETRY_BIT: "geom" + of VK_SHADER_STAGE_FRAGMENT_BIT: "frag" + of VK_SHADER_STAGE_COMPUTE_BIT: "comp" + else: "" + + when defined(nimcheck): # will not run if nimcheck is running + return result + + let + stagename = stage2string(stage) + shaderHash = hash(shaderSource) + shaderfile = getTempDir() / &"shader_{shaderHash}.{stagename}" + + if not shaderfile.fileExists: + echo "shader of type ", stage + for i, line in enumerate(shaderSource.splitlines()): + echo " ", i + 1, " ", line + # var glslExe = currentSourcePath.parentDir.parentDir.parentDir / "tools" / "glslangValidator" + var glslExe = currentSourcePath.parentDir / "tools" / "glslangValidator" + when defined(windows): + glslExe = glslExe & "." & ExeExt + let command = &"{glslExe} --entry-point main -V --stdin -S {stagename} -o {shaderfile}" + echo "run: ", command + discard StaticExecChecked( + command = command, + input = shaderSource + ) + else: + echo &"shaderfile {shaderfile} is up-to-date" + + when defined(mingw) and defined(linux): # required for crosscompilation, path separators get messed up + let shaderbinary = staticRead shaderfile.replace("\\", "/") + else: + let shaderbinary = staticRead shaderfile + + var i = 0 + while i < shaderbinary.len: + result.add( + (uint32(shaderbinary[i + 0]) shl 0) or + (uint32(shaderbinary[i + 1]) shl 8) or + (uint32(shaderbinary[i + 2]) shl 16) or + (uint32(shaderbinary[i + 3]) shl 24) + ) + i += 4 + +proc generateShaderSource[TShader](shader: TShader): (string, string) {.compileTime.} = + const GLSL_VERSION = "450" + var vsInput: seq[string] + var vsOutput: seq[string] + var fsInput: seq[string] + var fsOutput: seq[string] + var uniforms: seq[string] + var samplers: seq[string] + var vsInputLocation = 0'u32 + var passLocation = 0 + var fsOutputLocation = 0 + var descriptorBinding = 0 + + for fieldname, value in fieldPairs(shader): + # vertex shader inputs + when hasCustomPragma(value, VertexAttribute) or hasCustomPragma(value, InstanceAttribute): + assert typeof(value) is SupportedGPUType + vsInput.add "layout(location = " & $vsInputLocation & ") in " & GlslType(value) & " " & fieldname & ";" + for j in 0 ..< NumberOfVertexInputAttributeDescriptors(value): + vsInputLocation += NLocationSlots(value) + # intermediate values, passed between shaders + elif hasCustomPragma(value, Pass) or hasCustomPragma(value, PassFlat): + let flat = if hasCustomPragma(value, PassFlat): "flat " else: "" + vsOutput.add "layout(location = " & $passLocation & ") " & flat & "out " & GlslType(value) & " " & fieldname & ";" + fsInput.add "layout(location = " & $passLocation & ") " & flat & "in " & GlslType(value) & " " & fieldname & ";" + passLocation.inc + elif hasCustomPragma(value, ShaderOutput): + fsOutput.add &"layout(location = " & $fsOutputLocation & ") out " & GlslType(value) & " " & fieldname & ";" + fsOutputLocation.inc + elif typeof(value) is Texture: + samplers.add "layout(binding = " & $descriptorBinding & ") uniform " & GlslType(value) & " " & fieldname & ";" + descriptorBinding.inc + elif typeof(value) is object: + # TODO + uniforms.add "" + descriptorBinding.inc + elif typeof(value) is array: + when elementType(value) is Texture: + let arrayDecl = "[" & $typeof(value).len & "]" + samplers.add "layout(binding = " & $descriptorBinding & ") uniform " & GlslType(default(elementType(value))) & " " & fieldname & "" & arrayDecl & ";" + descriptorBinding.inc + elif elementType(value) is object: + # TODO + let arrayDecl = "[" & $typeof(value).len & "]" + # uniforms.add "layout(binding = " & $descriptorBinding & ") uniform " & GlslType(elementType(value)) & " " & fieldname & "" & arrayDecl & ";" + descriptorBinding.inc + else: + {.error: "Unsupported shader field " & fieldname.} + elif fieldname in ["vertexCode", "fragmentCode"]: + discard + else: + {.error: "Unsupported shader field '" & tt.name(TShader) & "." & fieldname & "' of type " & tt.name(typeof(value)).} + + result[0] = (@[&"#version {GLSL_VERSION}", "#extension GL_EXT_scalar_block_layout : require", ""] & + vsInput & + uniforms & + samplers & + vsOutput & + @[shader.vertexCode]).join("\n") + + result[1] = (@[&"#version {GLSL_VERSION}", "#extension GL_EXT_scalar_block_layout : require", ""] & + fsInput & + uniforms & + samplers & + fsOutput & + @[shader.fragmentCode]).join("\n") + +# proc CompileShader[TShader](shader: static TShader): (seq[uint32], seq[uint32]) {.compileTime.}= +proc CompileShader[TShader](device: VkDevice, shader: static TShader): ShaderObject[TShader] = + const (vertexShaderSource, fragmentShaderSource) = generateShaderSource(shader) + + let vertexBinary = compileGlslToSPIRV(VK_SHADER_STAGE_VERTEX_BIT, vertexShaderSource) + let fragmentBinary = compileGlslToSPIRV(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShaderSource) + + var createInfoVertex = VkShaderModuleCreateInfo( + sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + codeSize: csize_t(vertexBinary.len * sizeof(uint32)), + pCode: vertexBinary.ToCPointer, + ) + checkVkResult device.vkCreateShaderModule(addr(createInfoVertex), nil, addr(result.vertexShader)) + var createInfoFragment = VkShaderModuleCreateInfo( + sType: VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + codeSize: csize_t(fragmentBinary.len * sizeof(uint32)), + pCode: fragmentBinary.ToCPointer, + ) + checkVkResult device.vkCreateShaderModule(addr(createInfoFragment), nil, addr(result.fragmentShader)) + + +proc CreatePipeline*[TShader]( + device: VkDevice, + renderPass: VkRenderPass, + shader: ShaderObject[TShader], + 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[TShader] = + # assumptions/limitations: + # - we are only using vertex and fragment shaders (2 stages) + # - we only support one subpass + # = we only support one Uniform-Block + + # create pipeline + var layoutbindings: seq[VkDescriptorSetLayoutBinding] + var descriptorBindingNumber = 0'u32 + ForDescriptorFields(default(TShader), descriptorType, descriptorCount): + layoutbindings.add VkDescriptorSetLayoutBinding( + binding: descriptorBindingNumber, + descriptorType: descriptorType, + descriptorCount: descriptorCount, + stageFlags: VkShaderStageFlags(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, 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( + sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + stage: VK_SHADER_STAGE_VERTEX_BIT, + module: shader.vertexShader, + pName: "main", + ), + VkPipelineShaderStageCreateInfo( + sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + stage: VK_SHADER_STAGE_FRAGMENT_BIT, + module: shader.fragmentShader, + pName: "main", + ), + ] + var + bindings: seq[VkVertexInputBindingDescription] + attributes: seq[VkVertexInputAttributeDescription] + var inputBindingNumber = 0'u32 + var location = 0'u32 + ForVertexDataFields(default(TShader), 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, + location: location, + format: VkType(value), + offset: i * perDescriptorSize, + ) + location += 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: stages.ToCPointer, + 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) + ) + + # create descriptors, one per frame-in-flight + let nSamplers = 0'u32 + let nUniformBuffers = 0'u32 + + if nSamplers + nUniformBuffers > 0: + var poolSizes: seq[VkDescriptorPoolSize] + if nUniformBuffers > 0: + poolSizes.add VkDescriptorPoolSize(thetype: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, descriptorCount: nSamplers * INFLIGHTFRAMES) + if nSamplers > 0: + poolSizes.add VkDescriptorPoolSize(thetype: VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, descriptorCount: nUniformBuffers * INFLIGHTFRAMES) + var poolInfo = VkDescriptorPoolCreateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + poolSizeCount: uint32(poolSizes.len), + pPoolSizes: poolSizes.ToCPointer, + maxSets: (nUniformBuffers + nSamplers) * INFLIGHTFRAMES * 2, # good formula? no idea... + ) + var pool: VkDescriptorPool + checkVkResult vkCreateDescriptorPool(device, addr(poolInfo), nil, addr(pool)) + + var layouts = newSeqWith(result.descriptorSets.len, descriptorSetLayout) + var allocInfo = VkDescriptorSetAllocateInfo( + sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + descriptorPool: pool, + descriptorSetCount: uint32(layouts.len), + pSetLayouts: layouts.ToCPointer, + ) + checkVkResult vkAllocateDescriptorSets(device, addr(allocInfo), result.descriptorSets.ToCPointer) + + # write descriptor sets + # TODO + var descriptorSetWrites: seq[VkWriteDescriptorSet] + for XY in descriptors?: + + bufferInfos.add VkDescriptorBufferInfo( + buffer: descriptor.buffer.vk, + offset: descriptor.offset, + range: descriptor.size, + ) + descriptorSetWrites.add VkWriteDescriptorSet( + sType: VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, + dstSet: descriptorSet.vk, + dstBinding: i, + dstArrayElement: 0, + descriptorType: descriptor.vkType, + descriptorCount: uint32(descriptor.count), + pBufferInfo: addr bufferInfos[^1], + ) + + vkUpdateDescriptorSets(device, uint32(descriptorSetWrites.len), descriptorSetWrites.ToCPointer, 0, nil) + +proc CreateRenderable[TMesh, TInstance]( + mesh: TMesh, + instance: TInstance, + buffers: RenderBuffers, +): Renderable[TMesh, TInstance] = + result.indexType = None + +proc Bind(pipeline: Pipeline, commandBuffer: VkCommandBuffer, currentFrameInFlight: int) = + commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline) + if pipeline.descriptorSets[currentFrameInFlight] != VkDescriptorSet(0): + commandBuffer.vkCmdBindDescriptorSets( + VK_PIPELINE_BIND_POINT_GRAPHICS, + pipeline.layout, + 0, + 1, + addr pipeline.descriptorSets[currentFrameInFlight], + 0, + nil, + ) + +proc AssertCompatible(TShader, TMesh, TInstance, TGlobals: typedesc) = + # assert seq-fields of TMesh|TInstance == seq-fields of TShader + # assert normal fields of TMesh|Globals == normal fields of TShaderDescriptors + for inputName, inputValue in default(TShader).fieldPairs: + var foundField = false + when hasCustomPragma(inputValue, VertexAttribute): + assert typeof(inputValue) is SupportedGPUType + for meshName, meshValue in default(TMesh).fieldPairs: + when meshName == inputName: + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert elementType(meshValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but mesh attribute is of type '" & tt.name(elementType(meshValue)) & "'" + foundField = true + assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "'" + elif hasCustomPragma(inputValue, InstanceAttribute): + assert typeof(inputValue) is SupportedGPUType + for instanceName, instanceValue in default(TInstance).fieldPairs: + when instanceName == inputName: + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert elementType(instanceValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but instance attribute is of type '" & tt.name(elementType(instanceValue)) & "'" + foundField = true + assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TInstance) & "'" + elif typeof(inputValue) is Texture or typeof(inputValue) is object: + for meshName, meshValue in default(TMesh).fieldPairs: + when meshName == inputName: + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert typeof(meshValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but mesh attribute is of type '" & tt.name(elementType(meshValue)) & "'" + foundField = true + for globalName, globalValue in default(TGlobals).fieldPairs: + when globalName == inputName: + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert typeof(globalValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but global attribute is of type '" & tt.name(typeof(globalValue)) & "'" + foundField = true + assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "|" & tt.name(TGlobals) & "'" + elif typeof(inputValue) is array: + when (elementType(inputValue) is Texture or elementType(inputValue) is object): + for meshName, meshValue in default(TMesh).fieldPairs: + when meshName == inputName: + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert typeof(meshValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but mesh attribute is of type '" & tt.name(elementType(meshValue)) & "'" + foundField = true + for globalName, globalValue in default(TGlobals).fieldPairs: + when globalName == inputName: + assert foundField == false, "Shader input '" & tt.name(TShader) & "." & inputName & "' has been found more than once" + assert typeof(globalValue) is typeof(inputValue), "Shader input " & tt.name(TShader) & "." & inputName & " is of type '" & tt.name(typeof(inputValue)) & "' but global attribute is of type '" & tt.name(typeof(globalValue)) & "'" + foundField = true + assert foundField, "Shader input '" & tt.name(TShader) & "." & inputName & ": " & tt.name(typeof(inputValue)) & "' not found in '" & tt.name(TMesh) & "|" & tt.name(TGlobals) & "'" + + +proc Render[TShader, TMesh, TInstance, TGlobals]( + pipeline: Pipeline[TShader], + renderable: Renderable[TMesh, TInstance], + globals: TGlobals, + commandBuffer: VkCommandBuffer, +) = + {.error: "Need to write descriptor sets".} + static: AssertCompatible(TShader, TMesh, TInstance, TGlobals) + if renderable.vertexBuffers.len > 0: + commandBuffer.vkCmdBindVertexBuffers( + firstBinding = 0'u32, + bindingCount = uint32(renderable.vertexBuffers.len), + pBuffers = renderable.vertexBuffers.ToCPointer(), + pOffsets = renderable.bufferOffsets.ToCPointer() + ) + if renderable.indexType != None: + commandBuffer.vkCmdBindIndexBuffer( + renderable.indexBuffer, + renderable.indexBufferOffset, + renderable.indexType, + ) + commandBuffer.vkCmdDrawIndexed( + indexCount = renderable.indexCount, + instanceCount = renderable.instanceCount, + firstIndex = 0, + vertexOffset = 0, + firstInstance = 0 + ) + else: + commandBuffer.vkCmdDraw( + vertexCount = renderable.vertexCount, + instanceCount = renderable.instanceCount, + firstVertex = 0, + firstInstance = 0 + ) + +when isMainModule: + import semicongine/platform/window + import semicongine/vulkan/instance + import semicongine/vulkan/device + import semicongine/vulkan/physicaldevice + import semicongine/vulkan/renderpass + import semicongine/vulkan/commandbuffer + import std/options + + type + MaterialA = object + reflection: float32 + baseColor: Vec3f + ShaderSettings = object + brightness: float32 + MeshA = object + position: seq[Vec3f] + transparency: float + material: array[3, MaterialA] + materialTextures: array[3, Texture] + InstanceA = object + transform: seq[Mat4] + position: seq[Vec3f] + Globals = object + fontAtlas: Texture + settings: ShaderSettings + + ShaderA = object + # vertex input + position {.VertexAttribute.}: Vec3f + transform {.InstanceAttribute.}: Mat4 + # intermediate + test {.Pass.}: float32 + test1 {.PassFlat.}: Vec3f + # output + color {.ShaderOutput.}: Vec4f + # uniforms + material: array[3, MaterialA] + settings: ShaderSettings + # textures + fontAtlas: Texture + materialTextures: array[3, Texture] + # code + vertexCode: string = "void main() {}" + fragmentCode: string = "void main() {}" + + let w = CreateWindow("test2") + putEnv("VK_LAYER_ENABLES", "VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD,VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_NVIDIA,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXTVK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT") + let i = w.CreateInstance( + vulkanVersion = VK_MAKE_API_VERSION(0, 1, 3, 0), + instanceExtensions = @[], + layers = @["VK_LAYER_KHRONOS_validation"], + ) + + + let selectedPhysicalDevice = i.GetPhysicalDevices().FilterBestGraphics() + let d = i.CreateDevice( + selectedPhysicalDevice, + enabledExtensions = @[], + selectedPhysicalDevice.FilterForGraphicsPresentationQueues() + ) + + var r: Renderable[MeshA, InstanceA] + var g: Globals + + const shader = ShaderA() + let shaderObject = d.vk.CompileShader(shader) + let rp = d.vk.CreateRenderPass(d.physicalDevice.GetSurfaceFormats().FilterSurfaceFormat().format) + var p = CreatePipeline(d.vk, renderPass = rp, shaderObject) + + let commandBufferPool = d.CreateCommandBufferPool(d.FirstGraphicsQueue().get().family, INFLIGHTFRAMES) + let cmd = commandBufferPool.buffers[0] + + checkVkResult cmd.vkResetCommandBuffer(VkCommandBufferResetFlags(0)) + let beginInfo = VkCommandBufferBeginInfo( + sType: VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + flags: VkCommandBufferUsageFlags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT), + ) + checkVkResult cmd.vkBeginCommandBuffer(addr(beginInfo)) + p.Bind(cmd, currentFrameInFlight = 0) + p.Render(r, g, cmd) + + checkVkResult cmd.vkEndCommandBuffer() diff -r 2addc5f6804f -r dc6e1660831d test2.nim --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test2.nim Wed Jun 26 17:46:36 2024 +0700 @@ -0,0 +1,41 @@ +import std/os + +import semicongine/platform/window +import semicongine/core/vulkanapi +import semicongine/vulkan/instance +import semicongine/vulkan/device +import semicongine/vulkan/physicaldevice +# import ./vulkan/shader + +import semicongine/core/vector +import semicongine/core/matrix + +type + MeshA = object + positions: seq[Vec3f] + colors: seq[Vec3f] + transparency: float32 + InstanceDataA = object + transforms: seq[Vec3f] + + Enemy = object + mesh: MeshA + enemies: InstanceDataA + +let e = Enemy() +echo e + +let w = CreateWindow("test2") +putEnv("VK_LAYER_ENABLES", "VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_AMD,VALIDATION_CHECK_ENABLE_VENDOR_SPECIFIC_NVIDIA,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXTVK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT") +let i = w.CreateInstance( + vulkanVersion = VK_MAKE_API_VERSION(0, 1, 3, 0), + instanceExtensions = @[], + layers = @["VK_LAYER_KHRONOS_validation"], +) + +let selectedPhysicalDevice = i.GetPhysicalDevices().FilterBestGraphics() +let d = i.CreateDevice( + selectedPhysicalDevice, + enabledExtensions = @[], + selectedPhysicalDevice.FilterForGraphicsPresentationQueues() +)