Mercurial > games > semicongine
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 1160:836dc1eda5e3 | 1161:dbca0528c714 |
|---|---|
| 1 import std/macros | 1 import std/macros |
| 2 import std/strformat | |
| 2 import std/typetraits | 3 import std/typetraits |
| 3 | 4 |
| 5 import semicongine/core/utils | |
| 6 import semicongine/core/imagetypes | |
| 4 import semicongine/core/vector | 7 import semicongine/core/vector |
| 5 import semicongine/core/matrix | 8 import semicongine/core/matrix |
| 6 import semicongine/core/vulkanapi | 9 import semicongine/core/vulkanapi |
| 10 import semicongine/vulkan/buffer | |
| 7 | 11 |
| 8 template VertexAttribute* {.pragma.} | 12 template VertexAttribute* {.pragma.} |
| 9 template InstanceAttribute* {.pragma.} | 13 template InstanceAttribute* {.pragma.} |
| 14 template DescriptorAttribute* {.pragma.} | |
| 15 | |
| 10 | 16 |
| 11 type | 17 type |
| 12 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] | 18 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] |
| 13 | 19 |
| 14 func VkType[T: SupportedGPUType](value: T): VkFormat = | 20 func VkType[T: SupportedGPUType](value: T): VkFormat = |
| 59 template getElementType(field: typed): untyped = | 65 template getElementType(field: typed): untyped = |
| 60 when not (typeof(field) is seq or typeof(field) is array): | 66 when not (typeof(field) is seq or typeof(field) is array): |
| 61 {.error: "getElementType can only be used with seq or array".} | 67 {.error: "getElementType can only be used with seq or array".} |
| 62 genericParams(typeof(field)).get(0) | 68 genericParams(typeof(field)).get(0) |
| 63 | 69 |
| 64 proc isVertexAttribute[T](value: T): bool {.compileTime.} = | 70 template ForVertexDataFields*(inputData: typed, fieldname, valuename, isinstancename, body: untyped): untyped = |
| 65 hasCustomPragma(T, VertexAttribute) | |
| 66 | |
| 67 proc isInstanceAttribute[T](value: T): bool {.compileTime.} = | |
| 68 hasCustomPragma(T, InstanceAttribute) | |
| 69 | |
| 70 template ForAttributeFields*(inputData: typed, fieldname, valuename, isinstancename, body: untyped): untyped = | |
| 71 for theFieldname, value in fieldPairs(inputData): | 71 for theFieldname, value in fieldPairs(inputData): |
| 72 when isVertexAttribute(value) or isInstanceAttribute(value): | 72 when hasCustomPragma(value, VertexAttribute) or hasCustomPragma(value, InstanceAttribute): |
| 73 when not typeof(value) is seq: | 73 when not typeof(value) is seq: |
| 74 {.error: "field '" & theFieldname & "' needs to be a seq".} | 74 {.error: "field '" & theFieldname & "' needs to be a seq".} |
| 75 when not typeof(value) is SupportedGPUType: | 75 when not typeof(value) is SupportedGPUType: |
| 76 {.error: "field '" & theFieldname & "' is not a supported GPU type".} | 76 {.error: "field '" & theFieldname & "' is not a supported GPU type".} |
| 77 block: | 77 block: |
| 78 let `fieldname` {.inject.} = theFieldname | 78 let `fieldname` {.inject.} = theFieldname |
| 79 let `valuename` {.inject.} = default(getElementType(value)) | 79 let `valuename` {.inject.} = default(getElementType(value)) |
| 80 let `isinstancename` {.inject.} = value.isInstanceAttribute() | 80 let `isinstancename` {.inject.} = value.isInstanceAttribute() |
| 81 body | |
| 82 | |
| 83 template ForDescriptorFields*(inputData: typed, fieldname, valuename, typename, countname, body: untyped): untyped = | |
| 84 for theFieldname, value in fieldPairs(inputData): | |
| 85 when hasCustomPragma(value, DescriptorAttribute): | |
| 86 when not ( | |
| 87 typeof(value) is SupportedGPUType | |
| 88 or (typeof(value) is array and elementType(value) is SupportedGPUType) | |
| 89 or typeof(value) is Texture | |
| 90 ): | |
| 91 {.error: "field '" & theFieldname & "' needs to be a SupportedGPUType or an array of SupportedGPUType".} | |
| 92 block: | |
| 93 let `fieldname` {.inject.} = theFieldname | |
| 94 let `valuename` {.inject.} = default(getElementType(value)) | |
| 95 | |
| 96 # TODO | |
| 97 let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER | |
| 98 let `typename` {.inject.} = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER | |
| 99 | |
| 100 when typeof(value) is array: | |
| 101 let `countname` {.inject.} = genericParams(typeof(value)).get(0) | |
| 102 else: | |
| 103 let `countname` {.inject.} = 1 | |
| 81 body | 104 body |
| 82 | 105 |
| 83 func NumberOfVertexInputAttributeDescriptors[T: SupportedGPUType](value: T): uint32 = | 106 func NumberOfVertexInputAttributeDescriptors[T: SupportedGPUType](value: T): uint32 = |
| 84 when T is TMat2[float32] or T is TMat2[float64] or T is TMat23[float32] or T is TMat23[float64]: | 107 when T is TMat2[float32] or T is TMat2[float64] or T is TMat23[float32] or T is TMat23[float64]: |
| 85 2 | 108 2 |
| 105 return 2 | 128 return 2 |
| 106 else: | 129 else: |
| 107 return 1 | 130 return 1 |
| 108 | 131 |
| 109 type | 132 type |
| 110 Renderable = object | 133 IndexType = enum |
| 111 buffers: seq[VkBuffer] | 134 None, UInt8, UInt16, UInt32 |
| 112 offsets: seq[VkDeviceSize] | 135 RenderBuffers = object |
| 136 deviceBuffers: seq[Buffer] # for fast reads | |
| 137 hostVisibleBuffers: seq[Buffer] # for fast writes | |
| 138 Renderable[TMesh, TInstance] = object | |
| 139 vertexBuffers: seq[VkBuffer] | |
| 140 bufferOffsets: seq[VkDeviceSize] | |
| 113 instanceCount: uint32 | 141 instanceCount: uint32 |
| 114 case indexType: VkIndexType | 142 case indexType: IndexType |
| 115 of VK_INDEX_TYPE_NONE_KHR: | 143 of None: |
| 116 vertexCount: uint32 | 144 vertexCount: uint32 |
| 117 of VK_INDEX_TYPE_UINT8_EXT, VK_INDEX_TYPE_UINT16, VK_INDEX_TYPE_UINT32: | 145 else: |
| 118 indexBuffer: VkBuffer | 146 indexBuffer: VkBuffer |
| 119 indexCount: uint32 | 147 indexCount: uint32 |
| 120 indexBufferOffset: VkDeviceSize | 148 indexBufferOffset: VkDeviceSize |
| 121 Pipeline = object | 149 Pipeline[TShaderInputs] = object |
| 122 pipeline: VkPipeline | 150 pipeline: VkPipeline |
| 123 layout: VkPipelineLayout | 151 layout: VkPipelineLayout |
| 124 descriptorSets: array[2, seq[VkDescriptorSet]] | 152 descriptorSets: array[2, seq[VkDescriptorSet]] |
| 125 ShaderSet[ShaderInputType, ShaderDescriptorType] = object | 153 ShaderSet[TShaderInputs] = object |
| 126 vertexShader: VkShaderModule | 154 vertexShader: VkShaderModule |
| 127 fragmentShader: VkShaderModule | 155 fragmentShader: VkShaderModule |
| 128 # TODO: I think this needs more fields? | 156 converter toVkIndexType(indexType: IndexType): VkIndexType = |
| 129 | 157 case indexType: |
| 130 proc CreatePipeline*[ShaderInputType, ShaderDescriptorType]( | 158 of None: VK_INDEX_TYPE_NONE_KHR |
| 159 of UInt8: VK_INDEX_TYPE_UINT8_EXT | |
| 160 of UInt16: VK_INDEX_TYPE_UINT16 | |
| 161 of UInt32: VK_INDEX_TYPE_UINT32 | |
| 162 | |
| 163 | |
| 164 proc CreatePipeline*[TShaderInputs]( | |
| 131 device: VkDevice, | 165 device: VkDevice, |
| 132 renderPass: VkRenderPass, | 166 renderPass: VkRenderPass, |
| 133 shaderSet: ShaderSet[ShaderInputType, ShaderDescriptorType], | 167 shaderSet: ShaderSet[TShaderInputs], |
| 134 topology: VkPrimitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, | 168 topology: VkPrimitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, |
| 135 polygonMode: VkPolygonMode = VK_POLYGON_MODE_FILL, | 169 polygonMode: VkPolygonMode = VK_POLYGON_MODE_FILL, |
| 136 cullMode: VkCullModeFlagBits = VK_CULL_MODE_BACK_BIT, | 170 cullMode: VkCullModeFlagBits = VK_CULL_MODE_BACK_BIT, |
| 137 frontFace: VkFrontFace = VK_FRONT_FACE_CLOCKWISE, | 171 frontFace: VkFrontFace = VK_FRONT_FACE_CLOCKWISE, |
| 138 ): Pipeline = | 172 ): Pipeline[TShaderInputs] = |
| 139 # assumptions/limitations: | 173 # assumptions/limitations: |
| 140 # - we are only using vertex and fragment shaders (2 stages) | 174 # - we are only using vertex and fragment shaders (2 stages) |
| 141 # - we only support one subpass | 175 # - we only support one subpass |
| 142 | 176 |
| 143 # CONTINUE HERE, WITH PIPELINE LAYOUT!!!! | 177 # CONTINUE HERE, WITH PIPELINE LAYOUT!!!! |
| 144 # Rely on ShaderDescriptorType | 178 # Rely on TShaderInputs |
| 145 checkVkResult vkCreatePipelineLayout(device.vk, addr(pipelineLayoutInfo), nil, addr(result.layout)) | 179 |
| 180 var layoutbindings: seq[VkDescriptorSetLayoutBinding] | |
| 181 let descriptors = [ | |
| 182 (VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), # more than 1 for arrays | |
| 183 (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1), | |
| 184 ] | |
| 185 var descriptorBindingNumber = 0'u32 | |
| 186 ForDescriptorFields(default(TShaderInputs), fieldname, value, descriptorCount): | |
| 187 layoutbindings.add VkDescriptorSetLayoutBinding( | |
| 188 binding: descriptorBindingNumber, | |
| 189 descriptorType: descriptorType, | |
| 190 descriptorCount: descriptorCount, | |
| 191 stageFlags: VK_SHADER_STAGE_ALL_GRAPHICS, | |
| 192 pImmutableSamplers: nil, | |
| 193 ) | |
| 194 inc descriptorBindingNumber | |
| 195 var layoutCreateInfo = VkDescriptorSetLayoutCreateInfo( | |
| 196 sType: VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, | |
| 197 bindingCount: uint32(layoutbindings.len), | |
| 198 pBindings: layoutbindings.ToCPointer | |
| 199 ) | |
| 200 var descriptorSetLayout: VkDescriptorSetLayout | |
| 201 checkVkResult vkCreateDescriptorSetLayout(device.vk, addr(layoutCreateInfo), nil, addr(descriptorSetLayout)) | |
| 202 let pipelineLayoutInfo = VkPipelineLayoutCreateInfo( | |
| 203 sType: VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, | |
| 204 setLayoutCount: 1, | |
| 205 pSetLayouts: addr(descriptorSetLayout), | |
| 206 # pushConstantRangeCount: uint32(pushConstants.len), | |
| 207 # pPushConstantRanges: pushConstants.ToCPointer, | |
| 208 ) | |
| 209 checkVkResult vkCreatePipelineLayout(device, addr(pipelineLayoutInfo), nil, addr(result.layout)) | |
| 146 | 210 |
| 147 let stages = [ | 211 let stages = [ |
| 148 VkPipelineShaderStageCreateInfo( | 212 VkPipelineShaderStageCreateInfo( |
| 149 sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, | 213 sType: VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, |
| 150 stage: VK_SHADER_STAGE_VERTEX_BIT, | 214 stage: VK_SHADER_STAGE_VERTEX_BIT, |
| 161 let | 225 let |
| 162 bindings: var seq[VkVertexInputBindingDescription] | 226 bindings: var seq[VkVertexInputBindingDescription] |
| 163 attributes: var seq[VkVertexInputAttributeDescription] | 227 attributes: var seq[VkVertexInputAttributeDescription] |
| 164 var inputBindingNumber = 0'u32 | 228 var inputBindingNumber = 0'u32 |
| 165 var inputLocationNumber = 0'u32 | 229 var inputLocationNumber = 0'u32 |
| 166 ForAttributeFields(default(ShaderInputType), fieldname, value, isInstanceAttr): | 230 ForVertexDataFields(default(TShaderInputs), fieldname, value, isInstanceAttr): |
| 167 bindings.add VkVertexInputBindingDescription( | 231 bindings.add VkVertexInputBindingDescription( |
| 168 binding: inputBindingNumber, | 232 binding: inputBindingNumber, |
| 169 stride: sizeof(value).uint32, | 233 stride: sizeof(value).uint32, |
| 170 inputRate: if isInstanceAttr: VK_VERTEX_INPUT_RATE_INSTANCE else: VK_VERTEX_INPUT_RATE_VERTEX, | 234 inputRate: if isInstanceAttr: VK_VERTEX_INPUT_RATE_INSTANCE else: VK_VERTEX_INPUT_RATE_VERTEX, |
| 171 ) | 235 ) |
| 268 addr(createInfo), | 332 addr(createInfo), |
| 269 nil, | 333 nil, |
| 270 addr(result.pipeline) | 334 addr(result.pipeline) |
| 271 ) | 335 ) |
| 272 | 336 |
| 273 | 337 proc CreateRenderable[TMesh, TInstance]( |
| 274 proc Render*(renderable: Renderable, commandBuffer: VkCommandBuffer, pipeline: Pipeline, frameInFlight: int) = | 338 mesh: TMesh, |
| 275 assert 0 <= frameInFlight and frameInFlight < 2 | 339 instance: TInstance, |
| 340 buffers: RenderBuffers, | |
| 341 ): Renderable[TMesh, TInstance] = | |
| 342 result.indexType = None | |
| 343 | |
| 344 proc Bind(pipeline: Pipeline, commandBuffer: VkCommandBuffer, currentFrameInFlight: int) = | |
| 276 commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline) | 345 commandBuffer.vkCmdBindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline) |
| 277 commandBuffer.vkCmdBindDescriptorSets( | 346 commandBuffer.vkCmdBindDescriptorSets( |
| 278 VK_PIPELINE_BIND_POINT_GRAPHICS, | 347 VK_PIPELINE_BIND_POINT_GRAPHICS, |
| 279 pipeline.layout, | 348 pipeline.layout, |
| 280 0, | 349 0, |
| 281 pipeline.descriptorSets[frameInFlight].len, | 350 pipeline.descriptorSets[currentFrameInFlight].len, |
| 282 pipeline.descriptorSets[frameInFlight], | 351 pipeline.descriptorSets[currentFrameInFlight], |
| 283 0, | 352 0, |
| 284 nil, | 353 nil, |
| 285 ) | 354 ) |
| 355 | |
| 356 proc AssertCompatible(TShaderInputs, TMesh, TInstance, TGlobals: typedesc) = | |
| 357 # assert seq-fields of TMesh|TInstance == seq-fields of TShaderInputs | |
| 358 # assert normal fields of TMesh|Globals == normal fields of TShaderDescriptors | |
| 359 for inputName, inputValue in default(TShaderInputs).fieldPairs: | |
| 360 echo "checking shader input '" & inputName & "'" | |
| 361 var foundField = false | |
| 362 when hasCustomPragma(inputValue, VertexAttribute): | |
| 363 echo " is vertex attribute" | |
| 364 for meshName, meshValue in default(TMesh).fieldPairs: | |
| 365 when meshName == inputName: | |
| 366 assert foundField == false, "Shader input '" & TShaderInputs.name & "." & inputName & "' has been found more than once" | |
| 367 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 & "'" | |
| 368 foundField = true | |
| 369 assert foundField, "Shader input '" & TShaderInputs.name & "." & inputName & ": " & typeof(inputValue).name & "' not found in '" & TMesh.name & "'" | |
| 370 elif hasCustomPragma(inputValue, InstanceAttribute): | |
| 371 echo " is instance attribute" | |
| 372 for instanceName, instanceValue in default(TInstance).fieldPairs: | |
| 373 when instanceName == inputName: | |
| 374 assert foundField == false, "Shader input '" & TShaderInputs.name & "." & inputName & "' has been found more than once" | |
| 375 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 & "'" | |
| 376 foundField = true | |
| 377 assert foundField, "Shader input '" & TShaderInputs.name & "." & inputName & ": " & typeof(inputValue).name & "' not found in '" & TInstance.name & "'" | |
| 378 elif hasCustomPragma(inputValue, DescriptorAttribute): | |
| 379 echo " is descriptor attribute" | |
| 380 for meshName, meshValue in default(TMesh).fieldPairs: | |
| 381 when meshName == inputName: | |
| 382 assert foundField == false, "Shader input '" & TShaderInputs.name & "." & inputName & "' has been found more than once" | |
| 383 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 & "'" | |
| 384 foundField = true | |
| 385 for globalName, globalValue in default(TGlobals).fieldPairs: | |
| 386 when globalName == inputName: | |
| 387 assert foundField == false, "Shader input '" & TShaderInputs.name & "." & inputName & "' has been found more than once" | |
| 388 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 & "'" | |
| 389 foundField = true | |
| 390 assert foundField, "Shader input '" & TShaderInputs.name & "." & inputName & ": " & typeof(inputValue).name & "' not found in '" & TMesh.name & "|" & TGlobals.name & "'" | |
| 391 echo " found" | |
| 392 | |
| 393 | |
| 394 proc Render[TShaderInputs, TMesh, TInstance, TGlobals]( | |
| 395 pipeline: Pipeline[TShaderInputs], | |
| 396 renderable: Renderable[TMesh, TInstance], | |
| 397 globals: TGlobals, | |
| 398 commandBuffer: VkCommandBuffer, | |
| 399 ) = | |
| 400 static: | |
| 401 AssertCompatible(TShaderInputs, TMesh, TInstance, TGlobals) | |
| 286 commandBuffer.vkCmdBindVertexBuffers( | 402 commandBuffer.vkCmdBindVertexBuffers( |
| 287 firstBinding = 0'u32, | 403 firstBinding = 0'u32, |
| 288 bindingCount = uint32(renderable.buffers.len), | 404 bindingCount = uint32(renderable.vertexBuffers.len), |
| 289 pBuffers = renderable.buffers.ToCPointer(), | 405 pBuffers = renderable.vertexBuffers.ToCPointer(), |
| 290 pOffsets = renderable.offsets.ToCPointer() | 406 pOffsets = renderable.bufferOffsets.ToCPointer() |
| 291 ) | 407 ) |
| 292 if renderable.indexType != VK_INDEX_TYPE_NONE_KHR: | 408 if renderable.indexType != None: |
| 293 commandBuffer.vkCmdBindIndexBuffer( | 409 commandBuffer.vkCmdBindIndexBuffer( |
| 294 renderable.indexBuffer, | 410 renderable.indexBuffer, |
| 295 renderable.indexBufferOffset, | 411 renderable.indexBufferOffset, |
| 296 IndexType, | 412 renderable.indexType, |
| 297 ) | 413 ) |
| 298 commandBuffer.vkCmdDrawIndexed( | 414 commandBuffer.vkCmdDrawIndexed( |
| 299 indexCount = drawable.indexCount, | 415 indexCount = renderable.indexCount, |
| 300 instanceCount = drawable.instanceCount, | 416 instanceCount = renderable.instanceCount, |
| 301 firstIndex = 0, | 417 firstIndex = 0, |
| 302 vertexOffset = 0, | 418 vertexOffset = 0, |
| 303 firstInstance = 0 | 419 firstInstance = 0 |
| 304 ) | 420 ) |
| 305 else: | 421 else: |
| 306 commandBuffer.vkCmdDraw( | 422 commandBuffer.vkCmdDraw( |
| 307 vertexCount = drawable.vertexCount, | 423 vertexCount = renderable.vertexCount, |
| 308 instanceCount = drawable.instanceCount, | 424 instanceCount = renderable.instanceCount, |
| 309 firstVertex = 0, | 425 firstVertex = 0, |
| 310 firstInstance = 0 | 426 firstInstance = 0 |
| 311 ) | 427 ) |
| 428 | |
| 429 when isMainModule: | |
| 430 type | |
| 431 MeshA = object | |
| 432 position: seq[Vec3f] | |
| 433 transparency: float | |
| 434 InstanceA = object | |
| 435 transform: seq[Mat4] | |
| 436 position: seq[Vec3f] | |
| 437 Globals = object | |
| 438 color: Vec4f | |
| 439 | |
| 440 ShaderInputsA = object | |
| 441 position {.VertexAttribute.}: Vec3f | |
| 442 transform {.InstanceAttribute.}: Mat4 | |
| 443 color {.DescriptorAttribute.}: Vec4f | |
| 444 | |
| 445 var p: Pipeline[ShaderInputsA] | |
| 446 var r: Renderable[MeshA, InstanceA] | |
| 447 var g: Globals | |
| 448 var s: ShaderSet[ShaderInputsA] | |
| 449 | |
| 450 var p1 = CreatePipeline(device = VkDevice(0), renderPass = VkRenderPass(0), shaderSet = s) | |
| 451 Render(p, r, g, VkCommandBuffer(0)) |
